subprocess-0.2.9/.cargo_vcs_info.json0000644000000001360000000000100132340ustar { "git": { "sha1": "ca3bf09d00f10d2a0b883bfcfa4e6c78ad6a7f7c" }, "path_in_vcs": "" }subprocess-0.2.9/.gitignore000064400000000000000000000000310072674642500140360ustar 00000000000000target Cargo.lock .idea/ subprocess-0.2.9/.travis.yml000064400000000000000000000002260072674642500141650ustar 00000000000000language: rust os: - linux - osx - windows rust: - stable - beta - nightly matrix: allow_failures: - rust: beta - rust: nightly subprocess-0.2.9/Cargo.lock0000644000000054770000000000100112240ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "fastrand" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "redox_syscall" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" dependencies = [ "winapi", ] [[package]] name = "subprocess" version = "0.2.9" dependencies = [ "lazy_static", "libc", "tempfile", "winapi", ] [[package]] name = "tempfile" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", "fastrand", "libc", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "winapi" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" subprocess-0.2.9/Cargo.toml0000644000000024170000000000100112360ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "subprocess" version = "0.2.9" authors = ["Hrvoje Nikšić "] description = "Execution of child processes and pipelines, inspired by Python's subprocess\nmodule, with Rust-specific extensions.\n" documentation = "https://docs.rs/subprocess/" readme = "README.md" keywords = ["execute", "process", "command", "redirect", "pipe"] license = "Apache-2.0/MIT" repository = "https://github.com/hniksic/rust-subprocess" [lib] name = "subprocess" path = "src/lib.rs" [dependencies.libc] version = "0.2.78" [dev-dependencies.lazy_static] version = "1.4.0" [dev-dependencies.tempfile] version = "3.3.0" [target."cfg(windows)".dependencies.winapi] version = "0.3.8" features = ["std", "handleapi", "namedpipeapi", "processenv", "synchapi", "winerror", "processthreadsapi", "winbase"] subprocess-0.2.9/Cargo.toml.orig000064400000000000000000000014350072674642500147460ustar 00000000000000[package] name = "subprocess" version = "0.2.9" authors = ["Hrvoje Nikšić "] readme = "README.md" keywords = ["execute", "process", "command", "redirect", "pipe"] license = "Apache-2.0/MIT" repository = "https://github.com/hniksic/rust-subprocess" documentation = "https://docs.rs/subprocess/" description = """ Execution of child processes and pipelines, inspired by Python's subprocess module, with Rust-specific extensions. """ edition = "2018" [dependencies] libc = "0.2.78" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.8", features = ["std", "handleapi", "namedpipeapi", "processenv", "synchapi", "winerror", "processthreadsapi", "winbase"] } [dev-dependencies] tempfile = "3.3.0" lazy_static = "1.4.0" [lib] name = "subprocess" path = "src/lib.rs" subprocess-0.2.9/LICENSE-APACHE000064400000000000000000000251370072674642500140100ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. subprocess-0.2.9/LICENSE-MIT000064400000000000000000000020430072674642500135070ustar 00000000000000Copyright (c) 2017 Hrvoje Nikšić Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. subprocess-0.2.9/README.md000064400000000000000000000143040072674642500133350ustar 00000000000000# subprocess [![](http://meritbadge.herokuapp.com/subprocess)](https://crates.io/crates/subprocess) [![Build Status](https://travis-ci.org/hniksic/rust-subprocess.svg?branch=master)](https://travis-ci.org/hniksic/rust-subprocess) [![docs.rs](https://docs.rs/subprocess/badge.svg)](https://docs.rs/subprocess) The `subprocess` library provides facilities for execution of and interaction with external processes and pipelines, inspired by Python's `subprocess` module. `subprocess` is [hosted on crates.io](https://crates.io/crates/subprocess), with [API Documentation on docs.rs](https://docs.rs/subprocess/). ## Features This library is about launching external processes with optional redirection of standard input, output, and error. It covers similar ground as the [`std::process`](https://doc.rust-lang.org/std/process/index.html) standard library module, but with additional functionality: * The *communicate* [family of methods](https://docs.rs/subprocess/latest/subprocess/struct.Popen.html#method.communicate_start) for deadlock-free capturing of subprocess output/error to memory, while simultaneously feeding data to its standard input. Capturing supports optional timeout and read size limit. * Connecting multiple commands into OS-level pipelines. * Flexible redirection options, such as connecting standard streams to arbitrary files, or merging output streams like shell's `2>&1` and `1>&2` operators. * Non-blocking and timeout methods to wait on the process: `poll`, `wait`, and `wait_timeout`. The crate has minimal dependencies to third-party crates, only requiring `libc` on Unix and `winapi` on Windows. It is intended to work on Unix-like platforms as well as on reasonably recent Windows. It is regularly tested on Linux, MacOS and Windows. ## API Overview The API is separated in two parts: the low-level `Popen` API similar to Python's [`subprocess.Popen`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen), and the higher-level API for convenient creation of commands and pipelines. The two can be mixed, so it is possible to use builder to create `Popen` instances, and then to continue working with them directly. While `Popen` loosely follows Python's [`subprocess` module](https://docs.python.org/3/library/subprocess.html#popen-constructor), it is not a literal translation. Some of the changes accommodate the differences between the languages, such as the lack of default and keyword arguments in Rust, and others take advantage of Rust's more advanced type system, or of additional capabilities such as the ownership system and the `Drop` trait. Python's utility functions such as `subprocess.run` are not included because they can be better expressed using the builder pattern. The high-level API offers an elegant process and pipeline creation interface, along with convenience methods for capturing their output and exit status. ## Examples ### Spawning and redirecting Execute an command and wait for it to complete: ```rust let exit_status = Exec::cmd("umount").arg(dirname).join()?; assert!(exit_status.success()); ``` To prevent quoting issues and injection attacks, subprocess will not spawn a shell unless explicitly requested. To execute a command using the OS shell, like C's `system`, use `Exec::shell`: ```rust Exec::shell("shutdown -h now").join()?; ``` Start a subprocess and obtain its output as a `Read` trait object, like C's `popen`: ```rust let stream = Exec::cmd("find /").stream_stdout()?; // Call stream.read_to_string, construct io::BufReader(stream) and iterate it // by lines, etc... ``` Capture the output of a command: ```rust let out = Exec::cmd("ls") .stdout(Redirection::Pipe) .capture()? .stdout_str(); ``` Redirect standard error to standard output, and capture them in a string: ```rust let out_and_err = Exec::cmd("ls") .stdout(Redirection::Pipe) .stderr(Redirection::Merge) .capture()? .stdout_str(); ``` Provide some input to the command and read its output: ```rust let out = Exec::cmd("sort") .stdin("b\nc\na\n") .stdout(Redirection::Pipe) .capture()? .stdout_str(); assert_eq!(out, "a\nb\nc\n"); ``` Connecting `stdin` to an open file would have worked as well. ### Pipelines `Popen` objects support connecting input and output to arbitrary open files, including other `Popen` objects. This can be used to form pipelines of processes. The builder API will do it automatically with the `|` operator on `Exec` objects. Execute a pipeline and return the exit status of the last command: ```rust let exit_status = (Exec::shell("ls *.bak") | Exec::cmd("xargs").arg("rm")).join()?; ``` Capture the pipeline's output: ```rust let dir_checksum = { Exec::shell("find . -type f") | Exec::cmd("sort") | Exec::cmd("sha1sum") }.capture()?.stdout_str(); ``` ### The low-level Popen type ```rust let mut p = Popen::create(&["command", "arg1", "arg2"], PopenConfig { stdout: Redirection::Pipe, ..Default::default() })?; // Since we requested stdout to be redirected to a pipe, the parent's // end of the pipe is available as p.stdout. It can either be read // directly, or processed using the communicate() method: let (out, err) = p.communicate(None)?; // check if the process is still alive if let Some(exit_status) = p.poll() { // the process has finished } else { // it is still running, terminate it p.terminate()?; } ``` ### Querying and terminating Check whether a previously launched process is still running: ```rust let mut p = Exec::cmd("sleep").arg("2").popen()?; thread::sleep(Duration::new(1, 0)); if p.poll().is_none() { // poll() returns Some(exit_status) if the process has completed println!("process is still running"); } ``` Give the process 1 second to run, and kill it if it didn't complete by then. ```rust let mut p = Exec::cmd("sleep").arg("2").popen()?; if let Some(status) = p.wait_timeout(Duration::new(1, 0))? { println!("process finished as {:?}", status); } else { p.kill()?; p.wait()?; println!("process killed"); } ``` ## License `subprocess` is distributed under the terms of both the MIT license and the Apache License (Version 2.0). See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. Contributing changes is assumed to signal agreement with these licensing terms. subprocess-0.2.9/examples/sample.rs000064400000000000000000000004320072674642500155200ustar 00000000000000extern crate subprocess; use std::io::{BufRead, BufReader}; use subprocess::Exec; fn main() { let x = Exec::cmd("ls").stream_stdout().unwrap(); let br = BufReader::new(x); for (i, line) in br.lines().enumerate() { println!("{}: {}", i, line.unwrap()); } } subprocess-0.2.9/src/bin/just-echo.rs000064400000000000000000000001040072674642500156550ustar 00000000000000fn main() { print!("{}", ::std::env::args().nth(1).unwrap()); } subprocess-0.2.9/src/builder.rs000064400000000000000000001202150072674642500146400ustar 00000000000000#[cfg(unix)] mod os { pub const NULL_DEVICE: &str = "/dev/null"; pub const SHELL: [&str; 2] = ["sh", "-c"]; } #[cfg(windows)] mod os { pub const NULL_DEVICE: &str = "nul"; pub const SHELL: [&str; 2] = ["cmd.exe", "/c"]; } pub use self::exec::{CaptureData, Exec, NullFile}; pub use self::os::*; pub use self::pipeline::Pipeline; #[cfg(unix)] pub use exec::unix; mod exec { use std::borrow::Cow; use std::collections::HashMap; use std::env; use std::ffi::{OsStr, OsString}; use std::fmt; use std::fs::{File, OpenOptions}; use std::io::{self, Read, Write}; use std::ops::BitOr; use std::path::Path; use crate::communicate::Communicator; use crate::os_common::ExitStatus; use crate::popen::{Popen, PopenConfig, Redirection, Result as PopenResult}; use super::os::*; use super::Pipeline; /// A builder for [`Popen`] instances, providing control and /// convenience methods. /// /// `Exec` provides a builder API for [`Popen::create`], and /// includes convenience methods for capturing the output, and for /// connecting subprocesses into pipelines. /// /// # Examples /// /// Execute an external command and wait for it to complete: /// /// ```no_run /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// # let dirname = "some_dir"; /// let exit_status = Exec::cmd("umount").arg(dirname).join()?; /// # Ok(()) /// # } /// ``` /// /// Execute the command using the OS shell, like C's `system`: /// /// ```no_run /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// Exec::shell("shutdown -h now").join()?; /// # Ok(()) /// # } /// ``` /// /// Start a subprocess and obtain its output as a `Read` trait object, /// like C's `popen`: /// /// ``` /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// let stream = Exec::cmd("ls").stream_stdout()?; /// // call stream.read_to_string, construct io::BufReader(stream), etc. /// # Ok(()) /// # } /// ``` /// /// Capture the output of a command: /// /// ``` /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// let out = Exec::cmd("ls") /// .stdout(Redirection::Pipe) /// .capture()? /// .stdout_str(); /// # Ok(()) /// # } /// ``` /// /// Redirect errors to standard output, and capture both in a single stream: /// /// ``` /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// let out_and_err = Exec::cmd("ls") /// .stdout(Redirection::Pipe) /// .stderr(Redirection::Merge) /// .capture()? /// .stdout_str(); /// # Ok(()) /// # } /// ``` /// /// Provide input to the command and read its output: /// /// ``` /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// let out = Exec::cmd("sort") /// .stdin("b\nc\na\n") /// .stdout(Redirection::Pipe) /// .capture()? /// .stdout_str(); /// assert!(out == "a\nb\nc\n"); /// # Ok(()) /// # } /// ``` /// /// [`Popen`]: struct.Popen.html /// [`Popen::create`]: struct.Popen.html#method.create #[must_use] pub struct Exec { command: OsString, args: Vec, config: PopenConfig, stdin_data: Option>, } impl Exec { /// Constructs a new `Exec`, configured to run `command`. /// /// The command will be run directly in the OS, without an /// intervening shell. To run it through a shell, use /// [`Exec::shell`] instead. /// /// By default, the command will be run without arguments, and /// none of the standard streams will be modified. /// /// [`Exec::shell`]: struct.Exec.html#method.shell pub fn cmd(command: impl AsRef) -> Exec { Exec { command: command.as_ref().to_owned(), args: vec![], config: PopenConfig::default(), stdin_data: None, } } /// Constructs a new `Exec`, configured to run `cmdstr` with /// the system shell. /// /// `subprocess` never spawns shells without an explicit /// request. This command requests the shell to be used; on /// Unix-like systems, this is equivalent to /// `Exec::cmd("sh").arg("-c").arg(cmdstr)`. On Windows, it /// runs `Exec::cmd("cmd.exe").arg("/c")`. /// /// `shell` is useful for porting code that uses the C /// `system` function, which also spawns a shell. /// /// When invoking this function, be careful not to interpolate /// arguments into the string run by the shell, such as /// `Exec::shell(format!("sort {}", filename))`. Such code is /// prone to errors and, if `filename` comes from an untrusted /// source, to shell injection attacks. Instead, use /// `Exec::cmd("sort").arg(filename)`. pub fn shell(cmdstr: impl AsRef) -> Exec { Exec::cmd(SHELL[0]).args(&SHELL[1..]).arg(cmdstr) } /// Appends `arg` to argument list. pub fn arg(mut self, arg: impl AsRef) -> Exec { self.args.push(arg.as_ref().to_owned()); self } /// Extends the argument list with `args`. pub fn args(mut self, args: &[impl AsRef]) -> Exec { self.args.extend(args.iter().map(|x| x.as_ref().to_owned())); self } /// Specifies that the process is initially detached. /// /// A detached process means that we will not wait for the /// process to finish when the object that owns it goes out of /// scope. pub fn detached(mut self) -> Exec { self.config.detached = true; self } fn ensure_env(&mut self) { if self.config.env.is_none() { self.config.env = Some(PopenConfig::current_env()); } } /// Clears the environment of the subprocess. /// /// When this is invoked, the subprocess will not inherit the /// environment of this process. pub fn env_clear(mut self) -> Exec { self.config.env = Some(vec![]); self } /// Sets an environment variable in the child process. /// /// If the same variable is set more than once, the last value /// is used. /// /// Other environment variables are by default inherited from /// the current process. If this is undesirable, call /// `env_clear` first. pub fn env(mut self, key: impl AsRef, value: impl AsRef) -> Exec { self.ensure_env(); self.config .env .as_mut() .unwrap() .push((key.as_ref().to_owned(), value.as_ref().to_owned())); self } /// Sets multiple environment variables in the child process. /// /// The keys and values of the variables are specified by the /// slice. If the same variable is set more than once, the /// last value is used. /// /// Other environment variables are by default inherited from /// the current process. If this is undesirable, call /// `env_clear` first. pub fn env_extend(mut self, vars: &[(impl AsRef, impl AsRef)]) -> Exec { self.ensure_env(); { let envvec = self.config.env.as_mut().unwrap(); for &(ref k, ref v) in vars { envvec.push((k.as_ref().to_owned(), v.as_ref().to_owned())); } } self } /// Removes an environment variable from the child process. /// /// Other environment variables are inherited by default. pub fn env_remove(mut self, key: impl AsRef) -> Exec { self.ensure_env(); self.config .env .as_mut() .unwrap() .retain(|&(ref k, ref _v)| k != key.as_ref()); self } /// Specifies the current working directory of the child process. /// /// If unspecified, the current working directory is inherited /// from the parent. pub fn cwd(mut self, dir: impl AsRef) -> Exec { self.config.cwd = Some(dir.as_ref().as_os_str().to_owned()); self } /// Specifies how to set up the standard input of the child process. /// /// Argument can be: /// /// * a [`Redirection`]; /// * a `File`, which is a shorthand for `Redirection::File(file)`; /// * a `Vec` or `&str`, which will set up a `Redirection::Pipe` /// for stdin, making sure that `capture` feeds that data into the /// standard input of the subprocess; /// * [`NullFile`], which will redirect the standard input to read from /// `/dev/null`. /// /// [`Redirection`]: enum.Redirection.html /// [`NullFile`]: struct.NullFile.html pub fn stdin(mut self, stdin: impl Into) -> Exec { match (&self.config.stdin, stdin.into()) { (&Redirection::None, InputRedirection::AsRedirection(new)) => { self.config.stdin = new } (&Redirection::Pipe, InputRedirection::AsRedirection(Redirection::Pipe)) => (), (&Redirection::None, InputRedirection::FeedData(data)) => { self.config.stdin = Redirection::Pipe; self.stdin_data = Some(data); } (_, _) => panic!("stdin is already set"), } self } /// Specifies how to set up the standard output of the child process. /// /// Argument can be: /// /// * a [`Redirection`]; /// * a `File`, which is a shorthand for `Redirection::File(file)`; /// * [`NullFile`], which will redirect the standard output to go to /// `/dev/null`. /// /// [`Redirection`]: enum.Redirection.html /// [`NullFile`]: struct.NullFile.html pub fn stdout(mut self, stdout: impl Into) -> Exec { match (&self.config.stdout, stdout.into().into_redirection()) { (&Redirection::None, new) => self.config.stdout = new, (&Redirection::Pipe, Redirection::Pipe) => (), (_, _) => panic!("stdout is already set"), } self } /// Specifies how to set up the standard error of the child process. /// /// Argument can be: /// /// * a [`Redirection`]; /// * a `File`, which is a shorthand for `Redirection::File(file)`; /// * [`NullFile`], which will redirect the standard error to go to /// `/dev/null`. /// /// [`Redirection`]: enum.Redirection.html /// [`NullFile`]: struct.NullFile.html pub fn stderr(mut self, stderr: impl Into) -> Exec { match (&self.config.stderr, stderr.into().into_redirection()) { (&Redirection::None, new) => self.config.stderr = new, (&Redirection::Pipe, Redirection::Pipe) => (), (_, _) => panic!("stderr is already set"), } self } fn check_no_stdin_data(&self, meth: &str) { if self.stdin_data.is_some() { panic!("{} called with input data specified", meth); } } // Terminators /// Starts the process, returning a `Popen` for the running process. pub fn popen(mut self) -> PopenResult { self.check_no_stdin_data("popen"); self.args.insert(0, self.command); let p = Popen::create(&self.args, self.config)?; Ok(p) } /// Starts the process, waits for it to finish, and returns /// the exit status. /// /// This method will wait for as long as necessary for the process to /// finish. If a timeout is needed, use /// `<...>.detached().popen()?.wait_timeout(...)` instead. pub fn join(self) -> PopenResult { self.check_no_stdin_data("join"); self.popen()?.wait() } /// Starts the process and returns a value implementing the `Read` /// trait that reads from the standard output of the child process. /// /// This will automatically set up /// `stdout(Redirection::Pipe)`, so it is not necessary to do /// that beforehand. /// /// When the trait object is dropped, it will wait for the /// process to finish. If this is undesirable, use /// `detached()`. pub fn stream_stdout(self) -> PopenResult { self.check_no_stdin_data("stream_stdout"); let p = self.stdout(Redirection::Pipe).popen()?; Ok(ReadOutAdapter(p)) } /// Starts the process and returns a value implementing the `Read` /// trait that reads from the standard error of the child process. /// /// This will automatically set up /// `stderr(Redirection::Pipe)`, so it is not necessary to do /// that beforehand. /// /// When the trait object is dropped, it will wait for the /// process to finish. If this is undesirable, use /// `detached()`. pub fn stream_stderr(self) -> PopenResult { self.check_no_stdin_data("stream_stderr"); let p = self.stderr(Redirection::Pipe).popen()?; Ok(ReadErrAdapter(p)) } /// Starts the process and returns a value implementing the `Write` /// trait that writes to the standard input of the child process. /// /// This will automatically set up `stdin(Redirection::Pipe)`, /// so it is not necessary to do that beforehand. /// /// When the trait object is dropped, it will wait for the /// process to finish. If this is undesirable, use /// `detached()`. pub fn stream_stdin(self) -> PopenResult { self.check_no_stdin_data("stream_stdin"); let p = self.stdin(Redirection::Pipe).popen()?; Ok(WriteAdapter(p)) } fn setup_communicate(mut self) -> PopenResult<(Communicator, Popen)> { let stdin_data = self.stdin_data.take(); if let (&Redirection::None, &Redirection::None) = (&self.config.stdout, &self.config.stderr) { self = self.stdout(Redirection::Pipe); } let mut p = self.popen()?; Ok((p.communicate_start(stdin_data), p)) } /// Starts the process and returns a `Communicator` handle. /// /// This is a lower-level API that offers more choice in how /// communication is performed, such as read size limit and timeout, /// equivalent to [`Popen::communicate`]. /// /// Unlike `capture()`, this method doesn't wait for the process to /// finish, effectively detaching it. /// /// [`Popen::communicate`]: struct.Popen.html#method.communicate pub fn communicate(self) -> PopenResult { let comm = self.detached().setup_communicate()?.0; Ok(comm) } /// Starts the process, collects its output, and waits for it /// to finish. /// /// The return value provides the standard output and standard /// error as bytes or optionally strings, as well as the exit /// status. /// /// Unlike `Popen::communicate`, this method actually waits /// for the process to finish, rather than simply waiting for /// its standard streams to close. If this is undesirable, /// use `detached()`. pub fn capture(self) -> PopenResult { let (mut comm, mut p) = self.setup_communicate()?; let (maybe_out, maybe_err) = comm.read()?; Ok(CaptureData { stdout: maybe_out.unwrap_or_else(Vec::new), stderr: maybe_err.unwrap_or_else(Vec::new), exit_status: p.wait()?, }) } // used for Debug impl fn display_escape(s: &str) -> Cow<'_, str> { fn nice_char(c: char) -> bool { match c { '-' | '_' | '.' | ',' | '/' => true, c if c.is_ascii_alphanumeric() => true, _ => false, } } if !s.chars().all(nice_char) { Cow::Owned(format!("'{}'", s.replace("'", r#"'\''"#))) } else { Cow::Borrowed(s) } } /// Show Exec as command-line string quoted in the Unix style. pub fn to_cmdline_lossy(&self) -> String { let mut out = String::new(); if let Some(ref cmd_env) = self.config.env { let current: Vec<_> = env::vars_os().collect(); let current_map: HashMap<_, _> = current.iter().map(|(x, y)| (x, y)).collect(); for (k, v) in cmd_env { if current_map.get(&k) == Some(&&v) { continue; } out.push_str(&Exec::display_escape(&k.to_string_lossy())); out.push('='); out.push_str(&Exec::display_escape(&v.to_string_lossy())); out.push(' '); } let cmd_env: HashMap<_, _> = cmd_env.iter().map(|(k, v)| (k, v)).collect(); for (k, _) in current { if !cmd_env.contains_key(&k) { out.push_str(&Exec::display_escape(&k.to_string_lossy())); out.push('='); out.push(' '); } } } out.push_str(&Exec::display_escape(&self.command.to_string_lossy())); for arg in &self.args { out.push(' '); out.push_str(&Exec::display_escape(&arg.to_string_lossy())); } out } } impl Clone for Exec { /// Returns a copy of the value. /// /// This method is guaranteed not to fail as long as none of /// the `Redirection` values contain a `Redirection::File` /// variant. If a redirection to `File` is present, cloning /// that field will use `File::try_clone` method, which /// duplicates a file descriptor and can (but is not likely /// to) fail. In that scenario, `Exec::clone` panics. fn clone(&self) -> Exec { Exec { command: self.command.clone(), args: self.args.clone(), config: self.config.try_clone().unwrap(), stdin_data: self.stdin_data.as_ref().cloned(), } } } impl BitOr for Exec { type Output = Pipeline; /// Create a `Pipeline` from `self` and `rhs`. fn bitor(self, rhs: Exec) -> Pipeline { Pipeline::new(self, rhs) } } impl fmt::Debug for Exec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Exec {{ {} }}", self.to_cmdline_lossy()) } } #[derive(Debug)] struct ReadOutAdapter(Popen); impl Read for ReadOutAdapter { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.stdout.as_mut().unwrap().read(buf) } } #[derive(Debug)] struct ReadErrAdapter(Popen); impl Read for ReadErrAdapter { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.stderr.as_mut().unwrap().read(buf) } } #[derive(Debug)] struct WriteAdapter(Popen); impl Write for WriteAdapter { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.stdin.as_mut().unwrap().write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.stdin.as_mut().unwrap().flush() } } // We must implement Drop in order to close the stream. The typical // use case for stream_stdin() is a process that reads something from // stdin. WriteAdapter going out of scope invokes Popen::drop(), // which waits for the process to exit. Without closing stdin, this // deadlocks because the child process hangs reading its stdin. impl Drop for WriteAdapter { fn drop(&mut self) { self.0.stdin.take(); } } /// Data captured by [`Exec::capture`] and [`Pipeline::capture`]. /// /// [`Exec::capture`]: struct.Exec.html#method.capture /// [`Pipeline::capture`]: struct.Pipeline.html#method.capture #[derive(Debug)] pub struct CaptureData { /// Standard output as bytes. pub stdout: Vec, /// Standard error as bytes. pub stderr: Vec, /// Exit status. pub exit_status: ExitStatus, } impl CaptureData { /// Returns the standard output as string, converted from bytes using /// `String::from_utf8_lossy`. pub fn stdout_str(&self) -> String { String::from_utf8_lossy(&self.stdout).into_owned() } /// Returns the standard error as string, converted from bytes using /// `String::from_utf8_lossy`. pub fn stderr_str(&self) -> String { String::from_utf8_lossy(&self.stderr).into_owned() } /// True if the exit status of the process or pipeline is 0. pub fn success(&self) -> bool { self.exit_status.success() } } #[derive(Debug)] pub enum InputRedirection { AsRedirection(Redirection), FeedData(Vec), } impl From for InputRedirection { fn from(r: Redirection) -> Self { if let Redirection::Merge = r { panic!("Redirection::Merge is only allowed for output streams"); } InputRedirection::AsRedirection(r) } } impl From for InputRedirection { fn from(f: File) -> Self { InputRedirection::AsRedirection(Redirection::File(f)) } } /// Marker value for [`stdin`], [`stdout`], and [`stderr`] methods /// of [`Exec`] and [`Pipeline`]. /// /// Use of this value means that the corresponding stream should /// be redirected to the devnull device. /// /// [`stdin`]: struct.Exec.html#method.stdin /// [`stdout`]: struct.Exec.html#method.stdout /// [`stderr`]: struct.Exec.html#method.stderr /// [`Exec`]: struct.Exec.html /// [`Pipeline`]: struct.Pipeline.html #[derive(Debug)] pub struct NullFile; impl From for InputRedirection { fn from(_nf: NullFile) -> Self { let null_file = OpenOptions::new().read(true).open(NULL_DEVICE).unwrap(); InputRedirection::AsRedirection(Redirection::File(null_file)) } } impl From> for InputRedirection { fn from(v: Vec) -> Self { InputRedirection::FeedData(v) } } impl<'a> From<&'a str> for InputRedirection { fn from(s: &'a str) -> Self { InputRedirection::FeedData(s.as_bytes().to_vec()) } } #[derive(Debug)] pub struct OutputRedirection(Redirection); impl OutputRedirection { pub fn into_redirection(self) -> Redirection { self.0 } } impl From for OutputRedirection { fn from(r: Redirection) -> Self { OutputRedirection(r) } } impl From for OutputRedirection { fn from(f: File) -> Self { OutputRedirection(Redirection::File(f)) } } impl From for OutputRedirection { fn from(_nf: NullFile) -> Self { let null_file = OpenOptions::new().write(true).open(NULL_DEVICE).unwrap(); OutputRedirection(Redirection::File(null_file)) } } #[cfg(unix)] pub mod unix { use super::Exec; pub trait ExecExt { fn setuid(self, uid: u32) -> Self; fn setgid(self, gid: u32) -> Self; } impl ExecExt for Exec { fn setuid(mut self, uid: u32) -> Exec { self.config.setuid = Some(uid); self } fn setgid(mut self, gid: u32) -> Exec { self.config.setgid = Some(gid); self } } } } mod pipeline { use std::fmt; use std::fs::File; use std::io::{self, Read, Write}; use std::ops::BitOr; use std::rc::Rc; use crate::communicate::{self, Communicator}; use crate::os_common::ExitStatus; use crate::popen::{Popen, Redirection, Result as PopenResult}; use super::exec::{CaptureData, Exec, InputRedirection, OutputRedirection}; /// A builder for multiple [`Popen`] instances connected via /// pipes. /// /// A pipeline is a sequence of two or more [`Exec`] commands /// connected via pipes. Just like in a Unix shell pipeline, each /// command receives standard input from the previous command, and /// passes standard output to the next command. Optionally, the /// standard input of the first command can be provided from the /// outside, and the output of the last command can be captured. /// /// In most cases you do not need to create [`Pipeline`] instances /// directly; instead, combine [`Exec`] instances using the `|` /// operator which produces `Pipeline`. /// /// # Examples /// /// Execute a pipeline and return the exit status of the last command: /// /// ```no_run /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// let exit_status = /// (Exec::shell("ls *.bak") | Exec::cmd("xargs").arg("rm")).join()?; /// # Ok(()) /// # } /// ``` /// /// Capture the pipeline's output: /// /// ```no_run /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// let dir_checksum = { /// Exec::cmd("find . -type f") | Exec::cmd("sort") | Exec::cmd("sha1sum") /// }.capture()?.stdout_str(); /// # Ok(()) /// # } /// ``` /// /// [`Popen`]: struct.Popen.html /// [`Exec`]: struct.Exec.html /// [`Pipeline`]: struct.Pipeline.html #[must_use] pub struct Pipeline { cmds: Vec, stdin: Redirection, stdout: Redirection, stderr_file: Option, stdin_data: Option>, } impl Pipeline { /// Creates a new pipeline by combining two commands. /// /// Equivalent to `cmd1 | cmd2`. pub fn new(cmd1: Exec, cmd2: Exec) -> Pipeline { Pipeline { cmds: vec![cmd1, cmd2], stdin: Redirection::None, stdout: Redirection::None, stderr_file: None, stdin_data: None, } } /// Creates a new pipeline from a list of commands. Useful if /// a pipeline should be created dynamically. /// /// Example: /// ``` /// use subprocess::Exec; /// /// let commands = vec![ /// Exec::shell("echo tset"), /// Exec::shell("tr '[:lower:]' '[:upper:]'"), /// Exec::shell("rev") /// ]; /// /// let pipeline = subprocess::Pipeline::from_exec_iter(commands); /// let output = pipeline.capture().unwrap().stdout_str(); /// assert_eq!(output, "TEST\n"); /// ``` /// ```should_panic /// use subprocess::Exec; /// /// let commands = vec![ /// Exec::shell("echo tset"), /// ]; /// /// // This will panic as the iterator contains less than two (2) items. /// let pipeline = subprocess::Pipeline::from_exec_iter(commands); /// ``` /// Errors: /// - Panics when the passed iterator contains less than two (2) items. pub fn from_exec_iter(iterable: I) -> Pipeline where I: IntoIterator, { let cmds: Vec<_> = iterable.into_iter().collect(); if cmds.len() < 2 { panic!("iterator needs to contain at least two (2) elements") } Pipeline { cmds, stdin: Redirection::None, stdout: Redirection::None, stderr_file: None, stdin_data: None, } } /// Specifies how to set up the standard input of the first /// command in the pipeline. /// /// Argument can be: /// /// * a [`Redirection`]; /// * a `File`, which is a shorthand for `Redirection::File(file)`; /// * a `Vec` or `&str`, which will set up a `Redirection::Pipe` /// for stdin, making sure that `capture` feeds that data into the /// standard input of the subprocess. /// * `NullFile`, which will redirect the standard input to read from /// /dev/null. /// /// [`Redirection`]: enum.Redirection.html pub fn stdin(mut self, stdin: impl Into) -> Pipeline { match stdin.into() { InputRedirection::AsRedirection(r) => self.stdin = r, InputRedirection::FeedData(data) => { self.stdin = Redirection::Pipe; self.stdin_data = Some(data); } }; self } /// Specifies how to set up the standard output of the last /// command in the pipeline. /// /// Argument can be: /// /// * a [`Redirection`]; /// * a `File`, which is a shorthand for `Redirection::File(file)`; /// * `NullFile`, which will redirect the standard output to write to /// /dev/null. /// /// [`Redirection`]: enum.Redirection.html pub fn stdout(mut self, stdout: impl Into) -> Pipeline { self.stdout = stdout.into().into_redirection(); self } /// Specifies a file to which to redirect the standard error of all /// the commands in the pipeline. /// /// It is useful for capturing the standard error of the pipeline as a /// whole. Unlike `stdout()`, which only affects the last command in /// the pipeline, this affects all commands. The difference is /// because standard output is piped from one command to the next, so /// only the output of the last command is "free". In contrast, the /// standard errors are not connected in any way. This is also the /// reason only a `File` is supported - it allows for efficient /// sharing of the same file by all commands. pub fn stderr_to(mut self, to: File) -> Pipeline { self.stderr_file = Some(to); self } fn check_no_stdin_data(&self, meth: &str) { if self.stdin_data.is_some() { panic!("{} called with input data specified", meth); } } // Terminators: /// Starts all commands in the pipeline, and returns a /// `Vec` whose members correspond to running commands. /// /// If some command fails to start, the remaining commands /// will not be started, and the appropriate error will be /// returned. The commands that have already started will be /// waited to finish (but will probably exit immediately due /// to missing output), except for the ones for which /// `detached()` was called. This is equivalent to what the /// shell does. pub fn popen(mut self) -> PopenResult> { self.check_no_stdin_data("popen"); assert!(self.cmds.len() >= 2); if let Some(stderr_to) = self.stderr_file { let stderr_to = Rc::new(stderr_to); self.cmds = self .cmds .into_iter() .map(|cmd| cmd.stderr(Redirection::RcFile(Rc::clone(&stderr_to)))) .collect(); } let first_cmd = self.cmds.drain(..1).next().unwrap(); self.cmds.insert(0, first_cmd.stdin(self.stdin)); let last_cmd = self.cmds.drain(self.cmds.len() - 1..).next().unwrap(); self.cmds.push(last_cmd.stdout(self.stdout)); let mut ret = Vec::::new(); let cnt = self.cmds.len(); for (idx, mut runner) in self.cmds.into_iter().enumerate() { if idx != 0 { let prev_stdout = ret[idx - 1].stdout.take().unwrap(); runner = runner.stdin(prev_stdout); } if idx != cnt - 1 { runner = runner.stdout(Redirection::Pipe); } ret.push(runner.popen()?); } Ok(ret) } /// Starts the pipeline, waits for it to finish, and returns /// the exit status of the last command. pub fn join(self) -> PopenResult { self.check_no_stdin_data("join"); let mut v = self.popen()?; // Waiting on a pipeline waits for all commands, but // returns the status of the last one. This is how the // shells do it. If the caller needs more precise control // over which status is returned, they can call popen(). v.last_mut().unwrap().wait() } /// Starts the pipeline and returns a value implementing the `Read` /// trait that reads from the standard output of the last command. /// /// This will automatically set up /// `stdout(Redirection::Pipe)`, so it is not necessary to do /// that beforehand. /// /// When the trait object is dropped, it will wait for the /// pipeline to finish. If this is undesirable, use /// `detached()`. pub fn stream_stdout(self) -> PopenResult { self.check_no_stdin_data("stream_stdout"); let v = self.stdout(Redirection::Pipe).popen()?; Ok(ReadPipelineAdapter(v)) } /// Starts the pipeline and returns a value implementing the `Write` /// trait that writes to the standard input of the last command. /// /// This will automatically set up `stdin(Redirection::Pipe)`, /// so it is not necessary to do that beforehand. /// /// When the trait object is dropped, it will wait for the /// process to finish. If this is undesirable, use /// `detached()`. pub fn stream_stdin(self) -> PopenResult { self.check_no_stdin_data("stream_stdin"); let v = self.stdin(Redirection::Pipe).popen()?; Ok(WritePipelineAdapter(v)) } fn setup_communicate(mut self) -> PopenResult<(Communicator, Vec)> { assert!(self.cmds.len() >= 2); let (err_read, err_write) = crate::popen::make_pipe()?; self = self.stderr_to(err_write); let stdin_data = self.stdin_data.take(); let mut v = self.stdout(Redirection::Pipe).popen()?; let vlen = v.len(); let comm = communicate::communicate( v[0].stdin.take(), v[vlen - 1].stdout.take(), Some(err_read), stdin_data, ); Ok((comm, v)) } /// Starts the pipeline and returns a `Communicator` handle. /// /// This is a lower-level API that offers more choice in how /// communication is performed, such as read size limit and timeout, /// equivalent to [`Popen::communicate`]. /// /// Unlike `capture()`, this method doesn't wait for the pipeline to /// finish, effectively detaching it. /// /// [`Popen::communicate`]: struct.Popen.html#method.communicate pub fn communicate(mut self) -> PopenResult { self.cmds = self.cmds.into_iter().map(|cmd| cmd.detached()).collect(); let comm = self.setup_communicate()?.0; Ok(comm) } /// Starts the pipeline, collects its output, and waits for all /// commands to finish. /// /// The return value provides the standard output of the last command, /// the combined standard error of all commands, and the exit status /// of the last command. The captured outputs can be accessed as /// bytes or strings. /// /// Unlike `Popen::communicate`, this method actually waits for the /// processes to finish, rather than simply waiting for the output to /// close. If this is undesirable, use `detached()`. pub fn capture(self) -> PopenResult { let (mut comm, mut v) = self.setup_communicate()?; let (out, err) = comm.read()?; let out = out.unwrap_or_else(Vec::new); let err = err.unwrap(); let vlen = v.len(); let status = v[vlen - 1].wait()?; Ok(CaptureData { stdout: out, stderr: err, exit_status: status, }) } } impl Clone for Pipeline { /// Returns a copy of the value. /// /// This method is guaranteed not to fail as long as none of /// the `Redirection` values contain a `Redirection::File` /// variant. If a redirection to `File` is present, cloning /// that field will use `File::try_clone` method, which /// duplicates a file descriptor and can (but is not likely /// to) fail. In that scenario, `Exec::clone` panics. fn clone(&self) -> Pipeline { Pipeline { cmds: self.cmds.clone(), stdin: self.stdin.try_clone().unwrap(), stdout: self.stdout.try_clone().unwrap(), stderr_file: self.stderr_file.as_ref().map(|f| f.try_clone().unwrap()), stdin_data: self.stdin_data.clone(), } } } impl BitOr for Pipeline { type Output = Pipeline; /// Append a command to the pipeline and return a new pipeline. fn bitor(mut self, rhs: Exec) -> Pipeline { self.cmds.push(rhs); self } } impl BitOr for Pipeline { type Output = Pipeline; /// Append a pipeline to the pipeline and return a new pipeline. fn bitor(mut self, rhs: Pipeline) -> Pipeline { self.cmds.extend(rhs.cmds); self.stdout = rhs.stdout; self } } impl fmt::Debug for Pipeline { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut args = vec![]; for cmd in &self.cmds { args.push(cmd.to_cmdline_lossy()); } write!(f, "Pipeline {{ {} }}", args.join(" | ")) } } #[derive(Debug)] struct ReadPipelineAdapter(Vec); impl Read for ReadPipelineAdapter { fn read(&mut self, buf: &mut [u8]) -> io::Result { let last = self.0.last_mut().unwrap(); last.stdout.as_mut().unwrap().read(buf) } } #[derive(Debug)] struct WritePipelineAdapter(Vec); impl WritePipelineAdapter { fn stdin(&mut self) -> &mut File { let first = self.0.first_mut().unwrap(); first.stdin.as_mut().unwrap() } } impl Write for WritePipelineAdapter { fn write(&mut self, buf: &[u8]) -> io::Result { self.stdin().write(buf) } fn flush(&mut self) -> io::Result<()> { self.stdin().flush() } } impl Drop for WritePipelineAdapter { // the same rationale as Drop for WriteAdapter fn drop(&mut self) { let first = &mut self.0[0]; first.stdin.take(); } } } subprocess-0.2.9/src/communicate.rs000064400000000000000000000510520072674642500155200ustar 00000000000000use std::error::Error; use std::fmt; use std::fs::File; use std::io::{self, ErrorKind}; use std::time::{Duration, Instant}; #[cfg(unix)] mod raw { use crate::posix; use std::cmp::min; use std::fs::File; use std::io::{self, Read, Write}; use std::time::{Duration, Instant}; fn as_pollfd<'a>(f: Option<&'a File>, for_read: bool) -> posix::PollFd<'a> { let events = if for_read { posix::POLLIN } else { posix::POLLOUT }; posix::PollFd::new(f, events) } fn maybe_poll( fin: Option<&File>, fout: Option<&File>, ferr: Option<&File>, deadline: Option, ) -> io::Result<(bool, bool, bool)> { // Polling is needed to prevent deadlock when interacting with // multiple streams, and for timeout. If we're interacting with a // single stream without timeout, we can skip the actual poll() // syscall and just tell the caller to go ahead with reading/writing. if deadline.is_none() { match (&fin, &fout, &ferr) { (None, None, Some(..)) => return Ok((false, false, true)), (None, Some(..), None) => return Ok((false, true, false)), (Some(..), None, None) => return Ok((true, false, false)), _ => (), } } let timeout = deadline.map(|deadline| { let now = Instant::now(); if now >= deadline { Duration::from_secs(0) } else { deadline - now } }); let mut fds = [ as_pollfd(fin, false), as_pollfd(fout, true), as_pollfd(ferr, true), ]; posix::poll(&mut fds, timeout)?; Ok(( fds[0].test(posix::POLLOUT | posix::POLLHUP), fds[1].test(posix::POLLIN | posix::POLLHUP), fds[2].test(posix::POLLIN | posix::POLLHUP), )) } #[derive(Debug)] pub struct RawCommunicator { stdin: Option, stdout: Option, stderr: Option, input_data: Vec, input_pos: usize, } impl RawCommunicator { pub fn new( stdin: Option, stdout: Option, stderr: Option, input_data: Option>, ) -> RawCommunicator { let input_data = input_data.unwrap_or_else(Vec::new); RawCommunicator { stdin, stdout, stderr, input_data, input_pos: 0, } } fn do_read( source_ref: &mut Option<&File>, dest: &mut Vec, size_limit: Option, total_read: usize, ) -> io::Result<()> { let mut buf = &mut [0u8; 4096][..]; if let Some(size_limit) = size_limit { if total_read >= size_limit { return Ok(()); } if size_limit - total_read < buf.len() { buf = &mut buf[0..size_limit - total_read]; } } let n = source_ref.unwrap().read(buf)?; if n != 0 { dest.extend_from_slice(&buf[..n]); } else { *source_ref = None; } Ok(()) } fn read_into( &mut self, deadline: Option, size_limit: Option, outvec: &mut Vec, errvec: &mut Vec, ) -> io::Result<()> { // Note: chunk size for writing must be smaller than the pipe buffer // size. A large enough write to a pipe deadlocks despite polling. const WRITE_SIZE: usize = 4096; let mut stdout_ref = self.stdout.as_ref(); let mut stderr_ref = self.stderr.as_ref(); loop { if let Some(size_limit) = size_limit { if outvec.len() + errvec.len() >= size_limit { break; } } if let (None, None, None) = (self.stdin.as_ref(), stdout_ref, stderr_ref) { // When no stream remains, we are done. break; } let (in_ready, out_ready, err_ready) = maybe_poll(self.stdin.as_ref(), stdout_ref, stderr_ref, deadline)?; if !in_ready && !out_ready && !err_ready { return Err(io::Error::new(io::ErrorKind::TimedOut, "timeout")); } if in_ready { let input = &self.input_data[self.input_pos..]; let chunk = &input[..min(WRITE_SIZE, input.len())]; let n = self.stdin.as_ref().unwrap().write(chunk)?; self.input_pos += n; if self.input_pos == self.input_data.len() { // close stdin when done writing, so the child receives EOF self.stdin.take(); // deallocate the input data, we don't need it any more self.input_data = Vec::new(); } } if out_ready { RawCommunicator::do_read( &mut stdout_ref, outvec, size_limit, outvec.len() + errvec.len(), )?; } if err_ready { RawCommunicator::do_read( &mut stderr_ref, errvec, size_limit, outvec.len() + errvec.len(), )?; } } Ok(()) } pub fn read( &mut self, deadline: Option, size_limit: Option, ) -> (Option, (Option>, Option>)) { let mut outvec = vec![]; let mut errvec = vec![]; let err = self .read_into(deadline, size_limit, &mut outvec, &mut errvec) .err(); let output = ( self.stdout.as_ref().map(|_| outvec), self.stderr.as_ref().map(|_| errvec), ); (err, output) } } } #[cfg(windows)] mod raw { use std::fs::File; use std::io::{self, Read, Write}; use std::sync::mpsc::{self, RecvTimeoutError, SyncSender}; use std::thread; use std::time::Instant; #[derive(Debug, Copy, Clone)] enum StreamIdent { In = 1 << 0, Out = 1 << 1, Err = 1 << 2, } enum Payload { Data(Vec), EOF, Err(io::Error), } // Messages exchanged between RawCommunicator's helper threads. type Message = (StreamIdent, Payload); fn read_and_transmit(mut outfile: File, ident: StreamIdent, sink: SyncSender) { let mut chunk = [0u8; 4096]; // Note: failing to send to the sink means we're done. Sending will // fail if the main thread drops the RawCommunicator (and with it the // receiver) prematurely e.g. because a limit was reached or another // helper encountered an IO error. loop { match outfile.read(&mut chunk) { Ok(0) => { let _ = sink.send((ident, Payload::EOF)); break; } Ok(nread) => { if let Err(_) = sink.send((ident, Payload::Data(chunk[..nread].to_vec()))) { break; } } Err(e) => { let _ = sink.send((ident, Payload::Err(e))); break; } } } } fn spawn_with_arg(f: impl FnOnce(T) + Send + 'static, arg: T) { thread::spawn(move || f(arg)); } #[derive(Debug)] pub struct RawCommunicator { rx: mpsc::Receiver, helper_set: u8, requested_streams: u8, leftover: Option<(StreamIdent, Vec)>, } struct Timeout; impl RawCommunicator { pub fn new( stdin: Option, stdout: Option, stderr: Option, input_data: Option>, ) -> RawCommunicator { let mut helper_set = 0u8; let mut requested_streams = 0u8; let read_stdout = stdout.map(|stdout| { helper_set |= StreamIdent::Out as u8; requested_streams |= StreamIdent::Out as u8; |tx| read_and_transmit(stdout, StreamIdent::Out, tx) }); let read_stderr = stderr.map(|stderr| { helper_set |= StreamIdent::Err as u8; requested_streams |= StreamIdent::Err as u8; |tx| read_and_transmit(stderr, StreamIdent::Err, tx) }); let write_stdin = stdin.map(|mut stdin| { let input_data = input_data.expect("must provide input to redirected stdin"); helper_set |= StreamIdent::In as u8; move |tx: SyncSender<_>| match stdin.write_all(&input_data) { Ok(()) => drop(tx.send((StreamIdent::In, Payload::EOF))), Err(e) => drop(tx.send((StreamIdent::In, Payload::Err(e)))), } }); let (tx, rx) = mpsc::sync_channel(0); read_stdout.map(|f| spawn_with_arg(f, tx.clone())); read_stderr.map(|f| spawn_with_arg(f, tx.clone())); write_stdin.map(|f| spawn_with_arg(f, tx.clone())); RawCommunicator { rx, helper_set, requested_streams, leftover: None, } } fn recv_until(&self, deadline: Option) -> Result { if let Some(deadline) = deadline { match self .rx .recv_timeout(deadline.saturating_duration_since(Instant::now())) { Ok(message) => Ok(message), Err(RecvTimeoutError::Timeout) => Err(Timeout), // should never be disconnected, the helper threads always // announce their exit beforehand Err(RecvTimeoutError::Disconnected) => unreachable!(), } } else { Ok(self.rx.recv().unwrap()) } } fn read_into( &mut self, deadline: Option, size_limit: Option, outvec: &mut Vec, errvec: &mut Vec, ) -> io::Result<()> { let mut grow_result = |ident, mut data: &[u8], leftover: &mut Option<(StreamIdent, Vec)>| { if let Some(size_limit) = size_limit { let total_read = outvec.len() + errvec.len(); if total_read >= size_limit { return false; } let remaining = size_limit - total_read; if data.len() > remaining { *leftover = Some((ident, data[remaining..].to_vec())); data = &data[..remaining]; } } match ident { StreamIdent::Out => outvec.extend_from_slice(data), StreamIdent::Err => errvec.extend_from_slice(data), StreamIdent::In => unreachable!(), } if let Some(size_limit) = size_limit { if outvec.len() + errvec.len() >= size_limit { return false; } } return true; }; if let Some((ident, data)) = self.leftover.take() { if !grow_result(ident, &data, &mut self.leftover) { return Ok(()); } } while self.helper_set != 0 { match self.recv_until(deadline) { Ok((ident, Payload::EOF)) => { self.helper_set &= !(ident as u8); continue; } Ok((ident, Payload::Data(data))) => { assert!(data.len() != 0); if !grow_result(ident, &data, &mut self.leftover) { break; } } Ok((_ident, Payload::Err(e))) => { return Err(e); } Err(Timeout) => { return Err(io::Error::new(io::ErrorKind::TimedOut, "timeout")); } } } Ok(()) } pub fn read( &mut self, deadline: Option, size_limit: Option, ) -> (Option, (Option>, Option>)) { // Create both vectors immediately. This doesn't allocate, and if // one of those is not needed, it just won't get resized. let mut outvec = vec![]; let mut errvec = vec![]; let err = self .read_into(deadline, size_limit, &mut outvec, &mut errvec) .err(); let output = { let (mut o, mut e) = (None, None); if self.requested_streams & StreamIdent::Out as u8 != 0 { o = Some(outvec); } else { assert!(outvec.len() == 0); } if self.requested_streams & StreamIdent::Err as u8 != 0 { e = Some(errvec); } else { assert!(errvec.len() == 0); } (o, e) }; (err, output) } } } use raw::RawCommunicator; /// Unattended data exchange with the subprocess. /// /// When a subprocess both expects input and provides output, care must be /// taken to avoid deadlock. The issue arises when the subprocess responds to /// part of the input data by providing some output which must be read for the /// subprocess to accept further input. If the parent process is blocked on /// writing the input, it cannot read the output and a deadlock occurs. This /// implementation avoids this issue by by reading from and writing to the /// subprocess in parallel. On Unix-like systems this is achieved using /// `poll()`, and on Windows using threads. #[must_use] #[derive(Debug)] pub struct Communicator { inner: RawCommunicator, size_limit: Option, time_limit: Option, } impl Communicator { fn new( stdin: Option, stdout: Option, stderr: Option, input_data: Option>, ) -> Communicator { Communicator { inner: RawCommunicator::new(stdin, stdout, stderr, input_data), size_limit: None, time_limit: None, } } /// Communicate with the subprocess, return the contents of its standard /// output and error. /// /// This will write input data to the subprocess's standard input and /// simultaneously read its standard output and error. The output and /// error contents are returned as a pair of `Option`. The `None` /// options correspond to streams not specified as `Redirection::Pipe` /// when creating the subprocess. /// /// By default `read()` will read all data until end-of-file. /// /// If `limit_time` has been called, the method will read for no more than /// the specified duration. In case of timeout, an error of kind /// `io::ErrorKind::TimedOut` is returned. Communication may be resumed /// after the timeout by calling `read()` again. /// /// If `limit_size` has been called, it will limit the allocation done by /// this method. If the subprocess provides more data than the limit /// specifies, `read()` will successfully return as much data as specified /// by the limit. (It might internally read a bit more from the /// subprocess, but the data will remain available for future reads.) /// Subsequent data can be retrieved by calling `read()` again, which can /// be repeated until `read()` returns all-empty data, which marks EOF. /// /// Note that this method does not wait for the subprocess to finish, only /// to close its output/error streams. It is rare but possible for the /// program to continue running after having closed the streams, in which /// case `Popen::Drop` will wait for it to finish. If such a wait is /// undesirable, it can be prevented by waiting explicitly using `wait()`, /// by detaching the process using `detach()`, or by terminating it with /// `terminate()`. /// /// # Panics /// /// If `input_data` is provided and `stdin` was not redirected to a pipe. /// Also, if `input_data` is not provided and `stdin` was redirected to a /// pipe. /// /// # Errors /// /// * `Err(CommunicateError)` if a system call fails. In case of timeout, /// the underlying error kind will be `ErrorKind::TimedOut`. /// /// Regardless of the nature of the error, the content prior to the error /// can be retrieved using the [`capture`] attribute of the error. /// /// [`capture`]: struct.CommunicateError.html#structfield.capture pub fn read(&mut self) -> Result<(Option>, Option>), CommunicateError> { let deadline = self.time_limit.map(|timeout| Instant::now() + timeout); match self.inner.read(deadline, self.size_limit) { (None, capture) => Ok(capture), (Some(error), capture) => Err(CommunicateError { error, capture }), } } /// Return the subprocess's output and error contents as strings. /// /// Like `read()`, but returns strings instead of byte vectors. Invalid /// UTF-8 sequences, if found, are replaced with the the `U+FFFD` Unicode /// replacement character. pub fn read_string(&mut self) -> Result<(Option, Option), CommunicateError> { let (o, e) = self.read()?; Ok((o.map(from_utf8_lossy), e.map(from_utf8_lossy))) } /// Limit the amount of data the next `read()` will read from the /// subprocess. pub fn limit_size(mut self, size: usize) -> Communicator { self.size_limit = Some(size); self } /// Limit the amount of time the next `read()` will spend reading from the /// subprocess. pub fn limit_time(mut self, time: Duration) -> Communicator { self.time_limit = Some(time); self } } /// Like String::from_utf8_lossy(), but takes `Vec` and reuses its storage if /// possible. fn from_utf8_lossy(v: Vec) -> String { match String::from_utf8(v) { Ok(s) => s, Err(e) => String::from_utf8_lossy(e.as_bytes()).into(), } } pub fn communicate( stdin: Option, stdout: Option, stderr: Option, input_data: Option>, ) -> Communicator { if stdin.is_some() { input_data .as_ref() .expect("must provide input to redirected stdin"); } else { assert!( input_data.as_ref().is_none(), "cannot provide input to non-redirected stdin" ); } Communicator::new(stdin, stdout, stderr, input_data) } /// Error during communication. /// /// It holds the underlying `io::Error` in the `error` field, and also /// provides the data captured before the error was encountered in the /// `capture` field. /// /// The error description and cause are taken from the underlying IO error. #[derive(Debug)] pub struct CommunicateError { /// The underlying `io::Error`. pub error: io::Error, /// The data captured before the error was encountered. pub capture: (Option>, Option>), } impl CommunicateError { /// Returns the corresponding IO `ErrorKind` for this error. /// /// Equivalent to `self.error.kind()`. pub fn kind(&self) -> ErrorKind { self.error.kind() } } impl Error for CommunicateError { fn source(&self) -> Option<&(dyn Error + 'static)> { self.error.source() } } impl fmt::Display for CommunicateError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.error.fmt(f) } } subprocess-0.2.9/src/lib.rs000064400000000000000000000064000072674642500137570ustar 00000000000000//! Execution of and interaction with external processes and pipelines. //! //! The entry points to the crate are the [`Popen`] struct and the [`Exec`] //! builder. `Popen` is the interface to a running child process, inspired by //! Python's [`subprocess.Popen`]. `Exec` provides a builder-pattern API with //! convenient methods for streaming and capturing of output, as well as //! combining `Popen` instances into pipelines. //! //! Compared to `std::process`, the module follows the following //! additional features: //! //! * The *communicate* [family of methods] for deadlock-free capturing of //! subprocess output/error, while simultaneously feeding data to its //! standard input. Capturing supports optional timeout and read size //! limit. //! //! * Connecting multiple commands into OS-level [pipelines]. //! //! * Flexible [redirection options], such as connecting standard //! streams to arbitary [open files], or [merging] output streams //! like shell's `2>&1` and `1>&2` operators. //! //! * Non-blocking and timeout methods to wait on the process: //! [`poll`], [`wait`], and [`wait_timeout`]. //! //! # Examples //! //! Communicate with a process and optionally terminate it: //! //! ``` //! # use subprocess::*; //! # fn dummy() -> Result<()> { //! let mut p = Popen::create(&["ps", "x"], PopenConfig { //! stdout: Redirection::Pipe, ..Default::default() //! })?; //! //! // Obtain the output from the standard streams. //! let (out, err) = p.communicate(None)?; //! //! if let Some(exit_status) = p.poll() { //! // the process has finished //! } else { //! // it is still running, terminate it //! p.terminate()?; //! } //! # Ok(()) //! # } //! ``` //! //! Use the [`Exec`] builder to execute a pipeline of commands and //! capture the output: //! //! ```no_run //! # use subprocess::*; //! # fn dummy() -> Result<()> { //! let dir_checksum = { //! Exec::shell("find . -type f") | Exec::cmd("sort") | Exec::cmd("sha1sum") //! }.capture()?.stdout_str(); //! # Ok(()) //! # } //! ``` //! //! [`Popen`]: struct.Popen.html //! [`Exec`]: struct.Exec.html //! [family of methods]: struct.Popen.html#method.communicate_start //! [redirection options]: enum.Redirection.html //! [open files]: enum.Redirection.html#variant.File //! [merging]: enum.Redirection.html#variant.Merge //! [`poll`]: struct.Popen.html#method.poll //! [`wait`]: struct.Popen.html#method.wait //! [`wait_timeout`]: struct.Popen.html#method.wait_timeout //! [`subprocess.Popen`]: https://docs.python.org/3/library/subprocess.html#subprocess.Popen //! [pipelines]: struct.Pipeline.html #![warn(missing_debug_implementations, rust_2018_idioms, missing_docs)] #![allow(clippy::type_complexity, clippy::single_match)] mod builder; mod communicate; mod popen; #[cfg(unix)] mod posix; #[cfg(windows)] mod win32; mod os_common; pub use self::builder::{CaptureData, Exec, NullFile, Pipeline}; pub use self::communicate::{CommunicateError, Communicator}; pub use self::os_common::ExitStatus; pub use self::popen::{make_pipe, Popen, PopenConfig, PopenError, Redirection, Result}; /// Subprocess extensions for Unix platforms. pub mod unix { pub use super::popen::os_ext::*; } #[cfg(test)] mod tests { mod builder; mod common; #[cfg(unix)] mod posix; #[cfg(windows)] mod win32; } subprocess-0.2.9/src/os_common.rs000064400000000000000000000031550072674642500152060ustar 00000000000000/// Exit status of a process. #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum ExitStatus { /// The process exited with the specified exit code. /// /// Note that the exit code is limited to a much smaller range on /// most platforms. Exited(u32), /// The process exited due to a signal with the specified number. /// /// This variant is never created on Windows, where signals of /// Unix kind do not exist. Signaled(u8), /// The process exit status cannot be described by the preceding /// two variants. /// /// This should not occur in normal operation. Other(i32), /// It is known that the process has completed, but its exit /// status is unavailable. /// /// This should not occur in normal operation, but is possible if /// for example some foreign code calls `waitpid()` on the PID of /// the child process. Undetermined, } impl ExitStatus { /// True if the exit status of the process is 0. pub fn success(self) -> bool { matches!(self, ExitStatus::Exited(0)) } /// True if the subprocess was killed by a signal with the specified number. /// /// You can pass the concrete `libc` signal numbers to this function, such as /// `status.is_killed_by(libc::SIGABRT)`. pub fn is_killed_by>(self, signum: T) -> bool { if let ExitStatus::Signaled(n) = self { let n: T = n.into(); return n == signum; } false } } #[derive(Debug, Copy, Clone)] #[allow(dead_code)] pub enum StandardStream { Input = 0, Output = 1, Error = 2, } subprocess-0.2.9/src/popen.rs000064400000000000000000001367020072674642500143430ustar 00000000000000use std::cell::RefCell; use std::env; use std::error::Error; use std::ffi::{OsStr, OsString}; use std::fmt; use std::fs::File; use std::io; use std::rc::Rc; use std::result; use std::time::Duration; use crate::communicate; use crate::os_common::{ExitStatus, StandardStream}; use self::ChildState::*; pub use self::os::ext as os_ext; pub use self::os::make_pipe; pub use communicate::Communicator; /// Interface to a running subprocess. /// /// `Popen` is the parent's interface to a created subprocess. The /// child process is started in the constructor, so owning a `Popen` /// value indicates that the specified program has been successfully /// launched. To prevent accumulation of zombie processes, the child /// is waited upon when a `Popen` goes out of scope, which can be /// prevented using the [`detach`] method. /// /// Depending on how the subprocess was configured, its input, output, and /// error streams can be connected to the parent and available as [`stdin`], /// [`stdout`], and [`stderr`] public fields. If you need to read the output /// and errors into memory (or provide input as a memory slice), use the /// [`communicate`] family of methods. /// /// `Popen` instances can be obtained with the [`create`] method, or /// using the [`popen`] method of the [`Exec`] type. Subprocesses /// can be connected into pipes, most easily achieved using using /// [`Exec`]. /// /// [`Exec`]: struct.Exec.html /// [`popen`]: struct.Exec.html#method.popen /// [`stdin`]: struct.Popen.html#structfield.stdin /// [`stdout`]: struct.Popen.html#structfield.stdout /// [`stderr`]: struct.Popen.html#structfield.stderr /// [`create`]: struct.Popen.html#method.create /// [`communicate`]: struct.Popen.html#method.communicate /// [`detach`]: struct.Popen.html#method.detach #[derive(Debug)] pub struct Popen { /// If `stdin` was specified as `Redirection::Pipe`, this will /// contain a writeble `File` connected to the standard input of /// the child process. pub stdin: Option, /// If `stdout` was specified as `Redirection::Pipe`, this will /// contain a readable `File` connected to the standard output of /// the child process. pub stdout: Option, /// If `stderr` was specified as `Redirection::Pipe`, this will /// contain a readable `File` connected to the standard error of /// the child process. pub stderr: Option, child_state: ChildState, detached: bool, } #[derive(Debug)] enum ChildState { Preparing, // only during construction Running { pid: u32, #[allow(dead_code)] ext: os::ExtChildState, }, Finished(ExitStatus), } /// Options for [`Popen::create`]. /// /// When constructing `PopenConfig`, always use the [`Default`] trait, /// such as: /// /// ``` /// # use subprocess::*; /// # let argv = &["true"]; /// Popen::create(argv, PopenConfig { /// stdout: Redirection::Pipe, /// detached: true, /// // ... other fields you want to override ... /// ..Default::default() /// }) /// # .unwrap(); /// ``` /// /// This ensures that fields added later do not break existing code. /// /// An alternative to using `PopenConfig` directly is creating /// processes using [`Exec`], a builder for `Popen`. /// /// [`Popen::create`]: struct.Popen.html#method.create /// [`Exec`]: struct.Exec.html /// [`Default`]: https://doc.rust-lang.org/core/default/trait.Default.html #[derive(Debug)] pub struct PopenConfig { /// How to configure the executed program's standard input. pub stdin: Redirection, /// How to configure the executed program's standard output. pub stdout: Redirection, /// How to configure the executed program's standard error. pub stderr: Redirection, /// Whether the `Popen` instance is initially detached. pub detached: bool, /// Executable to run. /// /// If provided, this executable will be used to run the program /// instead of `argv[0]`. However, `argv[0]` will still be passed /// to the subprocess, which will see that as `argv[0]`. On some /// Unix systems, `ps` will show the string passed as `argv[0]`, /// even though `executable` is actually running. pub executable: Option, /// Environment variables to pass to the subprocess. /// /// If this is None, environment variables are inherited from the calling /// process. Otherwise, the specified variables are used instead. /// /// Duplicates are eliminated, with the value taken from the /// variable appearing later in the vector. pub env: Option>, /// Initial current working directory of the subprocess. /// /// None means inherit the working directory from the parent. pub cwd: Option, /// Set user ID for the subprocess. /// /// If specified, calls `setuid()` before execing the child process. #[cfg(unix)] pub setuid: Option, /// Set group ID for the subprocess. /// /// If specified, calls `setgid()` before execing the child process. /// /// Not to be confused with similarly named `setpgid`. #[cfg(unix)] pub setgid: Option, /// Make the subprocess belong to a new process group. /// /// If specified, calls `setpgid(0, 0)` before execing the child process. /// /// Not to be confused with similarly named `setgid`. #[cfg(unix)] pub setpgid: bool, // Add this field to force construction using ..Default::default() for // backward compatibility. Unfortunately we can't mark this non-public // because then ..Default::default() wouldn't work either. #[doc(hidden)] pub _use_default_to_construct: (), } impl PopenConfig { /// Clone the underlying [`PopenConfig`], or return an error. /// /// This is guaranteed not to fail as long as no /// [`Redirection::File`] variant is used for one of the standard /// streams. Otherwise, it fails if `File::try_clone` fails on /// one of the `Redirection`s. /// /// [`PopenConfig`]: struct.PopenConfig.html /// [`Redirection::File`]: enum.Redirection.html#variant.File pub fn try_clone(&self) -> io::Result { Ok(PopenConfig { stdin: self.stdin.try_clone()?, stdout: self.stdout.try_clone()?, stderr: self.stderr.try_clone()?, detached: self.detached, executable: self.executable.as_ref().cloned(), env: self.env.clone(), cwd: self.cwd.clone(), #[cfg(unix)] setuid: self.setuid, #[cfg(unix)] setgid: self.setgid, #[cfg(unix)] setpgid: self.setpgid, _use_default_to_construct: (), }) } /// Returns the environment of the current process. /// /// The returned value is in the format accepted by the `env` /// member of `PopenConfig`. pub fn current_env() -> Vec<(OsString, OsString)> { env::vars_os().collect() } } impl Default for PopenConfig { fn default() -> PopenConfig { PopenConfig { stdin: Redirection::None, stdout: Redirection::None, stderr: Redirection::None, detached: false, executable: None, env: None, cwd: None, #[cfg(unix)] setuid: None, #[cfg(unix)] setgid: None, #[cfg(unix)] setpgid: false, _use_default_to_construct: (), } } } /// Instruction what to do with a stream in the child process. /// /// `Redirection` values are used for the `stdin`, `stdout`, and /// `stderr` field of the `PopenConfig` struct. They tell /// `Popen::create` how to set up the standard streams in the child /// process and the corresponding fields of the `Popen` struct in the /// parent. #[derive(Debug)] pub enum Redirection { /// Do nothing with the stream. /// /// The stream is typically inherited from the parent. The field /// in `Popen` corresponding to the stream will be `None`. None, /// Redirect the stream to a pipe. /// /// This variant requests that a stream be redirected to a /// unidirectional pipe. One end of the pipe is passed to the /// child process and configured as one of its standard streams, /// and the other end is available to the parent for communicating /// with the child. /// /// The field with `Popen` corresponding to the stream will be /// `Some(file)`, `File` being the parent's end of the pipe. Pipe, /// Merge the stream to the other output stream. /// /// This variant is only valid when configuring redirection of /// standard output and standard error. Using /// `Redirection::Merge` for `PopenConfig::stderr` requests the /// child's stderr to refer to the same underlying file as the /// child's stdout (which may or may not itself be redirected), /// equivalent to the `2>&1` operator of the Bourne shell. /// Analogously, using `Redirection::Merge` for /// `PopenConfig::stdout` is equivalent to `1>&2` in the shell. /// /// Specifying `Redirection::Merge` for `PopenConfig::stdin` or /// specifying it for both `stdout` and `stderr` is invalid and /// will cause `Popen::create` to return /// `Err(PopenError::LogicError)`. /// /// The field in `Popen` corresponding to the stream will be /// `None`. Merge, /// Redirect the stream to the specified open `File`. /// /// This does not create a pipe, it simply spawns the child so /// that the specified stream sees that file. The child can read /// from or write to the provided file on its own, without any /// intervention by the parent. /// /// The field in `Popen` corresponding to the stream will be /// `None`. File(File), /// Like `File`, but the file is specified as `Rc`. /// /// This allows the same file to be used in multiple redirections. RcFile(Rc), } impl Redirection { /// Clone the underlying `Redirection`, or return an error. /// /// Can fail in `File` variant. pub fn try_clone(&self) -> io::Result { Ok(match *self { Redirection::None => Redirection::None, Redirection::Pipe => Redirection::Pipe, Redirection::Merge => Redirection::Merge, Redirection::File(ref f) => Redirection::File(f.try_clone()?), Redirection::RcFile(ref f) => Redirection::RcFile(Rc::clone(&f)), }) } } impl Popen { /// Execute an external program in a new process. /// /// `argv` is a slice containing the program followed by its /// arguments, such as `&["ps", "x"]`. `config` specifies details /// how to create and interface to the process. /// /// For example, this launches the `cargo update` command: /// /// ```no_run /// # use subprocess::*; /// # fn dummy() -> Result<()> { /// Popen::create(&["cargo", "update"], PopenConfig::default())?; /// # Ok(()) /// # } /// ``` /// /// # Errors /// /// If the external program cannot be executed for any reason, an /// error is returned. The most typical reason for execution to /// fail is that the program is missing on the `PATH`, but other /// errors are also possible. Note that this is distinct from the /// program running and then exiting with a failure code - this /// can be detected by calling the `wait` method to obtain its /// exit status. pub fn create(argv: &[impl AsRef], config: PopenConfig) -> Result { if argv.is_empty() { return Err(PopenError::LogicError("argv must not be empty")); } let argv: Vec = argv.iter().map(|p| p.as_ref().to_owned()).collect(); let mut inst = Popen { stdin: None, stdout: None, stderr: None, child_state: ChildState::Preparing, detached: config.detached, }; inst.os_start(argv, config)?; Ok(inst) } // Create the pipes requested by stdin, stdout, and stderr from // the PopenConfig used to construct us, and return the Files to // be given to the child process. // // For Redirection::Pipe, this stores the parent end of the pipe // to the appropriate self.std* field, and returns the child end // of the pipe. // // For Redirection::File, this transfers the ownership of the File // to the corresponding child. fn setup_streams( &mut self, stdin: Redirection, stdout: Redirection, stderr: Redirection, ) -> Result<(Option>, Option>, Option>)> { fn prepare_pipe( parent_writes: bool, parent_ref: &mut Option, child_ref: &mut Option>, ) -> Result<()> { // Store the parent's end of the pipe into the given // reference, and store the child end. let (read, write) = os::make_pipe()?; let (parent_end, child_end) = if parent_writes { (write, read) } else { (read, write) }; os::set_inheritable(&parent_end, false)?; *parent_ref = Some(parent_end); *child_ref = Some(Rc::new(child_end)); Ok(()) } fn prepare_file(file: File, child_ref: &mut Option>) -> io::Result<()> { // Make the File inheritable and store it for use in the child. os::set_inheritable(&file, true)?; *child_ref = Some(Rc::new(file)); Ok(()) } fn prepare_rc_file(file: Rc, child_ref: &mut Option>) -> io::Result<()> { // Like prepare_file, but for Rc os::set_inheritable(&file, true)?; *child_ref = Some(file); Ok(()) } fn reuse_stream( dest: &mut Option>, src: &mut Option>, src_id: StandardStream, ) -> io::Result<()> { // For Redirection::Merge, make stdout and stderr refer to // the same File. If the file is unavailable, use the // appropriate system output stream. if src.is_none() { *src = Some(get_standard_stream(src_id)?); } *dest = Some(Rc::clone(src.as_ref().unwrap())); Ok(()) } enum MergeKind { ErrToOut, // 2>&1 OutToErr, // 1>&2 None, } let mut merge: MergeKind = MergeKind::None; let (mut child_stdin, mut child_stdout, mut child_stderr) = (None, None, None); match stdin { Redirection::Pipe => prepare_pipe(true, &mut self.stdin, &mut child_stdin)?, Redirection::File(file) => prepare_file(file, &mut child_stdin)?, Redirection::RcFile(file) => prepare_rc_file(file, &mut child_stdin)?, Redirection::Merge => { return Err(PopenError::LogicError( "Redirection::Merge not valid for stdin", )); } Redirection::None => (), }; match stdout { Redirection::Pipe => prepare_pipe(false, &mut self.stdout, &mut child_stdout)?, Redirection::File(file) => prepare_file(file, &mut child_stdout)?, Redirection::RcFile(file) => prepare_rc_file(file, &mut child_stdout)?, Redirection::Merge => merge = MergeKind::OutToErr, Redirection::None => (), }; match stderr { Redirection::Pipe => prepare_pipe(false, &mut self.stderr, &mut child_stderr)?, Redirection::File(file) => prepare_file(file, &mut child_stderr)?, Redirection::RcFile(file) => prepare_rc_file(file, &mut child_stderr)?, Redirection::Merge => merge = MergeKind::ErrToOut, Redirection::None => (), }; // Handle Redirection::Merge after creating the output child // streams. Merge by cloning the child stream, or the // appropriate standard stream if we don't have a child stream // requested using Redirection::Pipe or Redirection::File. In // other words, 2>&1 (ErrToOut) is implemented by making // child_stderr point to a dup of child_stdout, or of the OS's // stdout stream. match merge { MergeKind::ErrToOut => { reuse_stream(&mut child_stderr, &mut child_stdout, StandardStream::Output)? } MergeKind::OutToErr => { reuse_stream(&mut child_stdout, &mut child_stderr, StandardStream::Error)? } MergeKind::None => (), } Ok((child_stdin, child_stdout, child_stderr)) } /// Mark the process as detached. /// /// This method has no effect on the OS level, it simply tells /// `Popen` not to wait for the subprocess to finish when going /// out of scope. If the child process has already finished, or /// if it is guaranteed to finish before `Popen` goes out of /// scope, calling `detach` has no effect. pub fn detach(&mut self) { self.detached = true; } /// Return the PID of the subprocess, if it is known to be still running. /// /// Note that this method won't actually *check* whether the child /// process is still running, it will only return the information /// last set using one of `create`, `wait`, `wait_timeout`, or /// `poll`. For a newly created `Popen`, `pid()` always returns /// `Some`. pub fn pid(&self) -> Option { match self.child_state { Running { pid, .. } => Some(pid), _ => None, } } /// Return the exit status of the subprocess, if it is known to have finished. /// /// Note that this method won't actually *check* whether the child /// process has finished, it only returns the previously available /// information. To check or wait for the process to finish, call /// `wait`, `wait_timeout`, or `poll`. pub fn exit_status(&self) -> Option { match self.child_state { Finished(exit_status) => Some(exit_status), _ => None, } } /// Prepare to communicate with the subprocess. /// /// Communicating refers to unattended data exchange with the subprocess. /// During communication the given `input_data` is written to the /// subprocess's standard input which is then closed, while simultaneously /// its standard output and error streams are read until end-of-file is /// reached. /// /// The difference between this and simply writing input data to /// `self.stdin` and then reading output from `self.stdout` and /// `self.stderr` is that the reading and the writing are performed /// simultaneously. A naive implementation that writes and then reads has /// an issue when the subprocess responds to part of the input by /// providing output. The output must be read for the subprocess to /// accept further input, but the parent process is still blocked on /// writing the rest of the input daata. Since neither process can /// proceed, a deadlock occurs. This is why a correct implementation must /// write and read at the same time. /// /// This method does not perform the actual communication, it just sets it /// up and returns a [`Communicator`]. Call the [`read`] or /// [`read_string`] method on the `Communicator` to exchange data with the /// subprocess. /// /// Compared to `communicate()` and `communicate_bytes()`, the /// `Communicator` provides more control, such as timeout, read size /// limit, and the ability to retrieve captured output in case of read /// error. /// /// [`Communicator`]: struct.Communicator.html /// [`read`]: struct.Communicator.html#method.read /// [`read_string`]: struct.Communicator.html#method.read_string pub fn communicate_start(&mut self, input_data: Option>) -> Communicator { communicate::communicate( self.stdin.take(), self.stdout.take(), self.stderr.take(), input_data, ) } /// Feed the subprocess with input data and capture its output. /// /// This will write the provided `input_data` to the subprocess's standard /// input, and simultaneously read its standard output and error. The /// output and error contents are returned as a pair of `Option>`. /// The `None` options correspond to streams not specified as /// `Redirection::Pipe` when creating the subprocess. /// /// This implementation reads and writes simultaneously, avoiding deadlock /// in case the subprocess starts writing output before reading the whole /// input - see [`communicate_start()`] for details. /// /// Note that this method does not wait for the subprocess to finish, only /// to close its output/error streams. It is rare but possible for the /// program to continue running after having closed the streams, in which /// case `Popen::Drop` will wait for it to finish. If such a wait is /// undesirable, it can be prevented by waiting explicitly using `wait()`, /// by detaching the process using `detach()`, or by terminating it with /// `terminate()`. /// /// For additional control over communication, such as timeout and size /// limit, call [`communicate_start()`]. /// /// # Panics /// /// If `input_data` is provided and `stdin` was not redirected to a pipe. /// Also, if `input_data` is not provided and `stdin` was redirected to a /// pipe. /// /// # Errors /// /// * `Err(::std::io::Error)` if a system call fails /// /// [`communicate_start()`]: struct.Popen.html#method.communicate_start pub fn communicate_bytes( &mut self, input_data: Option<&[u8]>, ) -> io::Result<(Option>, Option>)> { self.communicate_start(input_data.map(|i| i.to_vec())) .read() .map_err(|e| e.error) } /// Feed the subprocess with data and capture its output as string. /// /// This is a convenience method equivalent to [`communicate_bytes`], but /// with input as `&str` and output as `String`. Invalid UTF-8 sequences, /// if found, are replaced with the the `U+FFFD` Unicode replacement /// character. /// /// # Panics /// /// The same as with `communicate_bytes`. /// /// # Errors /// /// * `Err(::std::io::Error)` if a system call fails /// /// [`communicate_bytes`]: struct.Popen.html#method.communicate_bytes pub fn communicate( &mut self, input_data: Option<&str>, ) -> io::Result<(Option, Option)> { self.communicate_start(input_data.map(|s| s.as_bytes().to_vec())) .read_string() .map_err(|e| e.error) } /// Check whether the process is still running, without blocking or errors. /// /// This checks whether the process is still running and if it /// is still running, `None` is returned, otherwise /// `Some(exit_status)`. This method is guaranteed not to block /// and is exactly equivalent to /// `wait_timeout(Duration::from_secs(0)).unwrap_or(None)`. pub fn poll(&mut self) -> Option { self.wait_timeout(Duration::from_secs(0)).unwrap_or(None) } /// Wait for the process to finish, and return its exit status. /// /// If the process has already finished, it will exit immediately, /// returning the exit status. Calling `wait` after that will /// return the cached exit status without executing any system /// calls. /// /// # Errors /// /// Returns an `Err` if a system call fails in an unpredicted way. /// This should not happen in normal usage. pub fn wait(&mut self) -> Result { self.os_wait() } /// Wait for the process to finish, timing out after the specified duration. /// /// This function behaves like `wait()`, except that the caller /// will be blocked for roughly no longer than `dur`. It returns /// `Ok(None)` if the timeout is known to have elapsed. /// /// On Unix-like systems, timeout is implemented by calling /// `waitpid(..., WNOHANG)` in a loop with adaptive sleep /// intervals between iterations. pub fn wait_timeout(&mut self, dur: Duration) -> Result> { self.os_wait_timeout(dur) } /// Terminate the subprocess. /// /// On Unix-like systems, this sends the `SIGTERM` signal to the /// child process, which can be caught by the child in order to /// perform cleanup before exiting. On Windows, it is equivalent /// to `kill()`. pub fn terminate(&mut self) -> io::Result<()> { self.os_terminate() } /// Kill the subprocess. /// /// On Unix-like systems, this sends the `SIGKILL` signal to the /// child process, which cannot be caught. /// /// On Windows, it invokes [`TerminateProcess`] on the process /// handle with equivalent semantics. /// /// [`TerminateProcess`]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686714(v=vs.85).aspx pub fn kill(&mut self) -> io::Result<()> { self.os_kill() } } trait PopenOs { fn os_start(&mut self, argv: Vec, config: PopenConfig) -> Result<()>; fn os_wait(&mut self) -> Result; fn os_wait_timeout(&mut self, dur: Duration) -> Result>; fn os_terminate(&mut self) -> io::Result<()>; fn os_kill(&mut self) -> io::Result<()>; } #[cfg(unix)] mod os { use super::*; use crate::posix; use std::collections::HashSet; use std::ffi::OsString; use std::fs::File; use std::io::{self, Read, Write}; use std::os::unix::io::AsRawFd; use std::time::{Duration, Instant}; use crate::os_common::ExitStatus; use crate::unix::PopenExt; pub type ExtChildState = (); impl super::PopenOs for Popen { fn os_start(&mut self, argv: Vec, config: PopenConfig) -> Result<()> { let mut exec_fail_pipe = posix::pipe()?; set_inheritable(&exec_fail_pipe.0, false)?; set_inheritable(&exec_fail_pipe.1, false)?; { let child_ends = self.setup_streams(config.stdin, config.stdout, config.stderr)?; let child_env = config.env.as_deref().map(format_env); let cmd_to_exec = config.executable.as_ref().unwrap_or(&argv[0]); let just_exec = posix::prep_exec(cmd_to_exec, &argv, child_env.as_deref())?; unsafe { // unsafe because after the call to fork() the // child is not allowed to allocate match posix::fork()? { Some(child_pid) => { self.child_state = Running { pid: child_pid, ext: (), }; } None => { drop(exec_fail_pipe.0); let result = Popen::do_exec( just_exec, child_ends, config.cwd.as_deref(), config.setuid, config.setgid, config.setpgid, ); // If we are here, it means that exec has failed. Notify // the parent and exit. let error_code = match result { Ok(()) => unreachable!(), Err(e) => e.raw_os_error().unwrap_or(-1), } as u32; exec_fail_pipe .1 .write_all(&[ error_code as u8, (error_code >> 8) as u8, (error_code >> 16) as u8, (error_code >> 24) as u8, ]) .ok(); posix::_exit(127); } } } } drop(exec_fail_pipe.1); let mut error_buf = [0u8; 4]; let read_cnt = exec_fail_pipe.0.read(&mut error_buf)?; if read_cnt == 0 { Ok(()) } else if read_cnt == 4 { let error_code: u32 = error_buf[0] as u32 | (error_buf[1] as u32) << 8 | (error_buf[2] as u32) << 16 | (error_buf[3] as u32) << 24; Err(PopenError::from(io::Error::from_raw_os_error( error_code as i32, ))) } else { Err(PopenError::LogicError("invalid read_count from exec pipe")) } } fn os_wait(&mut self) -> Result { while let Running { .. } = self.child_state { self.waitpid(true)?; } Ok(self.exit_status().unwrap()) } fn os_wait_timeout(&mut self, dur: Duration) -> Result> { use std::cmp::min; if let Finished(exit_status) = self.child_state { return Ok(Some(exit_status)); } let deadline = Instant::now() + dur; // double delay at every iteration, maxing at 100ms let mut delay = Duration::from_millis(1); loop { self.waitpid(false)?; if let Finished(exit_status) = self.child_state { return Ok(Some(exit_status)); } let now = Instant::now(); if now >= deadline { return Ok(None); } let remaining = deadline.duration_since(now); ::std::thread::sleep(min(delay, remaining)); delay = min(delay * 2, Duration::from_millis(100)); } } fn os_terminate(&mut self) -> io::Result<()> { self.send_signal(posix::SIGTERM) } fn os_kill(&mut self) -> io::Result<()> { self.send_signal(posix::SIGKILL) } } fn format_env(env: &[(OsString, OsString)]) -> Vec { // Convert Vec of (key, val) pairs to Vec of key=val, as required by // execvpe. Eliminate dups, in favor of later-appearing entries. let mut seen = HashSet::<&OsStr>::new(); let mut formatted: Vec<_> = env .iter() .rev() .filter(|&(k, _)| seen.insert(k)) .map(|(k, v)| { let mut fmt = k.clone(); fmt.push("="); fmt.push(v); fmt }) .collect(); formatted.reverse(); formatted } trait PopenOsImpl: super::PopenOs { fn do_exec( just_exec: impl FnOnce() -> io::Result<()>, child_ends: (Option>, Option>, Option>), cwd: Option<&OsStr>, setuid: Option, setgid: Option, setpgid: bool, ) -> io::Result<()>; fn waitpid(&mut self, block: bool) -> io::Result<()>; } impl PopenOsImpl for Popen { fn do_exec( just_exec: impl FnOnce() -> io::Result<()>, child_ends: (Option>, Option>, Option>), cwd: Option<&OsStr>, setuid: Option, setgid: Option, setpgid: bool, ) -> io::Result<()> { if let Some(cwd) = cwd { env::set_current_dir(cwd)?; } let (stdin, stdout, stderr) = child_ends; if let Some(stdin) = stdin { if stdin.as_raw_fd() != 0 { posix::dup2(stdin.as_raw_fd(), 0)?; } } if let Some(stdout) = stdout { if stdout.as_raw_fd() != 1 { posix::dup2(stdout.as_raw_fd(), 1)?; } } if let Some(stderr) = stderr { if stderr.as_raw_fd() != 2 { posix::dup2(stderr.as_raw_fd(), 2)?; } } posix::reset_sigpipe()?; if let Some(uid) = setuid { posix::setuid(uid)?; } if let Some(gid) = setgid { posix::setgid(gid)?; } if setpgid { posix::setpgid(0, 0)?; } just_exec()?; unreachable!(); } fn waitpid(&mut self, block: bool) -> io::Result<()> { match self.child_state { Preparing => panic!("child_state == Preparing"), Running { pid, .. } => { match posix::waitpid(pid, if block { 0 } else { posix::WNOHANG }) { Err(e) => { if let Some(errno) = e.raw_os_error() { if errno == posix::ECHILD { // Someone else has waited for the child // (another thread, a signal handler...). // The PID no longer exists and we cannot // find its exit status. self.child_state = Finished(ExitStatus::Undetermined); return Ok(()); } } return Err(e); } Ok((pid_out, exit_status)) => { if pid_out == pid { self.child_state = Finished(exit_status); } } } } Finished(..) => (), } Ok(()) } } pub fn set_inheritable(f: &File, inheritable: bool) -> io::Result<()> { if inheritable { // Unix pipes are inheritable by default. } else { let fd = f.as_raw_fd(); let old = posix::fcntl(fd, posix::F_GETFD, None)?; posix::fcntl(fd, posix::F_SETFD, Some(old | posix::FD_CLOEXEC))?; } Ok(()) } /// Create a pipe. /// /// This is a safe wrapper over `libc::pipe` or /// `winapi::um::namedpipeapi::CreatePipe`, depending on the operating /// system. pub fn make_pipe() -> io::Result<(File, File)> { posix::pipe() } pub mod ext { use crate::popen::ChildState::*; use crate::popen::Popen; use crate::posix; use std::io; /// Unix-specific extension methods for `Popen` pub trait PopenExt { /// Send the specified signal to the child process. /// /// The signal numbers are best obtained from the [`libc`] /// crate. /// /// If the child process is known to have finished (due to e.g. /// a previous call to [`wait`] or [`poll`]), this will do /// nothing and return `Ok`. /// /// [`poll`]: ../struct.Popen.html#method.poll /// [`wait`]: ../struct.Popen.html#method.wait /// [`libc`]: https://docs.rs/libc/ fn send_signal(&self, signal: i32) -> io::Result<()>; } impl PopenExt for Popen { fn send_signal(&self, signal: i32) -> io::Result<()> { match self.child_state { Preparing => panic!("child_state == Preparing"), Running { pid, .. } => posix::kill(pid, signal), Finished(..) => Ok(()), } } } } } #[cfg(windows)] mod os { use super::*; use std::collections::HashSet; use std::env; use std::ffi::{OsStr, OsString}; use std::fs::{self, File}; use std::io; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::os::windows::io::{AsRawHandle, RawHandle}; use std::time::Duration; use crate::os_common::{ExitStatus, StandardStream}; use crate::win32; #[derive(Debug)] pub struct ExtChildState(win32::Handle); impl super::PopenOs for Popen { fn os_start(&mut self, argv: Vec, config: PopenConfig) -> Result<()> { fn raw(opt: &Option>) -> Option { opt.as_ref().map(|f| f.as_raw_handle()) } let (mut child_stdin, mut child_stdout, mut child_stderr) = self.setup_streams(config.stdin, config.stdout, config.stderr)?; ensure_child_stream(&mut child_stdin, StandardStream::Input)?; ensure_child_stream(&mut child_stdout, StandardStream::Output)?; ensure_child_stream(&mut child_stderr, StandardStream::Error)?; let cmdline = assemble_cmdline(argv)?; let env_block = config.env.map(|env| format_env_block(&env)); // CreateProcess doesn't search for appname in the PATH. // We do it ourselves to match the Unix behavior. let executable = config.executable.map(locate_in_path); let (handle, pid) = win32::CreateProcess( executable.as_ref().map(OsString::as_ref), &cmdline, &env_block, &config.cwd.as_deref(), true, 0, raw(&child_stdin), raw(&child_stdout), raw(&child_stderr), win32::STARTF_USESTDHANDLES, )?; self.child_state = Running { pid: pid as u32, ext: ExtChildState(handle), }; Ok(()) } fn os_wait(&mut self) -> Result { self.wait_handle(None)?; match self.child_state { Preparing => panic!("child_state == Preparing"), Finished(exit_status) => Ok(exit_status), // Since we invoked wait_handle without timeout, exit // status should exist at this point. The only way // for it not to exist would be if something strange // happened, like WaitForSingleObject returning // something other than OBJECT_0. Running { .. } => Err(PopenError::LogicError("Failed to obtain exit status")), } } fn os_wait_timeout(&mut self, dur: Duration) -> Result> { if let Finished(exit_status) = self.child_state { return Ok(Some(exit_status)); } self.wait_handle(Some(dur))?; Ok(self.exit_status()) } fn os_terminate(&mut self) -> io::Result<()> { let mut new_child_state = None; if let Running { ext: ExtChildState(ref handle), .. } = self.child_state { match win32::TerminateProcess(handle, 1) { Err(err) => { if err.raw_os_error() != Some(win32::ERROR_ACCESS_DENIED as i32) { return Err(err); } let rc = win32::GetExitCodeProcess(handle)?; if rc == win32::STILL_ACTIVE { return Err(err); } new_child_state = Some(Finished(ExitStatus::Exited(rc))); } Ok(_) => (), } } if let Some(new_child_state) = new_child_state { self.child_state = new_child_state; } Ok(()) } fn os_kill(&mut self) -> io::Result<()> { self.terminate() } } fn format_env_block(env: &[(OsString, OsString)]) -> Vec { fn to_uppercase(s: &OsStr) -> OsString { OsString::from_wide( &s.encode_wide() .map(|c| { if c < 128 { (c as u8 as char).to_ascii_uppercase() as u16 } else { c } }) .collect::>(), ) } let mut pruned: Vec<_> = { let mut seen = HashSet::::new(); env.iter() .rev() .filter(|&(k, _)| seen.insert(to_uppercase(k))) .collect() }; pruned.reverse(); let mut block = vec![]; for (k, v) in pruned { block.extend(k.encode_wide()); block.push('=' as u16); block.extend(v.encode_wide()); block.push(0); } block.push(0); block } trait PopenOsImpl { fn wait_handle(&mut self, timeout: Option) -> io::Result>; } impl PopenOsImpl for Popen { fn wait_handle(&mut self, timeout: Option) -> io::Result> { let mut new_child_state = None; if let Running { ext: ExtChildState(ref handle), .. } = self.child_state { let event = win32::WaitForSingleObject(handle, timeout)?; if let win32::WaitEvent::OBJECT_0 = event { let exit_code = win32::GetExitCodeProcess(handle)?; new_child_state = Some(Finished(ExitStatus::Exited(exit_code))); } } if let Some(new_child_state) = new_child_state { self.child_state = new_child_state; } Ok(self.exit_status()) } } fn ensure_child_stream(stream: &mut Option>, which: StandardStream) -> io::Result<()> { // If no stream is sent to CreateProcess, the child doesn't // get a valid stream. This results in e.g. // Exec("sh").arg("-c").arg("echo foo >&2").stream_stderr() // failing because the shell tries to redirect stdout to // stderr, but fails because it didn't receive a valid stdout. if stream.is_none() { *stream = Some(get_standard_stream(which)?); } Ok(()) } pub fn set_inheritable(f: &File, inheritable: bool) -> io::Result<()> { win32::SetHandleInformation( f, win32::HANDLE_FLAG_INHERIT, if inheritable { 1 } else { 0 }, )?; Ok(()) } /// Create a pipe. /// /// This is a safe wrapper over `libc::pipe` or /// `winapi::um::namedpipeapi::CreatePipe`, depending on the operating /// system. pub fn make_pipe() -> io::Result<(File, File)> { win32::CreatePipe(true) } fn locate_in_path(executable: OsString) -> OsString { if let Some(path) = env::var_os("PATH") { for path in env::split_paths(&path) { let path = path .join(&executable) .with_extension(::std::env::consts::EXE_EXTENSION); if fs::metadata(&path).is_ok() { return path.into_os_string(); } } } executable } fn assemble_cmdline(argv: Vec) -> io::Result { let mut cmdline = vec![]; let mut is_first = true; for arg in argv { if !is_first { cmdline.push(' ' as u16); } else { is_first = false; } if arg.encode_wide().any(|c| c == 0) { return Err(io::Error::from_raw_os_error( win32::ERROR_BAD_PATHNAME as i32, )); } append_quoted(&arg, &mut cmdline); } Ok(OsString::from_wide(&cmdline)) } // Translated from ArgvQuote at http://tinyurl.com/zmgtnls fn append_quoted(arg: &OsStr, cmdline: &mut Vec) { if !arg.is_empty() && !arg.encode_wide().any(|c| { c == ' ' as u16 || c == '\t' as u16 || c == '\n' as u16 || c == '\x0b' as u16 || c == '\"' as u16 }) { cmdline.extend(arg.encode_wide()); return; } cmdline.push('"' as u16); let arg: Vec<_> = arg.encode_wide().collect(); let mut i = 0; while i < arg.len() { let mut num_backslashes = 0; while i < arg.len() && arg[i] == '\\' as u16 { i += 1; num_backslashes += 1; } if i == arg.len() { for _ in 0..num_backslashes * 2 { cmdline.push('\\' as u16); } break; } else if arg[i] == b'"' as u16 { for _ in 0..num_backslashes * 2 + 1 { cmdline.push('\\' as u16); } cmdline.push(arg[i]); } else { for _ in 0..num_backslashes { cmdline.push('\\' as u16); } cmdline.push(arg[i]); } i += 1; } cmdline.push('"' as u16); } pub mod ext {} } impl Drop for Popen { // Wait for the process to exit. To avoid the wait, call // detach(). fn drop(&mut self) { if let (false, &Running { .. }) = (self.detached, &self.child_state) { // Should we log error if one occurs during drop()? self.wait().ok(); } } } thread_local! { static STREAMS: RefCell<[Option>; 3]> = RefCell::default(); } #[cfg(unix)] use crate::posix::make_standard_stream; #[cfg(windows)] use crate::win32::make_standard_stream; fn get_standard_stream(which: StandardStream) -> io::Result> { STREAMS.with(|streams| { if let Some(ref stream) = streams.borrow()[which as usize] { return Ok(Rc::clone(&stream)); } let stream = make_standard_stream(which)?; streams.borrow_mut()[which as usize] = Some(Rc::clone(&stream)); Ok(stream) }) } /// Error in [`Popen`] calls. /// /// [`Popen`]: struct.Popen.html #[derive(Debug)] #[non_exhaustive] pub enum PopenError { /// An IO system call failed while executing the requested operation. IoError(io::Error), /// A logical error was made, e.g. invalid arguments detected at run-time. LogicError(&'static str), } impl From for PopenError { fn from(err: io::Error) -> PopenError { PopenError::IoError(err) } } impl From for PopenError { fn from(err: communicate::CommunicateError) -> PopenError { PopenError::IoError(err.error) } } impl Error for PopenError { fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { PopenError::IoError(ref err) => Some(err), PopenError::LogicError(_msg) => None, } } } impl fmt::Display for PopenError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { PopenError::IoError(ref err) => fmt::Display::fmt(err, f), PopenError::LogicError(desc) => f.write_str(desc), } } } /// Result returned by calls in the `subprocess` crate in places where /// `::std::io::Result` does not suffice. pub type Result = result::Result; subprocess-0.2.9/src/posix.rs000064400000000000000000000304430072674642500143570ustar 00000000000000use std::env; use std::ffi::{CString, OsStr, OsString}; use std::fs::File; use std::io::{Error, Result}; use std::iter; use std::marker::PhantomData; use std::mem; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::ptr; use std::rc::Rc; use std::time::{Duration, Instant}; use libc::{c_char, c_int}; use crate::os_common::{ExitStatus, StandardStream}; pub use libc::{ECHILD, ENOSPC}; fn check_err(num: T) -> Result { if num < T::default() { return Err(Error::last_os_error()); } Ok(num) } pub fn pipe() -> Result<(File, File)> { let mut fds = [0 as c_int; 2]; check_err(unsafe { libc::pipe(fds.as_mut_ptr()) })?; Ok(unsafe { (File::from_raw_fd(fds[0]), File::from_raw_fd(fds[1])) }) } // marked unsafe because the child must not allocate before exec-ing pub unsafe fn fork() -> Result> { let pid = check_err(libc::fork())?; if pid == 0 { Ok(None) // child } else { Ok(Some(pid as u32)) // parent } } pub fn setuid(uid: u32) -> Result<()> { check_err(unsafe { libc::setuid(uid as libc::uid_t) })?; Ok(()) } pub fn setgid(gid: u32) -> Result<()> { check_err(unsafe { libc::setgid(gid as libc::gid_t) })?; Ok(()) } pub fn setpgid(pid: u32, pgid: u32) -> Result<()> { check_err(unsafe { libc::setpgid(pid as _, pgid as _) })?; Ok(()) } fn os_to_cstring(s: &OsStr) -> Result { // Like CString::new, but returns an io::Result for consistency with // everything else. CString::new(s.as_bytes()).map_err(|_| Error::from_raw_os_error(libc::EINVAL)) } #[derive(Debug)] struct CVec { // Individual C strings. Each element self.ptrs[i] points to the // data of self.strings[i].as_bytes_with_nul().as_ptr(). #[allow(dead_code)] strings: Vec, // nullptr-terminated vector of pointers to data inside // self.strings. ptrs: Vec<*const c_char>, } impl CVec { fn new(slice: &[impl AsRef]) -> Result { let maybe_strings: Result> = slice.iter().map(|x| os_to_cstring(x.as_ref())).collect(); let strings = maybe_strings?; let ptrs: Vec<_> = strings .iter() .map(|s| s.as_bytes_with_nul().as_ptr() as _) .chain(iter::once(ptr::null())) .collect(); Ok(CVec { strings, ptrs }) } pub fn as_c_vec(&self) -> *const *const c_char { self.ptrs.as_ptr() } } fn split_path(mut path: &OsStr) -> impl Iterator { // Can't use `env::split`_path because it allocates OsString objects, and // we need to iterate over PATH after fork() when allocations are strictly // verboten. We can't use `str::split()` either because PATH is an // `OsStr`, and there is no `OsStr::split()`. std::iter::from_fn(move || { while let Some(pos) = path.as_bytes().iter().position(|&c| c == b':') { let piece = OsStr::from_bytes(&path.as_bytes()[..pos]); path = OsStr::from_bytes(&path.as_bytes()[pos + 1..]); if !piece.is_empty() { return Some(piece); } } let piece = path; path = OsStr::new(""); if !piece.is_empty() { return Some(piece); } None }) } #[cfg(test)] mod tests { use super::split_path; use std; use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; fn s(s: &str) -> Vec<&str> { split_path(OsStr::new(s)) .map(|osstr| std::str::from_utf8(osstr.as_bytes()).unwrap()) .collect() } #[test] fn test_split_path() { let empty = Vec::<&OsStr>::new(); assert_eq!(s("a:b"), vec!["a", "b"]); assert_eq!(s("one:twothree"), vec!["one", "twothree"]); assert_eq!(s("a:"), vec!["a"]); assert_eq!(s(""), empty); assert_eq!(s(":"), empty); assert_eq!(s("::"), empty); assert_eq!(s(":::"), empty); assert_eq!(s("a::b"), vec!["a", "b"]); assert_eq!(s(":a::::b:"), vec!["a", "b"]); } } struct PrepExec { cmd: OsString, argvec: CVec, envvec: Option, search_path: Option, prealloc_exe: Vec, } impl PrepExec { fn new( cmd: OsString, argvec: CVec, envvec: Option, search_path: Option, ) -> PrepExec { // Avoid allocation after fork() by pre-allocating the buffer // that will be used for constructing the executable C string. // Allocate enough room for "/\0", pathdir // being the longest component of PATH. let mut max_exe_len = cmd.len() + 1; if let Some(ref search_path) = search_path { // make sure enough room is present for the largest of the // PATH components, plus 1 for the intervening '/'. max_exe_len += 1 + split_path(search_path).map(OsStr::len).max().unwrap_or(0); } PrepExec { cmd, argvec, envvec, search_path, prealloc_exe: Vec::with_capacity(max_exe_len), } } fn exec(mut self) -> Result<()> { // Invoked after fork() - no heap allocation allowed let mut exe = std::mem::take(&mut self.prealloc_exe); if let Some(ref search_path) = self.search_path { let mut err = Ok(()); // POSIX requires execvp and execve, but not execvpe (although // glibc provides one), so we have to iterate over PATH ourselves for dir in split_path(search_path.as_os_str()) { err = self.libc_exec(PrepExec::assemble_exe( &mut exe, &[dir.as_bytes(), b"/", self.cmd.as_bytes()], )); // if exec succeeds, we won't run anymore; if we're here, it failed assert!(err.is_err()); } // we haven't found the command anywhere on the path, just return // the last error return err; } self.libc_exec(PrepExec::assemble_exe(&mut exe, &[self.cmd.as_bytes()]))?; // failed exec can only return Err(..) unreachable!(); } fn assemble_exe<'a>(storage: &'a mut Vec, components: &[&[u8]]) -> &'a [u8] { storage.truncate(0); for comp in components { storage.extend_from_slice(comp); } // `storage` will be passed to libc::execve so it must end with \0. storage.push(0u8); storage.as_slice() } fn libc_exec(&self, exe: &[u8]) -> Result<()> { unsafe { match self.envvec.as_ref() { Some(envvec) => { libc::execve(exe.as_ptr() as _, self.argvec.as_c_vec(), envvec.as_c_vec()) } None => libc::execv(exe.as_ptr() as _, self.argvec.as_c_vec()), } }; Err(Error::last_os_error()) } } /// Prepare everything needed to `exec()` the provided `cmd` after `fork()`. /// /// Since code executed in the child after a `fork()` is not allowed to /// allocate (because the lock might be held), this allocates everything /// beforehand. pub fn prep_exec( cmd: impl AsRef, args: &[impl AsRef], env: Option<&[impl AsRef]>, ) -> Result Result<()>> { let cmd = cmd.as_ref().to_owned(); let argvec = CVec::new(args)?; let envvec = if let Some(env) = env { Some(CVec::new(env)?) } else { None }; let search_path = if !cmd.as_bytes().iter().any(|&b| b == b'/') { env::var_os("PATH") // treat empty path as non-existent .and_then(|p| if p.len() == 0 { None } else { Some(p) }) } else { None }; // Allocate now and return a closure that just does the exec. let prep = PrepExec::new(cmd, argvec, envvec, search_path); Ok(move || prep.exec()) } pub fn _exit(status: u8) -> ! { unsafe { libc::_exit(status as c_int) } } pub const WNOHANG: i32 = libc::WNOHANG; pub fn waitpid(pid: u32, flags: i32) -> Result<(u32, ExitStatus)> { let mut status = 0 as c_int; let pid = check_err(unsafe { libc::waitpid( pid as libc::pid_t, &mut status as *mut c_int, flags as c_int, ) })?; Ok((pid as u32, decode_exit_status(status))) } fn decode_exit_status(status: i32) -> ExitStatus { if libc::WIFEXITED(status) { ExitStatus::Exited(libc::WEXITSTATUS(status) as u32) } else if libc::WIFSIGNALED(status) { ExitStatus::Signaled(libc::WTERMSIG(status) as u8) } else { ExitStatus::Other(status) } } pub use libc::{SIGKILL, SIGTERM}; pub fn kill(pid: u32, signal: i32) -> Result<()> { check_err(unsafe { libc::kill(pid as c_int, signal) })?; Ok(()) } pub const F_GETFD: i32 = libc::F_GETFD; pub const F_SETFD: i32 = libc::F_SETFD; pub const FD_CLOEXEC: i32 = libc::FD_CLOEXEC; pub fn fcntl(fd: i32, cmd: i32, arg1: Option) -> Result { check_err(unsafe { match arg1 { Some(arg1) => libc::fcntl(fd, cmd, arg1), None => libc::fcntl(fd, cmd), } }) } pub fn dup2(oldfd: i32, newfd: i32) -> Result<()> { check_err(unsafe { libc::dup2(oldfd, newfd) })?; Ok(()) } pub fn make_standard_stream(which: StandardStream) -> Result> { let stream = Rc::new(unsafe { File::from_raw_fd(which as RawFd) }); // Leak the Rc so the object we return doesn't close the underlying file // descriptor. We didn't open it, and it is shared by everything else, so // we are not allowed to close it either. mem::forget(Rc::clone(&stream)); Ok(stream) } pub fn reset_sigpipe() -> Result<()> { // This is called after forking to reset SIGPIPE handling to the // defaults that Unix programs expect. Quoting // std::process::Command::do_exec: // // """ // libstd ignores SIGPIPE, and signal-handling libraries often set // a mask. Child processes inherit ignored signals and the signal // mask from their parent, but most UNIX programs do not reset // these things on their own, so we need to clean things up now to // avoid confusing the program we're about to run. // """ unsafe { let mut set: mem::MaybeUninit = mem::MaybeUninit::uninit(); check_err(libc::sigemptyset(set.as_mut_ptr()))?; let set = set.assume_init(); check_err(libc::pthread_sigmask( libc::SIG_SETMASK, &set, ptr::null_mut(), ))?; match libc::signal(libc::SIGPIPE, libc::SIG_DFL) { libc::SIG_ERR => return Err(Error::last_os_error()), _ => (), } } Ok(()) } #[repr(C)] pub struct PollFd<'a>(libc::pollfd, PhantomData<&'a ()>); impl PollFd<'_> { pub fn new<'a>(file: Option<&'a File>, events: i16) -> PollFd<'a> { PollFd( libc::pollfd { fd: file.map(File::as_raw_fd).unwrap_or(-1), events, revents: 0, }, PhantomData, ) } pub fn test(&self, mask: i16) -> bool { self.0.revents & mask != 0 } } pub use libc::{POLLERR, POLLHUP, POLLIN, POLLNVAL, POLLOUT, POLLPRI}; pub fn poll(fds: &mut [PollFd<'_>], mut timeout: Option) -> Result { let deadline = timeout.map(|timeout| Instant::now() + timeout); loop { // poll() accepts a maximum timeout of 2**31-1 ms, which is // less than 25 days. The caller can specify Durations much // larger than that, so support them by waiting in a loop. let (timeout_ms, overflow) = timeout .map(|timeout| { let timeout = timeout.as_millis(); if timeout <= i32::max_value() as u128 { (timeout as i32, false) } else { (i32::max_value(), true) } }) .unwrap_or((-1, false)); let fds_ptr = fds.as_ptr() as *mut libc::pollfd; let cnt = unsafe { check_err(libc::poll(fds_ptr, fds.len() as libc::nfds_t, timeout_ms))? }; if cnt != 0 || !overflow { return Ok(cnt as usize); } let deadline = deadline.unwrap(); let now = Instant::now(); if now >= deadline { return Ok(0); } timeout = Some(deadline - now); } } subprocess-0.2.9/src/tests/builder.rs000064400000000000000000000252260072674642500160100ustar 00000000000000use std::borrow::Cow; use std::env; use std::fs::File; use std::sync::Mutex; use std::io::prelude::*; use std::sync::MutexGuard; use crate::{Exec, ExitStatus, NullFile, Redirection}; use lazy_static::lazy_static; use tempfile::TempDir; use crate::tests::common::read_whole_file; #[test] fn exec_join() { let status = Exec::cmd("true").join().unwrap(); assert_eq!(status, ExitStatus::Exited(0)); } #[test] fn null_file() { let mut p = Exec::cmd("cat") .stdin(NullFile) .stdout(Redirection::Pipe) .popen() .unwrap(); let (out, _) = p.communicate(None).unwrap(); assert_eq!(out.unwrap(), ""); } #[test] fn stream_stdout() { let stream = Exec::cmd("printf").arg("foo").stream_stdout().unwrap(); assert_eq!(read_whole_file(stream), "foo"); } #[test] fn stream_stderr() { let stream = Exec::cmd("sh") .args(&["-c", "printf foo >&2"]) .stream_stderr() .unwrap(); assert_eq!(read_whole_file(stream), "foo"); } #[test] fn stream_stdin() { let tmpdir = TempDir::new().unwrap(); let tmpname = tmpdir.path().join("output"); { let mut stream = Exec::cmd("cat") .stdout(File::create(&tmpname).unwrap()) .stream_stdin() .unwrap(); stream.write_all(b"foo").unwrap(); } assert_eq!(read_whole_file(File::open(&tmpname).unwrap()), "foo"); } #[test] fn communicate_out() { let mut comm = Exec::cmd("printf").arg("foo").communicate().unwrap(); assert_eq!(comm.read().unwrap(), (Some(b"foo".to_vec()), None)); } #[test] fn communicate_in_out() { let mut comm = Exec::cmd("cat").stdin("foo").communicate().unwrap(); assert_eq!(comm.read().unwrap(), (Some(b"foo".to_vec()), None)); } #[test] fn capture_out() { let c = Exec::cmd("printf").arg("foo").capture().unwrap(); assert_eq!(c.stdout_str(), "foo"); } #[test] fn capture_err() { let c = Exec::cmd("sh") .arg("-c") .arg("printf foo >&2") .stderr(Redirection::Pipe) .capture() .unwrap(); assert_eq!(c.stderr_str(), "foo"); } #[test] fn capture_out_with_input_data1() { let c = Exec::cmd("cat").stdin("foo").capture().unwrap(); assert_eq!(c.stdout_str(), "foo"); } #[test] fn capture_out_with_input_data2() { let c = Exec::cmd("cat").stdin(b"foo".to_vec()).capture().unwrap(); assert_eq!(c.stdout_str(), "foo"); } #[test] fn exec_shell() { let stream = Exec::shell("printf foo").stream_stdout().unwrap(); assert_eq!(read_whole_file(stream), "foo"); } #[test] fn pipeline_open() { let mut processes = { Exec::cmd("echo").arg("foo\nbar") | Exec::cmd("wc").arg("-l") } .stdout(Redirection::Pipe) .popen() .unwrap(); let (output, _) = processes[1].communicate(None).unwrap(); assert_eq!(output.unwrap().trim(), "2"); } #[test] fn pipeline_stream_out() { let stream = { Exec::cmd("echo").arg("foo\nbar") | Exec::cmd("wc").arg("-l") } .stream_stdout() .unwrap(); assert_eq!(read_whole_file(stream).trim(), "2"); } #[test] fn pipeline_stream_in() { let tmpdir = TempDir::new().unwrap(); let tmpname = tmpdir.path().join("output"); { let mut stream = { Exec::cmd("cat") | Exec::cmd("wc").arg("-l") } .stdout(File::create(&tmpname).unwrap()) .stream_stdin() .unwrap(); stream.write_all(b"foo\nbar\nbaz\n").unwrap(); } assert_eq!(read_whole_file(File::open(&tmpname).unwrap()).trim(), "3"); } #[test] fn pipeline_compose_pipelines() { let pipe1 = Exec::cmd("echo").arg("foo\nbar\nfoo") | Exec::cmd("sort"); let pipe2 = Exec::cmd("uniq") | Exec::cmd("wc").arg("-l"); let pipe = pipe1 | pipe2; let stream = pipe.stream_stdout().unwrap(); assert_eq!(read_whole_file(stream).trim(), "2"); } trait Crlf { fn to_crlf(self) -> Vec; } impl Crlf for Vec { #[cfg(windows)] fn to_crlf(self) -> Vec { self.iter() .flat_map(|&c| { if c == b'\n' { vec![b'\r', b'\n'] } else { vec![c] } }) .collect() } #[cfg(unix)] fn to_crlf(self) -> Vec { self } } #[test] fn pipeline_communicate_out() { let pipe1 = Exec::cmd("echo").arg("foo\nbar\nfoo") | Exec::cmd("sort"); let mut comm = pipe1.communicate().unwrap(); assert_eq!( comm.read().unwrap(), (Some(b"bar\nfoo\nfoo\n".to_vec().to_crlf()), Some(vec![])) ); } #[test] fn pipeline_communicate_in_out() { let pipe1 = Exec::cmd("grep").arg("foo") | Exec::cmd("sort"); let mut comm = pipe1.stdin("foobar\nbaz\nfoo\n").communicate().unwrap(); let (out, _err) = comm.read().unwrap(); assert_eq!(out, Some(b"foo\nfoobar\n".to_vec().to_crlf())); } #[test] fn pipeline_capture() { let c = { Exec::cmd("cat") | Exec::shell("wc -l") } .stdin("foo\nbar\nbaz\n") .capture() .unwrap(); assert_eq!(c.stdout_str().trim(), "3"); assert_eq!(c.stderr_str().trim(), ""); } #[test] fn pipeline_capture_error_1() { let c = { Exec::cmd("sh") .arg("-c") .arg("echo foo >&2; printf 'bar\nbaz\n'") | Exec::shell("wc -l") } .capture() .unwrap(); assert_eq!(c.stdout_str().trim(), "2"); assert_eq!(c.stderr_str().trim(), "foo"); } #[test] fn pipeline_capture_error_2() { let c = { Exec::cmd("cat") | Exec::cmd("sh") .arg("-c") .arg("cat; echo foo >&2; printf 'four\nfive\n'") | Exec::cmd("sh").arg("-c").arg("echo bar >&2; cat") | Exec::shell("wc -l") } .stdin("one\ntwo\nthree\n") .capture() .unwrap(); assert_eq!(c.stdout_str().trim(), "5"); assert!( c.stderr_str().trim() == "foo\nbar" || c.stderr_str().trim() == "bar\nfoo", "got {:?}", c.stderr_str() ); } #[test] fn pipeline_join() { let status = (Exec::cmd("true") | Exec::cmd("true")).join().unwrap(); assert_eq!(status, ExitStatus::Exited(0)); let status = (Exec::cmd("false") | Exec::cmd("true")).join().unwrap(); assert_eq!(status, ExitStatus::Exited(0)); let status = (Exec::cmd("true") | Exec::cmd("false")).join().unwrap(); assert_eq!(status, ExitStatus::Exited(1)); } #[test] fn pipeline_invalid_1() { let p = (Exec::cmd("echo").arg("foo") | Exec::cmd("no-such-command")).join(); assert!(p.is_err()); } #[test] fn pipeline_invalid_2() { let p = (Exec::cmd("no-such-command") | Exec::cmd("echo").arg("foo")).join(); assert!(p.is_err()); } #[test] #[should_panic] fn reject_input_data_popen() { Exec::cmd("true").stdin("xxx").popen().unwrap(); } #[test] #[should_panic] fn reject_input_data_join() { Exec::cmd("true").stdin("xxx").join().unwrap(); } #[test] #[should_panic] fn reject_input_data_stream_stdout() { Exec::cmd("true").stdin("xxx").stream_stdout().unwrap(); } #[test] #[should_panic] fn reject_input_data_stream_stderr() { Exec::cmd("true").stdin("xxx").stream_stderr().unwrap(); } #[test] #[should_panic] fn reject_input_data_stream_stdin() { Exec::cmd("true").stdin("xxx").stream_stdin().unwrap(); } #[test] fn env_set() { assert!(Exec::cmd("sh") .args(&["-c", r#"test "$SOMEVAR" = "foo""#]) .env("SOMEVAR", "foo") .join() .unwrap() .success()); } #[test] fn env_extend() { assert!(Exec::cmd("sh") .args(&["-c", r#"test "$VAR1" = "foo" && test "$VAR2" = "bar""#]) .env_extend(&[("VAR1", "foo"), ("VAR2", "bar")]) .join() .unwrap() .success()); } lazy_static! { static ref MUTATE_ENV: Mutex<()> = Mutex::new(()); } struct TmpEnvVar<'a> { varname: &'static str, #[allow(dead_code)] mutate_guard: MutexGuard<'a, ()>, } impl<'a> TmpEnvVar<'a> { fn new(varname: &'static str) -> TmpEnvVar<'a> { TmpEnvVar { varname, mutate_guard: MUTATE_ENV.lock().unwrap(), } } } impl Drop for TmpEnvVar<'_> { fn drop(&mut self) { env::remove_var(self.varname); } } fn tmp_env_var<'a>(varname: &'static str, tmp_value: &'static str) -> TmpEnvVar<'a> { env::set_var(varname, tmp_value); TmpEnvVar::new(varname) } #[test] fn env_inherit() { // use a unique name to avoid interference with other tests let varname = "TEST_ENV_INHERIT_VARNAME"; let _guard = tmp_env_var(varname, "inherited"); assert!(Exec::cmd("sh") .args(&["-c", &format!(r#"test "${}" = "inherited""#, varname)]) .join() .unwrap() .success()); } #[test] fn env_inherit_set() { // use a unique name to avoid interference with other tests let varname = "TEST_ENV_INHERIT_SET_VARNAME"; let _guard = tmp_env_var(varname, "inherited"); assert!(Exec::cmd("sh") .args(&["-c", &format!(r#"test "${}" = "new""#, varname)]) .env(varname, "new") .join() .unwrap() .success()); } // XXX move tests under the builder module so we can call // Exec::display_escape() instead of copying it. fn display_escape(s: &str) -> Cow<'_, str> { fn nice_char(c: char) -> bool { match c { '-' | '_' | '.' | ',' | '/' => true, c if c.is_ascii_alphanumeric() => true, _ => false, } } if !s.chars().all(nice_char) { Cow::Owned(format!("'{}'", s.replace("'", r#"'\''"#))) } else { Cow::Borrowed(s) } } #[test] fn exec_to_string() { let _guard = MUTATE_ENV.lock().unwrap(); let cmd = Exec::cmd("sh") .arg("arg1") .arg("don't") .arg("arg3 arg4") .arg("?") .arg(" ") // regular space .arg("\u{009c}"); // STRING TERMINATOR assert_eq!( format!("{:?}", cmd), "Exec { sh arg1 'don'\\''t' 'arg3 arg4' '?' ' ' '\u{009c}' }" ); let cmd = cmd.env("foo", "bar"); assert_eq!( format!("{:?}", cmd), "Exec { foo=bar sh arg1 'don'\\''t' 'arg3 arg4' '?' ' ' '\u{009c}' }" ); let cmd = cmd.env("bar", "baz"); assert_eq!( format!("{:?}", cmd), "Exec { foo=bar bar=baz sh arg1 'don'\\''t' 'arg3 arg4' '?' ' ' '\u{009c}' }" ); let cmd = cmd.env_clear(); assert_eq!( format!("{:?}", cmd), format!( "Exec {{ {} sh arg1 'don'\\''t' 'arg3 arg4' '?' ' ' '\u{009c}' }}", env::vars() .map(|(k, _)| format!("{}=", display_escape(&k))) .collect::>() .join(" ") ) ); } #[test] fn pipeline_to_string() { let pipeline = { Exec::cmd("command with space").arg("arg") | Exec::cmd("wc").arg("-l") }; assert_eq!( format!("{:?}", pipeline), "Pipeline { 'command with space' arg | wc -l }" ) } subprocess-0.2.9/src/tests/common.rs000064400000000000000000000343400072674642500156470ustar 00000000000000use tempfile::TempDir; use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::Write; use std::io::{self, Read}; use std::time::Duration; use crate::{ExitStatus, Popen, PopenConfig, PopenError, Redirection}; pub fn read_whole_file(mut f: T) -> String { let mut content = String::new(); f.read_to_string(&mut content).unwrap(); content } #[test] fn good_cmd() { let mut p = Popen::create(&["true"], PopenConfig::default()).unwrap(); assert!(p.wait().unwrap().success()); } #[test] fn bad_cmd() { let result = Popen::create(&["nosuchcommand"], PopenConfig::default()); assert!(result.is_err()); } #[test] fn reject_empty_argv() { let test = Popen::create(&[""; 0], PopenConfig::default()); if let Err(PopenError::LogicError(..)) = test { } else { assert!(false, "didn't get LogicError for empty argv"); } } #[test] fn err_exit() { let mut p = Popen::create(&["sh", "-c", "exit 13"], PopenConfig::default()).unwrap(); assert_eq!(p.wait().unwrap(), ExitStatus::Exited(13)); } #[test] fn terminate() { let mut p = Popen::create(&["sleep", "1000"], PopenConfig::default()).unwrap(); p.terminate().unwrap(); p.wait().unwrap(); } #[test] fn terminate_twice() { use std::thread; use std::time::Duration; let mut p = Popen::create(&["sleep", "1000"], PopenConfig::default()).unwrap(); p.terminate().unwrap(); thread::sleep(Duration::from_millis(100)); p.terminate().unwrap(); } #[test] fn read_from_stdout() { let mut p = Popen::create( &["echo", "foo"], PopenConfig { stdout: Redirection::Pipe, ..Default::default() }, ) .unwrap(); assert_eq!(read_whole_file(p.stdout.take().unwrap()), "foo\n"); assert!(p.wait().unwrap().success()); } #[test] fn input_from_file() { let tmpdir = TempDir::new().unwrap(); let tmpname = tmpdir.path().join("input"); { let mut outfile = File::create(&tmpname).unwrap(); outfile.write_all(b"foo").unwrap(); } let mut p = Popen::create( &["cat", tmpname.to_str().unwrap()], PopenConfig { stdin: Redirection::File(File::open(&tmpname).unwrap()), stdout: Redirection::Pipe, ..Default::default() }, ) .unwrap(); assert_eq!(read_whole_file(p.stdout.take().unwrap()), "foo"); assert!(p.wait().unwrap().success()); } #[test] fn output_to_file() { let tmpdir = TempDir::new().unwrap(); let tmpname = tmpdir.path().join("output"); let outfile = File::create(&tmpname).unwrap(); let mut p = Popen::create( &["printf", "foo"], PopenConfig { stdout: Redirection::File(outfile), ..Default::default() }, ) .unwrap(); assert!(p.wait().unwrap().success()); assert_eq!(read_whole_file(File::open(&tmpname).unwrap()), "foo"); } #[test] fn input_output_from_file() { let tmpdir = TempDir::new().unwrap(); let tmpname_in = tmpdir.path().join("input"); let tmpname_out = tmpdir.path().join("output"); { let mut f = File::create(&tmpname_in).unwrap(); f.write_all(b"foo").unwrap(); } let mut p = Popen::create( &["cat"], PopenConfig { stdin: Redirection::File(File::open(&tmpname_in).unwrap()), stdout: Redirection::File(File::create(&tmpname_out).unwrap()), ..Default::default() }, ) .unwrap(); assert!(p.wait().unwrap().success()); assert_eq!(read_whole_file(File::open(&tmpname_out).unwrap()), "foo"); } #[test] fn write_to_subprocess() { let tmpdir = TempDir::new().unwrap(); let tmpname = tmpdir.path().join("output"); let mut p = Popen::create( &[r"uniq", "-", tmpname.to_str().unwrap()], PopenConfig { stdin: Redirection::Pipe, ..Default::default() }, ) .unwrap(); p.stdin .take() .unwrap() .write_all(b"foo\nfoo\nbar\n") .unwrap(); assert_eq!(p.wait().unwrap(), ExitStatus::Exited(0)); assert_eq!(read_whole_file(File::open(tmpname).unwrap()), "foo\nbar\n"); } #[test] fn communicate_input() { let tmpdir = TempDir::new().unwrap(); let tmpname = tmpdir.path().join("input"); let mut p = Popen::create( &["cat"], PopenConfig { stdin: Redirection::Pipe, stdout: Redirection::File(File::create(&tmpname).unwrap()), ..Default::default() }, ) .unwrap(); if let (None, None) = p.communicate_bytes(Some(b"hello world")).unwrap() { } else { assert!(false); } assert!(p.wait().unwrap().success()); assert_eq!( read_whole_file(File::open(&tmpname).unwrap()), "hello world" ); } #[test] fn communicate_output() { let mut p = Popen::create( &["sh", "-c", "echo foo; echo bar >&2"], PopenConfig { stdout: Redirection::Pipe, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); if let (Some(out), Some(err)) = p.communicate_bytes(None).unwrap() { assert_eq!(out, b"foo\n"); assert_eq!(err, b"bar\n"); } else { assert!(false); } assert!(p.wait().unwrap().success()); } #[test] fn communicate_input_output() { let mut p = Popen::create( &["sh", "-c", "cat; echo foo >&2"], PopenConfig { stdin: Redirection::Pipe, stdout: Redirection::Pipe, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); if let (Some(out), Some(err)) = p.communicate_bytes(Some(b"hello world")).unwrap() { assert_eq!(out, b"hello world"); assert_eq!(err, b"foo\n"); } else { assert!(false); } assert!(p.wait().unwrap().success()); } #[test] fn communicate_input_output_long() { let mut p = Popen::create( &["sh", "-c", "cat; printf '%100000s' '' >&2"], PopenConfig { stdin: Redirection::Pipe, stdout: Redirection::Pipe, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let input = [65u8; 1_000_000]; if let (Some(out), Some(err)) = p.communicate_bytes(Some(&input)).unwrap() { assert_eq!(&out[..], &input[..]); assert_eq!(&err[..], &[32u8; 100_000][..]); } else { assert!(false); } assert!(p.wait().unwrap().success()); } #[test] fn communicate_timeout() { let mut p = Popen::create( &["sh", "-c", "printf foo; sleep 1"], PopenConfig { stdout: Redirection::Pipe, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); match p .communicate_start(None) .limit_time(Duration::from_millis(100)) .read() { Err(e) => { assert_eq!(e.kind(), io::ErrorKind::TimedOut); assert_eq!(e.capture, (Some(b"foo".to_vec()), Some(vec![]))); } other => panic!("unexpected result {:?}", other), } p.kill().unwrap(); } #[test] fn communicate_size_limit_small() { let mut p = Popen::create( &["sh", "-c", "printf '%5s' a"], PopenConfig { stdout: Redirection::Pipe, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let mut comm = p.communicate_start(None).limit_size(2); assert_eq!(comm.read().unwrap(), (Some(vec![32; 2]), Some(vec![]))); assert_eq!(comm.read().unwrap(), (Some(vec![32; 2]), Some(vec![]))); assert_eq!(comm.read().unwrap(), (Some(vec!['a' as u8]), Some(vec![]))); p.kill().unwrap(); } fn check_vec(v: Option>, size: usize, content: u8) { assert_eq!(v.as_ref().unwrap().len(), size); assert!(v.as_ref().unwrap().iter().all(|&c| c == content)); } #[test] fn communicate_size_limit_large() { let mut p = Popen::create( &["sh", "-c", "printf '%20001s' a"], PopenConfig { stdout: Redirection::Pipe, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let mut comm = p.communicate_start(None).limit_size(10_000); let (out, err) = comm.read().unwrap(); check_vec(out, 10_000, 32); assert_eq!(err, Some(vec![])); let (out, err) = comm.read().unwrap(); check_vec(out, 10_000, 32); assert_eq!(err, Some(vec![])); assert_eq!(comm.read().unwrap(), (Some(vec!['a' as u8]), Some(vec![]))); p.kill().unwrap(); } #[test] fn communicate_size_limit_different_sizes() { let mut p = Popen::create( &["sh", "-c", "printf '%20001s' a"], PopenConfig { stdout: Redirection::Pipe, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let comm = p.communicate_start(None); let mut comm = comm.limit_size(100); let (out, err) = comm.read().unwrap(); check_vec(out, 100, 32); assert_eq!(err, Some(vec![])); let mut comm = comm.limit_size(1_000); let (out, err) = comm.read().unwrap(); check_vec(out, 1_000, 32); assert_eq!(err, Some(vec![])); let mut comm = comm.limit_size(10_000); let (out, err) = comm.read().unwrap(); check_vec(out, 10_000, 32); assert_eq!(err, Some(vec![])); let mut comm = comm.limit_size(8_900); let (out, err) = comm.read().unwrap(); check_vec(out, 8_900, 32); assert_eq!(err, Some(vec![])); assert_eq!(comm.read().unwrap(), (Some(vec!['a' as u8]), Some(vec![]))); assert_eq!(comm.read().unwrap(), (Some(vec![]), Some(vec![]))); p.kill().unwrap(); } #[test] fn null_byte_in_cmd() { let try_p = Popen::create(&["echo\0foo"], PopenConfig::default()); assert!(try_p.is_err()); } #[test] fn merge_err_to_out_pipe() { let mut p = Popen::create( &["sh", "-c", "echo foo; echo bar >&2"], PopenConfig { stdout: Redirection::Pipe, stderr: Redirection::Merge, ..Default::default() }, ) .unwrap(); if let (Some(out), None) = p.communicate_bytes(None).unwrap() { assert_eq!(out, b"foo\nbar\n"); } else { assert!(false); } assert!(p.wait().unwrap().success()); } #[test] fn merge_out_to_err_pipe() { let mut p = Popen::create( &["sh", "-c", "echo foo; echo bar >&2"], PopenConfig { stdout: Redirection::Merge, stderr: Redirection::Pipe, ..Default::default() }, ) .unwrap(); if let (None, Some(err)) = p.communicate_bytes(None).unwrap() { assert_eq!(err, b"foo\nbar\n"); } else { assert!(false); } assert!(p.wait().unwrap().success()); } #[test] fn merge_err_to_out_file() { let tmpdir = TempDir::new().unwrap(); let tmpname = tmpdir.path().join("output"); let mut p = Popen::create( &["sh", "-c", "printf foo; printf bar >&2"], PopenConfig { stdout: Redirection::File(File::create(&tmpname).unwrap()), stderr: Redirection::Merge, ..Default::default() }, ) .unwrap(); assert!(p.wait().unwrap().success()); assert_eq!(read_whole_file(File::open(&tmpname).unwrap()), "foobar"); } #[test] fn simple_pipe() { let mut c1 = Popen::create( &["printf", "foo\\nbar\\nbaz\\n"], PopenConfig { stdout: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let mut c2 = Popen::create( &["wc", "-l"], PopenConfig { stdin: Redirection::File(c1.stdout.take().unwrap()), stdout: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let (wcout, _) = c2.communicate(None).unwrap(); assert_eq!(wcout.unwrap().trim(), "3"); } #[test] fn wait_timeout() { let mut p = Popen::create(&["sleep", "0.5"], PopenConfig::default()).unwrap(); let ret = p.wait_timeout(Duration::from_millis(100)).unwrap(); assert!(ret.is_none()); let ret = p.wait_timeout(Duration::from_millis(450)).unwrap(); assert_eq!(ret, Some(ExitStatus::Exited(0))); } #[test] fn setup_executable() { let mut p = Popen::create( &["foobar", "-c", r#"printf %s "$0""#], PopenConfig { executable: Some(OsStr::new("sh").to_owned()), stdout: Redirection::Pipe, ..Default::default() }, ) .unwrap(); assert_eq!(read_whole_file(p.stdout.take().unwrap()), "foobar"); } #[test] fn env_add() { let mut env = PopenConfig::current_env(); env.push((OsString::from("SOMEVAR"), OsString::from("foo"))); let mut p = Popen::create( &["sh", "-c", r#"test "$SOMEVAR" = "foo""#], PopenConfig { env: Some(env), ..Default::default() }, ) .unwrap(); assert!(p.wait().unwrap().success()); } #[test] fn env_dup() { let dups = vec![ (OsString::from("SOMEVAR"), OsString::from("foo")), (OsString::from("SOMEVAR"), OsString::from("bar")), ]; let mut p = Popen::create( &["sh", "-c", r#"test "$SOMEVAR" = "bar""#], PopenConfig { stdout: Redirection::Pipe, env: Some(dups), ..Default::default() }, ) .unwrap(); assert!(p.wait().unwrap().success()); } #[test] fn cwd() { let tmpdir = TempDir::new().unwrap(); let tmpdir_name = tmpdir.path().as_os_str().to_owned(); // Test that CWD works by cwd-ing into an empty temporary // directory and creating a file there. Trying to print the // directory's name and compare it to tmpdir fails due to MinGW // interference on Windows and symlinks on Unix. Popen::create( &["touch", "here"], PopenConfig { stdout: Redirection::Pipe, cwd: Some(tmpdir_name), ..Default::default() }, ) .unwrap(); assert!(tmpdir.path().join("here").exists()); } #[test] fn failed_cwd() { use crate::popen::PopenError::IoError; let ret = Popen::create( &["anything"], PopenConfig { stdout: Redirection::Pipe, cwd: Some("/nosuchdir".into()), ..Default::default() }, ); let err_num = match ret { Err(IoError(e)) => e.raw_os_error().unwrap_or(-1), _ => panic!("expected error return"), }; assert_eq!(err_num, libc::ENOENT); } subprocess-0.2.9/src/tests/posix.rs000064400000000000000000000032240072674642500155160ustar 00000000000000use std::ffi::OsString; use crate::unix::PopenExt; use crate::{ExitStatus, Popen, PopenConfig, Redirection}; use libc; #[test] fn err_terminate() { let mut p = Popen::create(&["sleep", "5"], PopenConfig::default()).unwrap(); assert!(p.poll().is_none()); p.terminate().unwrap(); assert_eq!(p.wait().unwrap(), ExitStatus::Signaled(libc::SIGTERM as u8)); } #[test] fn waitpid_echild() { let mut p = Popen::create(&["true"], PopenConfig::default()).unwrap(); let pid = p.pid().unwrap() as i32; let mut status = 0 as libc::c_int; let wpid = unsafe { libc::waitpid(pid, &mut status, 0) }; assert_eq!(wpid, pid); assert_eq!(status, 0); assert_eq!(p.wait().unwrap(), ExitStatus::Undetermined); } #[test] fn send_signal() { let mut p = Popen::create(&["sleep", "5"], PopenConfig::default()).unwrap(); p.send_signal(libc::SIGUSR1).unwrap(); assert_eq!(p.wait().unwrap(), ExitStatus::Signaled(libc::SIGUSR1 as u8)); } #[test] fn env_set_all_1() { let mut p = Popen::create( &["env"], PopenConfig { stdout: Redirection::Pipe, env: Some(Vec::new()), ..Default::default() }, ) .unwrap(); let (out, _err) = p.communicate(None).unwrap(); assert_eq!(out.unwrap(), ""); } #[test] fn env_set_all_2() { let mut p = Popen::create( &["env"], PopenConfig { stdout: Redirection::Pipe, env: Some(vec![(OsString::from("FOO"), OsString::from("bar"))]), ..Default::default() }, ) .unwrap(); let (out, _err) = p.communicate(None).unwrap(); assert_eq!(out.unwrap().trim_end(), "FOO=bar"); } subprocess-0.2.9/src/tests/win32.rs000064400000000000000000000004240072674642500153150ustar 00000000000000use crate::{ExitStatus, Popen, PopenConfig}; #[test] fn err_terminate() { let mut p = Popen::create(&["sleep", "5"], PopenConfig::default()).unwrap(); assert!(p.poll().is_none()); p.terminate().unwrap(); assert!(p.wait().unwrap() == ExitStatus::Exited(1)); } subprocess-0.2.9/src/win32.rs000064400000000000000000000166400072674642500141620ustar 00000000000000#![allow(non_snake_case, non_camel_case_types)] use std::fs::File; use std::io::{Error, Result}; use std::ffi::OsStr; use std::iter; use std::mem; use std::os::windows::ffi::OsStrExt; use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; use std::ptr; use std::rc::Rc; use std::time::{Duration, Instant}; use winapi; use winapi::shared::minwindef::{BOOL, DWORD, LPVOID}; use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; use winapi::um::minwinbase::{LPSECURITY_ATTRIBUTES, SECURITY_ATTRIBUTES}; use winapi::um::processthreadsapi::{CreateProcessW, PROCESS_INFORMATION, STARTUPINFOW}; use winapi::um::winbase::CREATE_UNICODE_ENVIRONMENT; use winapi::um::winnt::PHANDLE; use winapi::um::{handleapi, namedpipeapi, processenv, processthreadsapi, synchapi}; pub use winapi::shared::winerror::{ERROR_ACCESS_DENIED, ERROR_BAD_PATHNAME}; pub const STILL_ACTIVE: u32 = 259; use crate::os_common::StandardStream; #[derive(Debug)] pub struct Handle(RawHandle); unsafe impl Send for Handle {} unsafe impl Sync for Handle {} impl Drop for Handle { fn drop(&mut self) { unsafe { CloseHandle(self.as_raw_handle()); } } } impl AsRawHandle for Handle { fn as_raw_handle(&self) -> RawHandle { self.0 } } impl FromRawHandle for Handle { unsafe fn from_raw_handle(handle: RawHandle) -> Handle { Handle(handle) } } pub const HANDLE_FLAG_INHERIT: u32 = 1; pub const STARTF_USESTDHANDLES: DWORD = winapi::um::winbase::STARTF_USESTDHANDLES; fn check(status: BOOL) -> Result<()> { if status != 0 { Ok(()) } else { Err(Error::last_os_error()) } } fn check_handle(raw_handle: RawHandle) -> Result { if raw_handle != INVALID_HANDLE_VALUE { Ok(raw_handle) } else { Err(Error::last_os_error()) } } // OsStr to zero-terminated owned vector fn to_nullterm(s: &OsStr) -> Vec { s.encode_wide().chain(iter::once(0u16)).collect() } pub fn CreatePipe(inherit_handle: bool) -> Result<(File, File)> { let mut attributes = SECURITY_ATTRIBUTES { nLength: mem::size_of::() as DWORD, lpSecurityDescriptor: ptr::null_mut(), bInheritHandle: inherit_handle as BOOL, }; let (mut r, mut w) = (ptr::null_mut(), ptr::null_mut()); check(unsafe { namedpipeapi::CreatePipe( &mut r as PHANDLE, &mut w as PHANDLE, &mut attributes as LPSECURITY_ATTRIBUTES, 0, ) })?; Ok(unsafe { (File::from_raw_handle(r), File::from_raw_handle(w)) }) } pub fn SetHandleInformation(handle: &File, dwMask: u32, dwFlags: u32) -> Result<()> { check(unsafe { handleapi::SetHandleInformation(handle.as_raw_handle(), dwMask, dwFlags) })?; Ok(()) } pub fn CreateProcess( appname: Option<&OsStr>, cmdline: &OsStr, env_block: &Option>, cwd: &Option<&OsStr>, inherit_handles: bool, mut creation_flags: u32, stdin: Option, stdout: Option, stderr: Option, sinfo_flags: u32, ) -> Result<(Handle, u64)> { let mut sinfo: STARTUPINFOW = unsafe { mem::zeroed() }; sinfo.cb = mem::size_of::() as DWORD; sinfo.hStdInput = stdin.unwrap_or(ptr::null_mut()); sinfo.hStdOutput = stdout.unwrap_or(ptr::null_mut()); sinfo.hStdError = stderr.unwrap_or(ptr::null_mut()); sinfo.dwFlags = sinfo_flags; let mut pinfo: PROCESS_INFORMATION = unsafe { mem::zeroed() }; let mut cmdline = to_nullterm(cmdline); let wc_appname = appname.map(to_nullterm); let env_block_ptr = env_block .as_ref() .map(|v| v.as_ptr()) .unwrap_or(ptr::null()) as LPVOID; let cwd = cwd.map(to_nullterm); creation_flags |= CREATE_UNICODE_ENVIRONMENT; check(unsafe { CreateProcessW( wc_appname .as_ref() .map(|v| v.as_ptr()) .unwrap_or(ptr::null()), cmdline.as_mut_ptr(), ptr::null_mut(), // lpProcessAttributes ptr::null_mut(), // lpThreadAttributes inherit_handles as BOOL, // bInheritHandles creation_flags, // dwCreationFlags env_block_ptr, // lpEnvironment cwd.as_ref().map(|v| v.as_ptr()).unwrap_or(ptr::null()), // lpCurrentDirectory &mut sinfo, &mut pinfo, ) })?; unsafe { drop(Handle::from_raw_handle(pinfo.hThread)); Ok(( Handle::from_raw_handle(pinfo.hProcess), pinfo.dwProcessId as u64, )) } } pub enum WaitEvent { OBJECT_0, ABANDONED, TIMEOUT, } pub fn WaitForSingleObject(handle: &Handle, mut timeout: Option) -> Result { use winapi::shared::winerror::WAIT_TIMEOUT; use winapi::um::winbase::{INFINITE, WAIT_ABANDONED, WAIT_FAILED, WAIT_OBJECT_0}; let deadline = timeout.map(|timeout| Instant::now() + timeout); let result = loop { // Allow timeouts greater than 50 days by clamping the // timeout and sleeping in a loop. let (timeout_ms, overflow) = timeout .map(|timeout| { let timeout = timeout.as_millis(); if timeout < INFINITE as u128 { (timeout as u32, false) } else { (INFINITE - 1, true) } }) .unwrap_or((INFINITE, false)); let result = unsafe { synchapi::WaitForSingleObject(handle.as_raw_handle(), timeout_ms) }; if result != WAIT_TIMEOUT || !overflow { break result; } let deadline = deadline.unwrap(); let now = Instant::now(); if now >= deadline { break WAIT_TIMEOUT; } timeout = Some(deadline - now); }; if result == WAIT_OBJECT_0 { Ok(WaitEvent::OBJECT_0) } else if result == WAIT_ABANDONED { Ok(WaitEvent::ABANDONED) } else if result == WAIT_TIMEOUT { Ok(WaitEvent::TIMEOUT) } else if result == WAIT_FAILED { Err(Error::last_os_error()) } else { panic!(format!("WaitForSingleObject returned {}", result)); } } pub fn GetExitCodeProcess(handle: &Handle) -> Result { let mut exit_code = 0u32; check(unsafe { processthreadsapi::GetExitCodeProcess(handle.as_raw_handle(), &mut exit_code as *mut u32) })?; Ok(exit_code) } pub fn TerminateProcess(handle: &Handle, exit_code: u32) -> Result<()> { check(unsafe { processthreadsapi::TerminateProcess(handle.as_raw_handle(), exit_code) }) } unsafe fn GetStdHandle(which: StandardStream) -> Result { // private/unsafe because the raw handle it returns must be // duplicated or leaked before converting to an owned Handle. use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; let id = match which { StandardStream::Input => STD_INPUT_HANDLE, StandardStream::Output => STD_OUTPUT_HANDLE, StandardStream::Error => STD_ERROR_HANDLE, }; let raw_handle = check_handle(processenv::GetStdHandle(id))?; Ok(raw_handle) } pub fn make_standard_stream(which: StandardStream) -> Result> { unsafe { let raw = GetStdHandle(which)?; let stream = Rc::new(File::from_raw_handle(raw)); // Leak the Rc so the object we return doesn't close the underlying // system handle. mem::forget(Rc::clone(&stream)); Ok(stream) } } subprocess-0.2.9/tests/escape-args.rs000064400000000000000000000021130072674642500157530ustar 00000000000000extern crate subprocess; use std::io::Read; use std::path::PathBuf; use subprocess::{Popen, PopenConfig, Redirection}; fn just_echo_path() -> String { let prog = PathBuf::from(&::std::env::args().next().unwrap()); prog.parent() .unwrap() .join("../just-echo") .to_str() .unwrap() .to_owned() } #[test] fn escape_args() { // This is mostly relevant for Windows: test whether // assemble_cmdline does a good job with arguments that require // escaping. for &arg in &[ "x", "", " ", " ", r" \ ", r" \\ ", r" \\\ ", r#"""#, r#""""#, r#"\"\\""#, "æ÷", "šđ", "本", "❤", "☃", ] { let mut p = Popen::create( &[just_echo_path().as_ref(), arg], PopenConfig { stdout: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let mut output = p.stdout.take().unwrap(); let mut output_str = String::new(); output.read_to_string(&mut output_str).unwrap(); assert_eq!(output_str, arg); } } subprocess-0.2.9/tests/just-echo.rs000064400000000000000000000001140072674642500154610ustar 00000000000000fn main() { print!("{}", ::std::env::args().skip(1).next().unwrap()); } subprocess-0.2.9/tests/weird-args.rs000064400000000000000000000022230072674642500156270ustar 00000000000000extern crate subprocess; use std::io::Read; use std::path::Path; use subprocess::{Popen, PopenConfig, Redirection}; fn just_echo_path() -> String { let prog = Path::new(&::std::env::args().next().unwrap()).to_owned(); let prog = prog.parent().unwrap(); // dirname let prog = prog.parent().unwrap(); // parent dir prog.join("just-echo").to_str().unwrap().to_owned() } #[test] fn weird_args() { // This is mostly relevant for Windows: test whether // assemble_cmdline does a good job with arguments with // metacharacters. for &arg in [ "x", "", " ", " ", r" \ ", r" \\ ", r" \\\ ", r#"""#, r#""""#, r#"\"\\""#, ] .iter() { println!("running {:?} {:?}", arg, just_echo_path()); let mut p = Popen::create( &[just_echo_path(), arg.to_owned()], PopenConfig { stdout: Redirection::Pipe, ..Default::default() }, ) .unwrap(); let mut output = p.stdout.take().unwrap(); let mut output_str = String::new(); output.read_to_string(&mut output_str).unwrap(); assert_eq!(output_str, arg); } }