use std::path::Path;
use super::platform;
use crate::error::BootstrapResult;
#[cfg(any(doc, test, feature = "cluster-unit-tests", feature = "dev-worker"))]
pub type PostmasterPid = platform::PostmasterPid;
#[cfg(any(doc, test, feature = "cluster-unit-tests", feature = "dev-worker"))]
pub type PostmasterProcess = platform::PostmasterProcess;
#[cfg(any(doc, test, feature = "cluster-unit-tests", feature = "dev-worker"))]
pub fn read_postmaster_pid(data_dir: &Path) -> BootstrapResult<Option<PostmasterPid>> {
let pid_file = data_dir.join("postmaster.pid");
let contents = read_postmaster_pid_file(&pid_file)?;
let Some(first_line) = contents.lines().next() else {
return Ok(None);
};
if first_line.trim().is_empty() {
return Ok(None);
}
platform::parse_pid(first_line).map(Some).ok_or_else(|| {
color_eyre::eyre::eyre!("malformed postmaster PID in {}", pid_file.display()).into()
})
}
#[cfg(any(doc, test, feature = "cluster-unit-tests", feature = "dev-worker"))]
pub fn read_postmaster_process(data_dir: &Path) -> BootstrapResult<Option<PostmasterProcess>> {
read_postmaster_process_from_dir(data_dir)
}
pub(super) fn read_postmaster_process_from_dir(
data_dir: &Path,
) -> BootstrapResult<Option<platform::PostmasterProcess>> {
let pid_file = data_dir.join("postmaster.pid");
let contents = read_postmaster_pid_file(&pid_file)?;
if contents.trim().is_empty() {
return Ok(None);
}
platform::parse_postmaster_process(&contents)
.map(Some)
.ok_or_else(|| {
color_eyre::eyre::eyre!("malformed postmaster process in {}", pid_file.display()).into()
})
}
fn read_postmaster_pid_file(pid_file: &Path) -> BootstrapResult<String> {
match std::fs::read_to_string(pid_file) {
Ok(contents) => Ok(contents),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
Err(err) => {
Err(color_eyre::eyre::eyre!("failed to read {}: {err}", pid_file.display()).into())
}
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
fn write_pid_file(dir: &Path, contents: &str) -> std::io::Result<()> {
std::fs::write(dir.join("postmaster.pid"), contents)
}
#[test]
fn read_postmaster_pid_returns_none_when_file_missing() {
let temp = tempfile::tempdir().expect("tempdir");
assert!(
read_postmaster_pid(temp.path())
.expect("missing pid file is not an error")
.is_none()
);
}
#[test]
fn read_postmaster_pid_returns_none_when_first_line_blank() {
let temp = tempfile::tempdir().expect("tempdir");
write_pid_file(temp.path(), " \n/data\n").expect("write pid file");
assert!(
read_postmaster_pid(temp.path())
.expect("blank line")
.is_none()
);
}
#[test]
fn read_postmaster_pid_parses_leading_pid() {
let temp = tempfile::tempdir().expect("tempdir");
write_pid_file(temp.path(), "4242\n/var/lib/pgdata\n1700000000\n").expect("write pid file");
assert_eq!(
read_postmaster_pid(temp.path()).expect("valid pid file"),
Some(4242),
);
}
#[test]
fn read_postmaster_pid_rejects_malformed_pid() {
let temp = tempfile::tempdir().expect("tempdir");
write_pid_file(temp.path(), "not-a-pid\n/data\n").expect("write pid file");
assert!(
read_postmaster_pid(temp.path()).is_err(),
"malformed pid errors"
);
}
#[test]
fn read_postmaster_process_reads_identity() {
let temp = tempfile::tempdir().expect("tempdir");
write_pid_file(temp.path(), "5150\n/var/lib/pgdata\n").expect("write pid file");
assert_eq!(
read_postmaster_process(temp.path()).expect("valid pid file"),
Some(5150),
);
}
#[test]
fn read_postmaster_process_returns_none_when_empty() {
let temp = tempfile::tempdir().expect("tempdir");
write_pid_file(temp.path(), "\n").expect("write pid file");
assert!(
read_postmaster_process(temp.path())
.expect("empty pid file")
.is_none()
);
}
}
#[cfg(any(doc, test, feature = "cluster-unit-tests", feature = "dev-worker"))]
pub fn process_is_running(pid: PostmasterPid) -> BootstrapResult<bool> {
platform::process_is_running_for_platform(pid)
}
#[cfg(any(doc, test, feature = "cluster-unit-tests", feature = "dev-worker"))]
pub fn postmaster_process_is_running(process: PostmasterProcess) -> BootstrapResult<bool> {
platform::postmaster_process_is_running(process)
}