Skip to main content

pg_embedded_setup_unpriv/bootstrap/
mode.rs

1//! Detects execution privileges and selects the appropriate orchestration mode.
2
3use camino::Utf8PathBuf;
4
5use crate::error::{BootstrapError, BootstrapResult};
6
7#[cfg(unix)]
8use nix::unistd::geteuid;
9
10/// Represents the privileges the process is running with when bootstrapping `PostgreSQL`.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ExecutionPrivileges {
13    /// The process owns `root` privileges and must drop to `nobody` for filesystem work.
14    Root,
15    /// The process is already unprivileged, so bootstrap tasks run with the current UID/GID.
16    Unprivileged,
17}
18
19/// Selects how `PostgreSQL` lifecycle commands run when privileged execution is required.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ExecutionMode {
22    /// Execute lifecycle commands directly within the current process.
23    ///
24    /// This mode is only appropriate when the process already runs without elevated privileges.
25    InProcess,
26    /// Delegate lifecycle commands to a helper subprocess executed with reduced privileges.
27    Subprocess,
28}
29
30/// Detects whether the process is running with root privileges.
31///
32/// # Examples
33/// ```
34/// use pg_embedded_setup_unpriv::{detect_execution_privileges, ExecutionPrivileges};
35///
36/// let privileges = detect_execution_privileges();
37/// let mode = match privileges {
38///     ExecutionPrivileges::Root => "subprocess",
39///     ExecutionPrivileges::Unprivileged => "in-process",
40/// };
41/// assert!(matches!(mode, "subprocess" | "in-process"));
42/// ```
43#[must_use]
44pub fn detect_execution_privileges() -> ExecutionPrivileges {
45    #[cfg(unix)]
46    {
47        if geteuid().is_root() {
48            ExecutionPrivileges::Root
49        } else {
50            ExecutionPrivileges::Unprivileged
51        }
52    }
53
54    #[cfg(not(unix))]
55    {
56        ExecutionPrivileges::Unprivileged
57    }
58}
59
60pub(super) fn determine_execution_mode(
61    privileges: ExecutionPrivileges,
62    worker_binary: Option<&Utf8PathBuf>,
63) -> BootstrapResult<ExecutionMode> {
64    #[cfg(unix)]
65    {
66        match privileges {
67            ExecutionPrivileges::Root => {
68                if worker_binary.is_none() {
69                    Err(BootstrapError::from(color_eyre::eyre::eyre!(
70                        "PG_EMBEDDED_WORKER must be set when running with root privileges"
71                    )))
72                } else {
73                    Ok(ExecutionMode::Subprocess)
74                }
75            }
76            ExecutionPrivileges::Unprivileged => Ok(ExecutionMode::InProcess),
77        }
78    }
79
80    #[cfg(not(unix))]
81    {
82        let _ = worker_binary;
83        let _ = privileges;
84        Ok(ExecutionMode::InProcess)
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    //! Unit tests for execution mode determination.
91
92    use super::*;
93
94    #[cfg(unix)]
95    #[test]
96    fn determine_execution_mode_requires_worker_when_root() {
97        let err = determine_execution_mode(ExecutionPrivileges::Root, None)
98            .expect_err("root execution without worker must error");
99        let message = err.to_string();
100        assert!(
101            message.contains("PG_EMBEDDED_WORKER must be set"),
102            "unexpected error message: {message}",
103        );
104    }
105
106    #[cfg(unix)]
107    #[test]
108    fn determine_execution_mode_allows_subprocess_with_worker() {
109        let worker = Utf8PathBuf::from("/tmp/pg_worker");
110        let mode = determine_execution_mode(ExecutionPrivileges::Root, Some(&worker))
111            .expect("root execution with worker should succeed");
112        assert_eq!(mode, ExecutionMode::Subprocess);
113    }
114
115    #[cfg(unix)]
116    #[test]
117    fn determine_execution_mode_in_process_when_unprivileged() {
118        let mode = determine_execution_mode(ExecutionPrivileges::Unprivileged, None)
119            .expect("unprivileged execution should succeed");
120        assert_eq!(mode, ExecutionMode::InProcess);
121    }
122
123    #[cfg(unix)]
124    #[test]
125    fn determine_execution_mode_ignores_worker_when_unprivileged() {
126        let worker = Utf8PathBuf::from("/tmp/pg_worker");
127        let mode = determine_execution_mode(ExecutionPrivileges::Unprivileged, Some(&worker))
128            .expect("unprivileged execution should succeed with worker configured");
129        assert_eq!(mode, ExecutionMode::InProcess);
130    }
131
132    #[cfg(not(unix))]
133    #[test]
134    fn determine_execution_mode_defaults_to_in_process() {
135        let worker = Utf8PathBuf::from("/tmp/pg_worker");
136        let mode = determine_execution_mode(ExecutionPrivileges::Root, Some(&worker))
137            .expect("non-unix execution should succeed");
138        assert_eq!(mode, ExecutionMode::InProcess);
139    }
140}