1#![doc = include_str!("../README.md")]
2
3mod handle;
4mod spawn;
5
6use caps::{CapSet, CapsHashSet};
7use log::warn;
8use nix::unistd::{dup, pipe};
9use std::os::fd::AsFd;
10use std::{os::fd::OwnedFd, sync::LazyLock};
11
12pub use handle::Error as HandleError;
13pub use handle::Handle;
14pub use handle::Stream;
15pub use spawn::Error as SpawnError;
16pub use spawn::Spawner;
17pub use spawn::StreamMode;
18
19#[cfg(feature = "fork")]
20pub use fork::Error as ForkError;
21
22#[cfg(feature = "fork")]
23pub use fork::Fork;
24
25#[cfg(feature = "fork")]
26mod fork;
27
28static NULL: LazyLock<OwnedFd> = LazyLock::new(|| {
31 std::fs::File::open("/dev/null")
32 .expect("Failed to open /dev/null")
33 .into()
34});
35
36fn format_iter<T, V>(iter: T) -> String
38where
39 T: Iterator<Item = V>,
40 V: std::fmt::Display,
41{
42 let mut ret = String::new();
43 iter.for_each(|f| ret.push_str(&format!("{f} ")));
44 ret
45}
46
47fn clear_capabilities(diff: CapsHashSet) {
49 for set in [
50 CapSet::Ambient,
51 CapSet::Ambient,
52 CapSet::Effective,
53 CapSet::Inheritable,
54 CapSet::Permitted,
55 ] {
56 for cap in &diff {
57 if let Err(e) = caps::drop(None, set, *cap) {
58 warn!("Could not drop {cap}: {e}");
59 }
60 }
61 }
62}
63
64fn dup_null() -> Result<OwnedFd, SpawnError> {
66 dup(NULL.as_fd()).map_err(|e| SpawnError::Errno(None, "dup", e))
67}
68
69fn cond_pipe(cond: &StreamMode) -> Result<Option<(OwnedFd, OwnedFd)>, SpawnError> {
72 match cond {
73 StreamMode::Pipe | StreamMode::Log(_) => match pipe() {
74 Ok((r, w)) => Ok(Some((r, w))),
75 Err(e) => Err(SpawnError::Errno(None, "pipe", e)),
76 },
77 _ => Ok(None),
78 }
79}
80
81fn logger(level: log::Level, fd: OwnedFd, name: String) {
83 let stream = Stream::new(fd);
84 while let Some(line) = stream.read_line() {
85 log::log!(level, "{name}: {line}");
86 }
87}