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 {
65 libc::open(b"/dev/null\0".as_ptr() as *const libc::c_char,
66 libc::O_RDWR)
67 };
68 if fd < 0 {
69 return Err(CoreError::sys(
70 std::io::Error::last_os_error().raw_os_error().unwrap_or(-1),
71 "open:/dev/null",
72 ));
73 }
74 unsafe {
75 libc::dup2(fd, 0);
76 libc::dup2(fd, 1);
77 libc::dup2(fd, 2);
78 if fd > 2 { libc::close(fd); }
79 }
80 Ok(())
81}
82
83/// Set the signal sent to this process when its parent dies (`PR_SET_PDEATHSIG`).
84pub fn set_pdeathsig(sig: i32) -> Result<(), CoreError> {
85 syscall_ret(
86 unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, sig as libc::c_ulong, 0, 0, 0) },
87 "prctl:PR_SET_PDEATHSIG",
88 )
89}
90
91/// Duplicate `src_fd` onto `dst_fd` and close `src_fd`.
92///
93/// Equivalent to `dup2(src_fd, dst_fd); close(src_fd)`.
94///
95/// # Safety
96/// Manipulates raw file descriptors.
97pub unsafe fn redirect_fd_to(src_fd: i32, dst_fd: i32) {
98 unsafe {
99 libc::dup2(src_fd, dst_fd);
100 libc::close(src_fd);
101 }
102}
103
104/// Drop process privileges to the given UID (`setresuid`).
105///
106/// Sets real, effective, and saved UID to `uid`.
107pub fn setuid(uid: u32) -> Result<(), CoreError> {
108 syscall_ret(
109 unsafe { libc::setresuid(uid, uid, uid) },
110 "setresuid",
111 )
112}
113
114/// Drop process privileges to the given GID (`setresgid`).
115///
116/// Sets real, effective, and saved GID to `gid`.
117pub fn setgid(gid: u32) -> Result<(), CoreError> {
118 syscall_ret(
119 unsafe { libc::setresgid(gid, gid, gid) },
120 "setresgid",
121 )
122}
123
124/// Close all file descriptors >= `start`.
125///
126/// Silently ignores `EBADF` (already closed or never open).
127pub fn close_fds_from(start: i32) {
128 for fd in start..1024 {
129 unsafe { libc::close(fd) };
130 }
131}