reverie-process/src/builder.rs (357 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ use std::borrow::Cow; use std::collections::BTreeMap; use std::ffi::{OsStr, OsString}; use std::io; use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use syscalls::Errno; use super::seccomp; use super::util::to_cstring; use super::util::CStringArray; use super::Command; use super::Container; use super::Mount; use super::Namespace; use super::PtyChild; use super::Stdio; impl Command { /// Constructs a new `Command` for launching the program at path `program`, /// with the following default configuration: /// /// * No arguments to the program /// * Inherit the current process's environment /// * Inherit the current process's working directory /// * Inherit stdin/stdout/stderr for `spawn` or `status`, but create pipes /// for `output` /// /// Builder methods are provided to change these defaults and /// otherwise configure the process. /// /// If `program` is not an absolute path, the `PATH` will be searched in an /// OS-defined way. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::Command; /// let command = Command::new("sh"); /// ``` pub fn new<S: AsRef<OsStr>>(program: S) -> Self { let program = to_cstring(program); let mut args = CStringArray::with_capacity(1); args.push(program.clone()); Self { program, args, pre_exec: Vec::new(), container: Container::new(), } } /// Sets the path to the program. This can be used to override what was /// already set in [`Command::new`]. /// /// NOTE: This also changes argument 0 to match `program`. pub fn program<S: AsRef<OsStr>>(&mut self, program: S) -> &mut Self { let cstring = to_cstring(program); self.program = cstring.clone(); self.args.set(0, cstring); self } /// Explicitly sets the first argument. By default, this is the same as the /// program path and is what you want in most cases. pub fn arg0<S: AsRef<OsStr>>(&mut self, arg0: S) -> &mut Self { self.args.set(0, to_cstring(arg0)); self } /// Gets the first argument. Unless [`Command::arg0`] was used, this returns /// the same string as [`Command::get_program`]. pub fn get_arg0(&self) -> &OsStr { OsStr::from_bytes(self.args.get(0).to_bytes()) } /// Adds an argument to pass to the program. /// /// Only one argument can be passed per use. So instead of: /// /// ```no_run /// reverie_process::Command::new("sh") /// .arg("-C /path/to/repo"); /// ``` /// /// usage would be: /// /// ```no_run /// reverie_process::Command::new("sh") /// .arg("-C") /// .arg("/path/to/repo"); /// ``` /// /// To pass multiple arguments see [`args`]. /// /// [`args`]: method@Self::args /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .arg("-l") /// .arg("-a"); /// ``` pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self { self.args.push(to_cstring(arg)); self } /// Adds multiple arguments to pass to the program. /// /// To pass a single argument see [`arg`]. /// /// [`arg`]: method@Self::arg /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .args(&["-l", "-a"]); /// ``` pub fn args<I, S>(&mut self, args: I) -> &mut Self where I: IntoIterator<Item = S>, S: AsRef<OsStr>, { for arg in args { self.arg(arg); } self } /// Returns an iterator of the arguments that will be passed to the program. /// /// This does not include the program name itself. It only includes the /// arguments specified with [`Command::arg`] and [`Command::args`]. pub fn get_args(&self) -> impl Iterator<Item = &OsStr> { self.args .iter() .skip(1) .map(|arg| OsStr::from_bytes(arg.to_bytes())) } /// Prepends arguments to the beginning of the command. Note that arguments /// are prepended *after* arg0, but before the rest of the arguments. pub fn prepend_args<I, S>(&mut self, args: I) -> &mut Self where I: IntoIterator<Item = S>, S: AsRef<OsStr>, { let new_args = CStringArray::with_capacity(self.args.len() + 1); let mut old_args = core::mem::replace(&mut self.args, new_args).into_iter(); // Add arg0 first if let Some(arg0) = old_args.next() { self.args.push(arg0); } // Add the new arguments self.args(args); // Add the rest of the old arguments for arg in old_args { self.args.push(arg); } self } /// Inserts or updates an environment variable mapping. /// /// Note that environment variable names are case-insensitive (but /// case-preserving) on Windows, and case-sensitive on all other platforms. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .env("PATH", "/bin"); /// ``` pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self where K: AsRef<OsStr>, V: AsRef<OsStr>, { self.container.env(key, val); self } /// Adds or updates multiple environment variable mappings. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::{Command, Stdio}; /// use std::env; /// use std::collections::HashMap; /// /// let filtered_env : HashMap<String, String> = /// env::vars().filter(|&(ref k, _)| /// k == "TERM" || k == "TZ" || k == "LANG" || k == "PATH" /// ).collect(); /// /// let command = Command::new("printenv") /// .stdin(Stdio::null()) /// .stdout(Stdio::inherit()) /// .env_clear() /// .envs(&filtered_env); /// ``` pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>, { self.container.envs(vars); self } /// Removes an environment variable mapping. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .env_remove("PATH"); /// ``` pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self { self.container.env_remove(key); self } /// Clears the entire environment map for the child process. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .env_clear(); /// ``` pub fn env_clear(&mut self) -> &mut Self { self.container.env_clear(); self } /// Sets the working directory for the child process. /// /// # Interaction with `chroot` /// /// The working directory is set *after* the chroot is performed (if a chroot /// directory is specified). Thus, the path given is relative to the chroot /// directory. Otherwise, if no chroot directory is specified, the working /// directory is relative to the current working directory of the parent /// process at the time the child process is spawned. /// /// # Platform-specific behavior /// /// If the program path is relative (e.g., `"./script.sh"`), it's ambiguous /// whether it should be interpreted relative to the parent's working /// directory or relative to `current_dir`. The behavior in this case is /// platform specific and unstable, and it's recommended to use /// [`canonicalize`] to get an absolute program path instead. /// /// [`canonicalize`]: std::fs::canonicalize() /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .current_dir("/bin"); /// ``` pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self { self.container.current_dir(dir); self } /// Sets configuration for the child process's standard input (stdin) handle. /// /// Defaults to [`Stdio::inherit`] when used with `spawn` or `status`, and /// defaults to [`Stdio::piped`] when used with `output`. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::{Command, Stdio}; /// /// let command = Command::new("ls") /// .stdin(Stdio::null()); /// ``` pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self { self.container.stdin(cfg); self } /// Sets configuration for the child process's standard output (stdout) /// handle. /// /// Defaults to [`Stdio::inherit`] when used with `spawn` or `status`, and /// defaults to [`Stdio::piped`] when used with `output`. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::{Command, Stdio}; /// /// let command = Command::new("ls") /// .stdout(Stdio::null()); /// ``` pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self { self.container.stdout(cfg); self } /// Sets configuration for the child process's standard error (stderr) /// handle. /// /// Defaults to [`Stdio::inherit`] when used with `spawn` or `status`, and /// defaults to [`Stdio::piped`] when used with `output`. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use reverie_process::{Command, Stdio}; /// /// let command = Command::new("ls") /// .stderr(Stdio::null()); /// ``` pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self { self.container.stderr(cfg); self } /// Changes the root directory of the calling process to the specified path. /// This directory will be inherited by all child processes of the calling /// process. /// /// Note that changing the root directory may cause the program to not be /// found. As such, the program path should be relative to this directory. pub fn chroot<P: AsRef<Path>>(&mut self, chroot: P) -> &mut Self { self.container.chroot(chroot); self } /// Unshares parts of the process execution context that are normally shared /// with the parent process. This is useful for executing the child process /// in a new namespace. pub fn unshare(&mut self, namespace: Namespace) -> &mut Self { self.container.unshare(namespace); self } /// Schedules a closure to be run just before the `exec` function is invoked. /// /// The closure is allowed to return an I/O error whose OS error code will be /// communicated back to the parent and returned as an error from when the /// spawn was requested. /// /// Multiple closures can be registered and they will be called in order of /// their registration. If a closure returns `Err` then no further closures /// will be called and the spawn operation will immediately return with a /// failure. /// /// # Safety /// /// This closure will be run in the context of the child process after a /// `fork`. This primarily means that any modifications made to memory on /// behalf of this closure will **not** be visible to the parent process. /// This is often a very constrained environment where normal operations like /// `malloc` or acquiring a mutex are not guaranteed to work (due to other /// threads perhaps still running when the `fork` was run). /// /// This also means that all resources such as file descriptors and /// memory-mapped regions got duplicated. It is your responsibility to make /// sure that the closure does not violate library invariants by making /// invalid use of these duplicates. /// /// When this closure is run, aspects such as the stdio file descriptors and /// working directory have successfully been changed, so output to these /// locations may not appear where intended. pub unsafe fn pre_exec<F>(&mut self, f: F) -> &mut Self where F: FnMut() -> Result<(), Errno> + Send + Sync + 'static, { self.pre_exec.push(Box::new(f)); self } /// Returns the path to the program that was given to [`Command::new`]. /// /// # Examples /// /// ``` /// use reverie_process::Command; /// /// let cmd = Command::new("echo"); /// assert_eq!(cmd.get_program(), "echo"); /// ``` pub fn get_program(&self) -> &OsStr { OsStr::from_bytes(self.program.to_bytes()) } /// Returns the working directory for the child process. /// /// This returns None if the working directory will not be changed. pub fn get_current_dir(&self) -> Option<&Path> { self.container.get_current_dir() } /// Returns an iterator of the environment variables that will be set when /// the process is spawned. Note that this does not include any environment /// variables inherited from the parent process. pub fn get_envs(&self) -> impl Iterator<Item = (&OsStr, Option<&OsStr>)> { self.container.get_envs() } /// Returns a mapping of all environment variables that the new child process /// will inherit. pub fn get_captured_envs(&self) -> BTreeMap<OsString, OsString> { self.container.get_captured_envs() } /// Gets an environment variable. If the child process is to inherit this /// environment variable from the current process, then this returns the /// current process's environment variable unless it is to be overridden. pub fn get_env<K: AsRef<OsStr>>(&self, env: K) -> Option<Cow<OsStr>> { self.container.get_env(env) } /// Maps one user ID to another. /// /// Implies `Namespace::USER`. /// /// # Example /// /// This is can be used to gain `CAP_SYS_ADMIN` privileges in the user /// namespace by mapping the root user inside the container to the current /// user outside of the container. /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .map_uid(1, unsafe { libc::getuid() }); /// ``` /// /// # Implementation /// /// This modifies `/proc/{pid}/uid_map` where `{pid}` is the PID of the child /// process. See [`user_namespaces(7)`] for more details. /// /// [`user_namespaces(7)`]: https://man7.org/linux/man-pages/man7/user_namespaces.7.html pub fn map_uid(&mut self, inside_uid: libc::uid_t, outside_uid: libc::uid_t) -> &mut Self { self.container.map_uid(inside_uid, outside_uid); self } /// Maps potentially many user IDs inside the new user namespace to user IDs /// outside of the user namespace. /// /// Implies `Namespace::USER`. /// /// # Implementation /// /// This modifies `/proc/{pid}/uid_map` where `{pid}` is the PID of the child /// process. See [`user_namespaces(7)`] for more details. /// /// [`user_namespaces(7)`]: https://man7.org/linux/man-pages/man7/user_namespaces.7.html pub fn map_uid_range( &mut self, starting_inside_uid: libc::uid_t, starting_outside_uid: libc::uid_t, count: u32, ) -> &mut Self { self.container .map_uid_range(starting_inside_uid, starting_outside_uid, count); self } /// Convience function for mapping root (inside the container) to the current /// user ID (outside the container). This is useful for gaining new /// capabilities inside the container, such as being able to mount file /// systems. /// /// Implies `Namespace::USER`. /// /// This is the same as: /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("ls") /// .map_uid(0, unsafe { libc::geteuid() }) /// .map_gid(0, unsafe { libc::getegid() }); /// ``` pub fn map_root(&mut self) -> &mut Self { self.container.map_root(); self } /// Maps one group ID to another. /// /// Implies `Namespace::USER`. /// /// # Implementation /// /// This modifies `/proc/{pid}/gid_map` where `{pid}` is the PID of the child /// process. See [`user_namespaces(7)`] for more details. /// /// [`user_namespaces(7)`]: https://man7.org/linux/man-pages/man7/user_namespaces.7.html pub fn map_gid(&mut self, inside_gid: libc::gid_t, outside_gid: libc::gid_t) -> &mut Self { self.container.map_gid(inside_gid, outside_gid); self } /// Maps potentially many group IDs inside the new user namespace to group /// IDs outside of the user namespace. /// /// Implies `Namespace::USER`. /// /// # Implementation /// /// This modifies `/proc/{pid}/gid_map` where `{pid}` is the PID of the child /// process. See [`user_namespaces(7)`] for more details. /// /// [`user_namespaces(7)`]: https://man7.org/linux/man-pages/man7/user_namespaces.7.html pub fn map_gid_range( &mut self, starting_inside_gid: libc::gid_t, starting_outside_gid: libc::gid_t, count: u32, ) -> &mut Self { self.container .map_gid_range(starting_inside_gid, starting_outside_gid, count); self } /// Sets the hostname of the container. /// /// Implies `Namespace::UTS`, which requires `CAP_SYS_ADMIN`. /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("cat") /// .arg("/proc/sys/kernel/hostname") /// .map_root() /// .hostname("foobar.local"); /// ``` pub fn hostname<S: Into<OsString>>(&mut self, hostname: S) -> &mut Self { self.container.hostname(hostname); self } /// Sets the domain name of the container. /// /// Implies `Namespace::UTS`, which requires `CAP_SYS_ADMIN`. /// /// # Example /// /// ```no_run /// use reverie_process::Command; /// /// let command = Command::new("cat") /// .arg("/proc/sys/kernel/domainname") /// .map_root() /// .domainname("foobar"); /// ``` pub fn domainname<S: Into<OsString>>(&mut self, domainname: S) -> &mut Self { self.container.domainname(domainname); self } /// Gets the hostname of the container. pub fn get_hostname(&self) -> Option<&OsStr> { self.container.get_hostname() } /// Gets the domainname of the container. pub fn get_domainname(&self) -> Option<&OsStr> { self.container.get_domainname() } /// Adds a file system to be mounted. Note that these are mounted in the same /// order as given. /// /// Implies `Namespace::MOUNT`. Note that `Namespace::USER` should also have /// been set and `map_uid` should have been called in order to gain the /// privileges required to mount. pub fn mount(&mut self, mount: Mount) -> &mut Self { self.container.mount(mount); self } /// Adds multiple mounts. pub fn mounts<I>(&mut self, mounts: I) -> &mut Self where I: IntoIterator<Item = Mount>, { self.container.mounts(mounts); self } /// Sets up the container to have local networking only. This will prevent /// any network communication to the outside world. /// /// Implies `Namespace::NETWORK` and `Namespace::MOUNT`. /// /// This also causes a fresh `/sys` to be mounted to avoid seeing the host /// network interfaces in `/sys/class/net`. pub fn local_networking_only(&mut self) -> &mut Self { self.container.local_networking_only(); self } /// Sets the seccomp filter. The filter is loaded immediately before `execve` /// and *after* all `pre_exec` callbacks have been executed. Thus, you will /// still be able to call filtered syscalls from `pre_exec` callbacks. pub fn seccomp(&mut self, filter: seccomp::Filter) -> &mut Self { self.container.seccomp(filter); self } /// Indicates that we want to listen for seccomp events using /// [seccomp_unotify(2)](https://man7.org/linux/man-pages/man2/seccomp_unotify.2.html). /// /// If this is set, the seccomp listener file descriptor will be accessible /// via the `Child`. pub fn seccomp_notify(&mut self) -> &mut Self { self.container.seccomp_notify(); self } /// Sets the controlling pseudoterminal for the child process). /// /// In the child process, this has the effect of: /// 1. Creating a new session (with `setsid()`). /// 2. Using an `ioctl` to set the controlling terminal. /// 3. Setting this file descriptor as the stdio streams. /// /// NOTE: Since this modifies the stdio streams, calling this will reset /// [`Self::stdin`], [`Self::stdout`], and [`Self::stderr`] back to /// [`Stdio::inherit()`]. pub fn pty(&mut self, child: PtyChild) -> &mut Self { self.container.pty(child); self } /// Finds the path to the program. pub fn find_program(&self) -> io::Result<PathBuf> { let program = Path::new(self.get_program()); if program.is_absolute() { // Note: We shouldn't canonicalize here since that will follow // symlinks. Instead, just make sure the file exists and is // executable. let metadata = program.metadata()?; if metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 { Ok(program.to_path_buf()) } else { Err(Errno::EPERM.into()) } } else if program.components().count() == 1 { let path = self.get_env("PATH").unwrap_or_default(); let paths = path .as_bytes() .split(|c| *c == b':') .map(|bytes| Path::new(OsStr::from_bytes(bytes))); find_program_in_paths(program, paths) .ok_or_else(|| { io::Error::new( io::ErrorKind::Other, format!("Could not find {:?} in $PATH", program), ) })? .canonicalize() } else { // Assume it's in the current directory let mut path = match self.get_current_dir() { Some(path) => path.to_owned(), None => std::env::current_dir()?, }; path.push(program); path.canonicalize() } } } fn find_program_in_paths<I, S>(program: &Path, iter: I) -> Option<PathBuf> where I: IntoIterator<Item = S>, S: AsRef<Path>, { for path in iter.into_iter() { let path = path.as_ref().join(program); if let Ok(metadata) = path.metadata() { if metadata.is_file() { if metadata.permissions().mode() & 0o111 != 0 { return Some(path); } else { continue; } #[cfg(not(unix))] return Some(path); } } } None } #[cfg(test)] mod tests { use super::*; #[test] fn find_program() { assert!(Command::new("cat").find_program().unwrap().is_absolute(),); } #[test] fn get_program() { assert_eq!(Command::new("cat").get_program(), "cat"); } #[test] fn get_arg0() { assert_eq!(Command::new("cat").get_arg0(), "cat"); assert_eq!(Command::new("cat").arg0("dog").get_arg0(), "dog"); assert_eq!( Command::new("cat").arg0("dog").program("catdog").get_arg0(), "catdog" ); } #[test] fn get_args() { assert_eq!( Command::new("cat") .arg("a") .arg("b") .arg("c") .get_args() .collect::<Vec<_>>(), &[OsStr::new("a"), OsStr::new("b"), OsStr::new("c")] ); } #[test] fn prepend_args() { let mut command = Command::new("echo"); command.args(["1", "2", "3"]); command.prepend_args(["a", "b", "c"]); assert_eq!(command.get_arg0(), "echo"); let args = command.get_args().collect::<Vec<_>>(); assert_eq!( args, &[ OsStr::new("a"), "b".as_ref(), "c".as_ref(), "1".as_ref(), "2".as_ref(), "3".as_ref() ] ); } }