pax_global_header00006660000000000000000000000064144140534440014515gustar00rootroot0000000000000052 comment=5e48a40d6cebc3c798364648f963f03c0f4f8195 async-process-1.7.0/000077500000000000000000000000001441405344400143135ustar00rootroot00000000000000async-process-1.7.0/.github/000077500000000000000000000000001441405344400156535ustar00rootroot00000000000000async-process-1.7.0/.github/dependabot.yml000066400000000000000000000002331441405344400205010ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: cargo directory: / schedule: interval: weekly commit-message: prefix: '' labels: [] async-process-1.7.0/.github/workflows/000077500000000000000000000000001441405344400177105ustar00rootroot00000000000000async-process-1.7.0/.github/workflows/ci.yml000066400000000000000000000043301441405344400210260ustar00rootroot00000000000000name: CI permissions: contents: read on: pull_request: push: branches: - master schedule: - cron: '0 2 * * 0' env: CARGO_INCREMENTAL: 0 CARGO_NET_GIT_FETCH_WITH_CLI: true CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings RUSTUP_MAX_RETRIES: 10 defaults: run: shell: bash jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] rust: [nightly, beta, stable] steps: - uses: actions/checkout@v3 - name: Install Rust # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} - run: cargo build --all --all-features --all-targets - name: Run cargo check (without dev-dependencies to catch missing feature flags) if: startsWith(matrix.rust, 'nightly') run: cargo check -Z features=dev_dep - run: cargo test msrv: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] # When updating this, the reminder to update the minimum supported # Rust version in Cargo.toml. rust: ['1.48'] steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - run: cargo build clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo clippy --all-features --all-targets fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo fmt --all --check security_audit: permissions: checks: write contents: read issues: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 # https://github.com/rustsec/audit-check/issues/2 - uses: rustsec/audit-check@master with: token: ${{ secrets.GITHUB_TOKEN }} async-process-1.7.0/.github/workflows/release.yml000066400000000000000000000006411441405344400220540ustar00rootroot00000000000000name: Release permissions: contents: write on: push: tags: - v[0-9]+.* jobs: create-release: if: github.repository_owner == 'smol-rs' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: taiki-e/create-gh-release-action@v1 with: changelog: CHANGELOG.md branch: master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} async-process-1.7.0/.gitignore000066400000000000000000000000231441405344400162760ustar00rootroot00000000000000/target Cargo.lock async-process-1.7.0/CHANGELOG.md000066400000000000000000000025201441405344400161230ustar00rootroot00000000000000# Version 1.7.0 - Replace direct dependency on libc with rustix. (#31) - Reduce the number of syscalls used in the `into_stdio` method. (#31) - Add windows::CommandExt::raw_arg on Rust 1.62+. (#32) - Update windows-sys to 0.48. (#39) # Version 1.6.0 - Switch from `winapi` to `windows-sys` (#27) - Remove the dependency on the `once_cell` crate to restore the MSRV (#26) - Fix build failure with minimal-versions (#28) # Version 1.5.0 - Implement `AsRawFd` for `ChildStd*` on Unix (#23) - Implement I/O safety traits on Rust 1.63+ on Unix (#23) # Version 1.4.0 - `Command::spawn` and `Command::output` no longer unconfigure stdio streams (#20) - Implement `From` for `Command` (#21) # Version 1.3.0 - Improve debug implementation of `Command` (#18) # Version 1.2.0 - Implement `AsRawHandle` on `Child` on `Windows` (#17) # Version 1.1.0 - Add `into_stdio` method to `ChildStdin`, `ChildStdout`, and `ChildStderr`. (#13) # Version 1.0.2 - Use `kill_on_drop` only when the last reference to `ChildGuard` is dropped. # Version 1.0.1 - Update `futures-lite`. # Version 1.0.0 - Update dependencies and stabilize. # Version 0.1.3 - Update dependencies. # Version 0.1.2 - Add Unix and Windows extensions. - Add `Command::reap_on_drop()` option. - Add `Command::kill_on_drop()` option. # Version 0.1.1 - Initial version async-process-1.7.0/Cargo.toml000066400000000000000000000021531441405344400162440ustar00rootroot00000000000000[package] name = "async-process" # When publishing a new version: # - Update CHANGELOG.md # - Create "v1.x.y" git tag version = "1.7.0" authors = ["Stjepan Glavina "] edition = "2018" rust-version = "1.48" description = "Async interface for working with processes" license = "Apache-2.0 OR MIT" repository = "https://github.com/smol-rs/async-process" keywords = ["process", "spawn", "command", "subprocess", "child"] categories = ["asynchronous", "os"] exclude = ["/.*"] [dependencies] async-lock = "2.6.0" cfg-if = "1.0" event-listener = "2.4.0" futures-lite = "1.11.0" [build-dependencies] autocfg = "1" [target.'cfg(unix)'.dependencies] async-io = "1.8" rustix = { version = "0.37", default-features = false, features = ["std", "fs"] } [target.'cfg(unix)'.dependencies.signal-hook] version = "0.3.0" features = ["iterator"] default-features = false [target.'cfg(windows)'.dependencies] blocking = "1.0.0" [target.'cfg(windows)'.dependencies.windows-sys] version = "0.48" default-features = false features = [ "Win32_Foundation", "Win32_System_Threading", ] [dev-dependencies] async-io = "1.8" async-process-1.7.0/LICENSE-APACHE000066400000000000000000000251371441405344400162470ustar00rootroot00000000000000 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. async-process-1.7.0/LICENSE-MIT000066400000000000000000000017771441405344400157630ustar00rootroot00000000000000Permission 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. async-process-1.7.0/README.md000066400000000000000000000042111441405344400155700ustar00rootroot00000000000000# async-process [![Build](https://github.com/smol-rs/async-process/workflows/Build%20and%20test/badge.svg)]( https://github.com/smol-rs/async-process/actions) [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)]( https://github.com/smol-rs/async-process) [![Cargo](https://img.shields.io/crates/v/async-process.svg)]( https://crates.io/crates/async-process) [![Documentation](https://docs.rs/async-process/badge.svg)]( https://docs.rs/async-process) Async interface for working with processes. This crate is an async version of `std::process`. ## Implementation A background thread named "async-process" is lazily created on first use, which waits for spawned child processes to exit and then calls the `wait()` syscall to clean up the "zombie" processes. This is unlike the `process` API in the standard library, where dropping a running `Child` leaks its resources. This crate uses [`async-io`] for async I/O on Unix-like systems and [`blocking`] for async I/O on Windows. [`async-io`]: https://docs.rs/async-io [`blocking`]: https://docs.rs/blocking ## Examples Spawn a process and collect its output: ```rust use async_process::Command; let out = Command::new("echo").arg("hello").arg("world").output().await?; assert_eq!(out.stdout, b"hello world\n"); ``` Read the output line-by-line as it gets produced: ```rust use async_process::{Command, Stdio}; use futures_lite::{io::BufReader, prelude::*}; let mut child = Command::new("find") .arg(".") .stdout(Stdio::piped()) .spawn()?; let mut lines = BufReader::new(child.stdout.take().unwrap()).lines(); while let Some(line) = lines.next().await { println!("{}", line?); } ``` ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. #### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. async-process-1.7.0/build.rs000066400000000000000000000007521441405344400157640ustar00rootroot00000000000000fn main() { let cfg = match autocfg::AutoCfg::new() { Ok(cfg) => cfg, Err(e) => { println!( "cargo:warning=async-process: failed to detect compiler features: {}", e ); return; } }; if !cfg.probe_rustc_version(1, 62) { autocfg::emit("async_process_no_windows_raw_arg"); } if !cfg.probe_rustc_version(1, 63) { autocfg::emit("async_process_no_io_safety"); } } async-process-1.7.0/examples/000077500000000000000000000000001441405344400161315ustar00rootroot00000000000000async-process-1.7.0/examples/timeout.rs000066400000000000000000000045421441405344400201720ustar00rootroot00000000000000//! An example of running a `Command` with a timeout. use async_io::Timer; use async_process::{Command, Stdio}; use futures_lite::{future, prelude::*}; use std::io; fn main() -> io::Result<()> { async_io::block_on(async { // Spawn a a command of your choice. let mut child = Command::new("sleep") .arg("3") .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; // Run a future to drain the stdout of the child. // We can't use output() here because it would be cancelled along with the child when the timeout // expires. let mut stdout = String::new(); let drain_stdout = { let buffer = &mut stdout; let mut stdout = child.stdout.take().unwrap(); async move { stdout.read_to_string(buffer).await?; // Wait for the child to exit or the timeout. future::pending().await } }; // Run a future to drain the stderr of the child. let mut stderr = String::new(); let drain_stderr = { let buffer = &mut stderr; let mut stderr = child.stderr.take().unwrap(); async move { stderr.read_to_string(buffer).await?; // Wait for the child to exit or the timeout. future::pending().await } }; // Run a future that waits for the child to exit. let wait = async move { child.status().await?; // Child exited. io::Result::Ok(false) }; // Run a future that times out after 1 second. let timeout_s = 1; let timeout = async move { Timer::after(std::time::Duration::from_secs(timeout_s)).await; // Timed out. Ok(true) }; // Run the futures concurrently. // Note: For larger scale programs than this you should probably spawn each individual future on // a separate task in an executor. let timed_out = drain_stdout.or(drain_stderr).or(wait).or(timeout).await?; if timed_out { println!("The child timed out."); } else { println!("The child exited."); } println!("Stdout:\n{}", stdout); println!("Stderr:\n{}", stderr); Ok(()) }) } async-process-1.7.0/src/000077500000000000000000000000001441405344400151025ustar00rootroot00000000000000async-process-1.7.0/src/lib.rs000066400000000000000000001036511441405344400162240ustar00rootroot00000000000000//! Async interface for working with processes. //! //! This crate is an async version of [`std::process`]. //! //! # Implementation //! //! A background thread named "async-process" is lazily created on first use, which waits for //! spawned child processes to exit and then calls the `wait()` syscall to clean up the "zombie" //! processes. This is unlike the `process` API in the standard library, where dropping a running //! `Child` leaks its resources. //! //! This crate uses [`async-io`] for async I/O on Unix-like systems and [`blocking`] for async I/O //! on Windows. //! //! [`async-io`]: https://docs.rs/async-io //! [`blocking`]: https://docs.rs/blocking //! //! # Examples //! //! Spawn a process and collect its output: //! //! ```no_run //! # futures_lite::future::block_on(async { //! use async_process::Command; //! //! let out = Command::new("echo").arg("hello").arg("world").output().await?; //! assert_eq!(out.stdout, b"hello world\n"); //! # std::io::Result::Ok(()) }); //! ``` //! //! Read the output line-by-line as it gets produced: //! //! ```no_run //! # futures_lite::future::block_on(async { //! use async_process::{Command, Stdio}; //! use futures_lite::{io::BufReader, prelude::*}; //! //! let mut child = Command::new("find") //! .arg(".") //! .stdout(Stdio::piped()) //! .spawn()?; //! //! let mut lines = BufReader::new(child.stdout.take().unwrap()).lines(); //! //! while let Some(line) = lines.next().await { //! println!("{}", line?); //! } //! # std::io::Result::Ok(()) }); //! ``` #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] use std::ffi::OsStr; use std::fmt; use std::path::Path; use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; use std::thread; #[cfg(unix)] use async_io::Async; #[cfg(all(not(async_process_no_io_safety), unix))] use std::convert::{TryFrom, TryInto}; #[cfg(all(not(async_process_no_io_safety), unix))] use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd}; #[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd}; #[cfg(windows)] use blocking::Unblock; use async_lock::OnceCell; use event_listener::Event; use futures_lite::{future, io, prelude::*}; #[doc(no_inline)] pub use std::process::{ExitStatus, Output, Stdio}; #[cfg(unix)] pub mod unix; #[cfg(windows)] pub mod windows; mod sealed { pub trait Sealed {} } /// An event delivered every time the SIGCHLD signal occurs. static SIGCHLD: Event = Event::new(); /// A guard that can kill child processes, or push them into the zombie list. struct ChildGuard { inner: Option, reap_on_drop: bool, kill_on_drop: bool, } impl ChildGuard { fn get_mut(&mut self) -> &mut std::process::Child { self.inner.as_mut().unwrap() } } /// A spawned child process. /// /// The process can be in running or exited state. Use [`status()`][`Child::status()`] or /// [`output()`][`Child::output()`] to wait for it to exit. /// /// If the [`Child`] is dropped, the process keeps running in the background. /// /// # Examples /// /// Spawn a process and wait for it to complete: /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// Command::new("cp").arg("a.txt").arg("b.txt").status().await?; /// # std::io::Result::Ok(()) }); /// ``` pub struct Child { /// The handle for writing to the child's standard input (stdin), if it has been captured. pub stdin: Option, /// The handle for reading from the child's standard output (stdout), if it has been captured. pub stdout: Option, /// The handle for reading from the child's standard error (stderr), if it has been captured. pub stderr: Option, /// The inner child process handle. child: Arc>, } impl Child { /// Wraps the inner child process handle and registers it in the global process list. /// /// The "async-process" thread waits for processes in the global list and cleans up the /// resources when they exit. fn new(cmd: &mut Command) -> io::Result { let mut child = cmd.inner.spawn()?; // Convert sync I/O types into async I/O types. let stdin = child.stdin.take().map(wrap).transpose()?.map(ChildStdin); let stdout = child.stdout.take().map(wrap).transpose()?.map(ChildStdout); let stderr = child.stderr.take().map(wrap).transpose()?.map(ChildStderr); cfg_if::cfg_if! { if #[cfg(windows)] { use std::ffi::c_void; use std::os::windows::io::AsRawHandle; use std::sync::mpsc; use windows_sys::Win32::{ Foundation::{BOOLEAN, HANDLE}, System::Threading::{ RegisterWaitForSingleObject, INFINITE, WT_EXECUTEINWAITTHREAD, WT_EXECUTEONLYONCE, }, }; // This channel is used to simulate SIGCHLD on Windows. fn callback_channel() -> (&'static mpsc::SyncSender<()>, &'static Mutex>) { static CALLBACK: OnceCell<(mpsc::SyncSender<()>, Mutex>)> = OnceCell::new(); let (s, r) = CALLBACK.get_or_init_blocking(|| { let (s, r) = mpsc::sync_channel(1); (s, Mutex::new(r)) }); (s, r) } // Called when a child exits. unsafe extern "system" fn callback(_: *mut c_void, _: BOOLEAN) { callback_channel().0.try_send(()).ok(); } // Register this child process to invoke `callback` on exit. let mut wait_object = 0; let ret = unsafe { RegisterWaitForSingleObject( &mut wait_object, child.as_raw_handle() as HANDLE, Some(callback), std::ptr::null_mut(), INFINITE, WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE, ) }; if ret == 0 { return Err(io::Error::last_os_error()); } // Waits for the next SIGCHLD signal. fn wait_sigchld() { callback_channel().1.lock().unwrap().recv().ok(); } // Wraps a sync I/O type into an async I/O type. fn wrap(io: T) -> io::Result> { Ok(Unblock::new(io)) } } else if #[cfg(unix)] { static SIGNALS: OnceCell> = OnceCell::new(); // Make sure the signal handler is registered before interacting with the process. SIGNALS.get_or_init_blocking(|| Mutex::new( signal_hook::iterator::Signals::new(&[signal_hook::consts::SIGCHLD]) .expect("cannot set signal handler for SIGCHLD"), )); // Waits for the next SIGCHLD signal. fn wait_sigchld() { SIGNALS.get().expect("Signals not registered").lock().unwrap().forever().next(); } // Wraps a sync I/O type into an async I/O type. fn wrap(io: T) -> io::Result> { Async::new(io) } } } static ZOMBIES: OnceCell>> = OnceCell::new(); // Make sure the thread is started. ZOMBIES.get_or_init_blocking(|| { // Start a thread that handles SIGCHLD and notifies tasks when child processes exit. thread::Builder::new() .name("async-process".to_string()) .spawn(move || { loop { // Wait for the next SIGCHLD signal. wait_sigchld(); // Notify all listeners waiting on the SIGCHLD event. SIGCHLD.notify(std::usize::MAX); // Reap zombie processes. let mut zombies = ZOMBIES.get().unwrap().lock().unwrap(); let mut i = 0; while i < zombies.len() { if let Ok(None) = zombies[i].try_wait() { i += 1; } else { zombies.swap_remove(i); } } } }) .expect("cannot spawn async-process thread"); Mutex::new(Vec::new()) }); // When the last reference to the child process is dropped, push it into the zombie list. impl Drop for ChildGuard { fn drop(&mut self) { if self.kill_on_drop { self.get_mut().kill().ok(); } if self.reap_on_drop { let mut zombies = ZOMBIES.get().unwrap().lock().unwrap(); if let Ok(None) = self.get_mut().try_wait() { zombies.push(self.inner.take().unwrap()); } } } } Ok(Child { stdin, stdout, stderr, child: Arc::new(Mutex::new(ChildGuard { inner: Some(child), reap_on_drop: cmd.reap_on_drop, kill_on_drop: cmd.kill_on_drop, })), }) } /// Returns the OS-assigned process identifier associated with this child. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// let mut child = Command::new("ls").spawn()?; /// println!("id: {}", child.id()); /// # std::io::Result::Ok(()) }); /// ``` pub fn id(&self) -> u32 { self.child.lock().unwrap().get_mut().id() } /// Forces the child process to exit. /// /// If the child has already exited, an [`InvalidInput`] error is returned. /// /// This is equivalent to sending a SIGKILL on Unix platforms. /// /// [`InvalidInput`]: `std::io::ErrorKind::InvalidInput` /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// let mut child = Command::new("yes").spawn()?; /// child.kill()?; /// println!("exit status: {}", child.status().await?); /// # std::io::Result::Ok(()) }); /// ``` pub fn kill(&mut self) -> io::Result<()> { self.child.lock().unwrap().get_mut().kill() } /// Returns the exit status if the process has exited. /// /// Unlike [`status()`][`Child::status()`], this method will not drop the stdin handle. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// let mut child = Command::new("ls").spawn()?; /// /// match child.try_status()? { /// None => println!("still running"), /// Some(status) => println!("exited with: {}", status), /// } /// # std::io::Result::Ok(()) }); /// ``` pub fn try_status(&mut self) -> io::Result> { self.child.lock().unwrap().get_mut().try_wait() } /// Drops the stdin handle and waits for the process to exit. /// /// Closing the stdin of the process helps avoid deadlocks. It ensures that the process does /// not block waiting for input from the parent process while the parent waits for the child to /// exit. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::{Command, Stdio}; /// /// let mut child = Command::new("cp") /// .arg("a.txt") /// .arg("b.txt") /// .spawn()?; /// /// println!("exit status: {}", child.status().await?); /// # std::io::Result::Ok(()) }); /// ``` pub fn status(&mut self) -> impl Future> { self.stdin.take(); let child = self.child.clone(); async move { let mut listener = None; loop { if let Some(status) = child.lock().unwrap().get_mut().try_wait()? { return Ok(status); } match listener.take() { None => listener = Some(SIGCHLD.listen()), Some(listener) => listener.await, } } } } /// Drops the stdin handle and collects the output of the process. /// /// Closing the stdin of the process helps avoid deadlocks. It ensures that the process does /// not block waiting for input from the parent process while the parent waits for the child to /// exit. /// /// In order to capture the output of the process, [`Command::stdout()`] and /// [`Command::stderr()`] must be configured with [`Stdio::piped()`]. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::{Command, Stdio}; /// /// let child = Command::new("ls") /// .stdout(Stdio::piped()) /// .stderr(Stdio::piped()) /// .spawn()?; /// /// let out = child.output().await?; /// # std::io::Result::Ok(()) }); /// ``` pub fn output(mut self) -> impl Future> { // A future that waits for the exit status. let status = self.status(); // A future that collects stdout. let stdout = self.stdout.take(); let stdout = async move { let mut v = Vec::new(); if let Some(mut s) = stdout { s.read_to_end(&mut v).await?; } io::Result::Ok(v) }; // A future that collects stderr. let stderr = self.stderr.take(); let stderr = async move { let mut v = Vec::new(); if let Some(mut s) = stderr { s.read_to_end(&mut v).await?; } io::Result::Ok(v) }; async move { let (stdout, stderr) = future::try_zip(stdout, stderr).await?; let status = status.await?; Ok(Output { status, stdout, stderr, }) } } } impl fmt::Debug for Child { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Child") .field("stdin", &self.stdin) .field("stdout", &self.stdout) .field("stderr", &self.stderr) .finish() } } /// A handle to a child process's standard input (stdin). /// /// When a [`ChildStdin`] is dropped, the underlying handle gets clossed. If the child process was /// previously blocked on input, it becomes unblocked after dropping. #[derive(Debug)] pub struct ChildStdin( #[cfg(windows)] Unblock, #[cfg(unix)] Async, ); impl ChildStdin { /// Convert async_process::ChildStdin into std::process::Stdio. /// /// You can use it to associate to the next process. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// use std::process::Stdio; /// /// let mut ls_child = Command::new("ls").stdin(Stdio::piped()).spawn()?; /// let stdio:Stdio = ls_child.stdin.take().unwrap().into_stdio().await?; /// /// let mut echo_child = Command::new("echo").arg("./").stdout(stdio).spawn()?; /// /// # std::io::Result::Ok(()) }); /// ``` pub async fn into_stdio(self) -> io::Result { cfg_if::cfg_if! { if #[cfg(windows)] { Ok(self.0.into_inner().await.into()) } else if #[cfg(unix)] { let child_stdin = self.0.into_inner()?; blocking_fd(rustix::fd::AsFd::as_fd(&child_stdin))?; Ok(child_stdin.into()) } } } } impl io::AsyncWrite for ChildStdin { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.0).poll_write(cx, buf) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0).poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0).poll_close(cx) } } #[cfg(unix)] impl AsRawFd for ChildStdin { fn as_raw_fd(&self) -> RawFd { self.0.as_raw_fd() } } /// **Note:** This implementation is only available on Rust 1.63+. #[cfg(all(not(async_process_no_io_safety), unix))] impl AsFd for ChildStdin { fn as_fd(&self) -> BorrowedFd<'_> { self.0.as_fd() } } /// **Note:** This implementation is only available on Rust 1.63+. #[cfg(all(not(async_process_no_io_safety), unix))] impl TryFrom for OwnedFd { type Error = io::Error; fn try_from(value: ChildStdin) -> Result { value.0.try_into() } } // TODO(notgull): Add mirroring AsRawHandle impls for all of the child handles // // at the moment this is pretty hard to do because of how they're wrapped in // Unblock, meaning that we can't always access the underlying handle. async-fs // gets around this by putting the handle in an Arc, but there's still some decision // to be made about how to handle this (no pun intended) /// A handle to a child process's standard output (stdout). /// /// When a [`ChildStdout`] is dropped, the underlying handle gets closed. #[derive(Debug)] pub struct ChildStdout( #[cfg(windows)] Unblock, #[cfg(unix)] Async, ); impl ChildStdout { /// Convert async_process::ChildStdout into std::process::Stdio. /// /// You can use it to associate to the next process. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// use std::process::Stdio; /// use std::io::Read; /// use futures_lite::AsyncReadExt; /// /// let mut ls_child = Command::new("ls").stdout(Stdio::piped()).spawn()?; /// let stdio:Stdio = ls_child.stdout.take().unwrap().into_stdio().await?; /// /// let mut echo_child = Command::new("echo").stdin(stdio).stdout(Stdio::piped()).spawn()?; /// let mut buf = vec![]; /// echo_child.stdout.take().unwrap().read(&mut buf).await; /// # std::io::Result::Ok(()) }); /// ``` pub async fn into_stdio(self) -> io::Result { cfg_if::cfg_if! { if #[cfg(windows)] { Ok(self.0.into_inner().await.into()) } else if #[cfg(unix)] { let child_stdout = self.0.into_inner()?; blocking_fd(rustix::fd::AsFd::as_fd(&child_stdout))?; Ok(child_stdout.into()) } } } } impl io::AsyncRead for ChildStdout { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { Pin::new(&mut self.0).poll_read(cx, buf) } } #[cfg(unix)] impl AsRawFd for ChildStdout { fn as_raw_fd(&self) -> RawFd { self.0.as_raw_fd() } } /// **Note:** This implementation is only available on Rust 1.63+. #[cfg(all(not(async_process_no_io_safety), unix))] impl AsFd for ChildStdout { fn as_fd(&self) -> BorrowedFd<'_> { self.0.as_fd() } } /// **Note:** This implementation is only available on Rust 1.63+. #[cfg(all(not(async_process_no_io_safety), unix))] impl TryFrom for OwnedFd { type Error = io::Error; fn try_from(value: ChildStdout) -> Result { value.0.try_into() } } /// A handle to a child process's standard error (stderr). /// /// When a [`ChildStderr`] is dropped, the underlying handle gets closed. #[derive(Debug)] pub struct ChildStderr( #[cfg(windows)] Unblock, #[cfg(unix)] Async, ); impl ChildStderr { /// Convert async_process::ChildStderr into std::process::Stdio. /// /// You can use it to associate to the next process. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// use std::process::Stdio; /// /// let mut ls_child = Command::new("ls").arg("x").stderr(Stdio::piped()).spawn()?; /// let stdio:Stdio = ls_child.stderr.take().unwrap().into_stdio().await?; /// /// let mut echo_child = Command::new("echo").stdin(stdio).spawn()?; /// # std::io::Result::Ok(()) }); /// ``` pub async fn into_stdio(self) -> io::Result { cfg_if::cfg_if! { if #[cfg(windows)] { Ok(self.0.into_inner().await.into()) } else if #[cfg(unix)] { let child_stderr = self.0.into_inner()?; blocking_fd(rustix::fd::AsFd::as_fd(&child_stderr))?; Ok(child_stderr.into()) } } } } impl io::AsyncRead for ChildStderr { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { Pin::new(&mut self.0).poll_read(cx, buf) } } #[cfg(unix)] impl AsRawFd for ChildStderr { fn as_raw_fd(&self) -> RawFd { self.0.as_raw_fd() } } /// **Note:** This implementation is only available on Rust 1.63+. #[cfg(all(not(async_process_no_io_safety), unix))] impl AsFd for ChildStderr { fn as_fd(&self) -> BorrowedFd<'_> { self.0.as_fd() } } /// **Note:** This implementation is only available on Rust 1.63+. #[cfg(all(not(async_process_no_io_safety), unix))] impl TryFrom for OwnedFd { type Error = io::Error; fn try_from(value: ChildStderr) -> Result { value.0.try_into() } } /// A builder for spawning processes. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// let output = if cfg!(target_os = "windows") { /// Command::new("cmd").args(&["/C", "echo hello"]).output().await? /// } else { /// Command::new("sh").arg("-c").arg("echo hello").output().await? /// }; /// # std::io::Result::Ok(()) }); /// ``` pub struct Command { inner: std::process::Command, stdin: bool, stdout: bool, stderr: bool, reap_on_drop: bool, kill_on_drop: bool, } impl Command { /// Constructs a new [`Command`] for launching `program`. /// /// The initial configuration (the working directory and environment variables) is inherited /// from the current process. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("ls"); /// ``` pub fn new>(program: S) -> Command { Self::from(std::process::Command::new(program)) } /// Adds a single argument to pass to the program. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("echo"); /// cmd.arg("hello"); /// cmd.arg("world"); /// ``` pub fn arg>(&mut self, arg: S) -> &mut Command { self.inner.arg(arg); self } /// Adds multiple arguments to pass to the program. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("echo"); /// cmd.args(&["hello", "world"]); /// ``` pub fn args(&mut self, args: I) -> &mut Command where I: IntoIterator, S: AsRef, { self.inner.args(args); self } /// Configures an environment variable for the new process. /// /// Note that environment variable names are case-insensitive (but case-preserving) on Windows, /// and case-sensitive on all other platforms. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("ls"); /// cmd.env("PATH", "/bin"); /// ``` pub fn env(&mut self, key: K, val: V) -> &mut Command where K: AsRef, V: AsRef, { self.inner.env(key, val); self } /// Configures multiple environment variables for the new process. /// /// Note that environment variable names are case-insensitive (but case-preserving) on Windows, /// and case-sensitive on all other platforms. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("ls"); /// cmd.envs(vec![("PATH", "/bin"), ("TERM", "xterm-256color")]); /// ``` pub fn envs(&mut self, vars: I) -> &mut Command where I: IntoIterator, K: AsRef, V: AsRef, { self.inner.envs(vars); self } /// Removes an environment variable mapping. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("ls"); /// cmd.env_remove("PATH"); /// ``` pub fn env_remove>(&mut self, key: K) -> &mut Command { self.inner.env_remove(key); self } /// Removes all environment variable mappings. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("ls"); /// cmd.env_clear(); /// ``` pub fn env_clear(&mut self) -> &mut Command { self.inner.env_clear(); self } /// Configures the working directory for the new process. /// /// # Examples /// /// ``` /// use async_process::Command; /// /// let mut cmd = Command::new("ls"); /// cmd.current_dir("/"); /// ``` pub fn current_dir>(&mut self, dir: P) -> &mut Command { self.inner.current_dir(dir); self } /// Configures the standard input (stdin) for the new process. /// /// # Examples /// /// ``` /// use async_process::{Command, Stdio}; /// /// let mut cmd = Command::new("cat"); /// cmd.stdin(Stdio::null()); /// ``` pub fn stdin>(&mut self, cfg: T) -> &mut Command { self.stdin = true; self.inner.stdin(cfg); self } /// Configures the standard output (stdout) for the new process. /// /// # Examples /// /// ``` /// use async_process::{Command, Stdio}; /// /// let mut cmd = Command::new("ls"); /// cmd.stdout(Stdio::piped()); /// ``` pub fn stdout>(&mut self, cfg: T) -> &mut Command { self.stdout = true; self.inner.stdout(cfg); self } /// Configures the standard error (stderr) for the new process. /// /// # Examples /// /// ``` /// use async_process::{Command, Stdio}; /// /// let mut cmd = Command::new("ls"); /// cmd.stderr(Stdio::piped()); /// ``` pub fn stderr>(&mut self, cfg: T) -> &mut Command { self.stderr = true; self.inner.stderr(cfg); self } /// Configures whether to reap the zombie process when [`Child`] is dropped. /// /// When the process finishes, it becomes a "zombie" and some resources associated with it /// remain until [`Child::try_status()`], [`Child::status()`], or [`Child::output()`] collects /// its exit code. /// /// If its exit code is never collected, the resources may leak forever. This crate has a /// background thread named "async-process" that collects such "zombie" processes and then /// "reaps" them, thus preventing the resource leaks. /// /// The default value of this option is `true`. /// /// # Examples /// /// ``` /// use async_process::{Command, Stdio}; /// /// let mut cmd = Command::new("cat"); /// cmd.reap_on_drop(false); /// ``` pub fn reap_on_drop(&mut self, reap_on_drop: bool) -> &mut Command { self.reap_on_drop = reap_on_drop; self } /// Configures whether to kill the process when [`Child`] is dropped. /// /// The default value of this option is `false`. /// /// # Examples /// /// ``` /// use async_process::{Command, Stdio}; /// /// let mut cmd = Command::new("cat"); /// cmd.kill_on_drop(true); /// ``` pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Command { self.kill_on_drop = kill_on_drop; self } /// Executes the command and returns the [`Child`] handle to it. /// /// If not configured, stdin, stdout and stderr will be set to [`Stdio::inherit()`]. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// let child = Command::new("ls").spawn()?; /// # std::io::Result::Ok(()) }); /// ``` pub fn spawn(&mut self) -> io::Result { if !self.stdin { self.inner.stdin(Stdio::inherit()); } if !self.stdout { self.inner.stdout(Stdio::inherit()); } if !self.stderr { self.inner.stderr(Stdio::inherit()); } Child::new(self) } /// Executes the command, waits for it to exit, and returns the exit status. /// /// If not configured, stdin, stdout and stderr will be set to [`Stdio::inherit()`]. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// let status = Command::new("cp") /// .arg("a.txt") /// .arg("b.txt") /// .status() /// .await?; /// # std::io::Result::Ok(()) }); /// ``` pub fn status(&mut self) -> impl Future> { let child = self.spawn(); async { child?.status().await } } /// Executes the command and collects its output. /// /// If not configured, stdin will be set to [`Stdio::null()`], and stdout and stderr will be /// set to [`Stdio::piped()`]. /// /// # Examples /// /// ```no_run /// # futures_lite::future::block_on(async { /// use async_process::Command; /// /// let output = Command::new("cat") /// .arg("a.txt") /// .output() /// .await?; /// # std::io::Result::Ok(()) }); /// ``` pub fn output(&mut self) -> impl Future> { if !self.stdin { self.inner.stdin(Stdio::null()); } if !self.stdout { self.inner.stdout(Stdio::piped()); } if !self.stderr { self.inner.stderr(Stdio::piped()); } let child = Child::new(self); async { child?.output().await } } } impl From for Command { fn from(inner: std::process::Command) -> Self { Self { inner, stdin: false, stdout: false, stderr: false, reap_on_drop: true, kill_on_drop: false, } } } impl fmt::Debug for Command { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if f.alternate() { f.debug_struct("Command") .field("inner", &self.inner) .field("stdin", &self.stdin) .field("stdout", &self.stdout) .field("stderr", &self.stderr) .field("reap_on_drop", &self.reap_on_drop) .field("kill_on_drop", &self.kill_on_drop) .finish() } else { // Stdlib outputs command-line in Debug for Command. This does the // same, if not in "alternate" (long pretty-printed) mode. // This is useful for logs, for example. fmt::Debug::fmt(&self.inner, f) } } } /// Moves `Fd` out of non-blocking mode. #[cfg(unix)] fn blocking_fd(fd: rustix::fd::BorrowedFd<'_>) -> io::Result<()> { cfg_if::cfg_if! { // ioctl(FIONBIO) sets the flag atomically, but we use this only on Linux // for now, as with the standard library, because it seems to behave // differently depending on the platform. // https://github.com/rust-lang/rust/commit/efeb42be2837842d1beb47b51bb693c7474aba3d // https://github.com/libuv/libuv/blob/e9d91fccfc3e5ff772d5da90e1c4a24061198ca0/src/unix/poll.c#L78-L80 // https://github.com/tokio-rs/mio/commit/0db49f6d5caf54b12176821363d154384357e70a if #[cfg(target_os = "linux")] { rustix::io::ioctl_fionbio(fd, false)?; } else { let previous = rustix::fs::fcntl_getfl(fd)?; let new = previous & !rustix::fs::OFlags::NONBLOCK; if new != previous { rustix::fs::fcntl_setfl(fd, new)?; } } } Ok(()) } #[cfg(unix)] mod test { #[test] fn test_into_inner() { futures_lite::future::block_on(async { use crate::Command; use std::io::Result; use std::process::Stdio; use std::str::from_utf8; use futures_lite::AsyncReadExt; let mut ls_child = Command::new("cat") .arg("Cargo.toml") .stdout(Stdio::piped()) .spawn()?; let stdio: Stdio = ls_child.stdout.take().unwrap().into_stdio().await?; let mut echo_child = Command::new("grep") .arg("async") .stdin(stdio) .stdout(Stdio::piped()) .spawn()?; let mut buf = vec![]; let mut stdout = echo_child.stdout.take().unwrap(); stdout.read_to_end(&mut buf).await?; dbg!(from_utf8(&buf).unwrap_or("")); Result::Ok(()) }) .unwrap(); } } async-process-1.7.0/src/unix.rs000066400000000000000000000115061441405344400164360ustar00rootroot00000000000000//! Unix-specific extensions. use std::ffi::OsStr; use std::io; use std::os::unix::process::CommandExt as _; use crate::Command; /// Unix-specific extensions to the [`Command`] builder. /// /// This trait is sealed: it cannot be implemented outside `async-process`. /// This is so that future additional methods are not breaking changes. pub trait CommandExt: crate::sealed::Sealed { /// Sets the child process's user ID. This translates to a /// `setuid` call in the child process. Failure in the `setuid` /// call will cause the spawn to fail. fn uid(&mut self, id: u32) -> &mut Command; /// Similar to `uid`, but sets the group ID of the child process. This has /// the same semantics as the `uid` field. fn gid(&mut self, id: u32) -> &mut Command; /// Schedules a closure to be run just before the `exec` function is /// invoked. /// /// The closure is allowed to return an I/O error whose OS error code will /// be communicated back to the parent and returned as an error from when /// the spawn was requested. /// /// Multiple closures can be registered and they will be called in order of /// their registration. If a closure returns `Err` then no further closures /// will be called and the spawn operation will immediately return with a /// failure. /// /// # Safety /// /// This closure will be run in the context of the child process after a /// `fork`. This primarily means that any modifications made to memory on /// behalf of this closure will **not** be visible to the parent process. /// This is often a very constrained environment where normal operations /// like `malloc` or acquiring a mutex are not guaranteed to work (due to /// other threads perhaps still running when the `fork` was run). /// /// This also means that all resources such as file descriptors and /// memory-mapped regions got duplicated. It is your responsibility to make /// sure that the closure does not violate library invariants by making /// invalid use of these duplicates. /// /// When this closure is run, aspects such as the stdio file descriptors and /// working directory have successfully been changed, so output to these /// locations may not appear where intended. unsafe fn pre_exec(&mut self, f: F) -> &mut Command where F: FnMut() -> io::Result<()> + Send + Sync + 'static; /// Performs all the required setup by this `Command`, followed by calling /// the `execvp` syscall. /// /// On success this function will not return, and otherwise it will return /// an error indicating why the exec (or another part of the setup of the /// `Command`) failed. /// /// `exec` not returning has the same implications as calling /// [`std::process::exit`] – no destructors on the current stack or any other /// thread’s stack will be run. Therefore, it is recommended to only call /// `exec` at a point where it is fine to not run any destructors. Note, /// that the `execvp` syscall independently guarantees that all memory is /// freed and all file descriptors with the `CLOEXEC` option (set by default /// on all file descriptors opened by the standard library) are closed. /// /// This function, unlike `spawn`, will **not** `fork` the process to create /// a new child. Like spawn, however, the default behavior for the stdio /// descriptors will be to inherited from the current process. /// /// # Notes /// /// The process may be in a "broken state" if this function returns in /// error. For example the working directory, environment variables, signal /// handling settings, various user/group information, or aspects of stdio /// file descriptors may have changed. If a "transactional spawn" is /// required to gracefully handle errors it is recommended to use the /// cross-platform `spawn` instead. fn exec(&mut self) -> io::Error; /// Set executable argument /// /// Set the first process argument, `argv[0]`, to something other than the /// default executable path. fn arg0(&mut self, arg: S) -> &mut Command where S: AsRef; } impl crate::sealed::Sealed for Command {} impl CommandExt for Command { fn uid(&mut self, id: u32) -> &mut Command { self.inner.uid(id); self } fn gid(&mut self, id: u32) -> &mut Command { self.inner.gid(id); self } unsafe fn pre_exec(&mut self, f: F) -> &mut Command where F: FnMut() -> io::Result<()> + Send + Sync + 'static, { self.inner.pre_exec(f); self } fn exec(&mut self) -> io::Error { self.inner.exec() } fn arg0(&mut self, arg: S) -> &mut Command where S: AsRef, { self.inner.arg0(arg); self } } async-process-1.7.0/src/windows.rs000066400000000000000000000033661441405344400171520ustar00rootroot00000000000000//! Windows-specific extensions. #[cfg(not(async_process_no_windows_raw_arg))] use std::ffi::OsStr; use std::os::windows::io::{AsRawHandle, RawHandle}; use std::os::windows::process::CommandExt as _; use crate::{Child, Command}; /// Windows-specific extensions to the [`Command`] builder. /// /// This trait is sealed: it cannot be implemented outside `async-process`. /// This is so that future additional methods are not breaking changes. pub trait CommandExt: crate::sealed::Sealed { /// Sets the [process creation flags][1] to be passed to `CreateProcess`. /// /// These will always be ORed with `CREATE_UNICODE_ENVIRONMENT`. /// /// [1]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags fn creation_flags(&mut self, flags: u32) -> &mut Command; /// Append literal text to the command line without any quoting or escaping. /// /// This is useful for passing arguments to `cmd.exe /c`, which doesn't follow /// `CommandLineToArgvW` escaping rules. /// /// **Note:** This method is only available on Rust 1.62+. #[cfg(not(async_process_no_windows_raw_arg))] fn raw_arg>(&mut self, text_to_append_as_is: S) -> &mut Command; } impl crate::sealed::Sealed for Command {} impl CommandExt for Command { fn creation_flags(&mut self, flags: u32) -> &mut Command { self.inner.creation_flags(flags); self } #[cfg(not(async_process_no_windows_raw_arg))] fn raw_arg>(&mut self, text_to_append_as_is: S) -> &mut Command { self.inner.raw_arg(text_to_append_as_is); self } } impl AsRawHandle for Child { fn as_raw_handle(&self) -> RawHandle { self.child.lock().unwrap().get_mut().as_raw_handle() } } async-process-1.7.0/tests/000077500000000000000000000000001441405344400154555ustar00rootroot00000000000000async-process-1.7.0/tests/std.rs000066400000000000000000000265451441405344400166310ustar00rootroot00000000000000//! These tests are borrowed from the `std::process` test suite. use std::env; use std::io; use std::str; use async_process::{Command, Output, Stdio}; use futures_lite::{future, prelude::*}; #[test] fn smoke() { future::block_on(async { let p = if cfg!(target_os = "windows") { Command::new("cmd").args(&["/C", "exit 0"]).spawn() } else { Command::new("true").spawn() }; assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.status().await.unwrap().success()); }) } #[test] fn smoke_failure() { assert!(Command::new("if-this-is-a-binary-then-the-world-has-ended") .spawn() .is_err()); } #[test] fn exit_reported_right() { future::block_on(async { let p = if cfg!(target_os = "windows") { Command::new("cmd").args(&["/C", "exit 1"]).spawn() } else { Command::new("false").spawn() }; assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.status().await.unwrap().code() == Some(1)); drop(p.status().await); }) } #[test] #[cfg(unix)] fn signal_reported_right() { use std::os::unix::process::ExitStatusExt; future::block_on(async { let mut p = Command::new("/bin/sh") .arg("-c") .arg("read a") .stdin(Stdio::piped()) .spawn() .unwrap(); p.kill().unwrap(); match p.status().await.unwrap().signal() { Some(9) => {} result => panic!("not terminated by signal 9 (instead, {:?})", result), } }) } pub async fn run_output(mut cmd: Command) -> String { let p = cmd.spawn(); assert!(p.is_ok()); let mut p = p.unwrap(); assert!(p.stdout.is_some()); let mut ret = String::new(); p.stdout .as_mut() .unwrap() .read_to_string(&mut ret) .await .unwrap(); assert!(p.status().await.unwrap().success()); ret } #[test] fn stdout_works() { future::block_on(async { if cfg!(target_os = "windows") { let mut cmd = Command::new("cmd"); cmd.args(&["/C", "echo foobar"]).stdout(Stdio::piped()); assert_eq!(run_output(cmd).await, "foobar\r\n"); } else { let mut cmd = Command::new("echo"); cmd.arg("foobar").stdout(Stdio::piped()); assert_eq!(run_output(cmd).await, "foobar\n"); } }) } #[test] #[cfg_attr(windows, ignore)] fn set_current_dir_works() { future::block_on(async { let mut cmd = Command::new("/bin/sh"); cmd.arg("-c") .arg("pwd") .current_dir("/") .stdout(Stdio::piped()); assert_eq!(run_output(cmd).await, "/\n"); }) } #[test] #[cfg_attr(windows, ignore)] fn stdin_works() { future::block_on(async { let mut p = Command::new("/bin/sh") .arg("-c") .arg("read line; echo $line") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .unwrap(); p.stdin .as_mut() .unwrap() .write("foobar".as_bytes()) .await .unwrap(); drop(p.stdin.take()); let mut out = String::new(); p.stdout .as_mut() .unwrap() .read_to_string(&mut out) .await .unwrap(); assert!(p.status().await.unwrap().success()); assert_eq!(out, "foobar\n"); }) } #[test] fn test_process_status() { future::block_on(async { let mut status = if cfg!(target_os = "windows") { Command::new("cmd") .args(&["/C", "exit 1"]) .status() .await .unwrap() } else { Command::new("false").status().await.unwrap() }; assert!(status.code() == Some(1)); status = if cfg!(target_os = "windows") { Command::new("cmd") .args(&["/C", "exit 0"]) .status() .await .unwrap() } else { Command::new("true").status().await.unwrap() }; assert!(status.success()); }) } #[test] fn test_process_output_fail_to_start() { future::block_on(async { match Command::new("/no-binary-by-this-name-should-exist") .output() .await { Err(e) => assert_eq!(e.kind(), io::ErrorKind::NotFound), Ok(..) => panic!(), } }) } #[test] fn test_process_output_output() { future::block_on(async { let Output { status, stdout, stderr, } = if cfg!(target_os = "windows") { Command::new("cmd") .args(&["/C", "echo hello"]) .output() .await .unwrap() } else { Command::new("echo").arg("hello").output().await.unwrap() }; let output_str = str::from_utf8(&stdout).unwrap(); assert!(status.success()); assert_eq!(output_str.trim().to_string(), "hello"); assert_eq!(stderr, Vec::new()); }) } #[test] fn test_process_output_error() { future::block_on(async { let Output { status, stdout, stderr, } = if cfg!(target_os = "windows") { Command::new("cmd") .args(&["/C", "mkdir ."]) .output() .await .unwrap() } else { Command::new("mkdir").arg("./").output().await.unwrap() }; assert!(status.code() == Some(1)); assert_eq!(stdout, Vec::new()); assert!(!stderr.is_empty()); }) } #[test] fn test_finish_once() { future::block_on(async { let mut prog = if cfg!(target_os = "windows") { Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap() } else { Command::new("false").spawn().unwrap() }; assert!(prog.status().await.unwrap().code() == Some(1)); }) } #[test] fn test_finish_twice() { future::block_on(async { let mut prog = if cfg!(target_os = "windows") { Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap() } else { Command::new("false").spawn().unwrap() }; assert!(prog.status().await.unwrap().code() == Some(1)); assert!(prog.status().await.unwrap().code() == Some(1)); }) } #[test] fn test_wait_with_output_once() { future::block_on(async { let prog = if cfg!(target_os = "windows") { Command::new("cmd") .args(&["/C", "echo hello"]) .stdout(Stdio::piped()) .spawn() .unwrap() } else { Command::new("echo") .arg("hello") .stdout(Stdio::piped()) .spawn() .unwrap() }; let Output { status, stdout, stderr, } = prog.output().await.unwrap(); let output_str = str::from_utf8(&stdout).unwrap(); assert!(status.success()); assert_eq!(output_str.trim().to_string(), "hello"); assert_eq!(stderr, Vec::new()); }) } #[cfg(all(unix, not(target_os = "android")))] pub fn env_cmd() -> Command { Command::new("env") } #[cfg(target_os = "android")] pub fn env_cmd() -> Command { let mut cmd = Command::new("/system/bin/sh"); cmd.arg("-c").arg("set"); cmd } #[cfg(windows)] pub fn env_cmd() -> Command { let mut cmd = Command::new("cmd"); cmd.arg("/c").arg("set"); cmd } #[test] fn test_override_env() { future::block_on(async { // In some build environments (such as chrooted Nix builds), `env` can // only be found in the explicitly-provided PATH env variable, not in // default places such as /bin or /usr/bin. So we need to pass through // PATH to our sub-process. let mut cmd = env_cmd(); cmd.env_clear().env("RUN_TEST_NEW_ENV", "123"); if let Some(p) = env::var_os("PATH") { cmd.env("PATH", p); } let result = cmd.output().await.unwrap(); let output = String::from_utf8_lossy(&result.stdout).to_string(); assert!( output.contains("RUN_TEST_NEW_ENV=123"), "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output ); }) } #[test] fn test_add_to_env() { future::block_on(async { let result = env_cmd() .env("RUN_TEST_NEW_ENV", "123") .output() .await .unwrap(); let output = String::from_utf8_lossy(&result.stdout).to_string(); assert!( output.contains("RUN_TEST_NEW_ENV=123"), "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output ); }) } #[test] fn test_capture_env_at_spawn() { future::block_on(async { let mut cmd = env_cmd(); cmd.env("RUN_TEST_NEW_ENV1", "123"); // This variable will not be present if the environment has already // been captured above. env::set_var("RUN_TEST_NEW_ENV2", "456"); let result = cmd.output().await.unwrap(); env::remove_var("RUN_TEST_NEW_ENV2"); let output = String::from_utf8_lossy(&result.stdout).to_string(); assert!( output.contains("RUN_TEST_NEW_ENV1=123"), "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{}", output ); assert!( output.contains("RUN_TEST_NEW_ENV2=456"), "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{}", output ); }) } #[test] #[cfg(unix)] fn child_status_preserved_with_kill_on_drop() { future::block_on(async { let p = Command::new("true").kill_on_drop(true).spawn().unwrap(); // Calling output, since it takes ownership of the child // Child::status would work, but without special care, // dropping p inside of output would kill the subprocess early, // and report the wrong exit status let res = p.output().await; assert!(res.unwrap().status.success()); }) } #[test] #[cfg(windows)] fn child_as_raw_handle() { use std::os::windows::io::AsRawHandle; use windows_sys::Win32::System::Threading::GetProcessId; future::block_on(async { let p = Command::new("cmd.exe") .arg("/C") .arg("pause") .kill_on_drop(true) .spawn() .unwrap(); let std_pid = p.id(); assert!(std_pid > 0); let handle = p.as_raw_handle(); // We verify that we have the correct handle by obtaining the PID // with the Windows API rather than via std: let win_pid = unsafe { GetProcessId(handle as _) }; assert_eq!(win_pid, std_pid); }) } #[test] #[cfg(unix)] fn test_spawn_multiple_with_stdio() { let mut cmd = Command::new("/bin/sh"); cmd.arg("-c") .arg("echo foo; echo bar 1>&2") .stdout(Stdio::piped()) .stderr(Stdio::piped()); future::block_on(async move { let p1 = cmd.spawn().unwrap(); let out1 = p1.output().await.unwrap(); assert_eq!(out1.stdout, b"foo\n"); assert_eq!(out1.stderr, b"bar\n"); let p2 = cmd.spawn().unwrap(); let out2 = p2.output().await.unwrap(); assert_eq!(out2.stdout, b"foo\n"); assert_eq!(out2.stderr, b"bar\n"); }); }