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