tiny_std/
process.rs

1#[cfg(feature = "alloc")]
2use alloc::boxed::Box;
3#[cfg(feature = "alloc")]
4use alloc::vec;
5#[cfg(feature = "alloc")]
6use alloc::vec::Vec;
7use core::hint::unreachable_unchecked;
8
9use rusl::error::Errno;
10use rusl::platform::{Fd, GidT, OpenFlags, PidT, UidT, WaitPidFlags};
11use rusl::platform::{STDERR, STDIN, STDOUT};
12use rusl::string::unix_str::UnixStr;
13#[cfg(feature = "alloc")]
14use rusl::string::unix_str::UnixString;
15
16use crate::error::{Error, Result};
17use crate::fs::OpenOptions;
18use crate::io::{Read, Write};
19use crate::unix::fd::{BorrowedFd, OwnedFd, RawFd};
20
21const DEV_NULL: &UnixStr = UnixStr::from_str_checked("/dev/null\0");
22
23/// Terminates this process
24#[inline]
25pub fn exit(code: i32) -> ! {
26    rusl::process::exit(code)
27}
28
29#[cfg(feature = "alloc")]
30pub struct Command<'a> {
31    bin: &'a UnixStr,
32    args: Vec<&'a UnixStr>,
33    argv: Argv,
34    closures: Vec<Box<dyn FnMut() -> Result<()> + Send + Sync>>,
35    env: Environment,
36    cwd: Option<&'a UnixStr>,
37    uid: Option<UidT>,
38    gid: Option<GidT>,
39    stdin: Option<Stdio>,
40    stdout: Option<Stdio>,
41    stderr: Option<Stdio>,
42    pgroup: Option<PidT>,
43}
44
45// Create a new type for argv, so that we can make it `Send` and `Sync`
46#[cfg(feature = "alloc")]
47struct Argv(Vec<*const u8>);
48
49// It is safe to make `Argv` `Send` and `Sync`, because it contains
50// pointers to memory owned by `Command.args`
51#[cfg(feature = "alloc")]
52unsafe impl Send for Argv {}
53
54#[cfg(feature = "alloc")]
55unsafe impl Sync for Argv {}
56
57// Create a new type for argv, so that we can make it `Send` and `Sync`
58#[cfg(feature = "alloc")]
59struct Envp(Vec<*const u8>);
60
61// It is safe to make `Argv` `Send` and `Sync`, because it contains
62// pointers to memory owned by `Command.args`
63#[cfg(feature = "alloc")]
64unsafe impl Send for Envp {}
65
66#[cfg(feature = "alloc")]
67unsafe impl Sync for Envp {}
68
69#[cfg(feature = "alloc")]
70impl<'a> Command<'a> {
71    /// Constructs a new command, setting the first argument as the binary's name
72    /// # Errors
73    /// If the string is not `C string compatible`
74    pub fn new(bin: &'a UnixStr) -> Result<Self> {
75        let bin_ptr = bin.as_ptr();
76        Ok(Self {
77            bin,
78            args: vec![bin],
79            argv: Argv(vec![bin_ptr, core::ptr::null()]),
80            closures: vec![],
81            env: Environment::default(),
82            cwd: None,
83            uid: None,
84            gid: None,
85            stdin: None,
86            stdout: None,
87            stderr: None,
88            pgroup: None,
89        })
90    }
91
92    /// # Errors
93    /// If the string is not `C string compatible`
94    pub fn env(&mut self, env: UnixString) -> &mut Self {
95        #[cfg(feature = "start")]
96        if matches!(self.env, Environment::Inherit | Environment::None) {
97            self.env = Environment::Provided(ProvidedEnvironment {
98                vars: vec![],
99                envp: Envp(vec![core::ptr::null()]),
100            });
101        };
102        #[cfg(not(feature = "start"))]
103        if !matches!(self.env, Environment::None) {
104            self.env = Environment::Provided(ProvidedEnvironment {
105                vars: vec![],
106                envp: Envp(vec![core::ptr::null()]),
107            });
108        };
109        if let Environment::Provided(pe) = &mut self.env {
110            let s = env;
111            pe.envp.0[pe.vars.len()] = s.as_ptr();
112            pe.envp.0.push(core::ptr::null());
113            pe.vars.push(s);
114        }
115        self
116    }
117
118    /// # Errors
119    /// If the string is not `C string compatible`
120    pub fn envs(&mut self, envs: impl Iterator<Item = UnixString>) -> &mut Self {
121        for env in envs {
122            self.env(env);
123        }
124        self
125    }
126
127    /// # Errors
128    /// If the string is not `C string compatible`
129    pub fn arg(&mut self, arg: &'a UnixStr) -> &mut Self {
130        let unix_string = arg;
131        self.argv.0[self.args.len()] = unix_string.as_ptr();
132        self.argv.0.push(core::ptr::null());
133        self.args.push(unix_string);
134        self
135    }
136
137    /// # Errors
138    /// If the string is not `C string compatible`
139    pub fn args(&mut self, args: impl Iterator<Item = &'a UnixStr>) -> &mut Self {
140        for arg in args {
141            self.arg(arg);
142        }
143        self
144    }
145
146    /// A function to run after `forking` off the process but before the exec call
147    /// # Safety
148    /// Some things, such as some memory access will immediately cause UB, keep it simple, short, and
149    /// sweet.
150    pub unsafe fn pre_exec<F: FnMut() -> Result<()> + Send + Sync + 'static>(
151        &mut self,
152        f: F,
153    ) -> &mut Self {
154        self.closures.push(Box::new(f));
155        self
156    }
157
158    /// # Errors
159    /// If the string is not `C string compatible`
160    pub fn cwd(&mut self, dir: &'a UnixStr) -> &mut Self {
161        self.cwd = Some(dir);
162        self
163    }
164
165    pub fn uid(&mut self, id: UidT) -> &mut Self {
166        self.uid = Some(id);
167        self
168    }
169
170    pub fn gid(&mut self, id: GidT) -> &mut Self {
171        self.gid = Some(id);
172        self
173    }
174
175    pub fn pgroup(&mut self, pgroup: PidT) -> &mut Self {
176        self.pgroup = Some(pgroup);
177        self
178    }
179
180    pub fn stdin(&mut self, stdin: Stdio) -> &mut Self {
181        self.stdin = Some(stdin);
182        self
183    }
184
185    pub fn stdout(&mut self, stdout: Stdio) -> &mut Self {
186        self.stdout = Some(stdout);
187        self
188    }
189
190    pub fn stderr(&mut self, stderr: Stdio) -> &mut Self {
191        self.stderr = Some(stderr);
192        self
193    }
194
195    /// Spawns a new child process from this command.
196    /// # Errors
197    /// See `spawn`
198    pub fn spawn(&mut self) -> Result<Child> {
199        const NULL_ENV: [*const u8; 1] = [core::ptr::null()];
200        let envp = match &self.env {
201            #[cfg(feature = "start")]
202            Environment::Inherit => unsafe { crate::env::ENV.env_p },
203            Environment::None => NULL_ENV.as_ptr(),
204            Environment::Provided(provided) => provided.envp.0.as_ptr(),
205        };
206        unsafe {
207            do_spawn(
208                self.bin,
209                self.argv.0.as_ptr(),
210                envp,
211                Stdio::Inherit,
212                true,
213                self.stdin,
214                self.stdout,
215                self.stderr,
216                &mut self.closures,
217                self.cwd,
218                self.uid,
219                self.gid,
220                self.pgroup,
221            )
222        }
223    }
224
225    pub fn exec(&mut self) -> Error {
226        const NULL_ENV: [*const u8; 1] = [core::ptr::null()];
227        let envp = match &self.env {
228            #[cfg(feature = "start")]
229            Environment::Inherit => unsafe { crate::env::ENV.env_p },
230            Environment::None => NULL_ENV.as_ptr(),
231            Environment::Provided(provided) => provided.envp.0.as_ptr(),
232        };
233        unsafe { do_exec(self.bin, self.argv.0.as_ptr(), envp, &mut self.closures) }
234    }
235}
236
237pub struct Child {
238    pub(crate) handle: Process,
239
240    pub stdin: Option<AnonPipe>,
241
242    pub stdout: Option<AnonPipe>,
243
244    pub stderr: Option<AnonPipe>,
245}
246
247impl Child {
248    /// Get the backing pid of this Child
249    #[inline]
250    #[must_use]
251    pub fn get_pid(&self) -> i32 {
252        self.handle.pid
253    }
254    /// Waits for this child process to finish retuning its exit code
255    /// # Errors
256    /// Os errors relating to waiting for process
257    #[inline]
258    pub fn wait(&mut self) -> Result<i32> {
259        drop(self.stdin.take());
260        self.handle.wait()
261    }
262
263    /// Attempts to wait for this child process to finish, returns Ok(None) if
264    /// child still hasn't finished, otherwise returns the exit code
265    /// # Errors
266    /// Os errors relating to waiting for process
267    #[inline]
268    pub fn try_wait(&mut self) -> Result<Option<i32>> {
269        self.handle.try_wait()
270    }
271}
272
273pub struct Process {
274    pid: i32,
275    status: Option<i32>,
276}
277
278impl Process {
279    fn wait(&mut self) -> Result<i32> {
280        if let Some(status) = self.status {
281            return Ok(status);
282        }
283        let res = rusl::process::wait_pid(self.pid, WaitPidFlags::empty())?;
284        self.status = Some(res.status);
285        Ok(res.status)
286    }
287
288    fn try_wait(&mut self) -> Result<Option<i32>> {
289        if let Some(status) = self.status {
290            return Ok(Some(status));
291        }
292        let res = rusl::process::wait_pid(self.pid, WaitPidFlags::WNOHANG)?;
293        if res.pid == 0 {
294            Ok(None)
295        } else {
296            self.status = Some(res.status);
297            Ok(Some(res.status))
298        }
299    }
300}
301
302#[derive(Debug, Copy, Clone)]
303pub enum Stdio {
304    Inherit,
305    Null,
306    MakePipe,
307    RawFd(Fd),
308}
309
310impl Stdio {
311    fn to_child_stdio(self, readable: bool) -> Result<(ChildStdio, Option<AnonPipe>)> {
312        match self {
313            Stdio::Inherit => Ok((ChildStdio::Inherit, None)),
314
315            Stdio::MakePipe => {
316                let pipe = rusl::unistd::pipe2(OpenFlags::O_CLOEXEC)?;
317                let (ours, theirs) = if readable {
318                    (pipe.out_pipe, pipe.in_pipe)
319                } else {
320                    (pipe.in_pipe, pipe.out_pipe)
321                };
322                Ok((
323                    ChildStdio::Owned(OwnedFd(theirs)),
324                    Some(AnonPipe(OwnedFd(ours))),
325                ))
326            }
327
328            Stdio::Null => {
329                let mut opts = OpenOptions::new();
330                opts.read(readable);
331                opts.write(!readable);
332                let fd = opts.open(DEV_NULL)?;
333                Ok((ChildStdio::Owned(fd.into_inner()), None))
334            }
335
336            Stdio::RawFd(fd) => Ok((ChildStdio::Owned(OwnedFd(fd)), None)),
337        }
338    }
339}
340
341pub enum ChildStdio {
342    Inherit,
343    Owned(OwnedFd),
344}
345
346impl ChildStdio {
347    fn fd(&self) -> Option<RawFd> {
348        match self {
349            ChildStdio::Inherit => None,
350            ChildStdio::Owned(fd) => Some(fd.0),
351        }
352    }
353}
354
355#[non_exhaustive]
356pub enum Environment {
357    #[cfg(feature = "start")]
358    Inherit,
359    None,
360    #[cfg(feature = "alloc")]
361    Provided(ProvidedEnvironment),
362}
363
364#[cfg(feature = "alloc")]
365pub struct ProvidedEnvironment {
366    vars: Vec<UnixString>,
367    envp: Envp,
368}
369
370#[expect(clippy::derivable_impls)]
371impl Default for Environment {
372    fn default() -> Self {
373        #[cfg(feature = "start")]
374        {
375            Environment::Inherit
376        }
377        #[cfg(not(feature = "start"))]
378        {
379            Environment::None
380        }
381    }
382}
383
384pub trait PreExec {
385    /// Run this routing pre exec
386    /// # Errors
387    /// Any errors occuring, it's up to the implementor to decide
388    fn run(&mut self) -> Result<()>;
389}
390
391#[cfg(feature = "alloc")]
392impl PreExec for Box<dyn FnMut() -> Result<()> + Send + Sync> {
393    #[inline]
394    fn run(&mut self) -> Result<()> {
395        (self)()
396    }
397}
398
399impl PreExec for &'_ mut (dyn FnMut() -> Result<()> + Send + Sync) {
400    #[inline]
401    fn run(&mut self) -> Result<()> {
402        (self)()
403    }
404}
405
406impl PreExec for () {
407    #[inline]
408    fn run(&mut self) -> Result<()> {
409        Ok(())
410    }
411}
412
413/// Execute a binary after running the provided closures.
414/// Will not return if successful.
415/// # Safety
416/// Pointers are valid.
417#[inline]
418pub unsafe fn do_exec<F: PreExec>(
419    bin: &UnixStr,
420    argv: *const *const u8,
421    envp: *const *const u8,
422    closures: &mut [F],
423) -> Error {
424    for closure in closures {
425        if let Err(e) = closure.run() {
426            return e;
427        }
428    }
429    let Err(e) = rusl::process::execve(bin, argv, envp) else {
430        // execve only returns on error.
431        unreachable_unchecked();
432    };
433    e.into()
434}
435
436#[inline]
437#[expect(clippy::too_many_arguments)]
438unsafe fn do_spawn<F: PreExec>(
439    bin: &UnixStr,
440    argv: *const *const u8,
441    envp: *const *const u8,
442    default_stdio: Stdio,
443    needs_stdin: bool,
444    stdin: Option<Stdio>,
445    stdout: Option<Stdio>,
446    stderr: Option<Stdio>,
447    closures: &mut [F],
448    cwd: Option<&UnixStr>,
449    uid: Option<UidT>,
450    gid: Option<GidT>,
451    pgroup: Option<PidT>,
452) -> Result<Child> {
453    const CLOEXEC_MSG_FOOTER: [u8; 4] = *b"NOEX";
454    let (ours, theirs) = setup_io(default_stdio, needs_stdin, stdin, stdout, stderr)?;
455    let sync_pipe = rusl::unistd::pipe2(OpenFlags::O_CLOEXEC)?;
456    let (read_pipe, write_pipe) = (sync_pipe.in_pipe, sync_pipe.out_pipe);
457    let child_pid = rusl::process::fork()?;
458    // From this point we're two processes
459    if child_pid == 0 {
460        // Executing as child process
461        let _ = rusl::unistd::close(read_pipe);
462        if let Some(fd) = theirs.stdin.fd() {
463            rusl::unistd::dup2(fd, STDIN)?;
464        }
465        if let Some(fd) = theirs.stdout.fd() {
466            rusl::unistd::dup2(fd, STDOUT)?;
467        }
468        if let Some(fd) = theirs.stderr.fd() {
469            rusl::unistd::dup2(fd, STDERR)?;
470        }
471        if let Some(cwd) = cwd {
472            rusl::unistd::chdir(cwd)?;
473        }
474        if let Some(uid) = uid {
475            rusl::unistd::setuid(uid)?;
476        }
477        if let Some(gid) = gid {
478            rusl::unistd::setgid(gid)?;
479        }
480        if let Some(pgroup) = pgroup {
481            rusl::unistd::setpgid(0, pgroup)?;
482        }
483        for closure in closures {
484            closure.run()?;
485        }
486        let Err(e) = rusl::process::execve(bin, argv, envp) else {
487            // execve only returns on error.
488            unreachable_unchecked();
489        };
490        let code: [u8; 4] = if let Some(code) = e.code {
491            code.raw().to_be_bytes()
492        } else {
493            rusl::process::exit(1)
494        };
495        let bytes = [
496            code[0],
497            code[1],
498            code[2],
499            code[3],
500            CLOEXEC_MSG_FOOTER[0],
501            CLOEXEC_MSG_FOOTER[1],
502            CLOEXEC_MSG_FOOTER[2],
503            CLOEXEC_MSG_FOOTER[3],
504        ];
505        let _ = rusl::unistd::write(write_pipe, &bytes);
506        rusl::process::exit(1);
507    }
508    let _ = rusl::unistd::close(write_pipe);
509    let mut process = Process {
510        pid: child_pid,
511        status: None,
512    };
513    let mut bytes = [0, 0, 0, 0, 0, 0, 0, 0];
514    loop {
515        match rusl::unistd::read(read_pipe, &mut bytes) {
516            Ok(0) => {
517                let child = Child {
518                    handle: process,
519                    stdin: ours.stdin,
520                    stdout: ours.stdout,
521                    stderr: ours.stderr,
522                };
523                return Ok(child);
524            }
525            Ok(8) => {
526                let (errno, footer) = bytes.split_at(4);
527                if CLOEXEC_MSG_FOOTER != footer {
528                    return Err(Error::no_code("Validation on the CLOEXEC pipe failed"));
529                }
530
531                let errno = Errno::new(i32::from_be_bytes(errno.try_into().unwrap_unchecked()));
532                process.wait()?;
533                return Err(Error::os("Failed to wait for process", errno));
534            }
535            Err(ref e) if matches!(e.code, Some(Errno::EINTR)) => {}
536            Err(_) => {
537                process.wait()?;
538                return Err(Error::no_code("The cloexec pipe failed"));
539            }
540            Ok(..) => {
541                // pipe I/O up to PIPE_BUF bytes should be atomic
542                process.wait()?;
543                return Err(Error::no_code("Short read on the CLOEXEC pipe"));
544            }
545        }
546    }
547}
548
549/// Spawns a process with the provided arguments. On no arguments, the binary will be set as the first
550/// argument as per best practice, on any args, it's up to the caller to follow that best practice or not.
551/// `arg_v` must null terminated, since there is currently no way to do constant ops, ie
552/// put an array of length `N + 1` on the stack, the last value is discarded
553/// # Errors
554/// OS errors relating to permission on the binary, as well as other errors relating to pipe creation
555/// and process spawning.
556/// # Notes
557/// We have to do some gating here, since we're copying the pointer out of the closure it'd dangle
558/// after if we did an allocation before that closure.
559#[cfg(not(feature = "alloc"))]
560#[expect(clippy::too_many_arguments)]
561pub fn spawn<const N: usize, CL: PreExec>(
562    bin: &UnixStr,
563    argv: [&UnixStr; N],
564    env: &Environment,
565    stdin: Option<Stdio>,
566    stdout: Option<Stdio>,
567    stderr: Option<Stdio>,
568    closures: &mut [CL],
569    cwd: Option<&UnixStr>,
570    uid: Option<UidT>,
571    gid: Option<GidT>,
572    pgroup: Option<PidT>,
573) -> Result<Child> {
574    const NO_ENV: [*const u8; 1] = [core::ptr::null()];
575    let mut no_args: [*const u8; 2] = [core::ptr::null_mut(), core::ptr::null_mut()];
576    let envp = match env {
577        #[cfg(feature = "start")]
578        Environment::Inherit => unsafe { crate::env::ENV.env_p },
579        Environment::None => NO_ENV.as_ptr(),
580    };
581    let mut new_args = [core::ptr::null(); N];
582    let arg_ptr = if argv.is_empty() {
583        // Make sure we at least send the bin as arg
584        no_args[0] = bin.as_ptr();
585        no_args.as_ptr()
586    } else {
587        for (ind, arg) in argv.into_iter().enumerate() {
588            new_args[ind] = arg.as_ptr();
589        }
590        new_args[N - 1] = core::ptr::null();
591        new_args.as_ptr()
592    };
593    // Only safe to do on no-alloc, since we may create a string there and the pointer will
594    // dangle if we take it out of the closure
595    unsafe {
596        do_spawn(
597            bin,
598            arg_ptr,
599            envp,
600            Stdio::Inherit,
601            true,
602            stdin,
603            stdout,
604            stderr,
605            closures,
606            cwd,
607            uid,
608            gid,
609            pgroup,
610        )
611    }
612}
613
614pub struct AnonPipe(OwnedFd);
615
616impl AnonPipe {
617    #[inline]
618    #[must_use]
619    pub fn borrow_fd(&self) -> BorrowedFd {
620        BorrowedFd::new(self.0 .0)
621    }
622}
623
624impl Read for AnonPipe {
625    #[inline]
626    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
627        Ok(rusl::unistd::read(self.0 .0, buf)?)
628    }
629}
630
631impl Write for AnonPipe {
632    #[inline]
633    fn write(&mut self, buf: &[u8]) -> Result<usize> {
634        Ok(rusl::unistd::write(self.0 .0, buf)?)
635    }
636
637    #[inline]
638    fn flush(&mut self) -> Result<()> {
639        Ok(())
640    }
641}
642
643// passed back to std::process with the pipes connected to the child, if any
644// were requested
645pub struct StdioPipes {
646    pub stdin: Option<AnonPipe>,
647    pub stdout: Option<AnonPipe>,
648    pub stderr: Option<AnonPipe>,
649}
650
651// passed to do_exec() with configuration of what the child stdio should look
652// like
653pub struct ChildPipes {
654    pub stdin: ChildStdio,
655    pub stdout: ChildStdio,
656    pub stderr: ChildStdio,
657}
658
659fn setup_io(
660    default: Stdio,
661    needs_stdin: bool,
662    stdin: Option<Stdio>,
663    stdout: Option<Stdio>,
664    stderr: Option<Stdio>,
665) -> Result<(StdioPipes, ChildPipes)> {
666    let null = Stdio::Null;
667    let default_stdin = if needs_stdin { default } else { null };
668    let stdin = stdin.unwrap_or(default_stdin);
669    let stdout = stdout.unwrap_or(default);
670    let stderr = stderr.unwrap_or(default);
671    let (their_stdin, our_stdin) = stdin.to_child_stdio(true)?;
672    let (their_stdout, our_stdout) = stdout.to_child_stdio(false)?;
673    let (their_stderr, our_stderr) = stderr.to_child_stdio(false)?;
674    let ours = StdioPipes {
675        stdin: our_stdin,
676        stdout: our_stdout,
677        stderr: our_stderr,
678    };
679    let theirs = ChildPipes {
680        stdin: their_stdin,
681        stdout: their_stdout,
682        stderr: their_stderr,
683    };
684    Ok((ours, theirs))
685}