Skip to main content

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