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