├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── timeout.rs ├── src ├── lib.rs ├── reaper │ ├── mod.rs │ ├── signal.rs │ └── wait.rs ├── unix.rs └── windows.rs └── tests ├── sleep.rs └── std.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | commit-message: 8 | prefix: '' 9 | labels: [] 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - master 11 | schedule: 12 | - cron: '0 2 * * 0' 13 | 14 | env: 15 | CARGO_INCREMENTAL: 0 16 | CARGO_NET_GIT_FETCH_WITH_CLI: true 17 | CARGO_NET_RETRY: 10 18 | CARGO_TERM_COLOR: always 19 | RUST_BACKTRACE: 1 20 | RUSTFLAGS: -D warnings 21 | RUSTDOCFLAGS: -D warnings 22 | RUSTUP_MAX_RETRIES: 10 23 | 24 | defaults: 25 | run: 26 | shell: bash 27 | 28 | jobs: 29 | fmt: 30 | uses: smol-rs/.github/.github/workflows/fmt.yml@main 31 | security_audit: 32 | uses: smol-rs/.github/.github/workflows/security_audit.yml@main 33 | permissions: 34 | checks: write 35 | contents: read 36 | issues: write 37 | secrets: inherit 38 | 39 | test: 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | os: [ubuntu-latest, windows-latest, macos-latest] 45 | rust: [nightly, beta, stable] 46 | steps: 47 | - uses: actions/checkout@v4 48 | - name: Install Rust 49 | # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. 50 | run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} 51 | - run: cargo build --all --all-features --all-targets 52 | - name: Run cargo check (without dev-dependencies to catch missing feature flags) 53 | if: startsWith(matrix.rust, 'nightly') 54 | run: cargo check -Z features=dev_dep 55 | - run: cargo test 56 | - run: cargo test 57 | env: 58 | RUSTFLAGS: ${{ env.RUSTFLAGS }} --cfg async_process_force_signal_backend 59 | if: matrix.os != 'windows-latest' 60 | 61 | test-android: 62 | runs-on: ubuntu-latest 63 | strategy: 64 | fail-fast: false 65 | matrix: 66 | rust: [nightly] 67 | target: 68 | - aarch64-linux-android 69 | steps: 70 | - uses: actions/checkout@v4 71 | - name: Install Rust 72 | # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. 73 | run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} 74 | - name: Install cross-compilation tools 75 | uses: taiki-e/setup-cross-toolchain-action@v1 76 | with: 77 | target: ${{ matrix.target }} 78 | # On nightly and `-Z doctest-xcompile` is available, 79 | # `$DOCTEST_XCOMPILE` is `-Zdoctest-xcompile`. 80 | # 81 | # On stable, `$DOCTEST_XCOMPILE` is not set. 82 | # Once `-Z doctest-xcompile` is stabilized, the corresponding flag 83 | # will be set to `$DOCTEST_XCOMPILE` (if it is available). 84 | - run: cargo test --verbose $DOCTEST_XCOMPILE 85 | 86 | msrv: 87 | runs-on: ${{ matrix.os }} 88 | strategy: 89 | fail-fast: false 90 | matrix: 91 | os: [ubuntu-latest, windows-latest] 92 | # When updating this, the reminder to update the minimum supported 93 | # Rust version in Cargo.toml. 94 | rust: ['1.63'] 95 | steps: 96 | - uses: actions/checkout@v4 97 | - name: Install Rust 98 | run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 99 | - run: cargo build 100 | 101 | clippy: 102 | runs-on: ubuntu-latest 103 | steps: 104 | - uses: actions/checkout@v4 105 | - name: Install Rust 106 | run: rustup update stable 107 | - run: cargo clippy --all-features --all-targets 108 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v[0-9]+.* 10 | 11 | jobs: 12 | create-release: 13 | if: github.repository_owner == 'smol-rs' 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: taiki-e/create-gh-release-action@v1 18 | with: 19 | changelog: CHANGELOG.md 20 | branch: master 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 2.3.1 2 | 3 | - Update rustix to 1.0. (#94) 4 | 5 | # Version 2.3.0 6 | 7 | - Use a more efficient backend on Windows. (#87) 8 | 9 | # Version 2.2.4 10 | 11 | - Update to `windows-sys` v0.59. (#85) 12 | 13 | # Version 2.2.3 14 | 15 | - Fix builds on Android by having Android unconditionally use the signal reaper 16 | backend. (#80) 17 | 18 | # Version 2.2.2 19 | 20 | - Fix a typo in the docs for `ChildStdin`. (#76) 21 | 22 | # Version 2.2.1 23 | 24 | - Fix a compilation error for 32-bit operating systems by using a 32-bit zombie counter. (#75) 25 | 26 | # Version 2.2.0 27 | 28 | - Port Linux to a new backend that tries to use `pidfd` if it is available. (#68) 29 | 30 | # Version 2.1.0 31 | 32 | - Update `event-listener` to v5.1.0. (#67) 33 | 34 | # Version 2.0.1 35 | 36 | - Update `event-listener` to v4.0.0. (#64) 37 | - Update `windows-sys` to v0.52.0. (#65) 38 | 39 | # Version 2.0.0 40 | 41 | - **Breaking:** Remove the `pre_exec` extension function on Unix. It is still available through the `From` implementation on `Command`. (#54) 42 | - Add the `driver()` function, which allows the processes to be driven without a separate thread. (#52) 43 | - Bump `async-io` to v2.0.0 and `async-channel` to v2.0.0. (#60) 44 | 45 | # Version 1.8.1 46 | 47 | - Bump `async-signal` to v0.2.3. (#56) 48 | 49 | # Version 1.8.0 50 | 51 | - Move from `signal-hook` to the `async-signal` crate. (#42) 52 | - Reorganize the internals of this crate to be more coherent. (#46) 53 | - Bump to `event-listener` v3.0.0. (#43) 54 | 55 | # Version 1.7.0 56 | 57 | - Replace direct dependency on libc with rustix. (#31) 58 | - Reduce the number of syscalls used in the `into_stdio` method. (#31) 59 | - Add windows::CommandExt::raw_arg on Rust 1.62+. (#32) 60 | - Update windows-sys to 0.48. (#39) 61 | 62 | # Version 1.6.0 63 | 64 | - Switch from `winapi` to `windows-sys` (#27) 65 | - Remove the dependency on the `once_cell` crate to restore the MSRV (#26) 66 | - Fix build failure with minimal-versions (#28) 67 | 68 | # Version 1.5.0 69 | 70 | - Implement `AsRawFd` for `ChildStd*` on Unix (#23) 71 | - Implement I/O safety traits on Rust 1.63+ on Unix (#23) 72 | 73 | # Version 1.4.0 74 | 75 | - `Command::spawn` and `Command::output` no longer unconfigure stdio streams (#20) 76 | - Implement `From` for `Command` (#21) 77 | 78 | # Version 1.3.0 79 | 80 | - Improve debug implementation of `Command` (#18) 81 | 82 | # Version 1.2.0 83 | 84 | - Implement `AsRawHandle` on `Child` on `Windows` (#17) 85 | 86 | # Version 1.1.0 87 | 88 | - Add `into_stdio` method to `ChildStdin`, `ChildStdout`, and `ChildStderr`. (#13) 89 | 90 | # Version 1.0.2 91 | 92 | - Use `kill_on_drop` only when the last reference to `ChildGuard` is dropped. 93 | 94 | # Version 1.0.1 95 | 96 | - Update `futures-lite`. 97 | 98 | # Version 1.0.0 99 | 100 | - Update dependencies and stabilize. 101 | 102 | # Version 0.1.3 103 | 104 | - Update dependencies. 105 | 106 | # Version 0.1.2 107 | 108 | - Add Unix and Windows extensions. 109 | - Add `Command::reap_on_drop()` option. 110 | - Add `Command::kill_on_drop()` option. 111 | 112 | # Version 0.1.1 113 | 114 | - Initial version 115 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-process" 3 | # When publishing a new version: 4 | # - Update CHANGELOG.md 5 | # - Create "v2.x.y" git tag 6 | version = "2.3.1" 7 | authors = ["Stjepan Glavina "] 8 | edition = "2021" 9 | rust-version = "1.63" 10 | description = "Async interface for working with processes" 11 | license = "Apache-2.0 OR MIT" 12 | repository = "https://github.com/smol-rs/async-process" 13 | keywords = ["process", "spawn", "command", "subprocess", "child"] 14 | categories = ["asynchronous", "os"] 15 | exclude = ["/.*"] 16 | 17 | [dependencies] 18 | async-lock = "3.0.0" 19 | async-io = "2.1.0" 20 | cfg-if = "1.0" 21 | event-listener = "5.1.0" 22 | futures-lite = "2.0.0" 23 | tracing = { version = "0.1.40", default-features = false } 24 | 25 | [target.'cfg(unix)'.dependencies] 26 | async-signal = "0.2.3" 27 | rustix = { version = "1.0", default-features = false, features = ["std", "fs", "process"] } 28 | 29 | [target.'cfg(any(windows, target_os = "linux"))'.dependencies] 30 | async-channel = "2.0.0" 31 | async-task = "4.7.0" 32 | 33 | [target.'cfg(windows)'.dependencies] 34 | blocking = "1.0.0" 35 | 36 | [lints.rust] 37 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(async_process_force_signal_backend)'] } 38 | 39 | [dev-dependencies] 40 | async-executor = "1.5.1" 41 | 42 | [target.'cfg(windows)'.dev-dependencies.windows-sys] 43 | version = "0.59" 44 | default-features = false 45 | features = [ 46 | "Win32_Foundation", 47 | "Win32_System_Threading", 48 | ] 49 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-process 2 | 3 | [![Build](https://github.com/smol-rs/async-process/workflows/Build%20and%20test/badge.svg)]( 4 | https://github.com/smol-rs/async-process/actions) 5 | [![License](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)]( 6 | https://github.com/smol-rs/async-process) 7 | [![Cargo](https://img.shields.io/crates/v/async-process.svg)]( 8 | https://crates.io/crates/async-process) 9 | [![Documentation](https://docs.rs/async-process/badge.svg)]( 10 | https://docs.rs/async-process) 11 | 12 | Async interface for working with processes. 13 | 14 | This crate is an async version of `std::process`. 15 | 16 | ## Implementation 17 | 18 | A background thread named "async-process" is lazily created on first use, which waits for 19 | spawned child processes to exit and then calls the `wait()` syscall to clean up the "zombie" 20 | processes. This is unlike the `process` API in the standard library, where dropping a running 21 | `Child` leaks its resources. 22 | 23 | This crate uses [`async-io`] for async I/O on Unix-like systems and [`blocking`] for async I/O 24 | on Windows. 25 | 26 | [`async-io`]: https://docs.rs/async-io 27 | [`blocking`]: https://docs.rs/blocking 28 | 29 | ## Examples 30 | 31 | Spawn a process and collect its output: 32 | 33 | ```rust 34 | use async_process::Command; 35 | 36 | let out = Command::new("echo").arg("hello").arg("world").output().await?; 37 | assert_eq!(out.stdout, b"hello world\n"); 38 | ``` 39 | 40 | Read the output line-by-line as it gets produced: 41 | 42 | ```rust 43 | use async_process::{Command, Stdio}; 44 | use futures_lite::{io::BufReader, prelude::*}; 45 | 46 | let mut child = Command::new("find") 47 | .arg(".") 48 | .stdout(Stdio::piped()) 49 | .spawn()?; 50 | 51 | let mut lines = BufReader::new(child.stdout.take().unwrap()).lines(); 52 | 53 | while let Some(line) = lines.next().await { 54 | println!("{}", line?); 55 | } 56 | ``` 57 | 58 | ## License 59 | 60 | Licensed under either of 61 | 62 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 63 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 64 | 65 | at your option. 66 | 67 | #### Contribution 68 | 69 | Unless you explicitly state otherwise, any contribution intentionally submitted 70 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 71 | dual licensed as above, without any additional terms or conditions. 72 | -------------------------------------------------------------------------------- /examples/timeout.rs: -------------------------------------------------------------------------------- 1 | //! An example of running a `Command` with a timeout. 2 | 3 | use async_io::Timer; 4 | use async_process::{Command, Stdio}; 5 | use futures_lite::{future, prelude::*}; 6 | use std::io; 7 | 8 | fn main() -> io::Result<()> { 9 | async_io::block_on(async { 10 | // Spawn a a command of your choice. 11 | let mut child = Command::new("sleep") 12 | .arg("3") 13 | .stdout(Stdio::piped()) 14 | .stderr(Stdio::piped()) 15 | .spawn()?; 16 | 17 | // Run a future to drain the stdout of the child. 18 | // We can't use output() here because it would be cancelled along with the child when the timeout 19 | // expires. 20 | let mut stdout = String::new(); 21 | let drain_stdout = { 22 | let buffer = &mut stdout; 23 | let mut stdout = child.stdout.take().unwrap(); 24 | 25 | async move { 26 | stdout.read_to_string(buffer).await?; 27 | 28 | // Wait for the child to exit or the timeout. 29 | future::pending().await 30 | } 31 | }; 32 | 33 | // Run a future to drain the stderr of the child. 34 | let mut stderr = String::new(); 35 | let drain_stderr = { 36 | let buffer = &mut stderr; 37 | let mut stderr = child.stderr.take().unwrap(); 38 | 39 | async move { 40 | stderr.read_to_string(buffer).await?; 41 | 42 | // Wait for the child to exit or the timeout. 43 | future::pending().await 44 | } 45 | }; 46 | 47 | // Run a future that waits for the child to exit. 48 | let wait = async move { 49 | child.status().await?; 50 | 51 | // Child exited. 52 | io::Result::Ok(false) 53 | }; 54 | 55 | // Run a future that times out after 1 second. 56 | let timeout_s = 1; 57 | let timeout = async move { 58 | Timer::after(std::time::Duration::from_secs(timeout_s)).await; 59 | 60 | // Timed out. 61 | Ok(true) 62 | }; 63 | 64 | // Run the futures concurrently. 65 | // Note: For larger scale programs than this you should probably spawn each individual future on 66 | // a separate task in an executor. 67 | let timed_out = drain_stdout.or(drain_stderr).or(wait).or(timeout).await?; 68 | 69 | if timed_out { 70 | println!("The child timed out."); 71 | } else { 72 | println!("The child exited."); 73 | } 74 | 75 | println!("Stdout:\n{}", stdout); 76 | println!("Stderr:\n{}", stderr); 77 | 78 | Ok(()) 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Async interface for working with processes. 2 | //! 3 | //! This crate is an async version of [`std::process`]. 4 | //! 5 | //! # Implementation 6 | //! 7 | //! A background thread named "async-process" is lazily created on first use, which waits for 8 | //! spawned child processes to exit and then calls the `wait()` syscall to clean up the "zombie" 9 | //! processes. This is unlike the `process` API in the standard library, where dropping a running 10 | //! `Child` leaks its resources. 11 | //! 12 | //! This crate uses [`async-io`] for async I/O on Unix-like systems and [`blocking`] for async I/O 13 | //! on Windows. 14 | //! 15 | //! [`async-io`]: https://docs.rs/async-io 16 | //! [`blocking`]: https://docs.rs/blocking 17 | //! 18 | //! # Examples 19 | //! 20 | //! Spawn a process and collect its output: 21 | //! 22 | //! ```no_run 23 | //! # futures_lite::future::block_on(async { 24 | //! use async_process::Command; 25 | //! 26 | //! let out = Command::new("echo").arg("hello").arg("world").output().await?; 27 | //! assert_eq!(out.stdout, b"hello world\n"); 28 | //! # std::io::Result::Ok(()) }); 29 | //! ``` 30 | //! 31 | //! Read the output line-by-line as it gets produced: 32 | //! 33 | //! ```no_run 34 | //! # futures_lite::future::block_on(async { 35 | //! use async_process::{Command, Stdio}; 36 | //! use futures_lite::{io::BufReader, prelude::*}; 37 | //! 38 | //! let mut child = Command::new("find") 39 | //! .arg(".") 40 | //! .stdout(Stdio::piped()) 41 | //! .spawn()?; 42 | //! 43 | //! let mut lines = BufReader::new(child.stdout.take().unwrap()).lines(); 44 | //! 45 | //! while let Some(line) = lines.next().await { 46 | //! println!("{}", line?); 47 | //! } 48 | //! # std::io::Result::Ok(()) }); 49 | //! ``` 50 | 51 | #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] 52 | #![doc( 53 | html_favicon_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png" 54 | )] 55 | #![doc( 56 | html_logo_url = "https://raw.githubusercontent.com/smol-rs/smol/master/assets/images/logo_fullsize_transparent.png" 57 | )] 58 | 59 | use std::convert::Infallible; 60 | use std::ffi::OsStr; 61 | use std::fmt; 62 | use std::path::Path; 63 | use std::pin::Pin; 64 | use std::sync::atomic::{AtomicUsize, Ordering}; 65 | use std::sync::{Arc, Mutex}; 66 | use std::task::{Context, Poll}; 67 | use std::thread; 68 | 69 | #[cfg(unix)] 70 | use async_io::Async; 71 | #[cfg(unix)] 72 | use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; 73 | 74 | #[cfg(windows)] 75 | use blocking::Unblock; 76 | 77 | use async_lock::OnceCell; 78 | use futures_lite::{future, io, prelude::*}; 79 | 80 | #[doc(no_inline)] 81 | pub use std::process::{ExitStatus, Output, Stdio}; 82 | 83 | #[cfg(unix)] 84 | pub mod unix; 85 | #[cfg(windows)] 86 | pub mod windows; 87 | 88 | mod reaper; 89 | 90 | mod sealed { 91 | pub trait Sealed {} 92 | } 93 | 94 | #[cfg(test)] 95 | static DRIVER_THREAD_SPAWNED: std::sync::atomic::AtomicBool = 96 | std::sync::atomic::AtomicBool::new(false); 97 | 98 | /// The zombie process reaper. 99 | /// 100 | /// This structure reaps zombie processes and emits the `SIGCHLD` signal. 101 | struct Reaper { 102 | /// Underlying system reaper. 103 | sys: reaper::Reaper, 104 | 105 | /// The number of tasks polling the SIGCHLD event. 106 | /// 107 | /// If this is zero, the `async-process` thread must be spawned. 108 | drivers: AtomicUsize, 109 | 110 | /// Number of live `Child` instances currently running. 111 | /// 112 | /// This is used to prevent the reaper thread from being spawned right as the program closes, 113 | /// when the reaper thread isn't needed. This represents the number of active processes. 114 | child_count: AtomicUsize, 115 | } 116 | 117 | impl Reaper { 118 | /// Get the singleton instance of the reaper. 119 | fn get() -> &'static Self { 120 | static REAPER: OnceCell = OnceCell::new(); 121 | 122 | REAPER.get_or_init_blocking(|| Reaper { 123 | sys: reaper::Reaper::new(), 124 | drivers: AtomicUsize::new(0), 125 | child_count: AtomicUsize::new(0), 126 | }) 127 | } 128 | 129 | /// Ensure that the reaper is driven. 130 | /// 131 | /// If there are no active `driver()` callers, this will spawn the `async-process` thread. 132 | #[inline] 133 | fn ensure_driven(&'static self) { 134 | if self 135 | .drivers 136 | .compare_exchange(0, 1, Ordering::SeqCst, Ordering::Acquire) 137 | .is_ok() 138 | { 139 | self.start_driver_thread(); 140 | } 141 | } 142 | 143 | /// Start the `async-process` thread. 144 | #[cold] 145 | fn start_driver_thread(&'static self) { 146 | #[cfg(test)] 147 | DRIVER_THREAD_SPAWNED 148 | .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) 149 | .unwrap_or_else(|_| unreachable!("Driver thread already spawned")); 150 | 151 | thread::Builder::new() 152 | .name("async-process".to_string()) 153 | .spawn(move || { 154 | let driver = async move { 155 | // No need to bump self.drivers, it was already bumped in ensure_driven. 156 | let guard = self.sys.lock().await; 157 | self.sys.reap(guard).await 158 | }; 159 | 160 | #[cfg(unix)] 161 | async_io::block_on(driver); 162 | 163 | #[cfg(not(unix))] 164 | future::block_on(driver); 165 | }) 166 | .expect("cannot spawn async-process thread"); 167 | } 168 | 169 | /// Register a process with this reaper. 170 | fn register(&'static self, child: std::process::Child) -> io::Result { 171 | self.ensure_driven(); 172 | self.sys.register(child) 173 | } 174 | } 175 | 176 | cfg_if::cfg_if! { 177 | if #[cfg(windows)] { 178 | // Wraps a sync I/O type into an async I/O type. 179 | fn wrap(io: T) -> io::Result> { 180 | Ok(Unblock::new(io)) 181 | } 182 | } else if #[cfg(unix)] { 183 | /// Wrap a file descriptor into a non-blocking I/O type. 184 | fn wrap(io: T) -> io::Result> { 185 | Async::new(io) 186 | } 187 | } 188 | } 189 | 190 | /// A guard that can kill child processes, or push them into the zombie list. 191 | struct ChildGuard { 192 | inner: reaper::ChildGuard, 193 | reap_on_drop: bool, 194 | kill_on_drop: bool, 195 | reaper: &'static Reaper, 196 | } 197 | 198 | impl ChildGuard { 199 | fn get_mut(&mut self) -> &mut std::process::Child { 200 | self.inner.get_mut() 201 | } 202 | } 203 | 204 | // When the last reference to the child process is dropped, push it into the zombie list. 205 | impl Drop for ChildGuard { 206 | fn drop(&mut self) { 207 | if self.kill_on_drop { 208 | self.get_mut().kill().ok(); 209 | } 210 | if self.reap_on_drop { 211 | self.inner.reap(&self.reaper.sys); 212 | } 213 | 214 | // Decrement number of children. 215 | self.reaper.child_count.fetch_sub(1, Ordering::Acquire); 216 | } 217 | } 218 | 219 | /// A spawned child process. 220 | /// 221 | /// The process can be in running or exited state. Use [`status()`][`Child::status()`] or 222 | /// [`output()`][`Child::output()`] to wait for it to exit. 223 | /// 224 | /// If the [`Child`] is dropped, the process keeps running in the background. 225 | /// 226 | /// # Examples 227 | /// 228 | /// Spawn a process and wait for it to complete: 229 | /// 230 | /// ```no_run 231 | /// # futures_lite::future::block_on(async { 232 | /// use async_process::Command; 233 | /// 234 | /// Command::new("cp").arg("a.txt").arg("b.txt").status().await?; 235 | /// # std::io::Result::Ok(()) }); 236 | /// ``` 237 | pub struct Child { 238 | /// The handle for writing to the child's standard input (stdin), if it has been captured. 239 | pub stdin: Option, 240 | 241 | /// The handle for reading from the child's standard output (stdout), if it has been captured. 242 | pub stdout: Option, 243 | 244 | /// The handle for reading from the child's standard error (stderr), if it has been captured. 245 | pub stderr: Option, 246 | 247 | /// The inner child process handle. 248 | child: Arc>, 249 | } 250 | 251 | impl Child { 252 | /// Wraps the inner child process handle and registers it in the global process list. 253 | /// 254 | /// The "async-process" thread waits for processes in the global list and cleans up the 255 | /// resources when they exit. 256 | fn new(cmd: &mut Command) -> io::Result { 257 | // Make sure the reaper exists before we spawn the child process. 258 | let reaper = Reaper::get(); 259 | let mut child = cmd.inner.spawn()?; 260 | 261 | // Convert sync I/O types into async I/O types. 262 | let stdin = child.stdin.take().map(wrap).transpose()?.map(ChildStdin); 263 | let stdout = child.stdout.take().map(wrap).transpose()?.map(ChildStdout); 264 | let stderr = child.stderr.take().map(wrap).transpose()?.map(ChildStderr); 265 | 266 | // Bump the child count. 267 | reaper.child_count.fetch_add(1, Ordering::Relaxed); 268 | 269 | // Register the child process in the global list. 270 | let inner = reaper.register(child)?; 271 | 272 | Ok(Child { 273 | stdin, 274 | stdout, 275 | stderr, 276 | child: Arc::new(Mutex::new(ChildGuard { 277 | inner, 278 | reap_on_drop: cmd.reap_on_drop, 279 | kill_on_drop: cmd.kill_on_drop, 280 | reaper, 281 | })), 282 | }) 283 | } 284 | 285 | /// Returns the OS-assigned process identifier associated with this child. 286 | /// 287 | /// # Examples 288 | /// 289 | /// ```no_run 290 | /// # futures_lite::future::block_on(async { 291 | /// use async_process::Command; 292 | /// 293 | /// let mut child = Command::new("ls").spawn()?; 294 | /// println!("id: {}", child.id()); 295 | /// # std::io::Result::Ok(()) }); 296 | /// ``` 297 | pub fn id(&self) -> u32 { 298 | self.child.lock().unwrap().get_mut().id() 299 | } 300 | 301 | /// Forces the child process to exit. 302 | /// 303 | /// If the child has already exited, an [`InvalidInput`] error is returned. 304 | /// 305 | /// This is equivalent to sending a SIGKILL on Unix platforms. 306 | /// 307 | /// [`InvalidInput`]: `std::io::ErrorKind::InvalidInput` 308 | /// 309 | /// # Examples 310 | /// 311 | /// ```no_run 312 | /// # futures_lite::future::block_on(async { 313 | /// use async_process::Command; 314 | /// 315 | /// let mut child = Command::new("yes").spawn()?; 316 | /// child.kill()?; 317 | /// println!("exit status: {}", child.status().await?); 318 | /// # std::io::Result::Ok(()) }); 319 | /// ``` 320 | pub fn kill(&mut self) -> io::Result<()> { 321 | self.child.lock().unwrap().get_mut().kill() 322 | } 323 | 324 | /// Returns the exit status if the process has exited. 325 | /// 326 | /// Unlike [`status()`][`Child::status()`], this method will not drop the stdin handle. 327 | /// 328 | /// # Examples 329 | /// 330 | /// ```no_run 331 | /// # futures_lite::future::block_on(async { 332 | /// use async_process::Command; 333 | /// 334 | /// let mut child = Command::new("ls").spawn()?; 335 | /// 336 | /// match child.try_status()? { 337 | /// None => println!("still running"), 338 | /// Some(status) => println!("exited with: {}", status), 339 | /// } 340 | /// # std::io::Result::Ok(()) }); 341 | /// ``` 342 | pub fn try_status(&mut self) -> io::Result> { 343 | self.child.lock().unwrap().get_mut().try_wait() 344 | } 345 | 346 | /// Drops the stdin handle and waits for the process to exit. 347 | /// 348 | /// Closing the stdin of the process helps avoid deadlocks. It ensures that the process does 349 | /// not block waiting for input from the parent process while the parent waits for the child to 350 | /// exit. 351 | /// 352 | /// # Examples 353 | /// 354 | /// ```no_run 355 | /// # futures_lite::future::block_on(async { 356 | /// use async_process::{Command, Stdio}; 357 | /// 358 | /// let mut child = Command::new("cp") 359 | /// .arg("a.txt") 360 | /// .arg("b.txt") 361 | /// .spawn()?; 362 | /// 363 | /// println!("exit status: {}", child.status().await?); 364 | /// # std::io::Result::Ok(()) }); 365 | /// ``` 366 | pub fn status(&mut self) -> impl Future> { 367 | self.stdin.take(); 368 | let child = self.child.clone(); 369 | 370 | async move { Reaper::get().sys.status(&child).await } 371 | } 372 | 373 | /// Drops the stdin handle and collects the output of the process. 374 | /// 375 | /// Closing the stdin of the process helps avoid deadlocks. It ensures that the process does 376 | /// not block waiting for input from the parent process while the parent waits for the child to 377 | /// exit. 378 | /// 379 | /// In order to capture the output of the process, [`Command::stdout()`] and 380 | /// [`Command::stderr()`] must be configured with [`Stdio::piped()`]. 381 | /// 382 | /// # Examples 383 | /// 384 | /// ```no_run 385 | /// # futures_lite::future::block_on(async { 386 | /// use async_process::{Command, Stdio}; 387 | /// 388 | /// let child = Command::new("ls") 389 | /// .stdout(Stdio::piped()) 390 | /// .stderr(Stdio::piped()) 391 | /// .spawn()?; 392 | /// 393 | /// let out = child.output().await?; 394 | /// # std::io::Result::Ok(()) }); 395 | /// ``` 396 | pub fn output(mut self) -> impl Future> { 397 | // A future that waits for the exit status. 398 | let status = self.status(); 399 | 400 | // A future that collects stdout. 401 | let stdout = self.stdout.take(); 402 | let stdout = async move { 403 | let mut v = Vec::new(); 404 | if let Some(mut s) = stdout { 405 | s.read_to_end(&mut v).await?; 406 | } 407 | io::Result::Ok(v) 408 | }; 409 | 410 | // A future that collects stderr. 411 | let stderr = self.stderr.take(); 412 | let stderr = async move { 413 | let mut v = Vec::new(); 414 | if let Some(mut s) = stderr { 415 | s.read_to_end(&mut v).await?; 416 | } 417 | io::Result::Ok(v) 418 | }; 419 | 420 | async move { 421 | let (stdout, stderr) = future::try_zip(stdout, stderr).await?; 422 | let status = status.await?; 423 | Ok(Output { 424 | status, 425 | stdout, 426 | stderr, 427 | }) 428 | } 429 | } 430 | } 431 | 432 | impl fmt::Debug for Child { 433 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 434 | f.debug_struct("Child") 435 | .field("stdin", &self.stdin) 436 | .field("stdout", &self.stdout) 437 | .field("stderr", &self.stderr) 438 | .finish() 439 | } 440 | } 441 | 442 | /// A handle to a child process's standard input (stdin). 443 | /// 444 | /// When a [`ChildStdin`] is dropped, the underlying handle gets closed. If the child process was 445 | /// previously blocked on input, it becomes unblocked after dropping. 446 | #[derive(Debug)] 447 | pub struct ChildStdin( 448 | #[cfg(windows)] Unblock, 449 | #[cfg(unix)] Async, 450 | ); 451 | 452 | impl ChildStdin { 453 | /// Convert async_process::ChildStdin into std::process::Stdio. 454 | /// 455 | /// You can use it to associate to the next process. 456 | /// 457 | /// # Examples 458 | /// 459 | /// ```no_run 460 | /// # futures_lite::future::block_on(async { 461 | /// use async_process::Command; 462 | /// use std::process::Stdio; 463 | /// 464 | /// let mut ls_child = Command::new("ls").stdin(Stdio::piped()).spawn()?; 465 | /// let stdio:Stdio = ls_child.stdin.take().unwrap().into_stdio().await?; 466 | /// 467 | /// let mut echo_child = Command::new("echo").arg("./").stdout(stdio).spawn()?; 468 | /// 469 | /// # std::io::Result::Ok(()) }); 470 | /// ``` 471 | pub async fn into_stdio(self) -> io::Result { 472 | cfg_if::cfg_if! { 473 | if #[cfg(windows)] { 474 | Ok(self.0.into_inner().await.into()) 475 | } else if #[cfg(unix)] { 476 | let child_stdin = self.0.into_inner()?; 477 | blocking_fd(rustix::fd::AsFd::as_fd(&child_stdin))?; 478 | Ok(child_stdin.into()) 479 | } 480 | } 481 | } 482 | } 483 | 484 | impl io::AsyncWrite for ChildStdin { 485 | fn poll_write( 486 | mut self: Pin<&mut Self>, 487 | cx: &mut Context<'_>, 488 | buf: &[u8], 489 | ) -> Poll> { 490 | Pin::new(&mut self.0).poll_write(cx, buf) 491 | } 492 | 493 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 494 | Pin::new(&mut self.0).poll_flush(cx) 495 | } 496 | 497 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 498 | Pin::new(&mut self.0).poll_close(cx) 499 | } 500 | } 501 | 502 | #[cfg(unix)] 503 | impl AsRawFd for ChildStdin { 504 | fn as_raw_fd(&self) -> RawFd { 505 | self.0.as_raw_fd() 506 | } 507 | } 508 | 509 | #[cfg(unix)] 510 | impl AsFd for ChildStdin { 511 | fn as_fd(&self) -> BorrowedFd<'_> { 512 | self.0.as_fd() 513 | } 514 | } 515 | 516 | #[cfg(unix)] 517 | impl TryFrom for OwnedFd { 518 | type Error = io::Error; 519 | 520 | fn try_from(value: ChildStdin) -> Result { 521 | value.0.try_into() 522 | } 523 | } 524 | 525 | // TODO(notgull): Add mirroring AsRawHandle impls for all of the child handles 526 | // 527 | // at the moment this is pretty hard to do because of how they're wrapped in 528 | // Unblock, meaning that we can't always access the underlying handle. async-fs 529 | // gets around this by putting the handle in an Arc, but there's still some decision 530 | // to be made about how to handle this (no pun intended) 531 | 532 | /// A handle to a child process's standard output (stdout). 533 | /// 534 | /// When a [`ChildStdout`] is dropped, the underlying handle gets closed. 535 | #[derive(Debug)] 536 | pub struct ChildStdout( 537 | #[cfg(windows)] Unblock, 538 | #[cfg(unix)] Async, 539 | ); 540 | 541 | impl ChildStdout { 542 | /// Convert async_process::ChildStdout into std::process::Stdio. 543 | /// 544 | /// You can use it to associate to the next process. 545 | /// 546 | /// # Examples 547 | /// 548 | /// ```no_run 549 | /// # futures_lite::future::block_on(async { 550 | /// use async_process::Command; 551 | /// use std::process::Stdio; 552 | /// use std::io::Read; 553 | /// use futures_lite::AsyncReadExt; 554 | /// 555 | /// let mut ls_child = Command::new("ls").stdout(Stdio::piped()).spawn()?; 556 | /// let stdio:Stdio = ls_child.stdout.take().unwrap().into_stdio().await?; 557 | /// 558 | /// let mut echo_child = Command::new("echo").stdin(stdio).stdout(Stdio::piped()).spawn()?; 559 | /// let mut buf = vec![]; 560 | /// echo_child.stdout.take().unwrap().read(&mut buf).await; 561 | /// # std::io::Result::Ok(()) }); 562 | /// ``` 563 | pub async fn into_stdio(self) -> io::Result { 564 | cfg_if::cfg_if! { 565 | if #[cfg(windows)] { 566 | Ok(self.0.into_inner().await.into()) 567 | } else if #[cfg(unix)] { 568 | let child_stdout = self.0.into_inner()?; 569 | blocking_fd(rustix::fd::AsFd::as_fd(&child_stdout))?; 570 | Ok(child_stdout.into()) 571 | } 572 | } 573 | } 574 | } 575 | 576 | impl io::AsyncRead for ChildStdout { 577 | fn poll_read( 578 | mut self: Pin<&mut Self>, 579 | cx: &mut Context<'_>, 580 | buf: &mut [u8], 581 | ) -> Poll> { 582 | Pin::new(&mut self.0).poll_read(cx, buf) 583 | } 584 | } 585 | 586 | #[cfg(unix)] 587 | impl AsRawFd for ChildStdout { 588 | fn as_raw_fd(&self) -> RawFd { 589 | self.0.as_raw_fd() 590 | } 591 | } 592 | 593 | #[cfg(unix)] 594 | impl AsFd for ChildStdout { 595 | fn as_fd(&self) -> BorrowedFd<'_> { 596 | self.0.as_fd() 597 | } 598 | } 599 | 600 | #[cfg(unix)] 601 | impl TryFrom for OwnedFd { 602 | type Error = io::Error; 603 | 604 | fn try_from(value: ChildStdout) -> Result { 605 | value.0.try_into() 606 | } 607 | } 608 | 609 | /// A handle to a child process's standard error (stderr). 610 | /// 611 | /// When a [`ChildStderr`] is dropped, the underlying handle gets closed. 612 | #[derive(Debug)] 613 | pub struct ChildStderr( 614 | #[cfg(windows)] Unblock, 615 | #[cfg(unix)] Async, 616 | ); 617 | 618 | impl ChildStderr { 619 | /// Convert async_process::ChildStderr into std::process::Stdio. 620 | /// 621 | /// You can use it to associate to the next process. 622 | /// 623 | /// # Examples 624 | /// 625 | /// ```no_run 626 | /// # futures_lite::future::block_on(async { 627 | /// use async_process::Command; 628 | /// use std::process::Stdio; 629 | /// 630 | /// let mut ls_child = Command::new("ls").arg("x").stderr(Stdio::piped()).spawn()?; 631 | /// let stdio:Stdio = ls_child.stderr.take().unwrap().into_stdio().await?; 632 | /// 633 | /// let mut echo_child = Command::new("echo").stdin(stdio).spawn()?; 634 | /// # std::io::Result::Ok(()) }); 635 | /// ``` 636 | pub async fn into_stdio(self) -> io::Result { 637 | cfg_if::cfg_if! { 638 | if #[cfg(windows)] { 639 | Ok(self.0.into_inner().await.into()) 640 | } else if #[cfg(unix)] { 641 | let child_stderr = self.0.into_inner()?; 642 | blocking_fd(rustix::fd::AsFd::as_fd(&child_stderr))?; 643 | Ok(child_stderr.into()) 644 | } 645 | } 646 | } 647 | } 648 | 649 | impl io::AsyncRead for ChildStderr { 650 | fn poll_read( 651 | mut self: Pin<&mut Self>, 652 | cx: &mut Context<'_>, 653 | buf: &mut [u8], 654 | ) -> Poll> { 655 | Pin::new(&mut self.0).poll_read(cx, buf) 656 | } 657 | } 658 | 659 | #[cfg(unix)] 660 | impl AsRawFd for ChildStderr { 661 | fn as_raw_fd(&self) -> RawFd { 662 | self.0.as_raw_fd() 663 | } 664 | } 665 | 666 | #[cfg(unix)] 667 | impl AsFd for ChildStderr { 668 | fn as_fd(&self) -> BorrowedFd<'_> { 669 | self.0.as_fd() 670 | } 671 | } 672 | 673 | #[cfg(unix)] 674 | impl TryFrom for OwnedFd { 675 | type Error = io::Error; 676 | 677 | fn try_from(value: ChildStderr) -> Result { 678 | value.0.try_into() 679 | } 680 | } 681 | 682 | /// Runs the driver for the asynchronous processes. 683 | /// 684 | /// This future takes control of global structures related to driving [`Child`]ren and reaping 685 | /// zombie processes. These responsibilities include listening for the `SIGCHLD` signal and 686 | /// making sure zombie processes are successfully waited on. 687 | /// 688 | /// If multiple tasks run `driver()` at once, only one will actually drive the reaper; the other 689 | /// ones will just sleep. If a task that is driving the reaper is dropped, a previously sleeping 690 | /// task will take over. If all tasks driving the reaper are dropped, the "async-process" thread 691 | /// will be spawned. The "async-process" thread just blocks on this future and will automatically 692 | /// be spawned if no tasks are driving the reaper once a [`Child`] is created. 693 | /// 694 | /// This future will never complete. It is intended to be ran on a background task in your 695 | /// executor of choice. 696 | /// 697 | /// # Examples 698 | /// 699 | /// ```no_run 700 | /// use async_executor::Executor; 701 | /// use async_process::{driver, Command}; 702 | /// 703 | /// # futures_lite::future::block_on(async { 704 | /// // Create an executor and run on it. 705 | /// let ex = Executor::new(); 706 | /// ex.run(async { 707 | /// // Run the driver future in the background. 708 | /// ex.spawn(driver()).detach(); 709 | /// 710 | /// // Run a command. 711 | /// Command::new("ls").output().await.ok(); 712 | /// }).await; 713 | /// # }); 714 | /// ``` 715 | #[allow(clippy::manual_async_fn)] 716 | #[inline] 717 | pub fn driver() -> impl Future + Send + 'static { 718 | async { 719 | // Get the reaper. 720 | let reaper = Reaper::get(); 721 | 722 | // Make sure the reaper knows we're driving it. 723 | reaper.drivers.fetch_add(1, Ordering::SeqCst); 724 | 725 | // Decrement the driver count when this future is dropped. 726 | let _guard = CallOnDrop(|| { 727 | let prev_count = reaper.drivers.fetch_sub(1, Ordering::SeqCst); 728 | 729 | // If this was the last driver, and there are still resources actively using the 730 | // reaper, make sure that there is a thread driving the reaper. 731 | if prev_count == 1 732 | && (reaper.child_count.load(Ordering::SeqCst) > 0 || reaper.sys.has_zombies()) 733 | { 734 | reaper.ensure_driven(); 735 | } 736 | }); 737 | 738 | // Acquire the reaper lock and start polling the SIGCHLD event. 739 | let guard = reaper.sys.lock().await; 740 | reaper.sys.reap(guard).await 741 | } 742 | } 743 | 744 | /// A builder for spawning processes. 745 | /// 746 | /// # Examples 747 | /// 748 | /// ```no_run 749 | /// # futures_lite::future::block_on(async { 750 | /// use async_process::Command; 751 | /// 752 | /// let output = if cfg!(target_os = "windows") { 753 | /// Command::new("cmd").args(&["/C", "echo hello"]).output().await? 754 | /// } else { 755 | /// Command::new("sh").arg("-c").arg("echo hello").output().await? 756 | /// }; 757 | /// # std::io::Result::Ok(()) }); 758 | /// ``` 759 | pub struct Command { 760 | inner: std::process::Command, 761 | stdin: bool, 762 | stdout: bool, 763 | stderr: bool, 764 | reap_on_drop: bool, 765 | kill_on_drop: bool, 766 | } 767 | 768 | impl Command { 769 | /// Constructs a new [`Command`] for launching `program`. 770 | /// 771 | /// The initial configuration (the working directory and environment variables) is inherited 772 | /// from the current process. 773 | /// 774 | /// # Examples 775 | /// 776 | /// ``` 777 | /// use async_process::Command; 778 | /// 779 | /// let mut cmd = Command::new("ls"); 780 | /// ``` 781 | pub fn new>(program: S) -> Command { 782 | Self::from(std::process::Command::new(program)) 783 | } 784 | 785 | /// Adds a single argument to pass to the program. 786 | /// 787 | /// # Examples 788 | /// 789 | /// ``` 790 | /// use async_process::Command; 791 | /// 792 | /// let mut cmd = Command::new("echo"); 793 | /// cmd.arg("hello"); 794 | /// cmd.arg("world"); 795 | /// ``` 796 | pub fn arg>(&mut self, arg: S) -> &mut Command { 797 | self.inner.arg(arg); 798 | self 799 | } 800 | 801 | /// Adds multiple arguments to pass to the program. 802 | /// 803 | /// # Examples 804 | /// 805 | /// ``` 806 | /// use async_process::Command; 807 | /// 808 | /// let mut cmd = Command::new("echo"); 809 | /// cmd.args(&["hello", "world"]); 810 | /// ``` 811 | pub fn args(&mut self, args: I) -> &mut Command 812 | where 813 | I: IntoIterator, 814 | S: AsRef, 815 | { 816 | self.inner.args(args); 817 | self 818 | } 819 | 820 | /// Configures an environment variable for the new process. 821 | /// 822 | /// Note that environment variable names are case-insensitive (but case-preserving) on Windows, 823 | /// and case-sensitive on all other platforms. 824 | /// 825 | /// # Examples 826 | /// 827 | /// ``` 828 | /// use async_process::Command; 829 | /// 830 | /// let mut cmd = Command::new("ls"); 831 | /// cmd.env("PATH", "/bin"); 832 | /// ``` 833 | pub fn env(&mut self, key: K, val: V) -> &mut Command 834 | where 835 | K: AsRef, 836 | V: AsRef, 837 | { 838 | self.inner.env(key, val); 839 | self 840 | } 841 | 842 | /// Configures multiple environment variables for the new process. 843 | /// 844 | /// Note that environment variable names are case-insensitive (but case-preserving) on Windows, 845 | /// and case-sensitive on all other platforms. 846 | /// 847 | /// # Examples 848 | /// 849 | /// ``` 850 | /// use async_process::Command; 851 | /// 852 | /// let mut cmd = Command::new("ls"); 853 | /// cmd.envs(vec![("PATH", "/bin"), ("TERM", "xterm-256color")]); 854 | /// ``` 855 | pub fn envs(&mut self, vars: I) -> &mut Command 856 | where 857 | I: IntoIterator, 858 | K: AsRef, 859 | V: AsRef, 860 | { 861 | self.inner.envs(vars); 862 | self 863 | } 864 | 865 | /// Removes an environment variable mapping. 866 | /// 867 | /// # Examples 868 | /// 869 | /// ``` 870 | /// use async_process::Command; 871 | /// 872 | /// let mut cmd = Command::new("ls"); 873 | /// cmd.env_remove("PATH"); 874 | /// ``` 875 | pub fn env_remove>(&mut self, key: K) -> &mut Command { 876 | self.inner.env_remove(key); 877 | self 878 | } 879 | 880 | /// Removes all environment variable mappings. 881 | /// 882 | /// # Examples 883 | /// 884 | /// ``` 885 | /// use async_process::Command; 886 | /// 887 | /// let mut cmd = Command::new("ls"); 888 | /// cmd.env_clear(); 889 | /// ``` 890 | pub fn env_clear(&mut self) -> &mut Command { 891 | self.inner.env_clear(); 892 | self 893 | } 894 | 895 | /// Configures the working directory for the new process. 896 | /// 897 | /// # Examples 898 | /// 899 | /// ``` 900 | /// use async_process::Command; 901 | /// 902 | /// let mut cmd = Command::new("ls"); 903 | /// cmd.current_dir("/"); 904 | /// ``` 905 | pub fn current_dir>(&mut self, dir: P) -> &mut Command { 906 | self.inner.current_dir(dir); 907 | self 908 | } 909 | 910 | /// Configures the standard input (stdin) for the new process. 911 | /// 912 | /// # Examples 913 | /// 914 | /// ``` 915 | /// use async_process::{Command, Stdio}; 916 | /// 917 | /// let mut cmd = Command::new("cat"); 918 | /// cmd.stdin(Stdio::null()); 919 | /// ``` 920 | pub fn stdin>(&mut self, cfg: T) -> &mut Command { 921 | self.stdin = true; 922 | self.inner.stdin(cfg); 923 | self 924 | } 925 | 926 | /// Configures the standard output (stdout) for the new process. 927 | /// 928 | /// # Examples 929 | /// 930 | /// ``` 931 | /// use async_process::{Command, Stdio}; 932 | /// 933 | /// let mut cmd = Command::new("ls"); 934 | /// cmd.stdout(Stdio::piped()); 935 | /// ``` 936 | pub fn stdout>(&mut self, cfg: T) -> &mut Command { 937 | self.stdout = true; 938 | self.inner.stdout(cfg); 939 | self 940 | } 941 | 942 | /// Configures the standard error (stderr) for the new process. 943 | /// 944 | /// # Examples 945 | /// 946 | /// ``` 947 | /// use async_process::{Command, Stdio}; 948 | /// 949 | /// let mut cmd = Command::new("ls"); 950 | /// cmd.stderr(Stdio::piped()); 951 | /// ``` 952 | pub fn stderr>(&mut self, cfg: T) -> &mut Command { 953 | self.stderr = true; 954 | self.inner.stderr(cfg); 955 | self 956 | } 957 | 958 | /// Configures whether to reap the zombie process when [`Child`] is dropped. 959 | /// 960 | /// When the process finishes, it becomes a "zombie" and some resources associated with it 961 | /// remain until [`Child::try_status()`], [`Child::status()`], or [`Child::output()`] collects 962 | /// its exit code. 963 | /// 964 | /// If its exit code is never collected, the resources may leak forever. This crate has a 965 | /// background thread named "async-process" that collects such "zombie" processes and then 966 | /// "reaps" them, thus preventing the resource leaks. 967 | /// 968 | /// The default value of this option is `true`. 969 | /// 970 | /// # Examples 971 | /// 972 | /// ``` 973 | /// use async_process::{Command, Stdio}; 974 | /// 975 | /// let mut cmd = Command::new("cat"); 976 | /// cmd.reap_on_drop(false); 977 | /// ``` 978 | pub fn reap_on_drop(&mut self, reap_on_drop: bool) -> &mut Command { 979 | self.reap_on_drop = reap_on_drop; 980 | self 981 | } 982 | 983 | /// Configures whether to kill the process when [`Child`] is dropped. 984 | /// 985 | /// The default value of this option is `false`. 986 | /// 987 | /// # Examples 988 | /// 989 | /// ``` 990 | /// use async_process::{Command, Stdio}; 991 | /// 992 | /// let mut cmd = Command::new("cat"); 993 | /// cmd.kill_on_drop(true); 994 | /// ``` 995 | pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Command { 996 | self.kill_on_drop = kill_on_drop; 997 | self 998 | } 999 | 1000 | /// Executes the command and returns the [`Child`] handle to it. 1001 | /// 1002 | /// If not configured, stdin, stdout and stderr will be set to [`Stdio::inherit()`]. 1003 | /// 1004 | /// # Examples 1005 | /// 1006 | /// ```no_run 1007 | /// # futures_lite::future::block_on(async { 1008 | /// use async_process::Command; 1009 | /// 1010 | /// let child = Command::new("ls").spawn()?; 1011 | /// # std::io::Result::Ok(()) }); 1012 | /// ``` 1013 | pub fn spawn(&mut self) -> io::Result { 1014 | if !self.stdin { 1015 | self.inner.stdin(Stdio::inherit()); 1016 | } 1017 | if !self.stdout { 1018 | self.inner.stdout(Stdio::inherit()); 1019 | } 1020 | if !self.stderr { 1021 | self.inner.stderr(Stdio::inherit()); 1022 | } 1023 | 1024 | Child::new(self) 1025 | } 1026 | 1027 | /// Executes the command, waits for it to exit, and returns the exit status. 1028 | /// 1029 | /// If not configured, stdin, stdout and stderr will be set to [`Stdio::inherit()`]. 1030 | /// 1031 | /// # Examples 1032 | /// 1033 | /// ```no_run 1034 | /// # futures_lite::future::block_on(async { 1035 | /// use async_process::Command; 1036 | /// 1037 | /// let status = Command::new("cp") 1038 | /// .arg("a.txt") 1039 | /// .arg("b.txt") 1040 | /// .status() 1041 | /// .await?; 1042 | /// # std::io::Result::Ok(()) }); 1043 | /// ``` 1044 | pub fn status(&mut self) -> impl Future> { 1045 | let child = self.spawn(); 1046 | async { child?.status().await } 1047 | } 1048 | 1049 | /// Executes the command and collects its output. 1050 | /// 1051 | /// If not configured, stdin will be set to [`Stdio::null()`], and stdout and stderr will be 1052 | /// set to [`Stdio::piped()`]. 1053 | /// 1054 | /// # Examples 1055 | /// 1056 | /// ```no_run 1057 | /// # futures_lite::future::block_on(async { 1058 | /// use async_process::Command; 1059 | /// 1060 | /// let output = Command::new("cat") 1061 | /// .arg("a.txt") 1062 | /// .output() 1063 | /// .await?; 1064 | /// # std::io::Result::Ok(()) }); 1065 | /// ``` 1066 | pub fn output(&mut self) -> impl Future> { 1067 | if !self.stdin { 1068 | self.inner.stdin(Stdio::null()); 1069 | } 1070 | if !self.stdout { 1071 | self.inner.stdout(Stdio::piped()); 1072 | } 1073 | if !self.stderr { 1074 | self.inner.stderr(Stdio::piped()); 1075 | } 1076 | 1077 | let child = Child::new(self); 1078 | async { child?.output().await } 1079 | } 1080 | } 1081 | 1082 | impl From for Command { 1083 | fn from(inner: std::process::Command) -> Self { 1084 | Self { 1085 | inner, 1086 | stdin: false, 1087 | stdout: false, 1088 | stderr: false, 1089 | reap_on_drop: true, 1090 | kill_on_drop: false, 1091 | } 1092 | } 1093 | } 1094 | 1095 | impl fmt::Debug for Command { 1096 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1097 | if f.alternate() { 1098 | f.debug_struct("Command") 1099 | .field("inner", &self.inner) 1100 | .field("stdin", &self.stdin) 1101 | .field("stdout", &self.stdout) 1102 | .field("stderr", &self.stderr) 1103 | .field("reap_on_drop", &self.reap_on_drop) 1104 | .field("kill_on_drop", &self.kill_on_drop) 1105 | .finish() 1106 | } else { 1107 | // Stdlib outputs command-line in Debug for Command. This does the 1108 | // same, if not in "alternate" (long pretty-printed) mode. 1109 | // This is useful for logs, for example. 1110 | fmt::Debug::fmt(&self.inner, f) 1111 | } 1112 | } 1113 | } 1114 | 1115 | /// Moves `Fd` out of non-blocking mode. 1116 | #[cfg(unix)] 1117 | fn blocking_fd(fd: rustix::fd::BorrowedFd<'_>) -> io::Result<()> { 1118 | cfg_if::cfg_if! { 1119 | // ioctl(FIONBIO) sets the flag atomically, but we use this only on Linux 1120 | // for now, as with the standard library, because it seems to behave 1121 | // differently depending on the platform. 1122 | // https://github.com/rust-lang/rust/commit/efeb42be2837842d1beb47b51bb693c7474aba3d 1123 | // https://github.com/libuv/libuv/blob/e9d91fccfc3e5ff772d5da90e1c4a24061198ca0/src/unix/poll.c#L78-L80 1124 | // https://github.com/tokio-rs/mio/commit/0db49f6d5caf54b12176821363d154384357e70a 1125 | if #[cfg(target_os = "linux")] { 1126 | rustix::io::ioctl_fionbio(fd, false)?; 1127 | } else { 1128 | let previous = rustix::fs::fcntl_getfl(fd)?; 1129 | let new = previous & !rustix::fs::OFlags::NONBLOCK; 1130 | if new != previous { 1131 | rustix::fs::fcntl_setfl(fd, new)?; 1132 | } 1133 | } 1134 | } 1135 | Ok(()) 1136 | } 1137 | 1138 | struct CallOnDrop(F); 1139 | 1140 | impl Drop for CallOnDrop { 1141 | fn drop(&mut self) { 1142 | (self.0)(); 1143 | } 1144 | } 1145 | 1146 | #[cfg(test)] 1147 | mod test { 1148 | #[test] 1149 | fn polled_driver() { 1150 | use super::{driver, Command}; 1151 | use futures_lite::future; 1152 | use futures_lite::prelude::*; 1153 | 1154 | let is_thread_spawned = 1155 | || super::DRIVER_THREAD_SPAWNED.load(std::sync::atomic::Ordering::SeqCst); 1156 | 1157 | #[cfg(unix)] 1158 | fn command() -> Command { 1159 | let mut cmd = Command::new("sh"); 1160 | cmd.arg("-c").arg("echo hello"); 1161 | cmd 1162 | } 1163 | 1164 | #[cfg(windows)] 1165 | fn command() -> Command { 1166 | let mut cmd = Command::new("cmd"); 1167 | cmd.arg("/C").arg("echo hello"); 1168 | cmd 1169 | } 1170 | 1171 | #[cfg(unix)] 1172 | const OUTPUT: &[u8] = b"hello\n"; 1173 | #[cfg(windows)] 1174 | const OUTPUT: &[u8] = b"hello\r\n"; 1175 | 1176 | future::block_on(async { 1177 | // Thread should not be spawned off the bat. 1178 | assert!(!is_thread_spawned()); 1179 | 1180 | // Spawn a driver. 1181 | let mut driver1 = Box::pin(driver()); 1182 | future::poll_once(&mut driver1).await; 1183 | assert!(!is_thread_spawned()); 1184 | 1185 | // We should be able to run the driver in parallel with a process future. 1186 | async { 1187 | (&mut driver1).await; 1188 | } 1189 | .or(async { 1190 | let output = command().output().await.unwrap(); 1191 | assert_eq!(output.stdout, OUTPUT); 1192 | }) 1193 | .await; 1194 | assert!(!is_thread_spawned()); 1195 | 1196 | // Spawn a second driver. 1197 | let mut driver2 = Box::pin(driver()); 1198 | future::poll_once(&mut driver2).await; 1199 | assert!(!is_thread_spawned()); 1200 | 1201 | // Poll both drivers in parallel. 1202 | async { 1203 | (&mut driver1).await; 1204 | } 1205 | .or(async { 1206 | (&mut driver2).await; 1207 | }) 1208 | .or(async { 1209 | let output = command().output().await.unwrap(); 1210 | assert_eq!(output.stdout, OUTPUT); 1211 | }) 1212 | .await; 1213 | assert!(!is_thread_spawned()); 1214 | 1215 | // Once one is dropped, the other should take over. 1216 | drop(driver1); 1217 | assert!(!is_thread_spawned()); 1218 | 1219 | // Poll driver2 in parallel with a process future. 1220 | async { 1221 | (&mut driver2).await; 1222 | } 1223 | .or(async { 1224 | let output = command().output().await.unwrap(); 1225 | assert_eq!(output.stdout, OUTPUT); 1226 | }) 1227 | .await; 1228 | assert!(!is_thread_spawned()); 1229 | 1230 | // Once driver2 is dropped, the thread should not be spawned, as there are no active 1231 | // child processes.. 1232 | drop(driver2); 1233 | assert!(!is_thread_spawned()); 1234 | 1235 | // We should now be able to poll the process future independently, it will spawn the 1236 | // thread. 1237 | let output = command().output().await.unwrap(); 1238 | assert_eq!(output.stdout, OUTPUT); 1239 | assert!(is_thread_spawned()); 1240 | }); 1241 | } 1242 | } 1243 | -------------------------------------------------------------------------------- /src/reaper/mod.rs: -------------------------------------------------------------------------------- 1 | //! The underlying system reaper. 2 | //! 3 | //! There are two backends: 4 | //! 5 | //! - signal, which waits for SIGCHLD. 6 | //! - wait, which waits directly on a process handle. 7 | //! 8 | //! "wait" is preferred, but is not available on all supported Linuxes. So we 9 | //! test to see if pidfd is supported first. If it is, we use wait. If not, we use 10 | //! signal. 11 | 12 | #![allow(irrefutable_let_patterns)] 13 | 14 | /// Enable the waiting reaper. 15 | #[cfg(any(windows, target_os = "linux"))] 16 | macro_rules! cfg_wait { 17 | ($($tt:tt)*) => {$($tt)*}; 18 | } 19 | 20 | /// Enable the waiting reaper. 21 | #[cfg(not(any(windows, target_os = "linux")))] 22 | macro_rules! cfg_wait { 23 | ($($tt:tt)*) => {}; 24 | } 25 | 26 | /// Enable signals. 27 | #[cfg(not(windows))] 28 | macro_rules! cfg_signal { 29 | ($($tt:tt)*) => {$($tt)*}; 30 | } 31 | 32 | /// Enable signals. 33 | #[cfg(windows)] 34 | macro_rules! cfg_signal { 35 | ($($tt:tt)*) => {}; 36 | } 37 | 38 | cfg_wait! { 39 | mod wait; 40 | } 41 | 42 | cfg_signal! { 43 | mod signal; 44 | } 45 | 46 | use std::io; 47 | use std::sync::Mutex; 48 | 49 | /// The underlying system reaper. 50 | pub(crate) enum Reaper { 51 | #[cfg(any(windows, target_os = "linux"))] 52 | /// The reaper based on the wait backend. 53 | Wait(wait::Reaper), 54 | 55 | /// The reaper based on the signal backend. 56 | #[cfg(not(windows))] 57 | Signal(signal::Reaper), 58 | } 59 | 60 | /// The wrapper around a child. 61 | pub(crate) enum ChildGuard { 62 | #[cfg(any(windows, target_os = "linux"))] 63 | /// The child guard based on the wait backend. 64 | Wait(wait::ChildGuard), 65 | 66 | /// The child guard based on the signal backend. 67 | #[cfg(not(windows))] 68 | Signal(signal::ChildGuard), 69 | } 70 | 71 | /// A lock on the reaper. 72 | pub(crate) enum Lock { 73 | #[cfg(any(windows, target_os = "linux"))] 74 | /// The wait-based reaper needs no lock. 75 | Wait, 76 | 77 | /// The lock for the signal-based reaper. 78 | #[cfg(not(windows))] 79 | Signal(signal::Lock), 80 | } 81 | 82 | impl Reaper { 83 | /// Create a new reaper. 84 | pub(crate) fn new() -> Self { 85 | cfg_wait! { 86 | if wait::available() && !cfg!(async_process_force_signal_backend) { 87 | return Self::Wait(wait::Reaper::new()); 88 | } 89 | } 90 | 91 | // Return the signal-based reaper. 92 | cfg_signal! { 93 | return Self::Signal(signal::Reaper::new()); 94 | } 95 | 96 | #[allow(unreachable_code)] 97 | { 98 | panic!("neither the signal backend nor the waiter backend is available") 99 | } 100 | } 101 | 102 | /// Lock the driver thread. 103 | /// 104 | /// This makes it so only one thread can reap at once. 105 | pub(crate) async fn lock(&'static self) -> Lock { 106 | cfg_wait! { 107 | if let Self::Wait(_this) = self { 108 | // No locking needed. 109 | return Lock::Wait; 110 | } 111 | } 112 | 113 | cfg_signal! { 114 | if let Self::Signal(this) = self { 115 | // We need to lock. 116 | return Lock::Signal(this.lock().await); 117 | } 118 | } 119 | 120 | unreachable!() 121 | } 122 | 123 | /// Reap zombie processes forever. 124 | pub(crate) async fn reap(&'static self, lock: Lock) -> ! { 125 | cfg_wait! { 126 | if let (Self::Wait(this), Lock::Wait) = (self, &lock) { 127 | this.reap().await; 128 | } 129 | } 130 | 131 | cfg_signal! { 132 | if let (Self::Signal(this), Lock::Signal(lock)) = (self, lock) { 133 | this.reap(lock).await; 134 | } 135 | } 136 | 137 | unreachable!() 138 | } 139 | 140 | /// Register a child into this reaper. 141 | pub(crate) fn register(&'static self, child: std::process::Child) -> io::Result { 142 | cfg_wait! { 143 | if let Self::Wait(this) = self { 144 | return this.register(child).map(ChildGuard::Wait); 145 | } 146 | } 147 | 148 | cfg_signal! { 149 | if let Self::Signal(this) = self { 150 | return this.register(child).map(ChildGuard::Signal); 151 | } 152 | } 153 | 154 | unreachable!() 155 | } 156 | 157 | /// Wait for the inner child to complete. 158 | pub(crate) async fn status( 159 | &'static self, 160 | child: &Mutex, 161 | ) -> io::Result { 162 | cfg_wait! { 163 | if let Self::Wait(this) = self { 164 | return this.status(child).await; 165 | } 166 | } 167 | 168 | cfg_signal! { 169 | if let Self::Signal(this) = self { 170 | return this.status(child).await; 171 | } 172 | } 173 | 174 | unreachable!() 175 | } 176 | 177 | /// Do we have any registered zombie processes? 178 | pub(crate) fn has_zombies(&'static self) -> bool { 179 | cfg_wait! { 180 | if let Self::Wait(this) = self { 181 | return this.has_zombies(); 182 | } 183 | } 184 | 185 | cfg_signal! { 186 | if let Self::Signal(this) = self { 187 | return this.has_zombies(); 188 | } 189 | } 190 | 191 | unreachable!() 192 | } 193 | } 194 | 195 | impl ChildGuard { 196 | /// Get a reference to the inner process. 197 | pub(crate) fn get_mut(&mut self) -> &mut std::process::Child { 198 | cfg_wait! { 199 | if let Self::Wait(this) = self { 200 | return this.get_mut(); 201 | } 202 | } 203 | 204 | cfg_signal! { 205 | if let Self::Signal(this) = self { 206 | return this.get_mut(); 207 | } 208 | } 209 | 210 | unreachable!() 211 | } 212 | 213 | /// Start reaping this child process. 214 | pub(crate) fn reap(&mut self, reaper: &'static Reaper) { 215 | cfg_wait! { 216 | if let (Self::Wait(this), Reaper::Wait(reaper)) = (&mut *self, reaper) { 217 | this.reap(reaper); 218 | return; 219 | } 220 | } 221 | 222 | cfg_signal! { 223 | if let (Self::Signal(this), Reaper::Signal(reaper)) = (self, reaper) { 224 | this.reap(reaper); 225 | return; 226 | } 227 | } 228 | 229 | unreachable!() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/reaper/signal.rs: -------------------------------------------------------------------------------- 1 | //! A version of the reaper that waits for a signal to check for process progress. 2 | 3 | use async_lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; 4 | use async_signal::{Signal, Signals}; 5 | use event_listener::Event; 6 | use futures_lite::{future, prelude::*}; 7 | 8 | use std::io; 9 | use std::mem; 10 | use std::sync::Mutex; 11 | 12 | pub(crate) type Lock = AsyncMutexGuard<'static, ()>; 13 | 14 | /// The zombie process reaper. 15 | pub(crate) struct Reaper { 16 | /// An event delivered every time the SIGCHLD signal occurs. 17 | sigchld: Event, 18 | 19 | /// The list of zombie processes. 20 | zombies: Mutex>, 21 | 22 | /// The pipe that delivers signal notifications. 23 | pipe: Pipe, 24 | 25 | /// Locking this mutex indicates that we are polling the SIGCHLD event. 26 | driver_guard: AsyncMutex<()>, 27 | } 28 | 29 | impl Reaper { 30 | /// Create a new reaper. 31 | pub(crate) fn new() -> Self { 32 | Reaper { 33 | sigchld: Event::new(), 34 | zombies: Mutex::new(Vec::new()), 35 | pipe: Pipe::new().expect("cannot create SIGCHLD pipe"), 36 | driver_guard: AsyncMutex::new(()), 37 | } 38 | } 39 | 40 | /// Lock the driver thread. 41 | pub(crate) async fn lock(&self) -> AsyncMutexGuard<'_, ()> { 42 | self.driver_guard.lock().await 43 | } 44 | 45 | /// Reap zombie processes forever. 46 | pub(crate) async fn reap(&'static self, _driver_guard: async_lock::MutexGuard<'_, ()>) -> ! { 47 | loop { 48 | // Wait for the next SIGCHLD signal. 49 | self.pipe.wait().await; 50 | 51 | // Notify all listeners waiting on the SIGCHLD event. 52 | self.sigchld.notify(usize::MAX); 53 | 54 | // Reap zombie processes, but make sure we don't hold onto the lock for too long! 55 | let mut zombies = mem::take(&mut *self.zombies.lock().unwrap()); 56 | let mut i = 0; 57 | 'reap_zombies: loop { 58 | for _ in 0..50 { 59 | if i >= zombies.len() { 60 | break 'reap_zombies; 61 | } 62 | 63 | if let Ok(None) = zombies[i].try_wait() { 64 | i += 1; 65 | } else { 66 | #[allow(clippy::zombie_processes)] // removed only when process done or errored 67 | zombies.swap_remove(i); 68 | } 69 | } 70 | 71 | // Be a good citizen; yield if there are a lot of processes. 72 | // 73 | // After we yield, check if there are more zombie processes. 74 | future::yield_now().await; 75 | zombies.append(&mut self.zombies.lock().unwrap()); 76 | } 77 | 78 | // Put zombie processes back. 79 | self.zombies.lock().unwrap().append(&mut zombies); 80 | } 81 | } 82 | 83 | /// Register a process with this reaper. 84 | pub(crate) fn register(&'static self, child: std::process::Child) -> io::Result { 85 | self.pipe.register(&child)?; 86 | Ok(ChildGuard { inner: Some(child) }) 87 | } 88 | 89 | /// Wait for an event to occur for a child process. 90 | pub(crate) async fn status( 91 | &'static self, 92 | child: &Mutex, 93 | ) -> io::Result { 94 | loop { 95 | // Wait on the child process. 96 | if let Some(status) = child.lock().unwrap().get_mut().try_wait()? { 97 | return Ok(status); 98 | } 99 | 100 | // Start listening. 101 | event_listener::listener!(self.sigchld => listener); 102 | 103 | // Try again. 104 | if let Some(status) = child.lock().unwrap().get_mut().try_wait()? { 105 | return Ok(status); 106 | } 107 | 108 | // Wait on the listener. 109 | listener.await; 110 | } 111 | } 112 | 113 | /// Do we have any registered zombie processes? 114 | pub(crate) fn has_zombies(&'static self) -> bool { 115 | !self 116 | .zombies 117 | .lock() 118 | .unwrap_or_else(|x| x.into_inner()) 119 | .is_empty() 120 | } 121 | } 122 | 123 | /// The wrapper around the child. 124 | pub(crate) struct ChildGuard { 125 | inner: Option, 126 | } 127 | 128 | impl ChildGuard { 129 | /// Get a mutable reference to the inner child. 130 | pub(crate) fn get_mut(&mut self) -> &mut std::process::Child { 131 | self.inner.as_mut().unwrap() 132 | } 133 | 134 | /// Begin the reaping process for this child. 135 | pub(crate) fn reap(&mut self, reaper: &'static Reaper) { 136 | if let Ok(None) = self.get_mut().try_wait() { 137 | reaper 138 | .zombies 139 | .lock() 140 | .unwrap() 141 | .push(self.inner.take().unwrap()); 142 | } 143 | } 144 | } 145 | 146 | /// Waits for the next SIGCHLD signal. 147 | struct Pipe { 148 | /// The iterator over SIGCHLD signals. 149 | signals: Signals, 150 | } 151 | 152 | impl Pipe { 153 | /// Creates a new pipe. 154 | fn new() -> io::Result { 155 | Ok(Pipe { 156 | signals: Signals::new(Some(Signal::Child))?, 157 | }) 158 | } 159 | 160 | /// Waits for the next SIGCHLD signal. 161 | async fn wait(&self) { 162 | (&self.signals).next().await; 163 | } 164 | 165 | /// Register a process object into this pipe. 166 | fn register(&self, _child: &std::process::Child) -> io::Result<()> { 167 | Ok(()) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/reaper/wait.rs: -------------------------------------------------------------------------------- 1 | //! A version of the reaper that waits on some polling primitive. 2 | //! 3 | //! This uses: 4 | //! 5 | //! - pidfd on Linux 6 | //! - Waitable objects on Windows 7 | 8 | use async_channel::{Receiver, Sender}; 9 | use async_task::Runnable; 10 | use futures_lite::future; 11 | 12 | use std::io; 13 | use std::sync::atomic::{AtomicUsize, Ordering}; 14 | use std::sync::Mutex; 15 | use std::task::{Context, Poll}; 16 | 17 | /// The zombie process reaper. 18 | pub(crate) struct Reaper { 19 | /// The channel for sending new runnables. 20 | sender: Sender, 21 | 22 | /// The channel for receiving new runnables. 23 | recv: Receiver, 24 | 25 | /// Number of zombie processes. 26 | zombies: AtomicUsize, 27 | } 28 | 29 | impl Reaper { 30 | /// Create a new reaper. 31 | pub(crate) fn new() -> Self { 32 | let (sender, recv) = async_channel::unbounded(); 33 | Self { 34 | sender, 35 | recv, 36 | zombies: AtomicUsize::new(0), 37 | } 38 | } 39 | 40 | /// Reap zombie processes forever. 41 | pub(crate) async fn reap(&'static self) -> ! { 42 | loop { 43 | // Fetch the next task. 44 | let task = match self.recv.recv().await { 45 | Ok(task) => task, 46 | Err(_) => panic!("sender should never be closed"), 47 | }; 48 | 49 | // Poll the task. 50 | task.run(); 51 | } 52 | } 53 | 54 | /// Register a child into this reaper. 55 | pub(crate) fn register(&'static self, child: std::process::Child) -> io::Result { 56 | Ok(ChildGuard { 57 | inner: Some(WaitableChild::new(child)?), 58 | }) 59 | } 60 | 61 | /// Wait for a child to complete. 62 | pub(crate) async fn status( 63 | &'static self, 64 | child: &Mutex, 65 | ) -> io::Result { 66 | future::poll_fn(|cx| { 67 | // Lock the child. 68 | let mut child = child.lock().unwrap(); 69 | 70 | // Get the inner child value. 71 | let inner = match &mut child.inner { 72 | super::ChildGuard::Wait(inner) => inner, 73 | #[cfg(not(windows))] 74 | _ => unreachable!(), 75 | }; 76 | 77 | // Poll for the next value. 78 | inner.inner.as_mut().unwrap().poll_wait(cx) 79 | }) 80 | .await 81 | } 82 | 83 | /// Do we have any registered zombie processes? 84 | pub(crate) fn has_zombies(&'static self) -> bool { 85 | self.zombies.load(Ordering::SeqCst) > 0 86 | } 87 | } 88 | 89 | /// The wrapper around the child. 90 | pub(crate) struct ChildGuard { 91 | inner: Option, 92 | } 93 | 94 | impl ChildGuard { 95 | /// Get a mutable reference to the inner child. 96 | pub(crate) fn get_mut(&mut self) -> &mut std::process::Child { 97 | self.inner.as_mut().unwrap().get_mut() 98 | } 99 | 100 | /// Begin the reaping process for this child. 101 | pub(crate) fn reap(&mut self, reaper: &'static Reaper) { 102 | // Create a future for polling this child. 103 | let future = { 104 | let mut inner = self.inner.take().unwrap(); 105 | async move { 106 | // Increment the zombie count. 107 | reaper.zombies.fetch_add(1, Ordering::Relaxed); 108 | 109 | // Decrement the zombie count once we are done. 110 | let _guard = crate::CallOnDrop(|| { 111 | reaper.zombies.fetch_sub(1, Ordering::SeqCst); 112 | }); 113 | 114 | // Wait on this child forever. 115 | let result = future::poll_fn(|cx| inner.poll_wait(cx)).await; 116 | if let Err(e) = result { 117 | tracing::error!("error while polling zombie process: {}", e); 118 | } 119 | } 120 | }; 121 | 122 | // Create a function for scheduling this future. 123 | let schedule = move |runnable| { 124 | reaper.sender.try_send(runnable).ok(); 125 | }; 126 | 127 | // Spawn the task and run it forever. 128 | let (runnable, task) = async_task::spawn(future, schedule); 129 | task.detach(); 130 | runnable.schedule(); 131 | } 132 | } 133 | 134 | cfg_if::cfg_if! { 135 | if #[cfg(target_os = "linux")] { 136 | use async_io::Async; 137 | use rustix::process; 138 | use std::os::unix::io::OwnedFd; 139 | 140 | /// Waitable version of `std::process::Child` 141 | struct WaitableChild { 142 | child: std::process::Child, 143 | handle: Async, 144 | } 145 | 146 | impl WaitableChild { 147 | fn new(child: std::process::Child) -> io::Result { 148 | let pidfd = process::pidfd_open( 149 | process::Pid::from_child(&child), 150 | process::PidfdFlags::empty() 151 | )?; 152 | 153 | Ok(Self { 154 | child, 155 | handle: Async::new(pidfd)? 156 | }) 157 | } 158 | 159 | fn get_mut(&mut self) -> &mut std::process::Child { 160 | &mut self.child 161 | } 162 | 163 | fn poll_wait(&mut self, cx: &mut Context<'_>) -> Poll> { 164 | loop { 165 | if let Some(status) = self.child.try_wait()? { 166 | return Poll::Ready(Ok(status)); 167 | } 168 | 169 | // Wait for us to become readable. 170 | futures_lite::ready!(self.handle.poll_readable(cx))?; 171 | } 172 | } 173 | } 174 | 175 | /// Tell if we are able to use this backend. 176 | pub(crate) fn available() -> bool { 177 | // Create a Pidfd for the current process and see if it works. 178 | let result = process::pidfd_open( 179 | process::getpid(), 180 | process::PidfdFlags::empty() 181 | ); 182 | 183 | // Tell if it was okay or not. 184 | result.is_ok() 185 | } 186 | } else if #[cfg(windows)] { 187 | use async_io::os::windows::Waitable; 188 | 189 | /// Waitable version of `std::process::Child`. 190 | struct WaitableChild { 191 | inner: Waitable, 192 | } 193 | 194 | impl WaitableChild { 195 | fn new(child: std::process::Child) -> io::Result { 196 | Ok(Self { 197 | inner: Waitable::new(child)? 198 | }) 199 | } 200 | 201 | fn get_mut(&mut self) -> &mut std::process::Child { 202 | // SAFETY: We never move the child out. 203 | unsafe { 204 | self.inner.get_mut() 205 | } 206 | } 207 | 208 | fn poll_wait(&mut self, cx: &mut Context<'_>) -> Poll> { 209 | loop { 210 | if let Some(status) = self.get_mut().try_wait()? { 211 | return Poll::Ready(Ok(status)); 212 | } 213 | 214 | // Wait for us to become readable. 215 | futures_lite::ready!(self.inner.poll_ready(cx))?; 216 | } 217 | } 218 | } 219 | 220 | /// Tell if we are able to use this backend. 221 | pub(crate) fn available() -> bool { 222 | true 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/unix.rs: -------------------------------------------------------------------------------- 1 | //! Unix-specific extensions. 2 | 3 | use std::ffi::OsStr; 4 | use std::io; 5 | use std::os::unix::process::CommandExt as _; 6 | 7 | use crate::Command; 8 | 9 | /// Unix-specific extensions to the [`Command`] builder. 10 | /// 11 | /// This trait is sealed: it cannot be implemented outside `async-process`. 12 | /// This is so that future additional methods are not breaking changes. 13 | pub trait CommandExt: crate::sealed::Sealed { 14 | /// Sets the child process's user ID. This translates to a 15 | /// `setuid` call in the child process. Failure in the `setuid` 16 | /// call will cause the spawn to fail. 17 | fn uid(&mut self, id: u32) -> &mut Command; 18 | 19 | /// Similar to `uid`, but sets the group ID of the child process. This has 20 | /// the same semantics as the `uid` field. 21 | fn gid(&mut self, id: u32) -> &mut Command; 22 | 23 | /// Performs all the required setup by this `Command`, followed by calling 24 | /// the `execvp` syscall. 25 | /// 26 | /// On success this function will not return, and otherwise it will return 27 | /// an error indicating why the exec (or another part of the setup of the 28 | /// `Command`) failed. 29 | /// 30 | /// `exec` not returning has the same implications as calling 31 | /// [`std::process::exit`] – no destructors on the current stack or any other 32 | /// thread’s stack will be run. Therefore, it is recommended to only call 33 | /// `exec` at a point where it is fine to not run any destructors. Note, 34 | /// that the `execvp` syscall independently guarantees that all memory is 35 | /// freed and all file descriptors with the `CLOEXEC` option (set by default 36 | /// on all file descriptors opened by the standard library) are closed. 37 | /// 38 | /// This function, unlike `spawn`, will **not** `fork` the process to create 39 | /// a new child. Like spawn, however, the default behavior for the stdio 40 | /// descriptors will be to inherited from the current process. 41 | /// 42 | /// # Notes 43 | /// 44 | /// The process may be in a "broken state" if this function returns in 45 | /// error. For example the working directory, environment variables, signal 46 | /// handling settings, various user/group information, or aspects of stdio 47 | /// file descriptors may have changed. If a "transactional spawn" is 48 | /// required to gracefully handle errors it is recommended to use the 49 | /// cross-platform `spawn` instead. 50 | fn exec(&mut self) -> io::Error; 51 | 52 | /// Set executable argument 53 | /// 54 | /// Set the first process argument, `argv[0]`, to something other than the 55 | /// default executable path. 56 | fn arg0(&mut self, arg: S) -> &mut Command 57 | where 58 | S: AsRef; 59 | } 60 | 61 | impl crate::sealed::Sealed for Command {} 62 | impl CommandExt for Command { 63 | fn uid(&mut self, id: u32) -> &mut Command { 64 | self.inner.uid(id); 65 | self 66 | } 67 | 68 | fn gid(&mut self, id: u32) -> &mut Command { 69 | self.inner.gid(id); 70 | self 71 | } 72 | 73 | fn exec(&mut self) -> io::Error { 74 | self.inner.exec() 75 | } 76 | 77 | fn arg0(&mut self, arg: S) -> &mut Command 78 | where 79 | S: AsRef, 80 | { 81 | self.inner.arg0(arg); 82 | self 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | //! Windows-specific extensions. 2 | 3 | use std::ffi::OsStr; 4 | use std::os::windows::io::{AsRawHandle, RawHandle}; 5 | use std::os::windows::process::CommandExt as _; 6 | 7 | use crate::{Child, Command}; 8 | 9 | /// Windows-specific extensions to the [`Command`] builder. 10 | /// 11 | /// This trait is sealed: it cannot be implemented outside `async-process`. 12 | /// This is so that future additional methods are not breaking changes. 13 | pub trait CommandExt: crate::sealed::Sealed { 14 | /// Sets the [process creation flags][1] to be passed to `CreateProcess`. 15 | /// 16 | /// These will always be ORed with `CREATE_UNICODE_ENVIRONMENT`. 17 | /// 18 | /// [1]: https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags 19 | fn creation_flags(&mut self, flags: u32) -> &mut Command; 20 | 21 | /// Append literal text to the command line without any quoting or escaping. 22 | /// 23 | /// This is useful for passing arguments to `cmd.exe /c`, which doesn't follow 24 | /// `CommandLineToArgvW` escaping rules. 25 | fn raw_arg>(&mut self, text_to_append_as_is: S) -> &mut Command; 26 | } 27 | 28 | impl crate::sealed::Sealed for Command {} 29 | impl CommandExt for Command { 30 | fn creation_flags(&mut self, flags: u32) -> &mut Command { 31 | self.inner.creation_flags(flags); 32 | self 33 | } 34 | 35 | fn raw_arg>(&mut self, text_to_append_as_is: S) -> &mut Command { 36 | self.inner.raw_arg(text_to_append_as_is); 37 | self 38 | } 39 | } 40 | 41 | impl AsRawHandle for Child { 42 | fn as_raw_handle(&self) -> RawHandle { 43 | self.child.lock().unwrap().get_mut().as_raw_handle() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/sleep.rs: -------------------------------------------------------------------------------- 1 | //! Sleep test. 2 | 3 | use async_process::Command; 4 | use futures_lite::future::block_on; 5 | 6 | #[cfg(unix)] 7 | #[test] 8 | fn unix_sleep() { 9 | block_on(async { 10 | let status = Command::new("sleep").arg("1").status().await.unwrap(); 11 | assert!(status.success()); 12 | }); 13 | } 14 | 15 | #[cfg(windows)] 16 | #[test] 17 | fn windows_sleep() { 18 | block_on(async { 19 | let status = Command::new("ping") 20 | .args(["-n", "5", "127.0.0.1"]) 21 | .status() 22 | .await 23 | .unwrap(); 24 | assert!(status.success()); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /tests/std.rs: -------------------------------------------------------------------------------- 1 | //! These tests are borrowed from the `std::process` test suite. 2 | 3 | use std::env; 4 | use std::io; 5 | use std::str; 6 | 7 | use async_process::{Command, Output, Stdio}; 8 | use futures_lite::{future, prelude::*}; 9 | 10 | #[test] 11 | fn smoke() { 12 | future::block_on(async { 13 | let p = if cfg!(target_os = "windows") { 14 | Command::new("cmd").args(["/C", "exit 0"]).spawn() 15 | } else { 16 | Command::new("true").spawn() 17 | }; 18 | assert!(p.is_ok()); 19 | let mut p = p.unwrap(); 20 | assert!(p.status().await.unwrap().success()); 21 | }) 22 | } 23 | 24 | #[test] 25 | fn smoke_driven() { 26 | future::block_on( 27 | async { 28 | async_process::driver().await; 29 | } 30 | .or(async { 31 | let p = if cfg!(target_os = "windows") { 32 | Command::new("cmd").args(["/C", "exit 0"]).spawn() 33 | } else { 34 | Command::new("true").spawn() 35 | }; 36 | assert!(p.is_ok()); 37 | let mut p = p.unwrap(); 38 | assert!(p.status().await.unwrap().success()); 39 | }), 40 | ) 41 | } 42 | 43 | #[test] 44 | fn smoke_failure() { 45 | assert!(Command::new("if-this-is-a-binary-then-the-world-has-ended") 46 | .spawn() 47 | .is_err()); 48 | } 49 | 50 | #[test] 51 | fn exit_reported_right() { 52 | future::block_on(async { 53 | let p = if cfg!(target_os = "windows") { 54 | Command::new("cmd").args(["/C", "exit 1"]).spawn() 55 | } else { 56 | Command::new("false").spawn() 57 | }; 58 | assert!(p.is_ok()); 59 | let mut p = p.unwrap(); 60 | assert!(p.status().await.unwrap().code() == Some(1)); 61 | drop(p.status().await); 62 | }) 63 | } 64 | 65 | #[test] 66 | #[cfg(unix)] 67 | fn signal_reported_right() { 68 | use std::os::unix::process::ExitStatusExt; 69 | 70 | future::block_on(async { 71 | let mut p = Command::new("/bin/sh") 72 | .arg("-c") 73 | .arg("read a") 74 | .stdin(Stdio::piped()) 75 | .spawn() 76 | .unwrap(); 77 | p.kill().unwrap(); 78 | match p.status().await.unwrap().signal() { 79 | Some(9) => {} 80 | result => panic!("not terminated by signal 9 (instead, {:?})", result), 81 | } 82 | }) 83 | } 84 | 85 | pub async fn run_output(mut cmd: Command) -> String { 86 | let p = cmd.spawn(); 87 | assert!(p.is_ok()); 88 | let mut p = p.unwrap(); 89 | assert!(p.stdout.is_some()); 90 | let mut ret = String::new(); 91 | p.stdout 92 | .as_mut() 93 | .unwrap() 94 | .read_to_string(&mut ret) 95 | .await 96 | .unwrap(); 97 | assert!(p.status().await.unwrap().success()); 98 | ret 99 | } 100 | 101 | #[test] 102 | fn stdout_works() { 103 | future::block_on(async { 104 | if cfg!(target_os = "windows") { 105 | let mut cmd = Command::new("cmd"); 106 | cmd.args(["/C", "echo foobar"]).stdout(Stdio::piped()); 107 | assert_eq!(run_output(cmd).await, "foobar\r\n"); 108 | } else { 109 | let mut cmd = Command::new("echo"); 110 | cmd.arg("foobar").stdout(Stdio::piped()); 111 | assert_eq!(run_output(cmd).await, "foobar\n"); 112 | } 113 | }) 114 | } 115 | 116 | #[test] 117 | #[cfg_attr(windows, ignore)] 118 | fn set_current_dir_works() { 119 | future::block_on(async { 120 | let mut cmd = Command::new("/bin/sh"); 121 | cmd.arg("-c") 122 | .arg("pwd") 123 | .current_dir("/") 124 | .stdout(Stdio::piped()); 125 | assert_eq!(run_output(cmd).await, "/\n"); 126 | }) 127 | } 128 | 129 | #[test] 130 | #[cfg_attr(windows, ignore)] 131 | fn stdin_works() { 132 | future::block_on(async { 133 | let mut p = Command::new("/bin/sh") 134 | .arg("-c") 135 | .arg("read line; echo $line") 136 | .stdin(Stdio::piped()) 137 | .stdout(Stdio::piped()) 138 | .spawn() 139 | .unwrap(); 140 | p.stdin 141 | .as_mut() 142 | .unwrap() 143 | .write("foobar".as_bytes()) 144 | .await 145 | .unwrap(); 146 | drop(p.stdin.take()); 147 | let mut out = String::new(); 148 | p.stdout 149 | .as_mut() 150 | .unwrap() 151 | .read_to_string(&mut out) 152 | .await 153 | .unwrap(); 154 | assert!(p.status().await.unwrap().success()); 155 | assert_eq!(out, "foobar\n"); 156 | }) 157 | } 158 | 159 | #[test] 160 | fn test_process_status() { 161 | future::block_on(async { 162 | let mut status = if cfg!(target_os = "windows") { 163 | Command::new("cmd") 164 | .args(["/C", "exit 1"]) 165 | .status() 166 | .await 167 | .unwrap() 168 | } else { 169 | Command::new("false").status().await.unwrap() 170 | }; 171 | assert!(status.code() == Some(1)); 172 | 173 | status = if cfg!(target_os = "windows") { 174 | Command::new("cmd") 175 | .args(["/C", "exit 0"]) 176 | .status() 177 | .await 178 | .unwrap() 179 | } else { 180 | Command::new("true").status().await.unwrap() 181 | }; 182 | assert!(status.success()); 183 | }) 184 | } 185 | 186 | #[test] 187 | fn test_process_output_fail_to_start() { 188 | future::block_on(async { 189 | match Command::new("/no-binary-by-this-name-should-exist") 190 | .output() 191 | .await 192 | { 193 | Err(e) => assert_eq!(e.kind(), io::ErrorKind::NotFound), 194 | Ok(..) => panic!(), 195 | } 196 | }) 197 | } 198 | 199 | #[test] 200 | fn test_process_output_output() { 201 | future::block_on(async { 202 | let Output { 203 | status, 204 | stdout, 205 | stderr, 206 | } = if cfg!(target_os = "windows") { 207 | Command::new("cmd") 208 | .args(["/C", "echo hello"]) 209 | .output() 210 | .await 211 | .unwrap() 212 | } else { 213 | Command::new("echo").arg("hello").output().await.unwrap() 214 | }; 215 | let output_str = str::from_utf8(&stdout).unwrap(); 216 | 217 | assert!(status.success()); 218 | assert_eq!(output_str.trim().to_string(), "hello"); 219 | assert_eq!(stderr, Vec::new()); 220 | }) 221 | } 222 | 223 | #[test] 224 | fn test_process_output_error() { 225 | future::block_on(async { 226 | let Output { 227 | status, 228 | stdout, 229 | stderr, 230 | } = if cfg!(target_os = "windows") { 231 | Command::new("cmd") 232 | .args(["/C", "mkdir ."]) 233 | .output() 234 | .await 235 | .unwrap() 236 | } else { 237 | Command::new("mkdir").arg("./").output().await.unwrap() 238 | }; 239 | 240 | assert!(status.code() == Some(1)); 241 | assert_eq!(stdout, Vec::new()); 242 | assert!(!stderr.is_empty()); 243 | }) 244 | } 245 | 246 | #[test] 247 | fn test_finish_once() { 248 | future::block_on(async { 249 | let mut prog = if cfg!(target_os = "windows") { 250 | Command::new("cmd").args(["/C", "exit 1"]).spawn().unwrap() 251 | } else { 252 | Command::new("false").spawn().unwrap() 253 | }; 254 | assert!(prog.status().await.unwrap().code() == Some(1)); 255 | }) 256 | } 257 | 258 | #[test] 259 | fn test_finish_twice() { 260 | future::block_on(async { 261 | let mut prog = if cfg!(target_os = "windows") { 262 | Command::new("cmd").args(["/C", "exit 1"]).spawn().unwrap() 263 | } else { 264 | Command::new("false").spawn().unwrap() 265 | }; 266 | assert!(prog.status().await.unwrap().code() == Some(1)); 267 | assert!(prog.status().await.unwrap().code() == Some(1)); 268 | }) 269 | } 270 | 271 | #[test] 272 | fn test_wait_with_output_once() { 273 | future::block_on(async { 274 | let prog = if cfg!(target_os = "windows") { 275 | Command::new("cmd") 276 | .args(["/C", "echo hello"]) 277 | .stdout(Stdio::piped()) 278 | .spawn() 279 | .unwrap() 280 | } else { 281 | Command::new("echo") 282 | .arg("hello") 283 | .stdout(Stdio::piped()) 284 | .spawn() 285 | .unwrap() 286 | }; 287 | 288 | let Output { 289 | status, 290 | stdout, 291 | stderr, 292 | } = prog.output().await.unwrap(); 293 | let output_str = str::from_utf8(&stdout).unwrap(); 294 | 295 | assert!(status.success()); 296 | assert_eq!(output_str.trim().to_string(), "hello"); 297 | assert_eq!(stderr, Vec::new()); 298 | }) 299 | } 300 | 301 | #[cfg(all(unix, not(target_os = "android")))] 302 | pub fn env_cmd() -> Command { 303 | Command::new("env") 304 | } 305 | 306 | #[cfg(target_os = "android")] 307 | pub fn env_cmd() -> Command { 308 | let mut cmd = Command::new("/system/bin/sh"); 309 | cmd.arg("-c").arg("set"); 310 | cmd 311 | } 312 | 313 | #[cfg(windows)] 314 | pub fn env_cmd() -> Command { 315 | let mut cmd = Command::new("cmd"); 316 | cmd.arg("/c").arg("set"); 317 | cmd 318 | } 319 | 320 | #[test] 321 | fn test_override_env() { 322 | future::block_on(async { 323 | // In some build environments (such as chrooted Nix builds), `env` can 324 | // only be found in the explicitly-provided PATH env variable, not in 325 | // default places such as /bin or /usr/bin. So we need to pass through 326 | // PATH to our sub-process. 327 | let mut cmd = env_cmd(); 328 | cmd.env_clear().env("RUN_TEST_NEW_ENV", "123"); 329 | if let Some(p) = env::var_os("PATH") { 330 | cmd.env("PATH", p); 331 | } 332 | let result = cmd.output().await.unwrap(); 333 | let output = String::from_utf8_lossy(&result.stdout).to_string(); 334 | 335 | assert!( 336 | output.contains("RUN_TEST_NEW_ENV=123"), 337 | "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", 338 | output 339 | ); 340 | }) 341 | } 342 | 343 | #[test] 344 | fn test_add_to_env() { 345 | future::block_on(async { 346 | let result = env_cmd() 347 | .env("RUN_TEST_NEW_ENV", "123") 348 | .output() 349 | .await 350 | .unwrap(); 351 | let output = String::from_utf8_lossy(&result.stdout).to_string(); 352 | 353 | assert!( 354 | output.contains("RUN_TEST_NEW_ENV=123"), 355 | "didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", 356 | output 357 | ); 358 | }) 359 | } 360 | 361 | #[test] 362 | fn test_capture_env_at_spawn() { 363 | future::block_on(async { 364 | let mut cmd = env_cmd(); 365 | cmd.env("RUN_TEST_NEW_ENV1", "123"); 366 | 367 | // This variable will not be present if the environment has already 368 | // been captured above. 369 | env::set_var("RUN_TEST_NEW_ENV2", "456"); 370 | let result = cmd.output().await.unwrap(); 371 | env::remove_var("RUN_TEST_NEW_ENV2"); 372 | 373 | let output = String::from_utf8_lossy(&result.stdout).to_string(); 374 | 375 | assert!( 376 | output.contains("RUN_TEST_NEW_ENV1=123"), 377 | "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{}", 378 | output 379 | ); 380 | assert!( 381 | output.contains("RUN_TEST_NEW_ENV2=456"), 382 | "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{}", 383 | output 384 | ); 385 | }) 386 | } 387 | 388 | #[test] 389 | #[cfg(unix)] 390 | fn child_status_preserved_with_kill_on_drop() { 391 | future::block_on(async { 392 | let p = Command::new("true").kill_on_drop(true).spawn().unwrap(); 393 | 394 | // Calling output, since it takes ownership of the child 395 | // Child::status would work, but without special care, 396 | // dropping p inside of output would kill the subprocess early, 397 | // and report the wrong exit status 398 | let res = p.output().await; 399 | assert!(res.unwrap().status.success()); 400 | }) 401 | } 402 | 403 | #[test] 404 | #[cfg(windows)] 405 | fn child_as_raw_handle() { 406 | use std::os::windows::io::AsRawHandle; 407 | use windows_sys::Win32::System::Threading::GetProcessId; 408 | 409 | future::block_on(async { 410 | let p = Command::new("cmd.exe") 411 | .arg("/C") 412 | .arg("pause") 413 | .kill_on_drop(true) 414 | .spawn() 415 | .unwrap(); 416 | 417 | let std_pid = p.id(); 418 | assert!(std_pid > 0); 419 | 420 | let handle = p.as_raw_handle(); 421 | 422 | // We verify that we have the correct handle by obtaining the PID 423 | // with the Windows API rather than via std: 424 | let win_pid = unsafe { GetProcessId(handle as _) }; 425 | assert_eq!(win_pid, std_pid); 426 | }) 427 | } 428 | 429 | #[test] 430 | #[cfg(unix)] 431 | fn test_spawn_multiple_with_stdio() { 432 | let mut cmd = Command::new("/bin/sh"); 433 | cmd.arg("-c") 434 | .arg("echo foo; echo bar 1>&2") 435 | .stdout(Stdio::piped()) 436 | .stderr(Stdio::piped()); 437 | 438 | future::block_on(async move { 439 | let p1 = cmd.spawn().unwrap(); 440 | let out1 = p1.output().await.unwrap(); 441 | assert_eq!(out1.stdout, b"foo\n"); 442 | assert_eq!(out1.stderr, b"bar\n"); 443 | 444 | let p2 = cmd.spawn().unwrap(); 445 | let out2 = p2.output().await.unwrap(); 446 | assert_eq!(out2.stdout, b"foo\n"); 447 | assert_eq!(out2.stderr, b"bar\n"); 448 | }); 449 | } 450 | 451 | #[cfg(unix)] 452 | #[test] 453 | fn test_into_inner() { 454 | futures_lite::future::block_on(async { 455 | use crate::Command; 456 | 457 | use std::io::Result; 458 | use std::process::Stdio; 459 | use std::str::from_utf8; 460 | 461 | use futures_lite::AsyncReadExt; 462 | 463 | let mut ls_child = Command::new("cat") 464 | .arg("Cargo.toml") 465 | .stdout(Stdio::piped()) 466 | .spawn()?; 467 | 468 | let stdio: Stdio = ls_child.stdout.take().unwrap().into_stdio().await?; 469 | 470 | let mut echo_child = Command::new("grep") 471 | .arg("async") 472 | .stdin(stdio) 473 | .stdout(Stdio::piped()) 474 | .spawn()?; 475 | 476 | let mut buf = vec![]; 477 | let mut stdout = echo_child.stdout.take().unwrap(); 478 | 479 | stdout.read_to_end(&mut buf).await?; 480 | dbg!(from_utf8(&buf).unwrap_or("")); 481 | 482 | Result::Ok(()) 483 | }) 484 | .unwrap(); 485 | } 486 | --------------------------------------------------------------------------------