LCOV - code coverage report
Current view: top level - libs/postgres_initdb/src - lib.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 0.0 % 60 0
Test Date: 2025-07-16 12:29:03 Functions: 0.0 % 3 0

            Line data    Source code
       1              : //! The canonical way we run `initdb` in Neon.
       2              : //!
       3              : //! initdb has implicit defaults that are dependent on the environment, e.g., locales & collations.
       4              : //!
       5              : //! This module's job is to eliminate the environment-dependence as much as possible.
       6              : 
       7              : use std::fmt;
       8              : 
       9              : use camino::Utf8Path;
      10              : use postgres_versioninfo::PgMajorVersion;
      11              : 
      12              : pub struct RunInitdbArgs<'a> {
      13              :     pub superuser: &'a str,
      14              :     pub locale: &'a str,
      15              :     pub initdb_bin: &'a Utf8Path,
      16              :     pub pg_version: PgMajorVersion,
      17              :     pub library_search_path: &'a Utf8Path,
      18              :     pub pgdata: &'a Utf8Path,
      19              : }
      20              : 
      21              : #[derive(thiserror::Error, Debug)]
      22              : pub enum Error {
      23              :     Spawn(std::io::Error),
      24              :     Failed {
      25              :         status: std::process::ExitStatus,
      26              :         stderr: Vec<u8>,
      27              :     },
      28              :     WaitOutput(std::io::Error),
      29              :     Other(anyhow::Error),
      30              : }
      31              : 
      32              : impl fmt::Display for Error {
      33            0 :     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
      34            0 :         match self {
      35            0 :             Error::Spawn(e) => write!(f, "Error spawning command: {e:?}"),
      36            0 :             Error::Failed { status, stderr } => write!(
      37            0 :                 f,
      38            0 :                 "Command failed with status {:?}: {}",
      39              :                 status,
      40            0 :                 String::from_utf8_lossy(stderr)
      41              :             ),
      42            0 :             Error::WaitOutput(e) => write!(f, "Error waiting for command output: {e:?}"),
      43            0 :             Error::Other(e) => write!(f, "Error: {e:?}"),
      44              :         }
      45            0 :     }
      46              : }
      47              : 
      48            0 : pub async fn do_run_initdb(args: RunInitdbArgs<'_>) -> Result<(), Error> {
      49              :     let RunInitdbArgs {
      50            0 :         superuser,
      51            0 :         locale,
      52            0 :         initdb_bin: initdb_bin_path,
      53            0 :         pg_version,
      54            0 :         library_search_path,
      55            0 :         pgdata,
      56            0 :     } = args;
      57            0 :     let mut initdb_command = tokio::process::Command::new(initdb_bin_path);
      58            0 :     initdb_command
      59            0 :         .args(["--pgdata", pgdata.as_ref()])
      60            0 :         .args(["--username", superuser])
      61            0 :         .args(["--encoding", "utf8"])
      62            0 :         .args(["--locale", locale])
      63            0 :         .arg("--no-instructions")
      64            0 :         .arg("--no-sync")
      65            0 :         .env_clear()
      66            0 :         .env("LD_LIBRARY_PATH", library_search_path)
      67            0 :         .env("DYLD_LIBRARY_PATH", library_search_path)
      68            0 :         .env(
      69            0 :             "ASAN_OPTIONS",
      70            0 :             std::env::var("ASAN_OPTIONS").unwrap_or_default(),
      71            0 :         )
      72            0 :         .env(
      73            0 :             "UBSAN_OPTIONS",
      74            0 :             std::env::var("UBSAN_OPTIONS").unwrap_or_default(),
      75            0 :         )
      76            0 :         .stdin(std::process::Stdio::null())
      77            0 :         // stdout invocation produces the same output every time, we don't need it
      78            0 :         .stdout(std::process::Stdio::null())
      79              :         // we would be interested in the stderr output, if there was any
      80            0 :         .stderr(std::process::Stdio::piped());
      81              : 
      82              :     // Before version 14, only the libc provide was available.
      83            0 :     if pg_version > PgMajorVersion::PG14 {
      84              :         // Version 17 brought with it a builtin locale provider which only provides
      85              :         // C and C.UTF-8. While being safer for collation purposes since it is
      86              :         // guaranteed to be consistent throughout a major release, it is also more
      87              :         // performant.
      88            0 :         let locale_provider = if pg_version >= PgMajorVersion::PG17 {
      89            0 :             "builtin"
      90              :         } else {
      91            0 :             "libc"
      92              :         };
      93              : 
      94            0 :         initdb_command.args(["--locale-provider", locale_provider]);
      95            0 :     }
      96              : 
      97            0 :     let initdb_proc = initdb_command.spawn().map_err(Error::Spawn)?;
      98              : 
      99              :     // Ideally we'd select here with the cancellation token, but the problem is that
     100              :     // we can't safely terminate initdb: it launches processes of its own, and killing
     101              :     // initdb doesn't kill them. After we return from this function, we want the target
     102              :     // directory to be able to be cleaned up.
     103              :     // See https://github.com/neondatabase/neon/issues/6385
     104            0 :     let initdb_output = initdb_proc
     105            0 :         .wait_with_output()
     106            0 :         .await
     107            0 :         .map_err(Error::WaitOutput)?;
     108            0 :     if !initdb_output.status.success() {
     109            0 :         return Err(Error::Failed {
     110            0 :             status: initdb_output.status,
     111            0 :             stderr: initdb_output.stderr,
     112            0 :         });
     113            0 :     }
     114              : 
     115            0 :     Ok(())
     116            0 : }
        

Generated by: LCOV version 2.1-beta