blob: 5cedbf9792b3d25f1b4060c9dd502d3e3d8cb690 [file]
use super::CommandEnvs;
use super::env::{CommandEnv, CommandResolvedEnvs};
use crate::ffi::OsStr;
pub use crate::ffi::OsString as EnvKey;
use crate::num::NonZeroI32;
use crate::os::fd::{AsRawFd, FromRawFd};
use crate::os::motor::ffi::OsStrExt;
use crate::path::Path;
use crate::process::StdioPipes;
use crate::sys::fs::File;
use crate::sys::{IntoInner, map_motor_error};
use crate::{fmt, io};
pub enum Stdio {
Inherit,
Null,
MakePipe,
// There is no public `From<io::Stdin>` conversion yet.
#[expect(dead_code)]
ParentStdin,
ParentStdout,
ParentStderr,
Fd(crate::sys::fd::FileDesc),
}
impl Stdio {
fn into_rt(&self) -> moto_rt::RtFd {
match self {
Stdio::Inherit => moto_rt::process::STDIO_INHERIT,
Stdio::Null => moto_rt::process::STDIO_NULL,
Stdio::MakePipe => moto_rt::process::STDIO_MAKE_PIPE,
Stdio::ParentStdin => moto_rt::process::STDIO_PARENT_STDIN,
Stdio::ParentStdout => moto_rt::process::STDIO_PARENT_STDOUT,
Stdio::ParentStderr => moto_rt::process::STDIO_PARENT_STDERR,
Stdio::Fd(fd) => fd.as_raw_fd(),
}
}
}
#[derive(Default)]
pub struct Command {
program: String,
args: Vec<String>,
cwd: Option<String>,
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
env: CommandEnv,
}
impl Command {
pub fn new(program: &OsStr) -> Command {
Command { program: program.as_str().to_owned(), ..Default::default() }
}
pub fn arg(&mut self, arg: &OsStr) {
self.args.push(arg.as_str().to_owned())
}
pub fn env_mut(&mut self) -> &mut CommandEnv {
&mut self.env
}
pub fn cwd(&mut self, dir: &OsStr) {
self.cwd = Some(dir.as_str().to_owned())
}
pub fn stdin(&mut self, stdin: Stdio) {
self.stdin = Some(stdin);
}
pub fn stdout(&mut self, stdout: Stdio) {
self.stdout = Some(stdout);
}
pub fn stderr(&mut self, stderr: Stdio) {
self.stderr = Some(stderr);
}
pub fn get_program(&self) -> &OsStr {
OsStr::new(self.program.as_str())
}
pub fn get_args(&self) -> CommandArgs<'_> {
let iter = self.args.iter();
CommandArgs { iter }
}
pub fn get_envs(&self) -> CommandEnvs<'_> {
self.env.iter()
}
pub fn get_env_clear(&self) -> bool {
self.env.does_clear()
}
pub fn get_resolved_envs(&self) -> CommandResolvedEnvs {
CommandResolvedEnvs::new(self.env.capture())
}
pub fn get_current_dir(&self) -> Option<&Path> {
self.cwd.as_ref().map(Path::new)
}
pub fn spawn(
&mut self,
default: Stdio,
needs_stdin: bool,
) -> io::Result<(Process, StdioPipes)> {
let stdin = if let Some(stdin) = self.stdin.as_ref() {
stdin.into_rt()
} else if needs_stdin {
default.into_rt()
} else {
Stdio::Null.into_rt()
};
let stdout = if let Some(stdout) = self.stdout.as_ref() {
stdout.into_rt()
} else {
default.into_rt()
};
let stderr = if let Some(stderr) = self.stderr.as_ref() {
stderr.into_rt()
} else {
default.into_rt()
};
let mut env = Vec::<(String, String)>::new();
for (k, v) in self.env.capture() {
env.push((k.as_str().to_owned(), v.as_str().to_owned()));
}
let args = moto_rt::process::SpawnArgs {
program: self.program.clone(),
args: self.args.clone(),
env,
cwd: self.cwd.clone(),
stdin,
stdout,
stderr,
};
let res = moto_rt::process::spawn(args).map_err(map_motor_error)?;
let (handle, stdin, stdout, stderr) = (res.handle, res.stdin, res.stdout, res.stderr);
Ok((
Process { handle, pid: res.pid as u32 },
StdioPipes {
stdin: if stdin >= 0 {
Some(unsafe { ChildPipe::from_raw_fd(stdin) })
} else {
None
},
stdout: if stdout >= 0 {
Some(unsafe { ChildPipe::from_raw_fd(stdout) })
} else {
None
},
stderr: if stderr >= 0 {
Some(unsafe { ChildPipe::from_raw_fd(stderr) })
} else {
None
},
},
))
}
}
pub fn output(cmd: &mut Command) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> {
let (mut process, mut pipes) = cmd.spawn(Stdio::MakePipe, false)?;
drop(pipes.stdin.take());
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
crate::thread::scope(|scope| {
let waiter = scope.spawn(move || {
let status = process.wait();
drop(process);
status
});
let read_result = match (pipes.stdout.take(), pipes.stderr.take()) {
(None, None) => Ok(()),
(Some(out), None) => out.read_to_end(&mut stdout).map(|_| ()),
(None, Some(err)) => err.read_to_end(&mut stderr).map(|_| ()),
(Some(out), Some(err)) => read_output(out, &mut stdout, err, &mut stderr),
};
let status = waiter.join().expect("child wait thread panicked");
read_result?;
Ok((status?, stdout, stderr))
})
}
impl From<crate::sys::fd::FileDesc> for Stdio {
fn from(fd: crate::sys::fd::FileDesc) -> Stdio {
Stdio::Fd(fd)
}
}
impl From<File> for Stdio {
fn from(file: File) -> Stdio {
Stdio::Fd(file.into_inner())
}
}
impl From<io::Stdout> for Stdio {
fn from(_: io::Stdout) -> Stdio {
Stdio::ParentStdout
}
}
impl From<io::Stderr> for Stdio {
fn from(_: io::Stderr) -> Stdio {
Stdio::ParentStderr
}
}
impl fmt::Debug for Command {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct ExitStatus(i32);
impl ExitStatus {
pub fn exit_ok(&self) -> Result<(), ExitStatusError> {
if self.0 == 0 { Ok(()) } else { Err(ExitStatusError(*self)) }
}
pub fn code(&self) -> Option<i32> {
Some(self.0)
}
}
impl fmt::Display for ExitStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "exit code: {}", self.0)
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct ExitStatusError(ExitStatus);
impl Into<ExitStatus> for ExitStatusError {
fn into(self) -> ExitStatus {
self.0
}
}
impl ExitStatusError {
pub fn code(self) -> Option<NonZeroI32> {
NonZeroI32::new(self.0.0)
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct ExitCode(i32);
impl ExitCode {
pub const SUCCESS: ExitCode = ExitCode(0);
pub const FAILURE: ExitCode = ExitCode(1);
pub fn as_i32(&self) -> i32 {
self.0
}
}
impl From<u8> for ExitCode {
fn from(code: u8) -> Self {
Self(code as i32)
}
}
pub struct Process {
handle: u64,
pid: u32,
}
impl Drop for Process {
fn drop(&mut self) {
moto_rt::alloc::release_handle(self.handle).unwrap();
}
}
impl Process {
pub fn id(&self) -> u32 {
// The kernel bounds pids to the i32-positive range, so the pid the
// runtime reported at spawn is exact (pid-refactoring-design.md).
self.pid
}
pub fn kill(&mut self) -> io::Result<()> {
moto_rt::process::kill(self.handle).map_err(map_motor_error)
}
pub fn wait(&mut self) -> io::Result<ExitStatus> {
moto_rt::process::wait(self.handle).map(|c| ExitStatus(c)).map_err(map_motor_error)
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
match moto_rt::process::try_wait(self.handle) {
Ok(s) => Ok(Some(ExitStatus(s))),
Err(err) => match err {
moto_rt::Error::NotReady => Ok(None),
err => Err(map_motor_error(err)),
},
}
}
#[allow(unused)]
pub fn handle(&self) -> u64 {
self.handle
}
}
pub struct CommandArgs<'a> {
iter: crate::slice::Iter<'a, String>,
}
impl<'a> Iterator for CommandArgs<'a> {
type Item = &'a OsStr;
fn next(&mut self) -> Option<&'a OsStr> {
self.iter.next().map(|arg| OsStr::new(arg))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl<'a> ExactSizeIterator for CommandArgs<'a> {
fn len(&self) -> usize {
self.iter.len()
}
fn is_empty(&self) -> bool {
self.iter.is_empty()
}
}
impl<'a> fmt::Debug for CommandArgs<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter.clone()).finish()
}
}
pub type ChildPipe = crate::sys::pipe::Pipe;
pub fn read_output(
out: ChildPipe,
stdout: &mut Vec<u8>,
err: ChildPipe,
stderr: &mut Vec<u8>,
) -> io::Result<()> {
// Drain both pipes concurrently so the child can't deadlock filling one
// pipe while we block reading the other.
crate::thread::scope(|s| {
let err_reader = s.spawn(move || err.read_to_end(stderr));
let out_res = out.read_to_end(stdout);
let err_res = err_reader.join().expect("stderr reader thread panicked");
out_res?;
err_res?;
Ok(())
})
}
pub fn getpid() -> u32 {
// Motor OS pids are u64 in the ABI, but the kernel bounds them to i32
// to be compatible with the wider ecosystem.
moto_rt::process::current_pid().try_into().expect("current_pid() too large")
}