use std::{
process::{Child, Command},
time::Duration,
};
use tempfile::TempDir;
use crate::PgTempDBBuilder;
const CREATEDB_MAX_TRIES: u32 = 10;
const CREATEDB_RETRY_DELAY: Duration = Duration::from_millis(100);
fn current_user_is_root() -> bool {
unsafe { libc::getuid() == 0 }
}
pub fn init_db(builder: &mut PgTempDBBuilder) -> TempDir {
let temp_dir = {
if let Some(base_dir) = builder.temp_dir_prefix.clone() {
TempDir::with_prefix_in("pgtemp-", base_dir).expect("failed to create tempdir")
} else {
TempDir::with_prefix("pgtemp-").expect("failed to create tempdir")
}
};
if current_user_is_root() {
let chown_output = Command::new("chown")
.args(["-R", "postgres", temp_dir.path().to_str().unwrap()])
.output()
.expect("failed to chown data dir to postgres user");
if !chown_output.status.success() {
let stdout = chown_output.stdout;
let stderr = chown_output.stderr;
panic!(
"chowning data dir failed! stdout: {}\n\nstderr: {}",
String::from_utf8_lossy(&stdout),
String::from_utf8_lossy(&stderr)
);
}
}
let data_dir = temp_dir.path().join("pg_data_dir");
let data_dir_str = data_dir.to_str().unwrap();
let user = builder.get_user();
let password = builder.get_password();
let pwfile = temp_dir.path().join("user_password.txt");
let pwfile_str = pwfile.to_str().unwrap();
std::fs::write(&pwfile, password).expect("failed to write password file");
let initdb_path = builder
.bin_path
.as_ref()
.map_or("initdb".into(), |p| p.join("initdb"));
let mut cmd: Command;
if current_user_is_root() {
cmd = Command::new("sudo");
cmd.args(["-u", "postgres"]).arg(initdb_path);
} else {
cmd = Command::new(initdb_path);
}
cmd.args(["-D", data_dir_str])
.arg("-N") .args(["--username", &user])
.args(["--pwfile", pwfile_str]);
for (key, val) in &builder.initdb_args {
let formatted_key = if key.starts_with('-') {
key.to_string()
} else {
format!("--{}", key)
};
cmd.args([formatted_key.as_str(), val]);
}
let initdb_output = cmd
.output()
.expect("Failed to start initdb. Is it installed and on your path?");
if !initdb_output.status.success() {
let stdout = initdb_output.stdout;
let stderr = initdb_output.stderr;
panic!(
"initdb failed! stdout: {}\n\nstderr: {}",
String::from_utf8_lossy(&stdout),
String::from_utf8_lossy(&stderr)
);
}
temp_dir
}
pub fn run_db(temp_dir: &TempDir, mut builder: PgTempDBBuilder) -> Child {
let data_dir = temp_dir.path().join("pg_data_dir");
let data_dir_str = data_dir.to_str().unwrap();
let port = builder.get_port_or_set_random();
let postgres_path = builder
.bin_path
.as_ref()
.map_or("postgres".into(), |p| p.join("postgres"));
let mut pgcmd: Command;
if current_user_is_root() {
pgcmd = Command::new("sudo");
pgcmd.args(["-u", "postgres"]).arg(postgres_path);
} else {
pgcmd = Command::new(postgres_path);
};
pgcmd
.args(["-c", &format!("unix_socket_directories={}", data_dir_str)])
.args(["-c", &format!("port={port}")])
.args(["-c", "fsync=off"])
.args(["-c", "synchronous_commit=off"])
.args(["-c", "full_page_writes=off"])
.args(["-c", "autovacuum=off"])
.args(["-D", data_dir.to_str().unwrap()]);
for (key, val) in &builder.server_configs {
pgcmd.args(["-c", &format!("{}={}", key, val)]);
}
pgcmd
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let postgres_server_process = pgcmd
.spawn()
.expect("Failed to start postgres. Is it installed and on your path?");
std::thread::sleep(CREATEDB_RETRY_DELAY);
let user = builder.get_user();
let port = builder.get_port_or_set_random();
let dbname = builder.get_dbname();
if dbname != "postgres" {
let createdb_path = builder
.bin_path
.as_ref()
.map_or("createdb".into(), |p| p.join("createdb"));
let mut createdb_last_error_output = None;
for _ in 0..CREATEDB_MAX_TRIES {
let mut dbcmd = Command::new(createdb_path.clone());
dbcmd
.args(["--host", "localhost"])
.args(["--port", &port.to_string()])
.args(["--username", &user])
.arg("--no-password")
.arg(&dbname);
let output = dbcmd.output().expect("Failed to start createdb. Is it installed and on your path? It's typically part of the postgres-libs or postgres-client package.");
if output.status.success() {
createdb_last_error_output = None;
break;
}
createdb_last_error_output = Some(output);
std::thread::sleep(CREATEDB_RETRY_DELAY);
}
if let Some(output) = createdb_last_error_output {
let stdout = output.stdout;
let stderr = output.stderr;
panic!(
"createdb failed! stdout: {}\n\nstderr: {}",
String::from_utf8_lossy(&stdout),
String::from_utf8_lossy(&stderr)
);
}
}
postgres_server_process
}