leviath_sys/process.rs
1//! Process control: detached spawning.
2
3use std::process::Command;
4
5/// Configure `cmd` so the spawned child detaches into its own process group,
6/// surviving the terminal that launched it.
7///
8/// On Unix this uses the safe, stable `Command::process_group(0)`; on non-Unix
9/// platforms it is a no-op. Call this before `cmd.spawn()`.
10pub fn configure_detached(cmd: &mut Command) {
11 crate::platform::configure_detached(cmd);
12}
13
14/// SIGKILL every process in the group led by `pgid` (a no-op on platforms
15/// without process groups).
16///
17/// Killing a child shell is not enough to stop what it started: the shell's own
18/// children are reparented to init and keep running. A cancelled agent's
19/// `sleep 400` outliving the run that started it is exactly that. Spawning the
20/// shell into its own group (via [`configure_detached`]) and signalling the
21/// group tears down the whole tree.
22///
23/// Errors are the ordinary case (the group already exited) and are the caller's
24/// to ignore.
25pub fn kill_process_group(pgid: u32) -> std::io::Result<()> {
26 crate::platform::kill_process_group(pgid)
27}
28
29/// The calling user's numeric id.
30///
31/// Used to address a per-user service domain (`launchctl bootstrap gui/<uid>`).
32/// Returns `0` on platforms with no POSIX uid, where no such domain exists.
33pub fn current_uid() -> u32 {
34 crate::platform::current_uid()
35}
36
37/// The effective uid of the process on the other end of a connected
38/// Unix-domain socket.
39///
40/// The kernel's answer, not the peer's claim, so it cannot be spoofed by
41/// anything the peer sends. This is what makes the daemon's control socket
42/// safe: file permissions on a Unix socket are advisory on macOS and the BSDs
43/// (the mode is not consulted on `connect`), so the mode alone was never the
44/// guarantee it was documented to be.
45///
46/// `None` when the platform cannot report it - which the caller must treat as
47/// "refuse", since an unidentifiable peer is not an authorized one.
48///
49/// Unix-only: on Windows the control channel is not a Unix socket, so there is
50/// no fd to interrogate and no caller for this.
51#[cfg(unix)]
52pub fn peer_uid(sock: &impl std::os::fd::AsFd) -> Option<u32> {
53 crate::platform::peer_uid(sock)
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 /// The check that makes the daemon's control socket safe: the peer's uid
61 /// comes from the kernel, not from anything the peer claims. A socket we
62 /// connected to ourselves must report our own uid.
63 #[cfg(unix)]
64 #[test]
65 fn peer_uid_reports_our_own_uid_for_a_self_connection() {
66 use std::os::unix::net::UnixListener;
67
68 let dir = tempfile::tempdir().unwrap();
69 let path = dir.path().join("s.sock");
70 let listener = UnixListener::bind(&path).expect("bind");
71 let client = std::os::unix::net::UnixStream::connect(&path).expect("connect");
72 let (server, _) = listener.accept().expect("accept");
73
74 // Both ends agree, and both agree with the process's own uid - a peer
75 // check that returned `None` here would refuse every legitimate
76 // connection, which is the failure mode worth catching.
77 assert_eq!(peer_uid(&server), Some(current_uid()));
78 assert_eq!(peer_uid(&client), Some(current_uid()));
79 }
80
81 #[test]
82 fn current_uid_is_reported() {
83 // Just has to answer without panicking; root (0) is a legitimate value
84 // on Unix and the only value on non-Unix.
85 let _uid: u32 = current_uid();
86 }
87
88 #[test]
89 fn kill_process_group_public_wrapper_reports_a_missing_group() {
90 // Exercises the public shim (distinct from the platform impl its own
91 // tests cover). A group that cannot exist errors on Unix and is a no-op
92 // where the platform has no process groups - both are outcomes the
93 // caller ignores, so either result is acceptable here.
94 let _ = kill_process_group(0x7FFF_FFFF);
95 }
96
97 #[test]
98 fn configure_detached_public_wrapper_registers_without_spawning() {
99 // Exercises the public `process::configure_detached` shim (distinct
100 // from the platform impl its own tests cover); registering the hook
101 // must not fork/exec or panic.
102 let mut cmd = Command::new("true");
103 configure_detached(&mut cmd);
104 }
105}