running_process_platform_internal/
platform_linux.rs1pub fn shell_command(command: &str) -> std::process::Command {
4 let mut shell = std::process::Command::new("/bin/sh");
5 shell.arg("-c").arg(command);
6 shell
7}
8
9pub fn compat_shell_command(command: &str) -> std::process::Command {
10 let mut shell = std::process::Command::new("sh");
11 shell.arg("-lc").arg(command);
12 shell
13}
14
15pub fn configure_native_command(
16 command: &mut std::process::Command,
17 _windows_creation_flags: u32,
18 create_process_group: bool,
19 nice: Option<i32>,
20 address_space_limit_bytes: Option<u64>,
21) {
22 use std::os::unix::process::CommandExt;
23
24 if create_process_group || nice.is_some() || address_space_limit_bytes.is_some() {
25 unsafe {
26 command.pre_exec(move || {
27 if create_process_group && libc::setpgid(0, 0) == -1 {
28 return Err(std::io::Error::last_os_error());
29 }
30 if let Some(nice) = nice {
31 let result = libc::setpriority(libc::PRIO_PROCESS, 0, nice);
32 if result == -1 {
33 return Err(std::io::Error::last_os_error());
34 }
35 }
36 if let Some(limit) = address_space_limit_bytes {
37 let rlim = libc::rlimit {
38 rlim_cur: limit,
39 rlim_max: limit,
40 };
41 if libc::setrlimit(libc::RLIMIT_AS, &rlim) == -1 {
42 return Err(std::io::Error::last_os_error());
43 }
44 }
45 Ok(())
46 });
47 }
48 }
49}
50
51pub fn canonical_environment_pairs(pairs: Vec<(String, String)>) -> Vec<(String, String)> {
52 pairs
53}
54
55pub fn monitor_console_windows(
56 _duration: std::time::Duration,
57) -> Vec<crate::platform::process::ConsoleWindowInfo> {
58 Vec::new()
59}
60
61use std::ffi::OsStr;
62use std::io;
63use std::io::Read;
64use std::os::fd::{AsRawFd, RawFd};
65use std::os::unix::net::UnixStream;
66use std::sync::Mutex;
67
68use tokio::process::{Child, Command};
69
70use crate::SpawnSpec;
71
72#[derive(Default)]
73pub struct CaptureCancellation {
74 wakers: Mutex<CaptureWakers>,
75}
76
77#[derive(Default)]
78struct CaptureWakers {
79 stdout: Option<UnixStream>,
80 stderr: Option<UnixStream>,
81}
82
83struct CancelableCaptureReader<R> {
84 reader: R,
85 wake_reader: UnixStream,
86}
87
88impl<R: Read + AsRawFd> Read for CancelableCaptureReader<R> {
89 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
90 if buf.is_empty() { return Ok(0); }
91 loop {
92 let mut poll_fds = [
93 libc::pollfd { fd: self.reader.as_raw_fd(), events: libc::POLLIN | libc::POLLHUP | libc::POLLERR, revents: 0 },
94 libc::pollfd { fd: self.wake_reader.as_raw_fd(), events: libc::POLLIN | libc::POLLHUP | libc::POLLERR, revents: 0 },
95 ];
96 let polled = unsafe { libc::poll(poll_fds.as_mut_ptr(), poll_fds.len() as _, -1) };
98 if polled < 0 {
99 let error = io::Error::last_os_error();
100 if error.kind() == io::ErrorKind::Interrupted { continue; }
101 return Err(error);
102 }
103 if poll_fds[1].revents != 0 {
104 return Err(io::Error::new(io::ErrorKind::Interrupted, "capture reader cancelled"));
105 }
106 if poll_fds[0].revents != 0 {
107 match self.reader.read(buf) {
108 Err(error) if error.kind() == io::ErrorKind::WouldBlock => continue,
109 result => return result,
110 }
111 }
112 }
113 }
114}
115
116pub fn prepare_capture_reader<R>(
117 reader: R,
118 cancellation: &CaptureCancellation,
119 stream: crate::platform::process::CaptureStream,
120) -> io::Result<Box<dyn Read + Send>>
121where R: Read + AsRawFd + Send + 'static {
122 set_nonblocking(reader.as_raw_fd())?;
123 let (wake_reader, wake_writer) = UnixStream::pair()?;
124 wake_writer.set_nonblocking(true)?;
125 let mut wakers = cancellation.wakers.lock().expect("capture wakers mutex poisoned");
126 match stream {
127 crate::platform::process::CaptureStream::Stdout => wakers.stdout = Some(wake_writer),
128 crate::platform::process::CaptureStream::Stderr => wakers.stderr = Some(wake_writer),
129 }
130 Ok(Box::new(CancelableCaptureReader { reader, wake_reader }))
131}
132
133pub fn capture_reader_done(cancellation: &CaptureCancellation, stream: crate::platform::process::CaptureStream) {
134 let mut wakers = cancellation.wakers.lock().expect("capture wakers mutex poisoned");
135 match stream {
136 crate::platform::process::CaptureStream::Stdout => wakers.stdout = None,
137 crate::platform::process::CaptureStream::Stderr => wakers.stderr = None,
138 }
139}
140
141pub fn cancel_capture_reader(cancellation: &CaptureCancellation) {
142 let wakers = cancellation.wakers.lock().expect("capture wakers mutex poisoned");
143 let byte = [1_u8; 1];
144 for writer in [&wakers.stdout, &wakers.stderr].into_iter().flatten() {
145 let _ = unsafe { libc::write(writer.as_raw_fd(), byte.as_ptr().cast(), byte.len()) };
147 }
148}
149
150fn set_nonblocking(fd: RawFd) -> io::Result<()> {
151 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
153 if flags < 0 { return Err(io::Error::last_os_error()); }
154 if unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } < 0 {
156 return Err(io::Error::last_os_error());
157 }
158 Ok(())
159}
160
161#[path = "platform_linux_file_handles.rs"]
162mod file_handles;
163pub use file_handles::read_process_file_handles;
164#[path = "platform_linux_cmdline.rs"]
165mod cmdline;
166pub use cmdline::read_process_cmdline;
167
168#[path = "platform/process_tree.rs"]
169mod process_tree;
170
171pub fn kill_tree(pid: u32, timeout: std::time::Duration) -> io::Result<u32> {
172 process_tree::kill_tree(pid, timeout, |_pid, process| Ok(process.start_time()))
173}
174
175pub fn exit_code(status: std::process::ExitStatus) -> i32 {
176 use std::os::unix::process::ExitStatusExt;
177 status.code().unwrap_or_else(|| -status.signal().unwrap_or(1))
178}
179
180pub fn set_process_name(name: &str) {
181 let truncated: String = name.chars().take(15).collect();
182 let c_name = std::ffi::CString::new(truncated).unwrap_or_default();
183 unsafe { libc::prctl(libc::PR_SET_NAME, c_name.as_ptr() as libc::c_ulong, 0, 0, 0); }
184}
185
186pub fn configure_trampoline_command(_command: &mut std::process::Command) {}
187
188pub fn trampoline_exit_code(status: std::process::ExitStatus) -> i32 {
189 use std::os::unix::process::ExitStatusExt;
190 status.signal().map_or_else(|| status.code().unwrap_or(1), |signal| 128 + signal)
191}
192
193pub fn enable_descendant_subreaper() {
197 let _ = unsafe { libc::prctl(libc::PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) };
198}
199
200pub fn soft_terminate_process_group(pid: u32) -> io::Result<()> {
202 let result = unsafe { libc::kill(-(pid as i32), libc::SIGTERM) };
205 if result != 0 {
206 let error = io::Error::last_os_error();
207 if error.raw_os_error() != Some(libc::ESRCH) {
208 return Err(error);
209 }
210 }
211 Ok(())
212}
213
214pub fn process_snapshot() -> Vec<crate::platform::process::ProcessSnapshot> {
215 Vec::new()
216}
217
218pub fn process_snapshot_for_pid(_pid: u32) -> Option<crate::platform::process::ProcessSnapshot> {
219 None
220}
221
222pub unsafe fn unix_mark_extra_fds_close_on_exec() {
227 #[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "x86", target_arch = "arm", target_arch = "riscv64", target_arch = "powerpc64"))]
228 {
229 const SYS_CLOSE_RANGE: libc::c_long = 436;
230 const CLOSE_RANGE_CLOEXEC: libc::c_uint = 4;
231 if libc::syscall(SYS_CLOSE_RANGE, 3u32, libc::c_uint::MAX, CLOSE_RANGE_CLOEXEC) == 0 {
232 return;
233 }
234 }
235 mark_fds_from_directory_or_range();
236}
237
238pub fn configure_sync_daemon_command(command: &mut std::process::Command) -> io::Result<()> {
239 use std::os::unix::process::CommandExt;
240 unsafe {
241 command.pre_exec(|| {
242 let _ = libc::setsid();
243 unix_mark_extra_fds_close_on_exec();
244 Ok(())
245 });
246 }
247 Ok(())
248}
249
250pub fn configure_sync_contained_command(command: &mut std::process::Command) -> io::Result<()> {
251 use std::os::unix::process::CommandExt;
252 unsafe {
253 command.pre_exec(|| {
254 if libc::setpgid(0, 0) == -1 { return Err(io::Error::last_os_error()); }
255 if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) == -1 {
256 return Err(io::Error::last_os_error());
257 }
258 if libc::getppid() == 1 { libc::_exit(1); }
259 unix_mark_extra_fds_close_on_exec();
260 Ok(())
261 });
262 }
263 Ok(())
264}
265
266pub fn parent_has_console() -> bool { false }
267
268pub fn sync_child_native_handle(_child: &std::process::Child) -> usize { 0 }
269
270unsafe fn mark_fds_from_directory_or_range() {
271 let dir = libc::opendir(c"/dev/fd".as_ptr());
272 if !dir.is_null() {
273 let dir_fd = libc::dirfd(dir);
274 loop {
275 let entry = libc::readdir(dir);
276 if entry.is_null() { break; }
277 let mut fd: libc::c_int = 0;
278 let mut cursor = (*entry).d_name.as_ptr();
279 let mut numeric = false;
280 while *cursor != 0 {
281 let byte = *cursor as u8;
282 if !byte.is_ascii_digit() { numeric = false; break; }
283 fd = fd * 10 + (byte - b'0') as libc::c_int;
284 cursor = cursor.add(1);
285 numeric = true;
286 }
287 if numeric && fd > 2 && fd != dir_fd { set_cloexec(fd); }
288 }
289 libc::closedir(dir);
290 return;
291 }
292 let maximum = libc::sysconf(libc::_SC_OPEN_MAX);
293 for fd in 3..if maximum < 0 { 4096 } else { maximum as libc::c_int } { set_cloexec(fd); }
294}
295
296unsafe fn set_cloexec(fd: libc::c_int) {
297 let flags = libc::fcntl(fd, libc::F_GETFD);
298 if flags != -1 { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC); }
299}
300pub fn observer_backend(scope: crate::platform::process::ObserverScope, category: crate::platform::process::ObserverCategory) -> crate::platform::process::ObserverBackend {
301 use crate::platform::process::{ObserverBackend as B, ObserverCategory as C, ObserverScope as S, ObserverSupport as P};
302 match (scope, category) {
303 (S::SystemWide, C::File) => B { support:P::Unavailable, backend:"seccomp-user-notify", reason:"Phase 3: Linux seccomp user-notify file backend not yet implemented" },
304 (S::SystemWide, C::Network) => B { support:P::Unavailable, backend:"ebpf", reason:"Phase 3: Linux eBPF network backend not yet implemented" },
305 (S::SystemWide, C::Process) => B { support:P::Unavailable, backend:"seccomp-user-notify", reason:"Phase 3: Linux seccomp user-notify process backend not yet implemented" },
306 (S::LaunchedProcessTree, C::File) => B { support:P::Partial, backend:"proc-fd-snapshot", reason:"Linux /proc/<pid>/fd/* snapshot via read_process_file_handles (#539 slice 6 follow-up; no streaming file events)" },
307 (S::LaunchedProcessTree, C::Network) => B { support:P::Unavailable, backend:"none", reason:"#539: no-admin per-child network backend deferred to a follow-up issue" },
308 (S::LaunchedProcessTree, C::Process) => B { support:P::Supported, backend:"subreaper-proc-poll", reason:"Linux PR_SET_CHILD_SUBREAPER + /proc descendant polling (#539 slice 5)" },
309 }
310}
311
312pub fn unix_set_priority(pid: u32, nice: i32) -> io::Result<()> {
313 if unsafe { libc::setpriority(libc::PRIO_PROCESS, pid, nice) } == -1 { Err(io::Error::last_os_error()) } else { Ok(()) }
314}
315pub fn unix_signal_process(pid: u32, signal: crate::platform::process::UnixSignalKind) -> io::Result<()> {
316 if unsafe { libc::kill(pid as i32, unix_signal_raw(signal)) } == -1 { Err(io::Error::last_os_error()) } else { Ok(()) }
317}
318pub fn unix_signal_process_group(pid: i32, signal: crate::platform::process::UnixSignalKind) -> io::Result<()> {
319 if unsafe { libc::killpg(pid, unix_signal_raw(signal)) } == -1 { Err(io::Error::last_os_error()) } else { Ok(()) }
320}
321pub fn unix_signal_raw(signal: crate::platform::process::UnixSignalKind) -> i32 {
322 match signal { crate::platform::process::UnixSignalKind::Interrupt => libc::SIGINT, crate::platform::process::UnixSignalKind::Terminate => libc::SIGTERM, crate::platform::process::UnixSignalKind::Kill => libc::SIGKILL }
323}
324
325pub fn configure_compat_tokio_command(
326 command: &mut Command,
327 _show_console: bool,
328 kill_when_owner_dies: bool,
329) -> io::Result<()> {
330 configure_command(command, false, kill_when_owner_dies)
331}
332
333pub fn after_compat_tokio_spawn(_child: &Child, _kill_when_owner_dies: bool) {}
334
335pub(crate) fn configure_command(
336 command: &mut Command,
337 create_process_group: bool,
338 kill_when_owner_dies: bool,
339) -> io::Result<()> {
340 if create_process_group {
341 command.process_group(0);
342 }
343 if kill_when_owner_dies {
344 let owner_pid = unsafe { libc::getpid() };
345 unsafe {
347 command.pre_exec(move || {
348 if libc::prctl(
349 libc::PR_SET_PDEATHSIG,
350 libc::SIGTERM as libc::c_ulong,
351 0,
352 0,
353 0,
354 ) == -1
355 {
356 return Err(io::Error::last_os_error());
357 }
358 if libc::getppid() != owner_pid {
359 libc::kill(libc::getpid(), libc::SIGTERM);
360 }
361 Ok(())
362 });
363 }
364 }
365 Ok(())
366}
367
368pub(crate) fn after_spawn(_child: &Child, _kill_when_owner_dies: bool) {}
369
370pub(crate) fn signal_process(pid: u32) -> io::Result<()> {
371 unix_kill(pid as i32, libc::SIGKILL)
372}
373
374pub(crate) fn signal_process_group(pid: u32) -> io::Result<()> {
375 unix_kill(-(pid as i32), libc::SIGTERM)
376}
377
378fn unix_kill(target: i32, signal: i32) -> io::Result<()> {
379 let result = unsafe { libc::kill(target, signal) };
380 if result == 0 {
381 return Ok(());
382 }
383 let error = io::Error::last_os_error();
384 if error.raw_os_error() == Some(libc::ESRCH) {
385 Ok(())
386 } else {
387 Err(error)
388 }
389}
390
391pub(crate) fn shell_spec(command: &OsStr) -> SpawnSpec {
392 SpawnSpec::new("/bin/sh").arg("-c").arg(command)
393}
394#[path = "sync_spawn_group.rs"]
395mod sync_spawn;
396pub use sync_spawn::{spawn_sync, spawn_sync_daemon};