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#[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#[cfg(feature = "alloc")]
48struct Argv(Vec<*const u8>);
49
50#[cfg(feature = "alloc")]
53unsafe impl Send for Argv {}
54
55#[cfg(feature = "alloc")]
56unsafe impl Sync for Argv {}
57
58#[cfg(feature = "alloc")]
60struct Envp(Vec<*const u8>);
61
62#[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 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 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 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 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 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 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 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 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 #[inline]
258 #[must_use]
259 pub fn get_pid(&self) -> i32 {
260 self.handle.pid
261 }
262 #[inline]
266 pub fn wait(&mut self) -> Result<i32> {
267 drop(self.stdin.take());
268 self.handle.wait()
269 }
270
271 #[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 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#[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 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 if child_pid == 0 {
469 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 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 process.wait()?;
555 return Err(Error::no_code("Short read on the CLOEXEC pipe"));
556 }
557 }
558 }
559}
560
561#[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 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 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
657pub struct StdioPipes {
660 pub stdin: Option<AnonPipe>,
661 pub stdout: Option<AnonPipe>,
662 pub stderr: Option<AnonPipe>,
663}
664
665pub 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}