coreshift_core/process.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! Low-level process management primitives.
6//!
7//! Safe wrappers around `fork`, `setsid`, `setpgid`, `dup2`, `prctl`,
8//! and fd-range close — building blocks for double-fork supervisor patterns.
9
10use crate::CoreError;
11use crate::error::syscall_ret;
12
13/// Result of a [`fork`] call.
14pub enum ForkResult {
15 /// Returned in the parent with the child's PID.
16 Parent(i32),
17 /// Returned in the child (PID = 0).
18 Child,
19}
20
21/// Fork the current process.
22///
23/// # Safety
24/// After `fork`, only async-signal-safe operations are safe in the child
25/// before `exec`. Rust's allocator is not async-signal-safe; use this only
26/// in the narrow pattern of fork → exec or fork → immediate `_exit`.
27pub unsafe fn fork() -> Result<ForkResult, CoreError> {
28 let pid = unsafe { libc::fork() };
29 if pid < 0 {
30 return Err(CoreError::sys(
31 std::io::Error::last_os_error().raw_os_error().unwrap_or(-1),
32 "fork",
33 ));
34 }
35 if pid == 0 {
36 Ok(ForkResult::Child)
37 } else {
38 Ok(ForkResult::Parent(pid))
39 }
40}
41
42/// Create a new session and set the calling process as leader.
43pub fn setsid() -> Result<(), CoreError> {
44 let ret = unsafe { libc::setsid() };
45 if ret < 0 {
46 return Err(CoreError::sys(
47 std::io::Error::last_os_error().raw_os_error().unwrap_or(-1),
48 "setsid",
49 ));
50 }
51 Ok(())
52}
53
54/// Set the process group ID of `pid` to `pgid` (use 0 for self).
55pub fn setpgid(pid: i32, pgid: i32) -> Result<(), CoreError> {
56 syscall_ret(unsafe { libc::setpgid(pid, pgid) }, "setpgid")
57}
58
59/// Redirect stdin, stdout, and stderr to `/dev/null`.
60///
61/// # Safety
62/// Uses `dup2` on file descriptors 0/1/2.
63pub unsafe fn redirect_stdio_to_devnull() -> Result<(), CoreError> {
64 let fd = unsafe { libc::open(c"/dev/null".as_ptr(), libc::O_RDWR) };
65 if fd < 0 {
66 return Err(CoreError::sys(
67 std::io::Error::last_os_error().raw_os_error().unwrap_or(-1),
68 "open:/dev/null",
69 ));
70 }
71 unsafe {
72 libc::dup2(fd, 0);
73 libc::dup2(fd, 1);
74 libc::dup2(fd, 2);
75 if fd > 2 {
76 libc::close(fd);
77 }
78 }
79 Ok(())
80}
81
82/// Set the signal sent to this process when its parent dies (`PR_SET_PDEATHSIG`).
83pub fn set_pdeathsig(sig: i32) -> Result<(), CoreError> {
84 syscall_ret(
85 unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, sig as libc::c_ulong, 0, 0, 0) },
86 "prctl:PR_SET_PDEATHSIG",
87 )
88}
89
90/// Duplicate `src_fd` onto `dst_fd` and close `src_fd`.
91///
92/// Equivalent to `dup2(src_fd, dst_fd); close(src_fd)`.
93///
94/// # Safety
95/// Manipulates raw file descriptors.
96pub unsafe fn redirect_fd_to(src_fd: i32, dst_fd: i32) {
97 unsafe {
98 libc::dup2(src_fd, dst_fd);
99 // CORE-M8: when src == dst, `dup2` is a no-op and `close(src_fd)`
100 // would close the very fd meant to be kept.
101 if src_fd != dst_fd {
102 libc::close(src_fd);
103 }
104 }
105}
106
107pub fn getuid() -> u32 {
108 unsafe { libc::getuid() }
109}
110pub fn getgid() -> u32 {
111 unsafe { libc::getgid() }
112}
113
114/// Drop process privileges to the given UID (`setresuid`).
115///
116/// Sets real, effective, and saved UID to `uid`.
117pub fn setuid(uid: u32) -> Result<(), CoreError> {
118 syscall_ret(unsafe { libc::setresuid(uid, uid, uid) }, "setresuid")
119}
120
121/// Drop process privileges to the given GID (`setresgid`).
122///
123/// Sets real, effective, and saved GID to `gid`.
124pub fn setgid(gid: u32) -> Result<(), CoreError> {
125 syscall_ret(unsafe { libc::setresgid(gid, gid, gid) }, "setresgid")
126}
127
128/// Close all file descriptors >= `start`.
129///
130/// Enumerates `/proc/self/fd` to avoid EBADF on sparse fd tables.
131/// Falls back to a blind 3..1024 scan if `/proc/self/fd` is unreadable.
132pub fn close_fds_from(start: i32) {
133 // Open the dir handle ourselves so we own its fd: `std::fs::read_dir` hides
134 // it, and its dirfd is listed in `/proc/self/fd`, so a naive snapshot that
135 // includes it would close it mid-iteration and again on drop
136 // (double-close → in a multithreaded daemon the fd can be reused and an
137 // unrelated socket/log fd gets closed, finding 12).
138 let dir = unsafe { libc::opendir(c"/proc/self/fd".as_ptr()) };
139 if dir.is_null() {
140 for fd in start..1024 {
141 unsafe { libc::close(fd) };
142 }
143 return;
144 }
145 let dir_fd = unsafe { libc::dirfd(dir) };
146 let mut fds = Vec::new();
147 loop {
148 // readdir is not thread-safe against a concurrent close of the fd it is
149 // reading, but this runs in a forked single-threaded child; the fd
150 // snapshot is taken before any close happens below.
151 let ent = unsafe { libc::readdir(dir) };
152 if ent.is_null() {
153 break;
154 }
155 let name = unsafe { (*ent).d_name.as_ptr() };
156 let name = unsafe { std::ffi::CStr::from_ptr(name) };
157 let Ok(name) = name.to_str() else { continue };
158 if let Ok(fd) = name.parse::<i32>() {
159 if fd >= start && fd != dir_fd {
160 fds.push(fd);
161 }
162 }
163 }
164 // Close the dir handle first so its fd is never in the close set, then
165 // close the snapshot.
166 unsafe { libc::closedir(dir) };
167 for fd in fds {
168 unsafe { libc::close(fd) };
169 }
170}