├── tests ├── .gitignore ├── test_static.c └── test_basic.rs ├── examples └── README.md ├── .gitignore ├── .github └── dependabot.yml ├── Cargo.toml ├── src ├── cvt.rs ├── output.rs ├── lib.rs ├── command_env.rs ├── anon_pipe.rs ├── stdio.rs ├── file_desc.rs ├── process.rs ├── child.rs └── executable.rs ├── README.md └── Cargo.lock /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.bin -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # memfd_exec examples 2 | 3 | For now, all examples are tests, which you can check out [here](../tests/). Feel free 4 | to PR your own! -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | # Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memfd-exec" 3 | version = "0.2.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Run an executable directly from memory with a friendly interface." 7 | repository = "https://github.com/novafacing/memfd-exec.git" 8 | readme = "README.md" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | [dev-dependencies] 12 | tempfile = "3.10.1" 13 | tempfile = "3.5.0" 14 | reqwest = { version = "0.12.4", features = ["blocking"] } 15 | 16 | [dependencies] 17 | nix = "0.28.0" 18 | libc = "0.2.154" 19 | -------------------------------------------------------------------------------- /tests/test_static.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* Bind socket to INADDR_ANY, print out the port then print out the data received. */ 8 | int main() { 9 | int sock = socket(AF_INET, SOCK_STREAM, 0); 10 | struct sockaddr_in addr; 11 | addr.sin_family = AF_INET; 12 | addr.sin_port = htons(0); 13 | addr.sin_addr.s_addr = INADDR_ANY; 14 | int rc = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); 15 | assert(rc == 0); 16 | 17 | assert(listen(sock, 1) == 0); 18 | 19 | socklen_t addr_len = sizeof(addr); 20 | getsockname(sock, (struct sockaddr*)&addr, &addr_len); 21 | assert(addr.sin_port > 0); 22 | assert(printf("%hu", ntohs(addr.sin_port)) > 0); 23 | assert(fflush(stdout) == 0); 24 | 25 | int client = accept(sock, NULL, NULL); 26 | char buf[1024]; 27 | int len = read(client, buf, sizeof(buf)); 28 | assert(write(STDOUT_FILENO, buf, len) > 0); 29 | 30 | assert(close(client) == 0); 31 | assert(close(sock) == 0); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/cvt.rs: -------------------------------------------------------------------------------- 1 | //! I don't know what this is for but FileDesc uses it...idk, here it is. Copied from: 2 | //! 3 | 4 | use std::io::{Error, ErrorKind, Result}; 5 | 6 | pub trait IsMinusOne { 7 | fn is_minus_one(&self) -> bool; 8 | } 9 | 10 | macro_rules! impl_is_minus_one { 11 | ($($t:ident)*) => ($(impl IsMinusOne for $t { 12 | fn is_minus_one(&self) -> bool { 13 | *self == -1 14 | } 15 | })*) 16 | } 17 | 18 | impl_is_minus_one! { i8 i16 i32 i64 isize } 19 | 20 | pub fn cvt(t: T) -> Result { 21 | if t.is_minus_one() { 22 | Err(Error::last_os_error()) 23 | } else { 24 | Ok(t) 25 | } 26 | } 27 | 28 | pub fn cvt_r(mut f: F) -> Result 29 | where 30 | T: IsMinusOne, 31 | F: FnMut() -> T, 32 | { 33 | loop { 34 | match cvt(f()) { 35 | Err(ref e) if e.kind() == ErrorKind::Interrupted => {} 36 | other => return other, 37 | } 38 | } 39 | } 40 | 41 | pub fn cvt_nz(error: libc::c_int) -> Result<()> { 42 | if error == 0 { 43 | Ok(()) 44 | } else { 45 | Err(Error::from_raw_os_error(error)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/output.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter, Result}; 2 | use std::str::from_utf8; 3 | 4 | use crate::process::ExitStatus; 5 | 6 | /// The output of a child process, including the exit status and output streams. 7 | #[derive(PartialEq, Clone, Eq)] 8 | pub struct Output { 9 | /// The exit status of the child process 10 | pub status: ExitStatus, 11 | /// The data that the child process wrote to stdout 12 | pub stdout: Vec, 13 | /// The data that the child process wrote to stderr 14 | pub stderr: Vec, 15 | } 16 | 17 | impl Debug for Output { 18 | fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { 19 | let stdout_utf8 = from_utf8(&self.stdout); 20 | let stdout_debug: &dyn Debug = match stdout_utf8 { 21 | Ok(ref str) => str, 22 | Err(_) => &self.stdout, 23 | }; 24 | 25 | let stderr_utf8 = from_utf8(&self.stderr); 26 | let stderr_debug: &dyn Debug = match stderr_utf8 { 27 | Ok(ref str) => str, 28 | Err(_) => &self.stderr, 29 | }; 30 | 31 | fmt.debug_struct("Output") 32 | .field("status", &self.status) 33 | .field("stdout", stdout_debug) 34 | .field("stderr", stderr_debug) 35 | .finish() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a very simple crate that allows execution of in-memory only programs. Simply 2 | //! put, if you have the contents of a Linux executable in a `Vec`, you can use 3 | //! `memfd_exec` to execute the program without it ever touching your hard disk. Use 4 | //! cases for this may include: 5 | //! 6 | //! * Bundling a static executable with another program (for example, my motivation to 7 | //! create this package is that I want to ship a statically built QEMU with 8 | //! [cantrace](https://github.com/novafacing/cannoli)) 9 | //! * Sending executables over the network and running them, to reduce footprint and increase 10 | //! throughput 11 | //! 12 | //! # Example 13 | //! 14 | //! The following example will download and run a qemu-x86_64 executable from the internet, 15 | //! without ever writing the executable to disk. 16 | //! 17 | //! ``` 18 | //! use memfd_exec::{MemFdExecutable, Stdio}; 19 | //! use reqwest::blocking::get; 20 | //! 21 | //! const URL: &str = "https://novafacing.github.io/assets/qemu-x86_64"; 22 | //! let resp = get(URL).unwrap(); 23 | //! 24 | //! // The `MemFdExecutable` struct is at near feature-parity with `std::process::Command`, 25 | //! // so you can use it in the same way. The only difference is that you must provide the 26 | //! // executable contents as a `Vec` as well as telling it the argv[0] to use. 27 | //! let qemu = MemFdExecutable::new("qemu-x86_64", resp.bytes().unwrap().as_ref()) 28 | //! // We'll just get the version here, but you can do anything you want with the 29 | //! // args. 30 | //! .arg("-version") 31 | //! // We'll capture the stdout of the process, so we need to set up a pipe. 32 | //! .stdout(Stdio::piped()) 33 | //! // Spawn the process as a forked child 34 | //! .spawn() 35 | //! .unwrap(); 36 | //! 37 | //! // Get the output and status code of the process (this will block until the process 38 | //! // exits) 39 | //! let output = qemu.wait_with_output().unwrap(); 40 | //! assert!(output.status.into_raw() == 0); 41 | //! // Print out the version we got! 42 | //! println!("{}", String::from_utf8_lossy(&output.stdout)); 43 | //! ``` 44 | //! 45 | // #![feature(exact_size_is_empty)] 46 | // #![feature(exit_status_error)] 47 | // #![feature(raw_os_nonzero)] 48 | // #![feature(read_buf)] 49 | // #![feature(can_vector)] 50 | // #![feature(never_type)] 51 | 52 | mod anon_pipe; 53 | mod child; 54 | mod command_env; 55 | mod cvt; 56 | mod executable; 57 | mod file_desc; 58 | mod output; 59 | mod process; 60 | mod stdio; 61 | 62 | pub use child::{Child, ChildStderr, ChildStdin, ChildStdout}; 63 | pub use executable::MemFdExecutable; 64 | pub use output::Output; 65 | pub use process::ExitStatus; 66 | pub use stdio::Stdio; 67 | -------------------------------------------------------------------------------- /src/command_env.rs: -------------------------------------------------------------------------------- 1 | //! Wholesale copied from rust-lang/rust/library/std/src/sys_common/process.rs 2 | 3 | use std::collections::BTreeMap; 4 | use std::env; 5 | use std::ffi::{OsStr, OsString}; 6 | 7 | // Stores a set of changes to an environment 8 | #[derive(Clone, Debug, Default)] 9 | pub struct CommandEnv { 10 | clear: bool, 11 | saw_path: bool, 12 | vars: BTreeMap>, 13 | } 14 | 15 | impl CommandEnv { 16 | // Capture the current environment with these changes applied 17 | pub fn capture(&self) -> BTreeMap { 18 | let mut result = BTreeMap::::new(); 19 | if !self.clear { 20 | for (k, v) in env::vars_os() { 21 | result.insert(k, v); 22 | } 23 | } 24 | for (k, maybe_v) in &self.vars { 25 | match maybe_v { 26 | Some(v) => { 27 | result.insert(k.clone(), v.clone()); 28 | } 29 | _ => { 30 | result.remove(k); 31 | } 32 | } 33 | } 34 | result 35 | } 36 | 37 | pub fn is_unchanged(&self) -> bool { 38 | !self.clear && self.vars.is_empty() 39 | } 40 | 41 | pub fn capture_if_changed(&self) -> Option> { 42 | if self.is_unchanged() { 43 | None 44 | } else { 45 | Some(self.capture()) 46 | } 47 | } 48 | 49 | // The following functions build up changes 50 | pub fn set(&mut self, key: &OsStr, value: &OsStr) { 51 | let key = OsString::from(key); 52 | self.maybe_saw_path(&key); 53 | self.vars.insert(key, Some(value.to_owned())); 54 | } 55 | 56 | pub fn remove(&mut self, key: &OsStr) { 57 | let key = OsString::from(key); 58 | self.maybe_saw_path(&key); 59 | if self.clear { 60 | self.vars.remove(&key); 61 | } else { 62 | self.vars.insert(key, None); 63 | } 64 | } 65 | 66 | pub fn clear(&mut self) { 67 | self.clear = true; 68 | self.vars.clear(); 69 | } 70 | 71 | pub fn have_changed_path(&self) -> bool { 72 | self.saw_path || self.clear 73 | } 74 | 75 | fn maybe_saw_path(&mut self, key: &OsString) { 76 | if !self.saw_path && key == "PATH" { 77 | self.saw_path = true; 78 | } 79 | } 80 | } 81 | 82 | #[derive(Debug)] 83 | pub struct CommandEnvs<'a> { 84 | iter: std::collections::btree_map::Iter<'a, OsString, Option>, 85 | } 86 | 87 | impl<'a> Iterator for CommandEnvs<'a> { 88 | type Item = (&'a OsStr, Option<&'a OsStr>); 89 | fn next(&mut self) -> Option { 90 | self.iter 91 | .next() 92 | .map(|(key, value)| (key.as_ref(), value.as_deref())) 93 | } 94 | fn size_hint(&self) -> (usize, Option) { 95 | self.iter.size_hint() 96 | } 97 | } 98 | 99 | impl<'a> ExactSizeIterator for CommandEnvs<'a> { 100 | fn len(&self) -> usize { 101 | self.iter.len() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/anon_pipe.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{IoSlice, IoSliceMut, Result}, 3 | mem::zeroed, 4 | os::unix::prelude::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}, 5 | }; 6 | 7 | use crate::{ 8 | cvt::{cvt, cvt_r}, 9 | file_desc::FileDesc, 10 | }; 11 | 12 | pub struct AnonPipe(FileDesc); 13 | 14 | pub fn anon_pipe() -> Result<(AnonPipe, AnonPipe)> { 15 | let mut fds = [0; 2]; 16 | unsafe { 17 | cvt(libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC))?; 18 | Ok(( 19 | AnonPipe(FileDesc::from_raw_fd(fds[0])), 20 | AnonPipe(FileDesc::from_raw_fd(fds[1])), 21 | )) 22 | } 23 | } 24 | 25 | impl AnonPipe { 26 | pub fn read(&self, buf: &mut [u8]) -> Result { 27 | self.0.read(buf) 28 | } 29 | 30 | pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> Result { 31 | self.0.read_vectored(bufs) 32 | } 33 | 34 | #[inline] 35 | pub fn is_read_vectored(&self) -> bool { 36 | self.0.is_read_vectored() 37 | } 38 | 39 | pub fn write(&self, buf: &[u8]) -> Result { 40 | self.0.write(buf) 41 | } 42 | 43 | pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> Result { 44 | self.0.write_vectored(bufs) 45 | } 46 | 47 | #[inline] 48 | pub fn is_write_vectored(&self) -> bool { 49 | self.0.is_write_vectored() 50 | } 51 | 52 | pub fn set_nonblocking(&self, nonblocking: bool) -> Result<()> { 53 | self.0.set_nonblocking(nonblocking) 54 | } 55 | 56 | pub fn read_to_end(&self, buf: &mut Vec) -> Result { 57 | self.0.read_to_end(buf) 58 | } 59 | } 60 | 61 | impl AsRawFd for AnonPipe { 62 | fn as_raw_fd(&self) -> RawFd { 63 | self.0.as_raw_fd() 64 | } 65 | } 66 | 67 | impl AsFd for AnonPipe { 68 | fn as_fd(&self) -> BorrowedFd<'_> { 69 | self.0.as_fd() 70 | } 71 | } 72 | 73 | impl IntoRawFd for AnonPipe { 74 | fn into_raw_fd(self) -> RawFd { 75 | self.0.into_raw_fd() 76 | } 77 | } 78 | 79 | impl FromRawFd for AnonPipe { 80 | unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { 81 | Self(FromRawFd::from_raw_fd(raw_fd)) 82 | } 83 | } 84 | 85 | impl From for FileDesc { 86 | fn from(p: AnonPipe) -> FileDesc { 87 | p.0 88 | } 89 | } 90 | 91 | pub fn read2(p1: AnonPipe, v1: &mut Vec, p2: AnonPipe, v2: &mut Vec) -> Result<()> { 92 | // Set both pipes into nonblocking mode as we're gonna be reading from both 93 | // in the `select` loop below, and we wouldn't want one to block the other! 94 | p1.set_nonblocking(true)?; 95 | p2.set_nonblocking(true)?; 96 | 97 | let mut fds: [libc::pollfd; 2] = unsafe { zeroed() }; 98 | fds[0].fd = p1.as_raw_fd(); 99 | fds[0].events = libc::POLLIN; 100 | fds[1].fd = p2.as_raw_fd(); 101 | fds[1].events = libc::POLLIN; 102 | loop { 103 | // wait for either pipe to become readable using `poll` 104 | cvt_r(|| unsafe { libc::poll(fds.as_mut_ptr(), 2, -1) })?; 105 | 106 | if fds[0].revents != 0 && read(&p1.0, v1)? { 107 | p2.set_nonblocking(false)?; 108 | return p2.read_to_end(v2).map(drop); 109 | } 110 | if fds[1].revents != 0 && read(&p2.0, v2)? { 111 | p1.set_nonblocking(false)?; 112 | return p1.read_to_end(v1).map(drop); 113 | } 114 | } 115 | 116 | // Read as much as we can from each pipe, ignoring EWOULDBLOCK or 117 | // EAGAIN. If we hit EOF, then this will happen because the underlying 118 | // reader will return Ok(0), in which case we'll see `Ok` ourselves. In 119 | // this case we flip the other fd back into blocking mode and read 120 | // whatever's leftover on that file descriptor. 121 | fn read(fd: &FileDesc, dst: &mut Vec) -> Result { 122 | match fd.read_to_end(dst) { 123 | Ok(_) => Ok(true), 124 | Err(e) => { 125 | if e.raw_os_error() == Some(libc::EWOULDBLOCK) 126 | || e.raw_os_error() == Some(libc::EAGAIN) 127 | { 128 | Ok(false) 129 | } else { 130 | Err(e) 131 | } 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/stdio.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::fs::{File, OpenOptions}; 3 | use std::io::Result; 4 | use std::os::raw::c_int; 5 | use std::os::unix::prelude::{AsRawFd, FromRawFd}; 6 | use std::path::Path; 7 | 8 | use crate::anon_pipe::{anon_pipe, AnonPipe}; 9 | use crate::file_desc::FileDesc; 10 | 11 | const DEV_NULL: &str = "/dev/null\0"; 12 | 13 | pub struct StdioPipes { 14 | pub stdin: Option, 15 | pub stdout: Option, 16 | pub stderr: Option, 17 | } 18 | 19 | pub struct ChildPipes { 20 | pub stdin: ChildStdio, 21 | pub stdout: ChildStdio, 22 | pub stderr: ChildStdio, 23 | } 24 | 25 | pub enum ChildStdio { 26 | Inherit, 27 | Explicit(c_int), 28 | Owned(FileDesc), 29 | } 30 | 31 | /// Description of a stdio stream for a child process 32 | #[derive(Debug)] 33 | pub enum Stdio { 34 | /// Inherit the parent's stdio stream 35 | Inherit, 36 | /// Use a null stream, like /dev/null 37 | Null, 38 | /// Use a pipe to the input or output of the child process 39 | MakePipe, 40 | /// Use an existing file descriptor as the stdio stream 41 | Fd(FileDesc), 42 | } 43 | 44 | impl Stdio { 45 | pub fn to_child_stdio(&self, readable: bool) -> Result<(ChildStdio, Option)> { 46 | match *self { 47 | Stdio::Inherit => Ok((ChildStdio::Inherit, None)), 48 | 49 | // Make sure that the source descriptors are not an stdio 50 | // descriptor, otherwise the order which we set the child's 51 | // descriptors may blow away a descriptor which we are hoping to 52 | // save. For example, suppose we want the child's stderr to be the 53 | // parent's stdout, and the child's stdout to be the parent's 54 | // stderr. No matter which we dup first, the second will get 55 | // overwritten prematurely. 56 | Stdio::Fd(ref fd) => { 57 | if fd.as_raw_fd() >= 0 && fd.as_raw_fd() <= libc::STDERR_FILENO { 58 | Ok((ChildStdio::Owned(fd.duplicate()?), None)) 59 | } else { 60 | Ok((ChildStdio::Explicit(fd.as_raw_fd()), None)) 61 | } 62 | } 63 | 64 | Stdio::MakePipe => { 65 | let (reader, writer) = anon_pipe()?; 66 | let (ours, theirs) = if readable { 67 | (writer, reader) 68 | } else { 69 | (reader, writer) 70 | }; 71 | Ok((ChildStdio::Owned(theirs.into()), Some(ours))) 72 | } 73 | 74 | Stdio::Null => { 75 | let mut opts = OpenOptions::new(); 76 | opts.read(readable); 77 | opts.write(!readable); 78 | let path = unsafe { CStr::from_ptr(DEV_NULL.as_ptr() as *const _) }; 79 | let path = Path::new(path.to_str().unwrap()); 80 | let fd = File::open(path)?.as_raw_fd(); 81 | Ok(( 82 | ChildStdio::Owned(unsafe { FileDesc::from_raw_fd(fd) }), 83 | None, 84 | )) 85 | } 86 | } 87 | } 88 | 89 | /// Create a pipe for this file descriptor and use it in the child process as 90 | /// the given file descriptor to facilitate input or output redirection. See 91 | /// `MemFdExecutable::stdin` for an example. 92 | pub fn piped() -> Stdio { 93 | Stdio::MakePipe 94 | } 95 | 96 | /// Use a null file descriptor, like /dev/null, to either provide no input or to 97 | /// discard output. See `MemFdExecutable::stdout` for an example. 98 | pub fn null() -> Stdio { 99 | Stdio::Null 100 | } 101 | 102 | /// Inherit the parent's file descriptor. this is the default behavior, but is 103 | /// generally not the desired behavior. 104 | pub fn inherit() -> Stdio { 105 | Stdio::Inherit 106 | } 107 | } 108 | 109 | impl From for Stdio { 110 | fn from(pipe: AnonPipe) -> Stdio { 111 | Stdio::Fd(pipe.into()) 112 | } 113 | } 114 | 115 | impl ChildStdio { 116 | pub fn fd(&self) -> Option { 117 | match *self { 118 | ChildStdio::Inherit => None, 119 | ChildStdio::Explicit(fd) => Some(fd), 120 | ChildStdio::Owned(ref fd) => Some(fd.as_raw_fd()), 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memfd_exec ![crates.io](https://img.shields.io/crates/v/memfd-exec.svg) 2 | 3 | This is a very simple crate that allows execution of in-memory only programs. Simply 4 | put, if you have the contents of a Linux executable in a `Vec`, you can use 5 | `memfd_exec` to execute the program without it ever touching your hard disk. Use 6 | cases for this may include: 7 | 8 | * Bundling a static executable with another program (for example, my motivation to 9 | create this package is that I want to ship a statically built QEMU with 10 | [cantrace](https://github.com/novafacing/cannoli)) 11 | * Sending executables over the network and running them, to reduce footprint and increase 12 | throughput 13 | * Really hacky stuff that I haven't thought of, if you have a cool use case, feel free 14 | to make a PR to the README or add an example in [examples](examples) 15 | 16 | ## Using 17 | 18 | Just include `memfd-exec = "0.1.4"` in your `Cargo.toml` file. 19 | 20 | ## Features 21 | 22 | * Feature-parity API with `process::Command`, the only difference is we don't execute 23 | anything from disk. 24 | * Only two dependencies 25 | 26 | ## Examples 27 | 28 | ### Run an executable downloaded over the network 29 | 30 | For redteamers, this example will download and run an executable without ever writing it 31 | to disk. It may not bypass Advanced Threat Protection, but it at least won't leave 32 | a huge disk footprint! 33 | 34 | ```rust 35 | use memfd_exec::{MemFdExecutable, Stdio}; 36 | use reqwest::blocking::get; 37 | 38 | const URL: &str = "https://novafacing.github.io/assets/qemu-x86_64"; 39 | let resp = get(URL).unwrap(); 40 | 41 | // The `MemFdExecutable` struct is at near feature-parity with `std::process::Command`, 42 | // so you can use it in the same way. The only difference is that you must provide the 43 | // argv[0] to use as well as the executable contents as a byte slice. 44 | let qemu = MemFdExecutable::new("qemu-x86_64", resp.bytes().unwrap().to_vec()) 45 | // We'll just get the version here, but you can do anything you want with the 46 | // args. 47 | .arg("-version") 48 | // We'll capture the stdout of the process, so we need to set up a pipe. 49 | .stdout(Stdio::piped()) 50 | // Spawn the process as a forked child 51 | .spawn() 52 | .unwrap(); 53 | 54 | // Get the output and status code of the process (this will block until the process 55 | // exits) 56 | let output = qemu.wait_with_output().unwrap(); 57 | assert!(output.status.into_raw() == 0); 58 | // Print out the version we got! 59 | println!("{}", String::from_utf8_lossy(&output.stdout)); 60 | ``` 61 | 62 | ### Bundle and run a local static executable 63 | 64 | The motivating example for this project is to bundle an executable along with a rust 65 | program and be able to run the executable straight from memory instead of going 66 | through the tedious and slow process of writing the executable file to disk and then 67 | invoking it as a command. 68 | 69 | This example creates an executable with a bundled [program](tests/test_static.c) that 70 | opens a socket, reads a bit of input, and then prints out the input. Of course, the 71 | logical extension of the idea would be to use a static 72 | [netcat](https://github.com/openbsd/src/blob/master/usr.bin/nc/netcat.c) build or some 73 | such thing. 74 | 75 | ```rust 76 | 77 | use memfd_exec::{MemFdExecutable, Stdio}; 78 | 79 | const EXECUTABLE_FILE: &[u8] = include_bytes!("tets/test_static"); 80 | 81 | fn main() { 82 | const PORT = 1234; 83 | // We create an in-memory executable with an argv[0] "test" and an executable file 84 | // that we embedded in our rust binary 85 | let exe = MemFdExecutable::new("test", EXECUTABLE_FILE.to_vec()) 86 | // We pass one arg, the port number to listen on 87 | .arg(format!("{}", PORT)) 88 | // We tell it to use a pipe for stdout (stdin and stderr will default to Stdio::inherit()) 89 | .stdout(Stdio::piped()) 90 | // We spawn the child process as a forked child process 91 | .spawn() 92 | .expect("Failed to create process!"); 93 | 94 | // Wait until the process finishes and print its output 95 | let output = exe.wait_with_output().unwrap(); 96 | println!("Got output: {:?}", output.stdout); 97 | } 98 | ``` 99 | 100 | ## Testing 101 | 102 | For testing purposes, you need to install: 103 | 104 | - `clang` 105 | - `glibc-static` 106 | - `glibc` 107 | 108 | You should also have `/bin/cat` and `/bin/ls` on your system. This is default on the 109 | vast majority of Linux systems, but don't panic if this test fails if they are missing. 110 | -------------------------------------------------------------------------------- /src/file_desc.rs: -------------------------------------------------------------------------------- 1 | //! This essentially reimplements the code at: 2 | //! 3 | //! for external use to provide fds for sockets to perform Stdio to memfd_exec'ed processes. 4 | 5 | use libc::{self, off64_t}; 6 | use std::{ 7 | cmp, 8 | io::{self, IoSlice, IoSliceMut, Read}, 9 | os::unix::{ 10 | io::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd}, 11 | prelude::{BorrowedFd, RawFd}, 12 | }, 13 | }; 14 | 15 | use crate::cvt::cvt; 16 | 17 | #[derive(Debug)] 18 | pub struct FileDesc(OwnedFd); 19 | 20 | const READ_LIMIT: usize = libc::ssize_t::MAX as usize; 21 | 22 | const fn max_iov() -> usize { 23 | libc::UIO_MAXIOV as usize 24 | } 25 | 26 | impl FileDesc { 27 | pub fn read(&self, buf: &mut [u8]) -> io::Result { 28 | let ret = cvt(unsafe { 29 | libc::read( 30 | self.as_raw_fd(), 31 | buf.as_mut_ptr() as *mut libc::c_void, 32 | cmp::min(buf.len(), READ_LIMIT), 33 | ) 34 | })?; 35 | Ok(ret as usize) 36 | } 37 | 38 | pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { 39 | let ret = cvt(unsafe { 40 | libc::readv( 41 | self.as_raw_fd(), 42 | bufs.as_ptr() as *const libc::iovec, 43 | cmp::min(bufs.len(), max_iov()) as libc::c_int, 44 | ) 45 | })?; 46 | Ok(ret as usize) 47 | } 48 | 49 | #[inline] 50 | pub fn is_read_vectored(&self) -> bool { 51 | cfg!(not(any(target_os = "espidf", target_os = "horizon"))) 52 | } 53 | 54 | pub fn read_to_end(&self, buf: &mut Vec) -> io::Result { 55 | let mut me = self; 56 | (&mut me).read_to_end(buf) 57 | } 58 | 59 | pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { 60 | use libc::pread64; 61 | 62 | unsafe { 63 | cvt(pread64( 64 | self.as_raw_fd(), 65 | buf.as_mut_ptr() as *mut libc::c_void, 66 | cmp::min(buf.len(), READ_LIMIT), 67 | offset as off64_t, 68 | )) 69 | .map(|n| n as usize) 70 | } 71 | } 72 | 73 | pub fn write(&self, buf: &[u8]) -> io::Result { 74 | let ret = cvt(unsafe { 75 | libc::write( 76 | self.as_raw_fd(), 77 | buf.as_ptr() as *const libc::c_void, 78 | cmp::min(buf.len(), READ_LIMIT), 79 | ) 80 | })?; 81 | Ok(ret as usize) 82 | } 83 | 84 | pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { 85 | let ret = cvt(unsafe { 86 | libc::writev( 87 | self.as_raw_fd(), 88 | bufs.as_ptr() as *const libc::iovec, 89 | cmp::min(bufs.len(), max_iov()) as libc::c_int, 90 | ) 91 | })?; 92 | Ok(ret as usize) 93 | } 94 | 95 | #[inline] 96 | pub fn is_write_vectored(&self) -> bool { 97 | cfg!(not(any(target_os = "espidf", target_os = "horizon"))) 98 | } 99 | 100 | pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { 101 | use libc::pwrite64; 102 | 103 | unsafe { 104 | cvt(pwrite64( 105 | self.as_raw_fd(), 106 | buf.as_ptr() as *const libc::c_void, 107 | cmp::min(buf.len(), READ_LIMIT), 108 | offset as off64_t, 109 | )) 110 | .map(|n| n as usize) 111 | } 112 | } 113 | 114 | pub fn get_cloexec(&self) -> io::Result { 115 | unsafe { Ok((cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))? & libc::FD_CLOEXEC) != 0) } 116 | } 117 | 118 | pub fn set_cloexec(&self) -> io::Result<()> { 119 | unsafe { 120 | let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?; 121 | let new = previous | libc::FD_CLOEXEC; 122 | if new != previous { 123 | cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?; 124 | } 125 | Ok(()) 126 | } 127 | } 128 | pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { 129 | unsafe { 130 | let v = nonblocking as libc::c_int; 131 | cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?; 132 | Ok(()) 133 | } 134 | } 135 | 136 | #[inline] 137 | pub fn duplicate(&self) -> io::Result { 138 | Ok(Self(self.0.try_clone()?)) 139 | } 140 | } 141 | 142 | impl<'a> Read for &'a FileDesc { 143 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 144 | (**self).read(buf) 145 | } 146 | } 147 | 148 | impl AsFd for FileDesc { 149 | fn as_fd(&self) -> BorrowedFd<'_> { 150 | self.0.as_fd() 151 | } 152 | } 153 | 154 | impl AsRawFd for FileDesc { 155 | fn as_raw_fd(&self) -> RawFd { 156 | self.0.as_raw_fd() 157 | } 158 | } 159 | 160 | impl IntoRawFd for FileDesc { 161 | fn into_raw_fd(self) -> RawFd { 162 | self.0.into_raw_fd() 163 | } 164 | } 165 | 166 | impl FromRawFd for FileDesc { 167 | unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { 168 | Self(FromRawFd::from_raw_fd(raw_fd)) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | //! This basically implements Process from: 2 | //! 3 | 4 | use libc::c_int; 5 | use std::fmt::{Debug, Formatter, Result as FmtResult}; 6 | use std::io::{Error, Result}; 7 | 8 | use libc::pid_t; 9 | 10 | use crate::cvt::{cvt, cvt_r}; 11 | 12 | pub struct Process { 13 | pid: pid_t, 14 | status: Option, 15 | } 16 | 17 | impl Process { 18 | pub unsafe fn new(pid: pid_t) -> Self { 19 | // Safety: If `pidfd` is nonnegative, we assume it's valid and otherwise unowned. 20 | Process { pid, status: None } 21 | } 22 | 23 | pub fn id(&self) -> u32 { 24 | self.pid as u32 25 | } 26 | 27 | pub fn kill(&mut self) -> Result<()> { 28 | // If we've already waited on this process then the pid can be recycled 29 | // and used for another process, and we probably shouldn't be killing 30 | // random processes, so just return an error. 31 | if self.status.is_some() { 32 | Err(Error::new( 33 | std::io::ErrorKind::InvalidInput, 34 | "invalid argument: can't kill an exited process", 35 | )) 36 | } else { 37 | cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) 38 | } 39 | } 40 | 41 | pub fn wait(&mut self) -> Result { 42 | if let Some(status) = self.status { 43 | return Ok(status); 44 | } 45 | let mut status = 0 as c_int; 46 | cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?; 47 | self.status = Some(ExitStatus::new(status)); 48 | Ok(ExitStatus::new(status)) 49 | } 50 | 51 | pub fn try_wait(&mut self) -> Result> { 52 | if let Some(status) = self.status { 53 | return Ok(Some(status)); 54 | } 55 | let mut status = 0 as c_int; 56 | let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?; 57 | if pid == 0 { 58 | Ok(None) 59 | } else { 60 | self.status = Some(ExitStatus::new(status)); 61 | Ok(Some(ExitStatus::new(status))) 62 | } 63 | } 64 | } 65 | 66 | /// Describes the result of a process after it has terminated. 67 | #[derive(PartialEq, Eq, Clone, Copy)] 68 | pub struct ExitStatus(c_int); 69 | 70 | impl Debug for ExitStatus { 71 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 72 | f.debug_tuple("unix_wait_status").field(&self.0).finish() 73 | } 74 | } 75 | 76 | impl ExitStatus { 77 | pub(crate) fn new(status: c_int) -> ExitStatus { 78 | ExitStatus(status) 79 | } 80 | 81 | fn exited(&self) -> bool { 82 | libc::WIFEXITED(self.0) 83 | } 84 | 85 | /// Was termination successful? Returns a Result. 86 | pub fn exit_ok(&self) -> Result<()> { 87 | // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is 88 | // true on all actual versions of Unix, is widely assumed, and is specified in SuS 89 | // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html . If it is not 90 | // true for a platform pretending to be Unix, the tests (our doctests, and also 91 | // procsss_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too. 92 | #[allow(clippy::useless_conversion)] 93 | match c_int::try_from(self.0) { 94 | /* was nonzero */ 95 | Ok(failure) => Err(Error::new( 96 | std::io::ErrorKind::Other, 97 | format!("process exited with status {}", failure), 98 | )), 99 | /* was zero, couldn't convert */ 100 | Err(_) => Ok(()), 101 | } 102 | } 103 | 104 | /// Was termination successful? 105 | /// 106 | /// Signal termination is not considered a success, and success is defined 107 | /// as a zero exit status. 108 | pub fn success(&self) -> bool { 109 | self.exit_ok().is_ok() 110 | } 111 | 112 | /// Returns the exit code of the process, if any. 113 | /// 114 | /// In Unix terms the return value is the exit status: 115 | /// the value passed to exit, if the process finished by calling exit. 116 | /// Note that on Unix the exit status is truncated to 8 bits, and that 117 | /// values that didn’t come from a program’s call to exit may be invented 118 | /// by the runtime system (often, for example, 255, 254, 127 or 126). 119 | /// 120 | /// This will return None if the process was terminated by a signal. 121 | /// ExitStatusExt is an extension trait for extracting any such signal, 122 | /// and other details, from the ExitStatus. 123 | pub fn code(&self) -> Option { 124 | self.exited().then(|| libc::WEXITSTATUS(self.0)) 125 | } 126 | 127 | /// If the process was terminated by a signal, returns that signal. 128 | /// 129 | /// In other words, if WIFSIGNALED, this returns WTERMSIG. 130 | pub fn signal(&self) -> Option { 131 | libc::WIFSIGNALED(self.0).then(|| libc::WTERMSIG(self.0)) 132 | } 133 | 134 | /// If the process was terminated by a signal, says whether it dumped core. 135 | pub fn core_dumped(&self) -> bool { 136 | libc::WIFSIGNALED(self.0) && libc::WCOREDUMP(self.0) 137 | } 138 | 139 | /// If the process was stopped by a signal, returns that signal. 140 | /// 141 | /// In other words, if WIFSTOPPED, this returns WSTOPSIG. 142 | /// This is only possible if the status came from a wait system call 143 | /// which was passed WUNTRACED, and was then converted into an ExitStatus. 144 | pub fn stopped_signal(&self) -> Option { 145 | libc::WIFSTOPPED(self.0).then(|| libc::WSTOPSIG(self.0)) 146 | } 147 | 148 | /// Whether the process was continued from a stopped status. 149 | /// 150 | /// Ie, WIFCONTINUED. This is only possible if the status came from a 151 | /// wait system call which was passed WCONTINUED, and was then converted 152 | /// into an ExitStatus. 153 | pub fn continued(&self) -> bool { 154 | libc::WIFCONTINUED(self.0) 155 | } 156 | 157 | /// Returns the underlying raw wait status. 158 | /// 159 | /// The returned integer is a wait status, not an exit status. 160 | #[allow(clippy::wrong_self_convention)] 161 | pub fn into_raw(&self) -> c_int { 162 | self.0 163 | } 164 | } 165 | 166 | /// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. 167 | impl From for ExitStatus { 168 | fn from(a: c_int) -> ExitStatus { 169 | ExitStatus(a) 170 | } 171 | } 172 | 173 | #[derive(PartialEq, Eq, Clone, Copy)] 174 | pub struct ExitStatusError(c_int); 175 | 176 | impl From for ExitStatus { 177 | fn from(val: ExitStatusError) -> Self { 178 | ExitStatus(val.0) 179 | } 180 | } 181 | 182 | impl Debug for ExitStatusError { 183 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 184 | f.debug_tuple("unix_wait_status").field(&self.0).finish() 185 | } 186 | } 187 | 188 | impl ExitStatusError {} 189 | -------------------------------------------------------------------------------- /tests/test_basic.rs: -------------------------------------------------------------------------------- 1 | //! Test the `ls` command from the local system 2 | 3 | use std::{ 4 | fs::read, 5 | io::{Read, Write}, 6 | net::{SocketAddr, TcpStream}, 7 | path::PathBuf, 8 | process::{Command, Stdio as ProcessStdio}, 9 | str, 10 | thread::{sleep, spawn}, 11 | time::Duration, 12 | }; 13 | 14 | use serial_test::serial; 15 | 16 | use memfd_exec::{MemFdExecutable, Stdio}; 17 | 18 | const TEST_STATIC_CODE: &[u8] = include_bytes!("./test_static.c"); 19 | const CARGO_TARGET_TMPDIR: &str = env!("CARGO_TARGET_TMPDIR"); 20 | 21 | fn build_test_static() { 22 | let mut clang = Command::new("clang") 23 | .arg("-x") 24 | .arg("c") 25 | .arg("-static") 26 | .arg("-v") 27 | .arg("-o") 28 | .arg(PathBuf::from(CARGO_TARGET_TMPDIR).join("test_static.bin")) 29 | .arg("-") 30 | .stdin(ProcessStdio::piped()) 31 | .stdout(ProcessStdio::piped()) 32 | .stderr(ProcessStdio::piped()) 33 | .spawn() 34 | .expect("Failed to run clang"); 35 | 36 | let mut clang_stdin = clang.stdin.take().expect("Failed to open stdin"); 37 | 38 | spawn(move || { 39 | clang_stdin 40 | .write_all(TEST_STATIC_CODE) 41 | .expect("Could not write to clang stdin"); 42 | }); 43 | 44 | let output = clang.wait_with_output().expect("Failed to run clang"); 45 | 46 | assert!( 47 | output.status.success(), 48 | "Failed to compile static test:\nstdout: {}\nstderr: {}", 49 | String::from_utf8_lossy(&output.stdout), 50 | String::from_utf8_lossy(&output.stderr) 51 | ); 52 | } 53 | 54 | fn build_test_dynamic() { 55 | let mut clang = Command::new("clang") 56 | .arg("-x") 57 | .arg("c") 58 | .arg("-o") 59 | .arg(PathBuf::from(CARGO_TARGET_TMPDIR).join("test_dynamic.bin")) 60 | .arg("-") 61 | .stdin(ProcessStdio::piped()) 62 | .stdout(ProcessStdio::piped()) 63 | .stderr(ProcessStdio::piped()) 64 | .spawn() 65 | .expect("Failed to run clang"); 66 | 67 | let mut clang_stdin = clang.stdin.take().expect("Failed to open stdin"); 68 | 69 | spawn(move || { 70 | clang_stdin 71 | .write_all(TEST_STATIC_CODE) 72 | .expect("Could not write to clang stdin"); 73 | }); 74 | 75 | let output = clang.wait_with_output().expect("Failed to run clang"); 76 | 77 | assert!( 78 | output.status.success(), 79 | "Failed to compile static test:\nstdout: {}\nstderr: {}", 80 | String::from_utf8_lossy(&output.stdout), 81 | String::from_utf8_lossy(&output.stderr) 82 | ); 83 | } 84 | 85 | #[test] 86 | fn test_ls() { 87 | let ls_contents = read("/bin/ls").expect("Could not read /bin/ls"); 88 | let _ls = MemFdExecutable::new("ls", &ls_contents) 89 | .arg(".") 90 | .spawn() 91 | .expect("Failed to run ls"); 92 | } 93 | 94 | #[test] 95 | fn test_cat_simple() { 96 | let cat_contents = read("/bin/cat").expect("Could not read /bin/cat"); 97 | let _cat = MemFdExecutable::new("cat", &cat_contents) 98 | .arg("Cargo.toml") 99 | .spawn() 100 | .expect("Failed to run cat"); 101 | } 102 | 103 | #[test] 104 | fn test_cat_stdin() { 105 | let cat_contents = read("/bin/cat").expect("Could not read /bin/cat"); 106 | let mut cat = MemFdExecutable::new("cat", &cat_contents) 107 | .stdin(Stdio::piped()) 108 | .stdout(Stdio::piped()) 109 | .spawn() 110 | .expect("Failed to run cat"); 111 | 112 | let mut stdin = cat.stdin.take().expect("Failed to open stdin"); 113 | spawn(move || { 114 | stdin 115 | .write_all(b"Hello, world!") 116 | .expect("Failed to write to cat stdin"); 117 | }); 118 | 119 | let output = cat.wait_with_output().expect("Failed to run cat"); 120 | 121 | assert!( 122 | output.stdout.len() == b"Hello, world!".len(), 123 | "Output was too short (wanted at least {} bytes, got {})", 124 | b"Hello, world!".len(), 125 | output.stdout.len() 126 | ); 127 | } 128 | 129 | #[test] 130 | #[serial] 131 | fn test_static_included() { 132 | build_test_static(); 133 | 134 | let test_static_exe = PathBuf::from(CARGO_TARGET_TMPDIR).join("test_static.bin"); 135 | let test_static_exe_contents = read(test_static_exe).expect("Could not read static exe"); 136 | 137 | let test_static = MemFdExecutable::new("test_static.bin", &test_static_exe_contents) 138 | .stdout(Stdio::piped()) 139 | .spawn() 140 | .expect("Failed to spawn test_static"); 141 | 142 | let port = { 143 | let mut array = [0u8; 32]; 144 | let len = test_static 145 | .stdout 146 | .as_ref() 147 | .unwrap() 148 | .read(&mut array) 149 | .unwrap(); 150 | let port: u16 = str::from_utf8(&array[..len]).unwrap().parse().unwrap(); 151 | port 152 | }; 153 | 154 | let output_thread = spawn(move || { 155 | let output = test_static 156 | .wait_with_output() 157 | .expect("Failed to run test_static"); 158 | assert!( 159 | output.stdout.len() == b"Hello, world!\n\n".len(), 160 | "Output was too short (wanted at least {} bytes, got {})", 161 | b"Hello, world!".len(), 162 | output.stdout.len() 163 | ); 164 | }); 165 | 166 | let sock: SocketAddr = format!("127.0.0.1:{}", port) 167 | .parse() 168 | .expect("Failed to parse socket address"); 169 | 170 | let mut stream = TcpStream::connect(sock).unwrap(); 171 | stream 172 | .write_all(b"Hello, world!\n\n") 173 | .expect("Failed to write to socket"); 174 | drop(stream); 175 | 176 | output_thread.join().expect("Failed to join output thread"); 177 | } 178 | 179 | #[test] 180 | #[serial] 181 | fn test_dynamic_included() { 182 | build_test_dynamic(); 183 | 184 | let test_dynamic_exe = PathBuf::from(CARGO_TARGET_TMPDIR).join("test_dynamic.bin"); 185 | let test_dynamic_exe_contents = read(test_dynamic_exe).expect("Could not read dynamic exe"); 186 | 187 | let test_dynamic = MemFdExecutable::new("test_dynamic.bin", &test_dynamic_exe_contents) 188 | .stdout(Stdio::piped()) 189 | .spawn() 190 | .expect("Failed to spawn test_dynamic"); 191 | 192 | let port = { 193 | let mut array = [0u8; 32]; 194 | let len = test_dynamic 195 | .stdout 196 | .as_ref() 197 | .unwrap() 198 | .read(&mut array) 199 | .unwrap(); 200 | let port: u16 = str::from_utf8(&array[..len]).unwrap().parse().unwrap(); 201 | port 202 | }; 203 | 204 | let output_thread = spawn(move || { 205 | let output = test_dynamic 206 | .wait_with_output() 207 | .expect("Failed to run test_dynamic"); 208 | assert!( 209 | output.stdout.len() == b"Hello, world!\n\n".len(), 210 | "Output was too short (wanted at least {} bytes, got {})", 211 | b"Hello, world!".len(), 212 | output.stdout.len() 213 | ); 214 | }); 215 | 216 | let sock: SocketAddr = format!("127.0.0.1:{}", port) 217 | .parse() 218 | .expect("Failed to parse socket address"); 219 | 220 | for _ in 0..10 { 221 | if let Ok(mut stream) = TcpStream::connect(sock) { 222 | stream 223 | .write_all(b"Hello, world!\n\n") 224 | .expect("Failed to write to socket"); 225 | drop(stream); 226 | break; 227 | } 228 | sleep(Duration::from_millis(100)); 229 | } 230 | output_thread.join().expect("Failed to join output thread"); 231 | } 232 | 233 | // #[test] 234 | // fn test_net() { 235 | // use memfd_exec::{MemFdExecutable, Stdio}; 236 | // use reqwest::blocking::get; 237 | // 238 | // const URL: &str = "https://novafacing.github.io/assets/qemu-x86_64"; 239 | // let resp = get(URL).expect("Failed to download qemu"); 240 | // 241 | // // The `MemFdExecutable` struct is at near feature-parity with `std::process::Command`, 242 | // // so you can use it in the same way. The only difference is that you must provide the 243 | // // executable contents as a `Vec` as well as telling it the argv[0] to use. 244 | // let qemu = MemFdExecutable::new( 245 | // "qemu-x86_64", 246 | // resp.bytes() 247 | // .expect("Could not get bytes from qemu download") 248 | // ) 249 | // // We'll just get the version here, but you can do anything you want with the 250 | // // args. 251 | // .arg("-version") 252 | // // We'll capture the stdout of the process, so we need to set up a pipe. 253 | // .stdout(Stdio::piped()) 254 | // // Spawn the process as a forked child 255 | // .spawn() 256 | // .expect("Failed to spawn qemu"); 257 | // 258 | // // Get the output and status code of the process (this will block until the process 259 | // // exits) 260 | // let output = qemu.wait_with_output().expect("Failed to run qemu"); 261 | // assert!(output.status.into_raw() == 0); 262 | // // Print out the version we got! 263 | // println!("{}", String::from_utf8_lossy(&output.stdout)); 264 | // } 265 | -------------------------------------------------------------------------------- /src/child.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter, Result as FmtResult}; 2 | use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Read, Result, Write}; 3 | 4 | use crate::anon_pipe::{read2, AnonPipe}; 5 | use crate::output::Output; 6 | use crate::process::{ExitStatus, Process}; 7 | use crate::stdio::StdioPipes; 8 | 9 | /// A child process created from a `MemFdExecutable` with handles to input and output streams 10 | pub struct Child { 11 | handle: Process, 12 | /// The input stream to the child process 13 | pub stdin: Option, 14 | /// The output stream from the child process 15 | pub stdout: Option, 16 | /// The error stream from the child process 17 | pub stderr: Option, 18 | } 19 | 20 | impl Child { 21 | pub fn new(handle: Process, stdio: StdioPipes) -> Self { 22 | Self { 23 | handle, 24 | stdin: stdio.stdin.map(ChildStdin), 25 | stdout: stdio.stdout.map(ChildStdout), 26 | stderr: stdio.stderr.map(ChildStderr), 27 | } 28 | } 29 | 30 | /// Kill the child process 31 | pub fn kill(&mut self) -> Result<()> { 32 | self.handle.kill() 33 | } 34 | 35 | /// Return the id of the child process, probably a PID 36 | pub fn id(&self) -> u32 { 37 | self.handle.id() 38 | } 39 | 40 | /// Wait for the child process to exit, returning the exit status code 41 | pub fn wait(&mut self) -> Result { 42 | drop(self.stdin.take()); 43 | self.handle.wait() 44 | } 45 | 46 | /// Try and wait for the child process to exit, returning the exit status code if it has 47 | pub fn try_wait(&mut self) -> Result> { 48 | self.handle.try_wait() 49 | } 50 | 51 | /// Wait for the child process to exit, returning the exit status code and the output 52 | /// streams 53 | pub fn wait_with_output(mut self) -> Result { 54 | drop(self.stdin.take()); 55 | 56 | let (mut stdout, mut stderr) = (Vec::new(), Vec::new()); 57 | match (self.stdout.take(), self.stderr.take()) { 58 | (None, None) => {} 59 | (Some(mut out), None) => { 60 | out.read_to_end(&mut stdout)?; 61 | } 62 | (None, Some(mut err)) => { 63 | err.read_to_end(&mut stderr)?; 64 | } 65 | (Some(out), Some(err)) => { 66 | read2(out.0, &mut stdout, err.0, &mut stderr)?; 67 | } 68 | } 69 | 70 | Ok(Output { 71 | status: self.wait()?, 72 | stdout, 73 | stderr, 74 | }) 75 | } 76 | } 77 | 78 | /// A handle to a child process’s standard input (stdin). 79 | pub struct ChildStdin(AnonPipe); 80 | 81 | impl std::os::fd::AsRawFd for ChildStdin { 82 | #[inline] 83 | fn as_raw_fd(&self) -> std::os::fd::RawFd { 84 | self.0.as_raw_fd() 85 | } 86 | } 87 | 88 | impl Write for ChildStdin { 89 | fn write(&mut self, buf: &[u8]) -> Result { 90 | (&*self).write(buf) 91 | } 92 | 93 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 94 | (&*self).write_vectored(bufs) 95 | } 96 | 97 | fn flush(&mut self) -> Result<()> { 98 | (&*self).flush() 99 | } 100 | 101 | fn write_all(&mut self, buf: &[u8]) -> Result<()> { 102 | (&*self).write_all(buf) 103 | } 104 | } 105 | 106 | impl Write for &ChildStdin { 107 | fn write(&mut self, buf: &[u8]) -> Result { 108 | self.0.write(buf) 109 | } 110 | 111 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 112 | self.0.write_vectored(bufs) 113 | } 114 | 115 | fn flush(&mut self) -> Result<()> { 116 | Ok(()) 117 | } 118 | 119 | fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { 120 | while !buf.is_empty() { 121 | match self.write(buf) { 122 | Ok(0) => { 123 | return Err(Error::new( 124 | ErrorKind::WriteZero, 125 | "failed to write whole buffer", 126 | )) 127 | } 128 | Ok(n) => buf = &buf[n..], 129 | Err(ref e) if e.kind() == ErrorKind::Interrupted => {} 130 | Err(e) => return Err(e), 131 | } 132 | } 133 | Ok(()) 134 | } 135 | } 136 | 137 | impl Debug for ChildStdin { 138 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 139 | f.debug_struct("ChildStdin").finish_non_exhaustive() 140 | } 141 | } 142 | 143 | /// A handle to a child process’s standard output (stdout). 144 | pub struct ChildStdout(AnonPipe); 145 | 146 | impl std::os::fd::AsRawFd for ChildStdout { 147 | #[inline] 148 | fn as_raw_fd(&self) -> std::os::fd::RawFd { 149 | self.0.as_raw_fd() 150 | } 151 | } 152 | 153 | impl Write for ChildStdout { 154 | fn write(&mut self, buf: &[u8]) -> Result { 155 | (&*self).write(buf) 156 | } 157 | 158 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 159 | (&*self).write_vectored(bufs) 160 | } 161 | 162 | fn flush(&mut self) -> Result<()> { 163 | (&*self).flush() 164 | } 165 | 166 | fn write_all(&mut self, buf: &[u8]) -> Result<()> { 167 | (&*self).write_all(buf) 168 | } 169 | } 170 | 171 | impl Write for &ChildStdout { 172 | fn write(&mut self, buf: &[u8]) -> Result { 173 | self.0.write(buf) 174 | } 175 | 176 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 177 | self.0.write_vectored(bufs) 178 | } 179 | 180 | fn flush(&mut self) -> Result<()> { 181 | Ok(()) 182 | } 183 | 184 | fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { 185 | while !buf.is_empty() { 186 | match self.write(buf) { 187 | Ok(0) => { 188 | return Err(Error::new( 189 | ErrorKind::WriteZero, 190 | "failed to write whole buffer", 191 | )) 192 | } 193 | Ok(n) => buf = &buf[n..], 194 | Err(ref e) if e.kind() == ErrorKind::Interrupted => {} 195 | Err(e) => return Err(e), 196 | } 197 | } 198 | Ok(()) 199 | } 200 | } 201 | 202 | impl Read for ChildStdout { 203 | fn read(&mut self, buf: &mut [u8]) -> Result { 204 | (&*self).read(buf) 205 | } 206 | 207 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result { 208 | (&*self).read_vectored(bufs) 209 | } 210 | 211 | fn read_to_end(&mut self, buf: &mut Vec) -> Result { 212 | (&*self).read_to_end(buf) 213 | } 214 | } 215 | 216 | impl Read for &ChildStdout { 217 | fn read(&mut self, buf: &mut [u8]) -> Result { 218 | self.0.read(buf) 219 | } 220 | 221 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result { 222 | self.0.read_vectored(bufs) 223 | } 224 | 225 | fn read_to_end(&mut self, buf: &mut Vec) -> Result { 226 | self.0.read_to_end(buf) 227 | } 228 | } 229 | 230 | impl Debug for ChildStdout { 231 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 232 | f.debug_struct("ChildStdout").finish_non_exhaustive() 233 | } 234 | } 235 | 236 | /// A handle to a child process’s stderr. 237 | pub struct ChildStderr(AnonPipe); 238 | 239 | impl std::os::fd::AsRawFd for ChildStderr { 240 | #[inline] 241 | fn as_raw_fd(&self) -> std::os::fd::RawFd { 242 | self.0.as_raw_fd() 243 | } 244 | } 245 | 246 | impl Write for ChildStderr { 247 | fn write(&mut self, buf: &[u8]) -> Result { 248 | (&*self).write(buf) 249 | } 250 | 251 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 252 | (&*self).write_vectored(bufs) 253 | } 254 | 255 | fn flush(&mut self) -> Result<()> { 256 | (&*self).flush() 257 | } 258 | 259 | fn write_all(&mut self, buf: &[u8]) -> Result<()> { 260 | (&*self).write_all(buf) 261 | } 262 | } 263 | 264 | impl Write for &ChildStderr { 265 | fn write(&mut self, buf: &[u8]) -> Result { 266 | self.0.write(buf) 267 | } 268 | 269 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result { 270 | self.0.write_vectored(bufs) 271 | } 272 | 273 | fn flush(&mut self) -> Result<()> { 274 | Ok(()) 275 | } 276 | 277 | fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { 278 | while !buf.is_empty() { 279 | match self.write(buf) { 280 | Ok(0) => { 281 | return Err(Error::new( 282 | ErrorKind::WriteZero, 283 | "failed to write whole buffer", 284 | )) 285 | } 286 | Ok(n) => buf = &buf[n..], 287 | Err(ref e) if e.kind() == ErrorKind::Interrupted => {} 288 | Err(e) => return Err(e), 289 | } 290 | } 291 | Ok(()) 292 | } 293 | } 294 | 295 | impl Read for ChildStderr { 296 | fn read(&mut self, buf: &mut [u8]) -> Result { 297 | (&*self).read(buf) 298 | } 299 | 300 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result { 301 | (&*self).read_vectored(bufs) 302 | } 303 | 304 | fn read_to_end(&mut self, buf: &mut Vec) -> Result { 305 | (&*self).read_to_end(buf) 306 | } 307 | } 308 | 309 | impl Read for &ChildStderr { 310 | fn read(&mut self, buf: &mut [u8]) -> Result { 311 | self.0.read(buf) 312 | } 313 | 314 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result { 315 | self.0.read_vectored(bufs) 316 | } 317 | 318 | fn read_to_end(&mut self, buf: &mut Vec) -> Result { 319 | self.0.read_to_end(buf) 320 | } 321 | } 322 | 323 | impl Debug for ChildStderr { 324 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 325 | f.debug_struct("ChildStderr").finish_non_exhaustive() 326 | } 327 | } 328 | 329 | impl Debug for Child { 330 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 331 | f.debug_struct("Child") 332 | .field("stdin", &self.stdin) 333 | .field("stdout", &self.stdout) 334 | .field("stderr", &self.stderr) 335 | .finish_non_exhaustive() 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/executable.rs: -------------------------------------------------------------------------------- 1 | //! This essentially reimplements the code at: 2 | //! which is an internal implementation of the code at: 3 | //! 4 | //! 5 | //! 6 | //! for external use to provide a very similar interface to process::Command for in-memory executables 7 | 8 | use std::{ 9 | collections::BTreeMap, 10 | ffi::{CStr, CString, OsStr, OsString}, 11 | io::{Error, ErrorKind, Result}, 12 | mem::MaybeUninit, 13 | os::unix::prelude::{OsStrExt, OsStringExt}, 14 | path::Path, 15 | ptr::null_mut, 16 | }; 17 | 18 | use libc::{pid_t, sigemptyset, signal}; 19 | use nix::{ 20 | sys::memfd::{memfd_create, MemFdCreateFlag}, 21 | unistd::{close, fexecve, write}, 22 | }; 23 | 24 | use crate::{ 25 | anon_pipe::anon_pipe, 26 | child::Child, 27 | command_env::CommandEnv, 28 | cvt::{cvt, cvt_nz, cvt_r}, 29 | output::Output, 30 | process::{ExitStatus, Process}, 31 | stdio::{ChildPipes, Stdio, StdioPipes}, 32 | }; 33 | 34 | /// This is the main struct used to create an in-memory only executable. Wherever possible, it 35 | /// is intended to be a drop-in replacement for the standard library's `process::Command` struct. 36 | /// 37 | /// # Examples 38 | /// 39 | /// This example is the "motivating case" for this library. It shows how to execute a binary 40 | /// entirely from memory, without writing it to disk. This is useful for executing binaries 41 | /// sneakily, or (the real reason) for bundling executables that are a pain to set up and 42 | /// compile, but whose static versions are very portable. Here's a "sneaky" example: 43 | /// 44 | /// ```no_compile 45 | /// use memfd_exec::{MemFdExecutable, Stdio}; 46 | /// 47 | /// // You can include the entirety of a binary (for example, nc) 48 | /// let nc_binary= include_bytes!("/usr/bin/nc-static"); 49 | /// 50 | /// 51 | /// // The first argument is just the name for the program, you can pick anything but 52 | /// // if the program expects a specific argv[0] value, use that. 53 | /// // The second argument is the binary code to execute. 54 | /// let mut cmd = MemFdExecutable::new("nc", nc_binary) 55 | /// // We can pass arbitrary args just like with Command. Here, we'll execute nc 56 | /// // to listen on a port and run a shell for connections, entirely from memory. 57 | /// .arg("-l") 58 | /// .arg("1234") 59 | /// .arg("-e") 60 | /// .arg("/bin/sh") 61 | /// // And we can get piped stdin/stdout just like with Command 62 | /// .stdout(Stdio::piped()) 63 | /// // Spawn starts the child process and gives us a handle back 64 | /// .spawn() 65 | /// .expect("failed to execute process"); 66 | /// 67 | /// // Then, we can wait for the program to exit. 68 | /// cmd.wait(); 69 | /// ``` 70 | #[derive(Debug)] 71 | pub struct MemFdExecutable<'a> { 72 | /// The contents of the ELF executable to run. This content can be included in the file 73 | /// using the `include_bytes!()` macro, or you can do fancy things like read it in from 74 | /// a socket. 75 | code: &'a [u8], 76 | /// The name of the program, this value is the argv\[0\] argument to the binary when 77 | /// executed. If the program expects something specific here, that value should be 78 | /// used, otherwise any name will do 79 | program: CString, 80 | /// The arguments to the program, excluding the program name 81 | args: Vec, 82 | /// The whole argv array, including the program name 83 | argv: Argv, 84 | /// The environment variables to set for the program 85 | env: CommandEnv, 86 | /// The current working directory to set for the program 87 | cwd: Option, 88 | /// The program's stdin handle 89 | pub stdin: Option, 90 | /// The program's stdout handle 91 | pub stdout: Option, 92 | /// The program's stderr handle 93 | pub stderr: Option, 94 | /// Holdover from Command, whether there was a NUL in the arguments or not 95 | saw_nul: bool, 96 | } 97 | 98 | #[derive(Debug)] 99 | struct Argv(Vec); 100 | 101 | unsafe impl Send for Argv {} 102 | unsafe impl Sync for Argv {} 103 | 104 | fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString { 105 | CString::new(s.as_bytes()).unwrap_or_else(|_e| { 106 | *saw_nul = true; 107 | CString::new("").unwrap() 108 | }) 109 | } 110 | 111 | fn construct_envp(env: BTreeMap, saw_nul: &mut bool) -> Vec { 112 | let mut result = Vec::with_capacity(env.len()); 113 | for (mut k, v) in env { 114 | // Reserve additional space for '=' and null terminator 115 | k.reserve_exact(v.len() + 2); 116 | k.push("="); 117 | k.push(&v); 118 | 119 | // Add the new entry into the array 120 | if let Ok(item) = CString::new(k.into_vec()) { 121 | result.push(item); 122 | } else { 123 | *saw_nul = true; 124 | } 125 | } 126 | 127 | result 128 | } 129 | 130 | impl<'a> MemFdExecutable<'a> { 131 | /// Create a new MemFdExecutable with the given name and code. The name is the name of the 132 | /// program, and is used as the argv\[0\] argument to the program. The code is the binary 133 | /// code to execute (usually, the entire contents of an ELF file). 134 | /// 135 | /// # Examples 136 | /// 137 | /// You can run code that is included directly in your executable with `include_bytes!()`: 138 | /// 139 | /// ```no_compile 140 | /// use memfd_exec::MemFdExecutable; 141 | /// 142 | /// let code = include_bytes!("/usr/bin/nc-static"); 143 | /// 144 | /// let mut cmd = MemFdExecutable::new("nc", code) 145 | /// .arg("-l") 146 | /// .arg("1234") 147 | /// .arg("-e") 148 | /// .arg("/bin/sh") 149 | /// .status() 150 | /// .expect("failed to execute process"); 151 | /// ``` 152 | /// 153 | pub fn new>(name: S, code: &'a [u8]) -> Self { 154 | let mut saw_nul = false; 155 | let name = os2c(name.as_ref(), &mut saw_nul); 156 | Self { 157 | code, 158 | program: name.clone(), 159 | args: vec![name.clone()], 160 | argv: Argv(vec![name]), 161 | env: Default::default(), 162 | cwd: None, 163 | stdin: None, 164 | stdout: None, 165 | stderr: None, 166 | saw_nul, 167 | } 168 | } 169 | 170 | /// Add an argument to the program. This is equivalent to `Command::arg()`. 171 | pub fn arg>(&mut self, arg: S) -> &mut Self { 172 | let arg = os2c(arg.as_ref(), &mut self.saw_nul); 173 | self.argv.0.push(arg.clone()); 174 | self.args.push(arg); 175 | self 176 | } 177 | 178 | /// Add multiple arguments to the program. This is equivalent to `Command::args()`. 179 | pub fn args(&mut self, args: I) -> &mut Self 180 | where 181 | I: IntoIterator, 182 | S: AsRef, 183 | { 184 | for arg in args { 185 | self.arg(arg.as_ref()); 186 | } 187 | self 188 | } 189 | 190 | /// Add an environment variable to the program. This is equivalent to `Command::env()`. 191 | pub fn env(&mut self, key: K, val: V) -> &mut Self 192 | where 193 | K: AsRef, 194 | V: AsRef, 195 | { 196 | self.env_mut().set(key.as_ref(), val.as_ref()); 197 | self 198 | } 199 | 200 | /// Add multiple environment variables to the program. This is equivalent to `Command::envs()`. 201 | pub fn envs(&mut self, vars: I) -> &mut Self 202 | where 203 | I: IntoIterator, 204 | K: AsRef, 205 | V: AsRef, 206 | { 207 | for (ref key, ref val) in vars { 208 | self.env_mut().set(key.as_ref(), val.as_ref()); 209 | } 210 | self 211 | } 212 | 213 | /// Remove an environment variable from the program. This is equivalent to `Command::env_remove()`. 214 | pub fn env_remove>(&mut self, key: K) -> &mut Self { 215 | self.env_mut().remove(key.as_ref()); 216 | self 217 | } 218 | 219 | /// Clear all environment variables from the program. This is equivalent to `Command::env_clear()`. 220 | pub fn env_clear(&mut self) -> &mut Self { 221 | self.env_mut().clear(); 222 | self 223 | } 224 | 225 | /// Set the current working directory for the program. This is equivalent to `Command::current_dir()`. 226 | pub fn cwd>(&mut self, dir: P) -> &mut Self { 227 | self.cwd = Some(os2c(dir.as_ref().as_ref(), &mut self.saw_nul)); 228 | self 229 | } 230 | 231 | /// Set the stdin handle for the program. This is equivalent to `Command::stdin()`. The 232 | /// default is to inherit the current process's stdin. Note that this `Stdio` is not the 233 | /// same exactly as `process::Stdio`, but it is feature-equivalent. 234 | /// 235 | /// # Examples 236 | /// 237 | /// This example creates a `cat` process that will read in the contents passed to its 238 | /// stdin handle and write them to a null stdout (i.e. it will be discarded). The same 239 | /// methodology can be used to read from stderr/stdout. 240 | /// 241 | /// ```no_run 242 | /// use std::thread::spawn; 243 | /// use std::io::Write; 244 | /// 245 | /// use memfd_exec::{MemFdExecutable, Stdio}; 246 | /// 247 | /// let mut cat_cmd = MemFdExecutable::new("cat", include_bytes!("/bin/cat")) 248 | /// .stdin(Stdio::piped()) 249 | /// .stdout(Stdio::null()) 250 | /// .spawn() 251 | /// .expect("failed to spawn cat"); 252 | /// 253 | /// let mut cat_stdin = cat_cmd.stdin.take().expect("failed to open stdin"); 254 | /// spawn(move || { 255 | /// cat_stdin.write_all(b"hello world").expect("failed to write to stdin"); 256 | /// }); 257 | /// ``` 258 | pub fn stdin>(&mut self, cfg: T) -> &mut Self { 259 | self.stdin = Some(cfg.into()); 260 | self 261 | } 262 | 263 | /// Set the stdout handle for the program. This is equivalent to `Command::stdout()`. The 264 | /// 265 | /// # Arguments 266 | /// * `cfg` - The configuration for the stdout handle. This will usually be one of the following: 267 | /// * `Stdio::inherit()` - Inherit the current process's stdout handle 268 | /// * `Stdio::piped()` - Create a pipe to the child process's stdout. This can be read 269 | /// * `Stdio::null()` - Discard all output to stdout 270 | /// 271 | /// # Examples 272 | /// 273 | /// This example creates a `cat` process that will read in the contents passed to its stdin handle 274 | /// and read them from its stdout handle. The same methodology can be used to read from stderr/stdout. 275 | /// 276 | /// ``` 277 | /// use std::thread::spawn; 278 | /// use std::fs::read; 279 | /// use std::io::{Read, Write}; 280 | /// 281 | /// use memfd_exec::{MemFdExecutable, Stdio}; 282 | /// 283 | /// let mut cat = MemFdExecutable::new("cat", &read("/bin/cat").unwrap()) 284 | /// .stdin(Stdio::piped()) 285 | /// .stdout(Stdio::piped()) 286 | /// .spawn() 287 | /// .expect("failed to spawn cat"); 288 | /// 289 | /// let mut cat_stdin = cat.stdin.take().expect("failed to open stdin"); 290 | /// let mut cat_stdout = cat.stdout.take().expect("failed to open stdout"); 291 | /// 292 | /// spawn(move || { 293 | /// cat_stdin.write_all(b"hello world").expect("failed to write to stdin"); 294 | /// }); 295 | /// 296 | /// let mut output = Vec::new(); 297 | /// cat_stdout.read_to_end(&mut output).expect("failed to read from stdout"); 298 | /// assert_eq!(output, b"hello world"); 299 | /// cat.wait().expect("failed to wait on cat"); 300 | /// ``` 301 | pub fn stdout>(&mut self, cfg: T) -> &mut Self { 302 | self.stdout = Some(cfg.into()); 303 | self 304 | } 305 | 306 | /// Set the stderr handle for the program. This is equivalent to `Command::stderr()`. The 307 | /// 308 | /// # Arguments 309 | /// * `cfg` - The configuration for the stderr handle. This will usually be one of the following: 310 | /// * `Stdio::inherit()` - Inherit the current process's stderr handle 311 | /// * `Stdio::piped()` - Create a pipe to the child process's stderr. This can be read 312 | /// * `Stdio::null()` - Discard all output to stderr 313 | pub fn stderr>(&mut self, cfg: T) -> &mut Self { 314 | self.stderr = Some(cfg.into()); 315 | self 316 | } 317 | 318 | /// Spawn the program as a child process. This is equivalent to `Command::spawn()`. 319 | pub fn spawn(&mut self) -> Result { 320 | let default = Stdio::Inherit; 321 | let needs_stdin = true; 322 | const CLOEXEC_MSG_FOOTER: [u8; 4] = *b"NOEX"; 323 | 324 | let envp = self.capture_env(); 325 | 326 | if self.saw_nul() { 327 | // TODO: Need err? 328 | } 329 | 330 | let (ours, theirs) = self.setup_io(default, needs_stdin)?; 331 | 332 | let (input, output) = anon_pipe()?; 333 | 334 | // Whatever happens after the fork is almost for sure going to touch or 335 | // look at the environment in one way or another (PATH in `execvp` or 336 | // accessing the `environ` pointer ourselves). Make sure no other thread 337 | // is accessing the environment when we do the fork itself. 338 | // 339 | // Note that as soon as we're done with the fork there's no need to hold 340 | // a lock any more because the parent won't do anything and the child is 341 | // in its own process. Thus the parent drops the lock guard while the child 342 | // forgets it to avoid unlocking it on a new thread, which would be invalid. 343 | // TODO: Yeah....I had to remove the env lock. Whoops! Don't multithread env with this 344 | // you insane person 345 | let pid = unsafe { self.do_fork()? }; 346 | 347 | if pid == 0 { 348 | drop(input); 349 | let Err(err) = (unsafe { self.do_exec(theirs, envp) }) else { unreachable!("..."); }; 350 | panic!("failed to exec: {}", err); 351 | } 352 | 353 | drop(output); 354 | 355 | // Safety: We obtained the pidfd from calling `clone3` with 356 | // `CLONE_PIDFD` so it's valid an otherwise unowned. 357 | let mut p = unsafe { Process::new(pid) }; 358 | let mut bytes = [0; 8]; 359 | 360 | // loop to handle EINTR 361 | loop { 362 | match input.read(&mut bytes) { 363 | Ok(0) => return Ok(Child::new(p, ours)), 364 | Ok(8) => { 365 | let (errno, footer) = bytes.split_at(4); 366 | assert_eq!( 367 | CLOEXEC_MSG_FOOTER, footer, 368 | "Validation on the CLOEXEC pipe failed: {:?}", 369 | bytes 370 | ); 371 | let errno = i32::from_be_bytes(errno.try_into().unwrap()); 372 | assert!(p.wait().is_ok(), "wait() should either return Ok or panic"); 373 | return Err(Error::from_raw_os_error(errno)); 374 | } 375 | Err(ref e) if e.kind() == ErrorKind::Interrupted => {} 376 | Err(e) => { 377 | assert!(p.wait().is_ok(), "wait() should either return Ok or panic"); 378 | panic!("the CLOEXEC pipe failed: {e:?}") 379 | } 380 | Ok(..) => { 381 | // pipe I/O up to PIPE_BUF bytes should be atomic 382 | assert!(p.wait().is_ok(), "wait() should either return Ok or panic"); 383 | panic!("short read on the CLOEXEC pipe") 384 | } 385 | } 386 | } 387 | } 388 | 389 | /// Spawn the program as a child process and wait for it to complete, obtaining the 390 | /// output and exit status. This is equivalent to `Command::output()`. 391 | pub fn output(&mut self) -> Result { 392 | self.spawn()?.wait_with_output() 393 | } 394 | 395 | /// Spawn the program as a child process and wait for it to complete, obtaining the 396 | /// exit status. This is equivalent to `Command::status()`. 397 | pub fn status(&mut self) -> Result { 398 | self.spawn()?.wait() 399 | } 400 | 401 | /// Set the program name (argv\[0\]) to a new value. 402 | /// 403 | /// # Arguments 404 | /// * `name` - The new name for the program. This will be used as the first argument 405 | pub fn set_program(&mut self, program: &OsStr) { 406 | let arg = os2c(program, &mut self.saw_nul); 407 | self.argv.0[0] = arg.clone(); 408 | self.args[0] = arg; 409 | } 410 | 411 | fn env_mut(&mut self) -> &mut CommandEnv { 412 | &mut self.env 413 | } 414 | 415 | fn setup_io(&self, default: Stdio, needs_stdin: bool) -> Result<(StdioPipes, ChildPipes)> { 416 | let null = Stdio::Null; 417 | let default_stdin = if needs_stdin { &default } else { &null }; 418 | let stdin = self.stdin.as_ref().unwrap_or(default_stdin); 419 | let stdout = self.stdout.as_ref().unwrap_or(&default); 420 | let stderr = self.stderr.as_ref().unwrap_or(&default); 421 | let (their_stdin, our_stdin) = stdin.to_child_stdio(true)?; 422 | let (their_stdout, our_stdout) = stdout.to_child_stdio(false)?; 423 | let (their_stderr, our_stderr) = stderr.to_child_stdio(false)?; 424 | let ours = StdioPipes { 425 | stdin: our_stdin, 426 | stdout: our_stdout, 427 | stderr: our_stderr, 428 | }; 429 | let theirs = ChildPipes { 430 | stdin: their_stdin, 431 | stdout: their_stdout, 432 | stderr: their_stderr, 433 | }; 434 | Ok((ours, theirs)) 435 | } 436 | 437 | fn saw_nul(&self) -> bool { 438 | self.saw_nul 439 | } 440 | 441 | /// Get the current working directory for the child process. 442 | pub fn get_cwd(&self) -> &Option { 443 | &self.cwd 444 | } 445 | 446 | unsafe fn do_fork(&mut self) -> Result { 447 | cvt(libc::fork()) 448 | } 449 | 450 | fn capture_env(&mut self) -> Option> { 451 | let maybe_env = self.env.capture_if_changed(); 452 | maybe_env.map(|env| construct_envp(env, &mut self.saw_nul)) 453 | } 454 | 455 | /// Execute the command as a new process, replacing the current process. 456 | /// 457 | /// This function will not return. 458 | /// 459 | /// # Arguments 460 | /// * `default` - The default stdio to use if the child process does not specify. 461 | pub fn exec(&mut self, default: Stdio) -> Error { 462 | let envp = self.capture_env(); 463 | 464 | if self.saw_nul() { 465 | return Error::new(ErrorKind::InvalidInput, "nul byte found in provided data"); 466 | } 467 | 468 | match self.setup_io(default, true) { 469 | Ok((_, theirs)) => unsafe { 470 | let Err(e) = self.do_exec(theirs, envp) else { unreachable!("..."); }; 471 | e 472 | }, 473 | Err(e) => e, 474 | } 475 | } 476 | 477 | /// Get the program name to use for the child process as a C string. 478 | pub fn get_program_cstr(&self) -> &CStr { 479 | &self.program 480 | } 481 | 482 | /// Get the program argv to use for the child process. 483 | pub fn get_argv(&self) -> &Vec { 484 | &self.argv.0 485 | } 486 | 487 | /// Get whether PATH has been affected by changes to the environment variables 488 | /// of this command. 489 | pub fn env_saw_path(&self) -> bool { 490 | self.env.have_changed_path() 491 | } 492 | 493 | /// Get whether the program (argv\[0\]) is a path, as opposed to a name. 494 | pub fn program_is_path(&self) -> bool { 495 | self.program.to_bytes().contains(&b'/') 496 | } 497 | 498 | unsafe fn do_exec( 499 | &mut self, 500 | stdio: ChildPipes, 501 | maybe_envp: Option>, 502 | ) -> Result<()> { 503 | if let Some(fd) = stdio.stdin.fd() { 504 | cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO))?; 505 | } 506 | if let Some(fd) = stdio.stdout.fd() { 507 | cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO))?; 508 | } 509 | if let Some(fd) = stdio.stderr.fd() { 510 | cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO))?; 511 | } 512 | 513 | if let Some(ref cwd) = *self.get_cwd() { 514 | cvt(libc::chdir(cwd.as_ptr()))?; 515 | } 516 | 517 | { 518 | // Reset signal handling so the child process starts in a 519 | // standardized state. libstd ignores SIGPIPE, and signal-handling 520 | // libraries often set a mask. Child processes inherit ignored 521 | // signals and the signal mask from their parent, but most 522 | // UNIX programs do not reset these things on their own, so we 523 | // need to clean things up now to avoid confusing the program 524 | // we're about to run. 525 | let mut set = MaybeUninit::::uninit(); 526 | cvt(sigemptyset(set.as_mut_ptr()))?; 527 | cvt_nz(libc::pthread_sigmask( 528 | libc::SIG_SETMASK, 529 | set.as_ptr(), 530 | null_mut(), 531 | ))?; 532 | 533 | { 534 | let ret = signal(libc::SIGPIPE, libc::SIG_DFL); 535 | if ret == libc::SIG_ERR { 536 | return Err(Error::last_os_error()); 537 | } 538 | } 539 | } 540 | 541 | // TODO: Env resetting isn't implemented because we're using fexecve not execvp 542 | 543 | // Map the executable last, because it's a huge hit to memory if something else failed 544 | let mfd = memfd_create( 545 | CString::new("rust_exec").unwrap().as_c_str(), 546 | MemFdCreateFlag::MFD_CLOEXEC, 547 | ) 548 | .unwrap(); 549 | 550 | if let Ok(n) = write(mfd, self.code) { 551 | if n != self.code.len() { 552 | return Err(Error::new( 553 | ErrorKind::BrokenPipe, 554 | "Failed to write to memfd", 555 | )); 556 | } 557 | } else { 558 | return Err(Error::last_os_error()); 559 | } 560 | 561 | let argv = self 562 | .get_argv() 563 | .iter() 564 | .map(|s| s.as_c_str()) 565 | .collect::>(); 566 | 567 | let maybe_envp = maybe_envp.unwrap_or_default(); 568 | 569 | let envp = maybe_envp.iter().map(|s| s.as_c_str()).collect::>(); 570 | 571 | if let Err(err) = fexecve(mfd, &argv, &envp) { 572 | // If we failed to exec, we need to close the memfd 573 | // so that the child process doesn't leak it 574 | let _ = close(mfd); 575 | return Err(Error::new(ErrorKind::BrokenPipe, err)); 576 | } 577 | Err(Error::last_os_error()) 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "base64" 13 | version = "0.22.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "2.5.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 28 | 29 | [[package]] 30 | name = "bumpalo" 31 | version = "3.15.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" 34 | 35 | [[package]] 36 | name = "bytes" 37 | version = "1.2.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 40 | 41 | [[package]] 42 | name = "cc" 43 | version = "1.0.73" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 46 | 47 | [[package]] 48 | name = "cfg-if" 49 | version = "1.0.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 52 | 53 | [[package]] 54 | name = "cfg_aliases" 55 | version = "0.1.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" 58 | 59 | [[package]] 60 | name = "core-foundation" 61 | version = "0.9.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 64 | dependencies = [ 65 | "core-foundation-sys", 66 | "libc", 67 | ] 68 | 69 | [[package]] 70 | name = "core-foundation-sys" 71 | version = "0.8.3" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 74 | 75 | [[package]] 76 | name = "encoding_rs" 77 | version = "0.8.31" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 80 | dependencies = [ 81 | "cfg-if", 82 | ] 83 | 84 | [[package]] 85 | name = "equivalent" 86 | version = "1.0.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 89 | 90 | [[package]] 91 | name = "errno" 92 | version = "0.3.8" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 95 | dependencies = [ 96 | "libc", 97 | "windows-sys 0.52.0", 98 | ] 99 | 100 | [[package]] 101 | name = "fastrand" 102 | version = "2.0.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" 105 | 106 | [[package]] 107 | name = "fnv" 108 | version = "1.0.7" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 111 | 112 | [[package]] 113 | name = "foreign-types" 114 | version = "0.3.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 117 | dependencies = [ 118 | "foreign-types-shared", 119 | ] 120 | 121 | [[package]] 122 | name = "foreign-types-shared" 123 | version = "0.1.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 126 | 127 | [[package]] 128 | name = "form_urlencoded" 129 | version = "1.1.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 132 | dependencies = [ 133 | "percent-encoding", 134 | ] 135 | 136 | [[package]] 137 | name = "futures" 138 | version = "0.3.24" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" 141 | dependencies = [ 142 | "futures-channel", 143 | "futures-core", 144 | "futures-executor", 145 | "futures-io", 146 | "futures-sink", 147 | "futures-task", 148 | "futures-util", 149 | ] 150 | 151 | [[package]] 152 | name = "futures-channel" 153 | version = "0.3.24" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" 156 | dependencies = [ 157 | "futures-core", 158 | "futures-sink", 159 | ] 160 | 161 | [[package]] 162 | name = "futures-core" 163 | version = "0.3.24" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" 166 | 167 | [[package]] 168 | name = "futures-executor" 169 | version = "0.3.24" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" 172 | dependencies = [ 173 | "futures-core", 174 | "futures-task", 175 | "futures-util", 176 | ] 177 | 178 | [[package]] 179 | name = "futures-io" 180 | version = "0.3.24" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" 183 | 184 | [[package]] 185 | name = "futures-sink" 186 | version = "0.3.24" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" 189 | 190 | [[package]] 191 | name = "futures-task" 192 | version = "0.3.24" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" 195 | 196 | [[package]] 197 | name = "futures-util" 198 | version = "0.3.24" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" 201 | dependencies = [ 202 | "futures-channel", 203 | "futures-core", 204 | "futures-io", 205 | "futures-sink", 206 | "futures-task", 207 | "memchr", 208 | "pin-project-lite", 209 | "pin-utils", 210 | "slab", 211 | ] 212 | 213 | [[package]] 214 | name = "h2" 215 | version = "0.4.4" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" 218 | dependencies = [ 219 | "bytes", 220 | "fnv", 221 | "futures-core", 222 | "futures-sink", 223 | "futures-util", 224 | "http", 225 | "indexmap", 226 | "slab", 227 | "tokio", 228 | "tokio-util", 229 | "tracing", 230 | ] 231 | 232 | [[package]] 233 | name = "hashbrown" 234 | version = "0.14.3" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 237 | 238 | [[package]] 239 | name = "hermit-abi" 240 | version = "0.1.19" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 243 | dependencies = [ 244 | "libc", 245 | ] 246 | 247 | [[package]] 248 | name = "http" 249 | version = "1.1.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 252 | dependencies = [ 253 | "bytes", 254 | "fnv", 255 | "itoa", 256 | ] 257 | 258 | [[package]] 259 | name = "http-body" 260 | version = "1.0.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 263 | dependencies = [ 264 | "bytes", 265 | "http", 266 | ] 267 | 268 | [[package]] 269 | name = "http-body-util" 270 | version = "0.1.1" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" 273 | dependencies = [ 274 | "bytes", 275 | "futures-core", 276 | "http", 277 | "http-body", 278 | "pin-project-lite", 279 | ] 280 | 281 | [[package]] 282 | name = "httparse" 283 | version = "1.8.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 286 | 287 | [[package]] 288 | name = "hyper" 289 | version = "1.3.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" 292 | dependencies = [ 293 | "bytes", 294 | "futures-channel", 295 | "futures-util", 296 | "h2", 297 | "http", 298 | "http-body", 299 | "httparse", 300 | "itoa", 301 | "pin-project-lite", 302 | "smallvec", 303 | "tokio", 304 | "want", 305 | ] 306 | 307 | [[package]] 308 | name = "hyper-tls" 309 | version = "0.6.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 312 | dependencies = [ 313 | "bytes", 314 | "http-body-util", 315 | "hyper", 316 | "hyper-util", 317 | "native-tls", 318 | "tokio", 319 | "tokio-native-tls", 320 | "tower-service", 321 | ] 322 | 323 | [[package]] 324 | name = "hyper-util" 325 | version = "0.1.3" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" 328 | dependencies = [ 329 | "bytes", 330 | "futures-channel", 331 | "futures-util", 332 | "http", 333 | "http-body", 334 | "hyper", 335 | "pin-project-lite", 336 | "socket2 0.5.7", 337 | "tokio", 338 | "tower", 339 | "tower-service", 340 | "tracing", 341 | ] 342 | 343 | [[package]] 344 | name = "idna" 345 | version = "0.3.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 348 | dependencies = [ 349 | "unicode-bidi", 350 | "unicode-normalization", 351 | ] 352 | 353 | [[package]] 354 | name = "indexmap" 355 | version = "2.2.6" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 358 | dependencies = [ 359 | "equivalent", 360 | "hashbrown", 361 | ] 362 | 363 | [[package]] 364 | name = "ipnet" 365 | version = "2.5.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" 368 | 369 | [[package]] 370 | name = "itoa" 371 | version = "1.0.3" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 374 | 375 | [[package]] 376 | name = "js-sys" 377 | version = "0.3.60" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 380 | dependencies = [ 381 | "wasm-bindgen", 382 | ] 383 | 384 | [[package]] 385 | name = "lazy_static" 386 | version = "1.4.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 389 | 390 | [[package]] 391 | name = "libc" 392 | version = "0.2.154" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 395 | 396 | [[package]] 397 | name = "linux-raw-sys" 398 | version = "0.4.13" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 401 | 402 | [[package]] 403 | name = "lock_api" 404 | version = "0.4.9" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 407 | dependencies = [ 408 | "autocfg", 409 | "scopeguard", 410 | ] 411 | 412 | [[package]] 413 | name = "log" 414 | version = "0.4.17" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 417 | dependencies = [ 418 | "cfg-if", 419 | ] 420 | 421 | [[package]] 422 | name = "memchr" 423 | version = "2.5.0" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 426 | 427 | [[package]] 428 | name = "memfd-exec" 429 | version = "0.2.1" 430 | dependencies = [ 431 | "libc", 432 | "nix", 433 | "reqwest", 434 | "serial_test", 435 | "tempfile", 436 | ] 437 | 438 | [[package]] 439 | name = "mime" 440 | version = "0.3.16" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 443 | 444 | [[package]] 445 | name = "mio" 446 | version = "0.8.11" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 449 | dependencies = [ 450 | "libc", 451 | "log", 452 | "wasi", 453 | "windows-sys 0.48.0", 454 | ] 455 | 456 | [[package]] 457 | name = "native-tls" 458 | version = "0.2.10" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" 461 | dependencies = [ 462 | "lazy_static", 463 | "libc", 464 | "log", 465 | "openssl", 466 | "openssl-probe", 467 | "openssl-sys", 468 | "schannel", 469 | "security-framework", 470 | "security-framework-sys", 471 | "tempfile", 472 | ] 473 | 474 | [[package]] 475 | name = "nix" 476 | version = "0.28.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" 479 | dependencies = [ 480 | "bitflags 2.5.0", 481 | "cfg-if", 482 | "cfg_aliases", 483 | "libc", 484 | ] 485 | 486 | [[package]] 487 | name = "num_cpus" 488 | version = "1.13.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 491 | dependencies = [ 492 | "hermit-abi", 493 | "libc", 494 | ] 495 | 496 | [[package]] 497 | name = "once_cell" 498 | version = "1.19.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 501 | 502 | [[package]] 503 | name = "openssl" 504 | version = "0.10.64" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" 507 | dependencies = [ 508 | "bitflags 2.5.0", 509 | "cfg-if", 510 | "foreign-types", 511 | "libc", 512 | "once_cell", 513 | "openssl-macros", 514 | "openssl-sys", 515 | ] 516 | 517 | [[package]] 518 | name = "openssl-macros" 519 | version = "0.1.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 522 | dependencies = [ 523 | "proc-macro2", 524 | "quote", 525 | "syn 1.0.101", 526 | ] 527 | 528 | [[package]] 529 | name = "openssl-probe" 530 | version = "0.1.5" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 533 | 534 | [[package]] 535 | name = "openssl-sys" 536 | version = "0.9.102" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" 539 | dependencies = [ 540 | "cc", 541 | "libc", 542 | "pkg-config", 543 | "vcpkg", 544 | ] 545 | 546 | [[package]] 547 | name = "parking_lot" 548 | version = "0.12.1" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 551 | dependencies = [ 552 | "lock_api", 553 | "parking_lot_core", 554 | ] 555 | 556 | [[package]] 557 | name = "parking_lot_core" 558 | version = "0.9.3" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 561 | dependencies = [ 562 | "cfg-if", 563 | "libc", 564 | "redox_syscall", 565 | "smallvec", 566 | "windows-sys 0.36.1", 567 | ] 568 | 569 | [[package]] 570 | name = "percent-encoding" 571 | version = "2.2.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 574 | 575 | [[package]] 576 | name = "pin-project" 577 | version = "1.1.5" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 580 | dependencies = [ 581 | "pin-project-internal", 582 | ] 583 | 584 | [[package]] 585 | name = "pin-project-internal" 586 | version = "1.1.5" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 589 | dependencies = [ 590 | "proc-macro2", 591 | "quote", 592 | "syn 2.0.18", 593 | ] 594 | 595 | [[package]] 596 | name = "pin-project-lite" 597 | version = "0.2.9" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 600 | 601 | [[package]] 602 | name = "pin-utils" 603 | version = "0.1.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 606 | 607 | [[package]] 608 | name = "pkg-config" 609 | version = "0.3.25" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 612 | 613 | [[package]] 614 | name = "proc-macro2" 615 | version = "1.0.60" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" 618 | dependencies = [ 619 | "unicode-ident", 620 | ] 621 | 622 | [[package]] 623 | name = "quote" 624 | version = "1.0.28" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" 627 | dependencies = [ 628 | "proc-macro2", 629 | ] 630 | 631 | [[package]] 632 | name = "redox_syscall" 633 | version = "0.2.16" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 636 | dependencies = [ 637 | "bitflags 1.3.2", 638 | ] 639 | 640 | [[package]] 641 | name = "reqwest" 642 | version = "0.12.4" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" 645 | dependencies = [ 646 | "base64", 647 | "bytes", 648 | "encoding_rs", 649 | "futures-channel", 650 | "futures-core", 651 | "futures-util", 652 | "h2", 653 | "http", 654 | "http-body", 655 | "http-body-util", 656 | "hyper", 657 | "hyper-tls", 658 | "hyper-util", 659 | "ipnet", 660 | "js-sys", 661 | "log", 662 | "mime", 663 | "native-tls", 664 | "once_cell", 665 | "percent-encoding", 666 | "pin-project-lite", 667 | "rustls-pemfile", 668 | "serde", 669 | "serde_json", 670 | "serde_urlencoded", 671 | "sync_wrapper", 672 | "system-configuration", 673 | "tokio", 674 | "tokio-native-tls", 675 | "tower-service", 676 | "url", 677 | "wasm-bindgen", 678 | "wasm-bindgen-futures", 679 | "web-sys", 680 | "winreg", 681 | ] 682 | 683 | [[package]] 684 | name = "rustix" 685 | version = "0.38.32" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" 688 | dependencies = [ 689 | "bitflags 2.5.0", 690 | "errno", 691 | "libc", 692 | "linux-raw-sys", 693 | "windows-sys 0.52.0", 694 | ] 695 | 696 | [[package]] 697 | name = "rustls-pemfile" 698 | version = "2.1.2" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 701 | dependencies = [ 702 | "base64", 703 | "rustls-pki-types", 704 | ] 705 | 706 | [[package]] 707 | name = "rustls-pki-types" 708 | version = "1.6.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "51f344d206c5e1b010eec27349b815a4805f70a778895959d70b74b9b529b30a" 711 | 712 | [[package]] 713 | name = "ryu" 714 | version = "1.0.11" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 717 | 718 | [[package]] 719 | name = "scc" 720 | version = "2.1.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2" 723 | dependencies = [ 724 | "sdd", 725 | ] 726 | 727 | [[package]] 728 | name = "schannel" 729 | version = "0.1.20" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 732 | dependencies = [ 733 | "lazy_static", 734 | "windows-sys 0.36.1", 735 | ] 736 | 737 | [[package]] 738 | name = "scopeguard" 739 | version = "1.1.0" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 742 | 743 | [[package]] 744 | name = "sdd" 745 | version = "0.2.0" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" 748 | 749 | [[package]] 750 | name = "security-framework" 751 | version = "2.7.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" 754 | dependencies = [ 755 | "bitflags 1.3.2", 756 | "core-foundation", 757 | "core-foundation-sys", 758 | "libc", 759 | "security-framework-sys", 760 | ] 761 | 762 | [[package]] 763 | name = "security-framework-sys" 764 | version = "2.6.1" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 767 | dependencies = [ 768 | "core-foundation-sys", 769 | "libc", 770 | ] 771 | 772 | [[package]] 773 | name = "serde" 774 | version = "1.0.145" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" 777 | 778 | [[package]] 779 | name = "serde_json" 780 | version = "1.0.85" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" 783 | dependencies = [ 784 | "itoa", 785 | "ryu", 786 | "serde", 787 | ] 788 | 789 | [[package]] 790 | name = "serde_urlencoded" 791 | version = "0.7.1" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 794 | dependencies = [ 795 | "form_urlencoded", 796 | "itoa", 797 | "ryu", 798 | "serde", 799 | ] 800 | 801 | [[package]] 802 | name = "serial_test" 803 | version = "3.1.1" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" 806 | dependencies = [ 807 | "futures", 808 | "log", 809 | "once_cell", 810 | "parking_lot", 811 | "scc", 812 | "serial_test_derive", 813 | ] 814 | 815 | [[package]] 816 | name = "serial_test_derive" 817 | version = "3.1.1" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" 820 | dependencies = [ 821 | "proc-macro2", 822 | "quote", 823 | "syn 2.0.18", 824 | ] 825 | 826 | [[package]] 827 | name = "slab" 828 | version = "0.4.7" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 831 | dependencies = [ 832 | "autocfg", 833 | ] 834 | 835 | [[package]] 836 | name = "smallvec" 837 | version = "1.13.2" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 840 | 841 | [[package]] 842 | name = "socket2" 843 | version = "0.4.7" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 846 | dependencies = [ 847 | "libc", 848 | "winapi", 849 | ] 850 | 851 | [[package]] 852 | name = "socket2" 853 | version = "0.5.7" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 856 | dependencies = [ 857 | "libc", 858 | "windows-sys 0.52.0", 859 | ] 860 | 861 | [[package]] 862 | name = "static_assertions" 863 | version = "1.1.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 866 | 867 | [[package]] 868 | name = "syn" 869 | version = "1.0.101" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" 872 | dependencies = [ 873 | "proc-macro2", 874 | "quote", 875 | "unicode-ident", 876 | ] 877 | 878 | [[package]] 879 | name = "syn" 880 | version = "2.0.18" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" 883 | dependencies = [ 884 | "proc-macro2", 885 | "quote", 886 | "unicode-ident", 887 | ] 888 | 889 | [[package]] 890 | name = "sync_wrapper" 891 | version = "0.1.2" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 894 | 895 | [[package]] 896 | name = "system-configuration" 897 | version = "0.5.1" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 900 | dependencies = [ 901 | "bitflags 1.3.2", 902 | "core-foundation", 903 | "system-configuration-sys", 904 | ] 905 | 906 | [[package]] 907 | name = "system-configuration-sys" 908 | version = "0.5.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 911 | dependencies = [ 912 | "core-foundation-sys", 913 | "libc", 914 | ] 915 | 916 | [[package]] 917 | name = "tempfile" 918 | version = "3.10.1" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 921 | dependencies = [ 922 | "cfg-if", 923 | "fastrand", 924 | "rustix", 925 | "windows-sys 0.52.0", 926 | ] 927 | 928 | [[package]] 929 | name = "tinyvec" 930 | version = "1.6.0" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 933 | dependencies = [ 934 | "tinyvec_macros", 935 | ] 936 | 937 | [[package]] 938 | name = "tinyvec_macros" 939 | version = "0.1.0" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 942 | 943 | [[package]] 944 | name = "tokio" 945 | version = "1.26.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" 948 | dependencies = [ 949 | "autocfg", 950 | "bytes", 951 | "libc", 952 | "memchr", 953 | "mio", 954 | "num_cpus", 955 | "pin-project-lite", 956 | "socket2 0.4.7", 957 | "windows-sys 0.45.0", 958 | ] 959 | 960 | [[package]] 961 | name = "tokio-native-tls" 962 | version = "0.3.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 965 | dependencies = [ 966 | "native-tls", 967 | "tokio", 968 | ] 969 | 970 | [[package]] 971 | name = "tokio-util" 972 | version = "0.7.4" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 975 | dependencies = [ 976 | "bytes", 977 | "futures-core", 978 | "futures-sink", 979 | "pin-project-lite", 980 | "tokio", 981 | "tracing", 982 | ] 983 | 984 | [[package]] 985 | name = "tower" 986 | version = "0.4.13" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 989 | dependencies = [ 990 | "futures-core", 991 | "futures-util", 992 | "pin-project", 993 | "pin-project-lite", 994 | "tokio", 995 | "tower-layer", 996 | "tower-service", 997 | "tracing", 998 | ] 999 | 1000 | [[package]] 1001 | name = "tower-layer" 1002 | version = "0.3.2" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1005 | 1006 | [[package]] 1007 | name = "tower-service" 1008 | version = "0.3.2" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1011 | 1012 | [[package]] 1013 | name = "tracing" 1014 | version = "0.1.36" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" 1017 | dependencies = [ 1018 | "cfg-if", 1019 | "log", 1020 | "pin-project-lite", 1021 | "tracing-core", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "tracing-core" 1026 | version = "0.1.29" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" 1029 | dependencies = [ 1030 | "once_cell", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "try-lock" 1035 | version = "0.2.3" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1038 | 1039 | [[package]] 1040 | name = "unicode-bidi" 1041 | version = "0.3.8" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1044 | 1045 | [[package]] 1046 | name = "unicode-ident" 1047 | version = "1.0.4" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" 1050 | 1051 | [[package]] 1052 | name = "unicode-normalization" 1053 | version = "0.1.22" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1056 | dependencies = [ 1057 | "tinyvec", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "url" 1062 | version = "2.3.1" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 1065 | dependencies = [ 1066 | "form_urlencoded", 1067 | "idna", 1068 | "percent-encoding", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "vcpkg" 1073 | version = "0.2.15" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1076 | 1077 | [[package]] 1078 | name = "want" 1079 | version = "0.3.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1082 | dependencies = [ 1083 | "log", 1084 | "try-lock", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "wasi" 1089 | version = "0.11.0+wasi-snapshot-preview1" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1092 | 1093 | [[package]] 1094 | name = "wasm-bindgen" 1095 | version = "0.2.83" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 1098 | dependencies = [ 1099 | "cfg-if", 1100 | "wasm-bindgen-macro", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "wasm-bindgen-backend" 1105 | version = "0.2.83" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 1108 | dependencies = [ 1109 | "bumpalo", 1110 | "log", 1111 | "once_cell", 1112 | "proc-macro2", 1113 | "quote", 1114 | "syn 1.0.101", 1115 | "wasm-bindgen-shared", 1116 | ] 1117 | 1118 | [[package]] 1119 | name = "wasm-bindgen-futures" 1120 | version = "0.4.33" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" 1123 | dependencies = [ 1124 | "cfg-if", 1125 | "js-sys", 1126 | "wasm-bindgen", 1127 | "web-sys", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "wasm-bindgen-macro" 1132 | version = "0.2.83" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 1135 | dependencies = [ 1136 | "quote", 1137 | "wasm-bindgen-macro-support", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "wasm-bindgen-macro-support" 1142 | version = "0.2.83" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 1145 | dependencies = [ 1146 | "proc-macro2", 1147 | "quote", 1148 | "syn 1.0.101", 1149 | "wasm-bindgen-backend", 1150 | "wasm-bindgen-shared", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "wasm-bindgen-shared" 1155 | version = "0.2.83" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 1158 | 1159 | [[package]] 1160 | name = "web-sys" 1161 | version = "0.3.60" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 1164 | dependencies = [ 1165 | "js-sys", 1166 | "wasm-bindgen", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "winapi" 1171 | version = "0.3.9" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1174 | dependencies = [ 1175 | "winapi-i686-pc-windows-gnu", 1176 | "winapi-x86_64-pc-windows-gnu", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "winapi-i686-pc-windows-gnu" 1181 | version = "0.4.0" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1184 | 1185 | [[package]] 1186 | name = "winapi-x86_64-pc-windows-gnu" 1187 | version = "0.4.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1190 | 1191 | [[package]] 1192 | name = "windows-sys" 1193 | version = "0.36.1" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1196 | dependencies = [ 1197 | "windows_aarch64_msvc 0.36.1", 1198 | "windows_i686_gnu 0.36.1", 1199 | "windows_i686_msvc 0.36.1", 1200 | "windows_x86_64_gnu 0.36.1", 1201 | "windows_x86_64_msvc 0.36.1", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "windows-sys" 1206 | version = "0.45.0" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1209 | dependencies = [ 1210 | "windows-targets 0.42.2", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "windows-sys" 1215 | version = "0.48.0" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1218 | dependencies = [ 1219 | "windows-targets 0.48.0", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "windows-sys" 1224 | version = "0.52.0" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1227 | dependencies = [ 1228 | "windows-targets 0.52.5", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "windows-targets" 1233 | version = "0.42.2" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1236 | dependencies = [ 1237 | "windows_aarch64_gnullvm 0.42.2", 1238 | "windows_aarch64_msvc 0.42.2", 1239 | "windows_i686_gnu 0.42.2", 1240 | "windows_i686_msvc 0.42.2", 1241 | "windows_x86_64_gnu 0.42.2", 1242 | "windows_x86_64_gnullvm 0.42.2", 1243 | "windows_x86_64_msvc 0.42.2", 1244 | ] 1245 | 1246 | [[package]] 1247 | name = "windows-targets" 1248 | version = "0.48.0" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 1251 | dependencies = [ 1252 | "windows_aarch64_gnullvm 0.48.0", 1253 | "windows_aarch64_msvc 0.48.0", 1254 | "windows_i686_gnu 0.48.0", 1255 | "windows_i686_msvc 0.48.0", 1256 | "windows_x86_64_gnu 0.48.0", 1257 | "windows_x86_64_gnullvm 0.48.0", 1258 | "windows_x86_64_msvc 0.48.0", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "windows-targets" 1263 | version = "0.52.5" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1266 | dependencies = [ 1267 | "windows_aarch64_gnullvm 0.52.5", 1268 | "windows_aarch64_msvc 0.52.5", 1269 | "windows_i686_gnu 0.52.5", 1270 | "windows_i686_gnullvm", 1271 | "windows_i686_msvc 0.52.5", 1272 | "windows_x86_64_gnu 0.52.5", 1273 | "windows_x86_64_gnullvm 0.52.5", 1274 | "windows_x86_64_msvc 0.52.5", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "windows_aarch64_gnullvm" 1279 | version = "0.42.2" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1282 | 1283 | [[package]] 1284 | name = "windows_aarch64_gnullvm" 1285 | version = "0.48.0" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1288 | 1289 | [[package]] 1290 | name = "windows_aarch64_gnullvm" 1291 | version = "0.52.5" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1294 | 1295 | [[package]] 1296 | name = "windows_aarch64_msvc" 1297 | version = "0.36.1" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1300 | 1301 | [[package]] 1302 | name = "windows_aarch64_msvc" 1303 | version = "0.42.2" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1306 | 1307 | [[package]] 1308 | name = "windows_aarch64_msvc" 1309 | version = "0.48.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1312 | 1313 | [[package]] 1314 | version = "0.52.5" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1317 | 1318 | [[package]] 1319 | name = "windows_i686_gnu" 1320 | version = "0.36.1" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1323 | 1324 | [[package]] 1325 | name = "windows_i686_gnu" 1326 | version = "0.42.2" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1329 | 1330 | [[package]] 1331 | name = "windows_i686_gnu" 1332 | version = "0.48.0" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1335 | 1336 | [[package]] 1337 | name = "windows_i686_gnu" 1338 | version = "0.52.5" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1341 | 1342 | [[package]] 1343 | name = "windows_i686_gnullvm" 1344 | version = "0.52.5" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1347 | 1348 | [[package]] 1349 | name = "windows_i686_msvc" 1350 | version = "0.36.1" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1353 | 1354 | [[package]] 1355 | name = "windows_i686_msvc" 1356 | version = "0.42.2" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1359 | 1360 | [[package]] 1361 | name = "windows_i686_msvc" 1362 | version = "0.48.0" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1365 | 1366 | [[package]] 1367 | name = "windows_i686_msvc" 1368 | version = "0.52.5" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1371 | 1372 | [[package]] 1373 | name = "windows_x86_64_gnu" 1374 | version = "0.36.1" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1377 | 1378 | [[package]] 1379 | name = "windows_x86_64_gnu" 1380 | version = "0.42.2" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1383 | 1384 | [[package]] 1385 | name = "windows_x86_64_gnu" 1386 | version = "0.48.0" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1389 | 1390 | [[package]] 1391 | name = "windows_x86_64_gnu" 1392 | version = "0.52.5" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1395 | 1396 | [[package]] 1397 | name = "windows_x86_64_gnullvm" 1398 | version = "0.42.2" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1401 | 1402 | [[package]] 1403 | name = "windows_x86_64_gnullvm" 1404 | version = "0.48.0" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1407 | 1408 | [[package]] 1409 | name = "windows_x86_64_gnullvm" 1410 | version = "0.52.5" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1413 | 1414 | [[package]] 1415 | name = "windows_x86_64_msvc" 1416 | version = "0.36.1" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1419 | 1420 | [[package]] 1421 | name = "windows_x86_64_msvc" 1422 | version = "0.42.2" 1423 | source = "registry+https://github.com/rust-lang/crates.io-index" 1424 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1425 | 1426 | [[package]] 1427 | name = "windows_x86_64_msvc" 1428 | version = "0.48.0" 1429 | source = "registry+https://github.com/rust-lang/crates.io-index" 1430 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1431 | 1432 | [[package]] 1433 | name = "windows_x86_64_msvc" 1434 | version = "0.52.5" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1437 | 1438 | [[package]] 1439 | name = "winreg" 1440 | version = "0.52.0" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" 1443 | dependencies = [ 1444 | "cfg-if", 1445 | "windows-sys 0.48.0", 1446 | ] 1447 | --------------------------------------------------------------------------------