process_control-5.0.0/.cargo_vcs_info.json0000644000000001360000000000100142540ustar { "git": { "sha1": "bd88c6d5191ff3bdc78ce6d0444f87d526352adf" }, "path_in_vcs": "" }process_control-5.0.0/COPYRIGHT000064400000000000000000000005031046102023000143350ustar 00000000000000Copyright (c) 2020 dylni (https://github.com/dylni) Some files also include explicit copyright notices. Licensed under the Apache License, Version 2.0 or the MIT license , at your option. All files in this project may not be copied, modified, or distributed except according to those terms. process_control-5.0.0/Cargo.toml0000644000000040550000000000100122560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.75.0" name = "process_control" version = "5.0.0" authors = ["dylni"] build = "build.rs" exclude = [ ".*", "tests.rs", "/rustfmt.toml", "/src/bin", "/tests", ] autobins = false autoexamples = false autotests = false autobenches = false description = """ Ergonomically run processes with limits """ readme = "README.md" keywords = [ "kill", "process", "terminate", "timeout", "wait", ] categories = [ "concurrency", "os", ] license = "MIT OR Apache-2.0" repository = "https://github.com/dylni/process_control" [package.metadata.docs.rs] all-features = true rustc-args = [ "--cfg", "process_control_docs_rs", ] rustdoc-args = [ "--cfg", "process_control_docs_rs", ] [lib] name = "process_control" path = "src/lib.rs" [target.'cfg(all(unix, any(target_os = "espidf", target_os = "horizon", target_os = "openbsd", target_os = "redox", target_os = "tvos", target_os = "vxworks")))'.dependencies.parking_lot] version = "0.12" optional = true [target.'cfg(all(unix, any(target_os = "espidf", target_os = "horizon", target_os = "openbsd", target_os = "redox", target_os = "tvos", target_os = "vxworks")))'.dependencies.signal-hook] version = "0.3" [target."cfg(unix)".dependencies.libc] version = "0.2.120" [target."cfg(windows)".dependencies.windows-sys] version = "0.59" features = [ "Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_JobObjects", "Win32_System_IO", "Win32_System_Threading", ] [target."cfg(windows)".dev-dependencies.rustversion] version = "1.0" process_control-5.0.0/Cargo.toml.orig000064400000000000000000000022421046102023000157330ustar 00000000000000[package] name = "process_control" version = "5.0.0" authors = ["dylni"] edition = "2021" rust-version = "1.75.0" description = """ Ergonomically run processes with limits """ readme = "README.md" repository = "https://github.com/dylni/process_control" license = "MIT OR Apache-2.0" keywords = ["kill", "process", "terminate", "timeout", "wait"] categories = ["concurrency", "os"] exclude = [".*", "tests.rs", "/rustfmt.toml", "/src/bin", "/tests"] [package.metadata.docs.rs] all-features = true rustc-args = ["--cfg", "process_control_docs_rs"] rustdoc-args = ["--cfg", "process_control_docs_rs"] [target.'cfg(unix)'.dependencies] libc = "0.2.120" [target.'cfg(all(unix, any(target_os = "espidf", target_os = "horizon", target_os = "openbsd", target_os = "redox", target_os = "tvos", target_os = "vxworks")))'.dependencies] parking_lot = { version = "0.12", optional = true } signal-hook = "0.3" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_JobObjects", "Win32_System_IO", "Win32_System_Threading"] } [target.'cfg(windows)'.dev-dependencies] rustversion = "1.0" process_control-5.0.0/LICENSE-APACHE000064400000000000000000000236751046102023000150050ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS process_control-5.0.0/LICENSE-MIT000064400000000000000000000021011046102023000144720ustar 00000000000000MIT License Copyright (c) 2020 dylni (https://github.com/dylni) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. process_control-5.0.0/LICENSE-THIRD-PARTY000064400000000000000000000256261046102023000156310ustar 00000000000000=============================================================================== The Rust Programming Language https://github.com/rust-lang/rust/blob/835ed0021e149cacb2d464cdbc35816b5d551c0e/LICENSE-MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =============================================================================== The Rust Programming Language https://github.com/rust-lang/rust/blob/835ed0021e149cacb2d464cdbc35816b5d551c0e/LICENSE-APACHE Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS process_control-5.0.0/README.md000064400000000000000000000055401046102023000143270ustar 00000000000000# Process Control This crate allows running a process with resource limits, such as a running time, and the option to terminate it automatically afterward. The latter is surprisingly difficult to achieve on Unix, since process identifiers can be arbitrarily reassigned when no longer used. Thus, it would be extremely easy to inadvertently terminate an unexpected process. This crate protects against that possibility. Methods for setting limits are available on [`ChildExt`], which is implemented for [`Child`]. They each return a builder of options to configure how the limit should be applied. ***Warning**: This crate should not be used for security. There are many ways that a process can bypass resource limits. The limits are only intended for simple restriction of harmless processes.* [![GitHub Build Status](https://github.com/dylni/process_control/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/dylni/process_control/actions/workflows/build.yml?query=branch%3Amaster) ## Usage Add the following lines to your "Cargo.toml" file: ```toml [dependencies] process_control = "5.0" ``` See the [documentation] for available functionality and examples. ## Rust version support The minimum supported Rust toolchain version is currently Rust 1.75.0. Minor version updates may increase this version requirement. However, the previous two Rust releases will always be supported. If the minimum Rust version must not be increased, use a tilde requirement to prevent updating this crate's minor version: ```toml [dependencies] process_control = "~5.0" ``` ## License Licensing terms are specified in [COPYRIGHT]. Unless you explicitly state otherwise, any contribution submitted for inclusion in this crate, as defined in [LICENSE-APACHE], shall be licensed according to [COPYRIGHT], without any additional terms or conditions. ### Third-party content This crate includes copies and modifications of content developed by third parties: - [src/unix/read.rs] and [src/windows/read.rs] contain modifications of code from The Rust Programming Language, licensed under the MIT License or the Apache License, Version 2.0. See those files for more details. Copies of third-party licenses can be found in [LICENSE-THIRD-PARTY]. [`Child`]: https://doc.rust-lang.org/std/process/struct.Child.html [`ChildExt`]: https://docs.rs/process_control/*/process_control/trait.ChildExt.html [COPYRIGHT]: https://github.com/dylni/process_control/blob/master/COPYRIGHT [documentation]: https://docs.rs/process_control [LICENSE-APACHE]: https://github.com/dylni/process_control/blob/master/LICENSE-APACHE [LICENSE-THIRD-PARTY]: https://github.com/dylni/process_control/blob/master/LICENSE-THIRD-PARTY [src/unix/read.rs]: https://github.com/dylni/process_control/blob/master/src/unix/read.rs [src/windows/read.rs]: https://github.com/dylni/process_control/blob/master/src/windows/read.rs process_control-5.0.0/build.rs000064400000000000000000000026751046102023000145230ustar 00000000000000use std::env; use std::ffi::OsStr; macro_rules! cfg_var { ( $name:ident , $value:ident ) => { env::var_os(format!( "CARGO_{}_{}", stringify!($name), stringify!($value).to_uppercase(), )) .is_some() }; } macro_rules! targets { ( $name:ident => $($value:ident),+ ) => { env::var_os(concat!("CARGO_CFG_TARGET_", stringify!($name))) .as_deref() .and_then(OsStr::to_str) .is_some_and(|values| { let values: Vec<_> = values.split(',').collect(); [$(stringify!($value)),+] .into_iter() .any(|x| values.contains(&x)) }) }; } macro_rules! new_cfg { ( $name:expr , $condition:expr ) => {{ println!("cargo:rustc-check-cfg=cfg({})", $name); if $condition { println!("cargo:rustc-cfg={}", $name); } }}; } macro_rules! new_crate_cfg { ( $name:ident , $condition:expr $(,)? ) => { new_cfg!(concat!("process_control_", stringify!($name)), $condition) }; } fn main() { new_crate_cfg!(docs_rs, false); new_crate_cfg!( memory_limit, targets!(OS => android) || (targets!(OS => linux) && targets!(ENV => gnu, musl)) || cfg_var!(CFG, windows), ); new_crate_cfg!( unix_waitid, !targets!(OS => espidf, horizon, openbsd, redox, tvos, vxworks), ); } process_control-5.0.0/src/control/mod.rs000064400000000000000000000122731046102023000164450ustar 00000000000000use std::panic; use std::process::Child; use std::thread; use std::time::Duration; use super::imp; use super::Control; use super::ExitStatus; use super::Output; use super::PipeFilter; use super::WaitResult; mod pipe; pub(super) use pipe::Pipe; #[derive(Debug)] struct Options { #[cfg(process_control_memory_limit)] memory_limit: Option, time_limit: Option, stdout_filter: Option, stderr_filter: Option, } pub(super) trait Process { type Result: AsRef; fn get(&mut self) -> &mut Child; #[allow(private_interfaces)] fn run_wait(&mut self, options: Options) -> WaitResult; } impl Process for &mut Child { type Result = ExitStatus; fn get(&mut self) -> &mut Child { self } #[allow(private_interfaces)] fn run_wait(&mut self, options: Options) -> WaitResult { let result = self.try_wait(); if let Ok(Some(exit_status)) = result { return Ok(Some(exit_status.into())); } let mut handle = imp::Process::new(self); #[cfg(process_control_memory_limit)] if let Some(memory_limit) = options.memory_limit { handle.set_memory_limit(memory_limit)?; } let result = handle.wait(options.time_limit)?; result .map(|result| { self.try_wait().map(|std_result| { ExitStatus::new( result, std_result.expect("missing exit status"), ) }) }) .transpose() } } impl Process for Child { type Result = Output; fn get(&mut self) -> &mut Child { self } #[allow(private_interfaces)] fn run_wait(&mut self, mut options: Options) -> WaitResult { macro_rules! pipe { ( $pipe:ident , $filter:ident ) => {{ let filter = options.$filter.take(); self.$pipe.take().map(|x| Pipe::new(x.into(), filter)) }}; } let pipes = [pipe!(stdout, stdout_filter), pipe!(stderr, stderr_filter)]; let reader = thread::Builder::new().spawn(move || imp::read2(pipes))?; (&mut &mut *self) .run_wait(options)? .map(|status| { reader .join() .unwrap_or_else(|x| panic::resume_unwind(x)) .map(|[stdout, stderr]| Output { status, stdout, stderr, }) }) .transpose() } } #[derive(Debug)] pub(super) struct Buffer

where P: Process, { process: P, options: Options, strict_errors: bool, terminate_for_timeout: bool, } impl

Buffer

where P: Process, { pub(super) const fn new(process: P) -> Self { Self { process, options: Options { #[cfg(process_control_memory_limit)] memory_limit: None, time_limit: None, stdout_filter: None, stderr_filter: None, }, strict_errors: false, terminate_for_timeout: false, } } } impl

Control for Buffer

where P: Process, { type Result = P::Result; #[cfg(any(doc, process_control_memory_limit))] #[inline] fn memory_limit(mut self, limit: usize) -> Self { self.options.memory_limit = Some(limit); self } #[inline] fn time_limit(mut self, limit: Duration) -> Self { self.options.time_limit = Some(limit); self } #[inline] fn strict_errors(mut self) -> Self { self.strict_errors = true; self } #[inline] fn terminate_for_timeout(mut self) -> Self { self.terminate_for_timeout = true; self } #[inline] fn stdout_filter(mut self, filter: T) -> Self where Self: Control, T: PipeFilter, { assert!(self.process.get().stdout.is_some(), "stdout is not piped"); self.options.stdout_filter = Some(filter.into()); self } #[inline] fn stderr_filter(mut self, filter: T) -> Self where Self: Control, T: PipeFilter, { assert!(self.process.get().stderr.is_some(), "stderr is not piped"); self.options.stderr_filter = Some(filter.into()); self } #[inline] fn wait(mut self) -> WaitResult { let _ = self.process.get().stdin.take(); let mut result = self.process.run_wait(self.options); let process = self.process.get(); // If the process exited normally, identifier reuse might cause a // different process to be terminated. if self.terminate_for_timeout && !matches!(result, Ok(Some(_))) { let next_result = process.kill().and_then(|()| process.wait()); if self.strict_errors && result.is_ok() { if let Err(error) = next_result { result = Err(error); } } } result } } process_control-5.0.0/src/control/pipe.rs000064400000000000000000000022111046102023000166120ustar 00000000000000use std::fmt; use std::fmt::Debug; use std::fmt::Formatter; use std::io; use std::process::ChildStdout; use crate::imp; use crate::PipeFilter as Filter; pub(super) struct FilterWrapper(Box); impl Debug for FilterWrapper { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("FilterWrapper").finish_non_exhaustive() } } impl From for FilterWrapper where T: Filter, { #[inline] fn from(value: T) -> Self { Self(Box::new(value)) } } pub(crate) struct Pipe { pub(crate) inner: ChildStdout, filter: FilterWrapper, } impl Pipe { pub(super) fn new( pipe: imp::OwnedFd, filter: Option, ) -> Self { Self { inner: pipe.into(), filter: filter.unwrap_or_else(|| (|_: &_| Ok(true)).into()), } } pub(crate) fn run_filter( &mut self, buffer: &mut Vec, index: usize, ) -> io::Result<()> { debug_assert_ne!(index, buffer.len()); if !(self.filter.0)(&buffer[index..])? { buffer.truncate(index); } Ok(()) } } process_control-5.0.0/src/lib.rs000064400000000000000000000463221046102023000147560ustar 00000000000000//! This crate allows running a process with resource limits, such as a running //! time, and the option to terminate it automatically afterward. The latter is //! surprisingly difficult to achieve on Unix, since process identifiers can be //! arbitrarily reassigned when no longer used. Thus, it would be extremely //! easy to inadvertently terminate an unexpected process. This crate protects //! against that possibility. //! //! Methods for setting limits are available on [`ChildExt`], which is //! implemented for [`Child`]. They each return a builder of options to //! configure how the limit should be applied. //! //!

//! //! This crate should not be used for security. There are many ways that a //! process can bypass resource limits. The limits are only intended for simple //! restriction of harmless processes. //! //!
//! //! # Features //! //! These features are optional and can be enabled or disabled in a //! "Cargo.toml" file. //! //! ### Optional Features //! //! - **parking\_lot** - //! Changes the implementation to use crate [parking\_lot] on targets missing //! some syscalls. This feature will reduce the likelihood of resource //! starvation for those targets. //! //! # Implementation //! //! All traits are [sealed], meaning that they can only be implemented by this //! crate. Otherwise, backward compatibility would be more difficult to //! maintain for new features. //! //! # Comparable Crates //! //! - [wait-timeout] - //! Made for a related purpose but does not provide the same functionality. //! Processes cannot be terminated automatically, and there is no counterpart //! of [`ChildExt::controlled_with_output`] to read output while setting a //! timeout. This crate aims to fill in those gaps and simplify the //! implementation, now that [`Receiver::recv_timeout`] exists. //! //! # Examples //! //! ``` //! use std::io; //! use std::process::Command; //! use std::process::Stdio; //! use std::time::Duration; //! //! use process_control::ChildExt; //! use process_control::Control; //! //! let message = "hello world"; //! let process = Command::new("echo") //! .arg(message) //! .stdout(Stdio::piped()) //! .spawn()?; //! //! let output = process //! .controlled_with_output() //! .time_limit(Duration::from_secs(1)) //! .terminate_for_timeout() //! .wait()? //! .ok_or_else(|| { //! io::Error::new(io::ErrorKind::TimedOut, "Process timed out") //! })?; //! assert!(output.status.success()); //! assert_eq!(message.as_bytes(), &output.stdout[..message.len()]); //! # //! # Ok::<_, io::Error>(()) //! ``` //! //! [parking\_lot]: https://crates.io/crates/parking_lot //! [`Receiver::recv_timeout`]: ::std::sync::mpsc::Receiver::recv_timeout //! [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed //! [wait-timeout]: https://crates.io/crates/wait-timeout // Only require a nightly compiler when building documentation for docs.rs. // This is a private option that should not be used. // https://github.com/rust-lang/docs.rs/issues/147#issuecomment-389544407 #![cfg_attr(process_control_docs_rs, feature(doc_cfg))] #![warn(unused_results)] use std::fmt; use std::fmt::Debug; use std::fmt::Display; use std::fmt::Formatter; use std::io; #[cfg(any(doc, unix))] use std::os::raw::c_int; use std::process; use std::process::Child; use std::str; use std::time::Duration; mod control; #[cfg_attr(unix, path = "unix/mod.rs")] #[cfg_attr(windows, path = "windows/mod.rs")] mod imp; macro_rules! r#impl { ( $short_name:ident , $long_cfg:expr , ) => { const _: () = assert!( cfg!($short_name) == $long_cfg, concat!( "The configuration option '", stringify!($short_name), "' is private.", ), ); }; } r#impl!( process_control_memory_limit, cfg!(any( target_os = "android", all( target_os = "linux", any(target_env = "gnu", target_env = "musl"), ), windows, )), ); r#impl!( process_control_unix_waitid, cfg!(not(any( target_os = "espidf", target_os = "horizon", target_os = "openbsd", target_os = "redox", target_os = "tvos", target_os = "vxworks", ))), ); type WaitResult = io::Result>; #[rustfmt::skip] macro_rules! unix_method { ( $method:ident , $return_type:ty ) => { #[doc = concat!( "Equivalent to [`ExitStatusExt::", stringify!($method), "`][method]. [method]: ::std::os::unix::process::ExitStatusExt::", stringify!($method), )] #[cfg(any(doc, unix))] #[cfg_attr(process_control_docs_rs, doc(cfg(unix)))] #[inline] #[must_use] pub fn $method(&self) -> $return_type { self.inner.$method() } }; } /// Equivalent to [`process::ExitStatus`] but allows for greater accuracy. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[must_use] pub struct ExitStatus { inner: imp::ExitStatus, std: process::ExitStatus, } impl ExitStatus { fn new(inner: imp::ExitStatus, std: process::ExitStatus) -> Self { debug_assert_eq!(inner, std.into()); Self { inner, std } } /// Equivalent to [`process::ExitStatus::success`]. #[inline] #[must_use] pub fn success(self) -> bool { self.inner.success() } /// Equivalent to [`process::ExitStatus::code`], but a more accurate value /// will be returned if possible. #[inline] #[must_use] pub fn code(self) -> Option { self.inner.code().map(Into::into) } unix_method!(continued, bool); unix_method!(core_dumped, bool); unix_method!(signal, Option); unix_method!(stopped_signal, Option); /// Converts this structure to a corresponding [`process::ExitStatus`] /// instance. /// /// Since this type can represent more exit codes, it will attempt to /// provide an equivalent representation using the standard library type. /// However, if converted back to this structure, detailed information may /// have been lost. /// /// # Examples /// /// ``` /// # use std::io; /// use std::process::Command; /// use std::time::Duration; /// /// use process_control::ChildExt; /// use process_control::Control; /// /// let exit_status = Command::new("echo") /// .spawn()? /// .controlled() /// .time_limit(Duration::from_secs(1)) /// .terminate_for_timeout() /// .wait()? /// .expect("process timed out"); /// assert!(exit_status.success()); /// assert!(exit_status.into_std_lossy().success()); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] #[must_use] pub fn into_std_lossy(self) -> process::ExitStatus { self.std } } impl AsMut for ExitStatus { #[inline] fn as_mut(&mut self) -> &mut Self { self } } impl AsRef for ExitStatus { #[inline] fn as_ref(&self) -> &Self { self } } impl Display for ExitStatus { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.inner, f) } } impl From for ExitStatus { #[inline] fn from(value: process::ExitStatus) -> Self { Self::new(value.into(), value) } } /// Equivalent to [`process::Output`] but holds an instance of [`ExitStatus`] /// from this crate. #[derive(Clone, Eq, PartialEq)] #[must_use] pub struct Output { /// Equivalent to [`process::Output::status`]. pub status: ExitStatus, /// Equivalent to [`process::Output::stdout`]. pub stdout: Vec, /// Equivalent to [`process::Output::stderr`]. pub stderr: Vec, } impl Output { /// Converts this structure to a corresponding [`process::Output`] /// instance. /// /// For more information, see [`ExitStatus::into_std_lossy`]. /// /// # Examples /// /// ``` /// # use std::io; /// use std::process::Command; /// use std::process::Stdio; /// use std::time::Duration; /// /// use process_control::ChildExt; /// use process_control::Control; /// /// let message = "foobar"; /// let output = Command::new("echo") /// .arg(message) /// .stdout(Stdio::piped()) /// .spawn()? /// .controlled_with_output() /// .time_limit(Duration::from_secs(1)) /// .terminate_for_timeout() /// .wait()? /// .expect("process timed out"); /// assert!(output.status.success()); /// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]); /// /// let output = output.into_std_lossy(); /// assert!(output.status.success()); /// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] #[must_use] pub fn into_std_lossy(self) -> process::Output { process::Output { status: self.status.into_std_lossy(), stdout: self.stdout, stderr: self.stderr, } } } impl AsMut for Output { #[inline] fn as_mut(&mut self) -> &mut ExitStatus { &mut self.status } } impl AsRef for Output { #[inline] fn as_ref(&self) -> &ExitStatus { &self.status } } struct DebugBuffer<'a>(&'a [u8]); impl Debug for DebugBuffer<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("\"")?; let mut string = self.0; while !string.is_empty() { let mut invalid = &b""[..]; let valid = str::from_utf8(string).unwrap_or_else(|error| { let (valid, string) = string.split_at(error.valid_up_to()); let invalid_length = error.error_len().unwrap_or_else(|| string.len()); invalid = &string[..invalid_length]; // SAFETY: This slice was validated to be UTF-8. unsafe { str::from_utf8_unchecked(valid) } }); Display::fmt(&valid.escape_debug(), f)?; string = &string[valid.len()..]; for byte in invalid { write!(f, "\\x{:02X}", byte)?; } string = &string[invalid.len()..]; } f.write_str("\"") } } impl Debug for Output { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Output") .field("status", &self.status) .field("stdout", &DebugBuffer(&self.stdout)) .field("stderr", &DebugBuffer(&self.stderr)) .finish() } } impl From for Output { #[inline] fn from(value: process::Output) -> Self { Self { status: value.status.into(), stdout: value.stdout, stderr: value.stderr, } } } impl From for ExitStatus { #[inline] fn from(value: Output) -> Self { value.status } } /// A function to be called for reads from a specific process pipe ([stdout] or /// [stderr]). /// /// When additional bytes are read from the pipe, they are passed to this /// function, which determines whether to include them in [`Output`]. The /// number of bytes is not guaranteed to be consistent and may not match the /// number written at any time by the command on the other side of the stream. /// /// If this function returns `Ok(false)`, the passed output will be discarded /// and not included in [`Output`]. Errors will be propagated to /// [`Control::wait`]. For more complex cases, where specific portions of read /// bytes should be included, this function can return `false` and maintain the /// output buffer itself. /// /// # Examples /// /// ``` /// use std::io; /// use std::io::Write; /// use std::process::Command; /// use std::process::Stdio; /// /// use process_control::ChildExt; /// use process_control::Control; /// /// let message = "foobar"; /// let output = Command::new("echo") /// .arg(message) /// .stdout(Stdio::piped()) /// .spawn()? /// .controlled_with_output() /// // Stream output while collecting it. /// .stdout_filter(|x| io::stdout().write_all(x).map(|()| true)) /// .wait()? /// .expect("process timed out"); /// assert!(output.status.success()); /// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]); /// # /// # Ok::<_, io::Error>(()) /// ``` /// /// [stderr]: Control::stderr_filter /// [stdout]: Control::stdout_filter pub trait PipeFilter: 'static + FnMut(&[u8]) -> io::Result + Send { } impl PipeFilter for T where T: 'static + FnMut(&[u8]) -> io::Result + Send { } /// A temporary wrapper for process limits. #[must_use] pub trait Control: private::Sealed { /// The type returned by [`wait`]. /// /// [`wait`]: Self::wait type Result; /// Sets the total virtual memory limit for the process in bytes. /// /// If the process attempts to allocate memory in excess of this limit, the /// allocation will fail. The type of failure will depend on the platform, /// and the process might terminate if it cannot handle it. /// /// Small memory limits are safe, but they might prevent the operating /// system from starting the process. #[cfg(any(doc, process_control_memory_limit))] #[cfg_attr( process_control_docs_rs, doc(cfg(any( target_os = "android", all( target_os = "linux", any(target_env = "gnu", target_env = "musl"), ), windows, ))) )] #[must_use] fn memory_limit(self, limit: usize) -> Self; /// Sets the total time limit for the process in milliseconds. /// /// A process that exceeds this limit will not be terminated unless /// [`terminate_for_timeout`] is called. /// /// [`terminate_for_timeout`]: Self::terminate_for_timeout #[must_use] fn time_limit(self, limit: Duration) -> Self; /// Causes [`wait`] to never suppress an error. /// /// Typically, errors terminating the process will be ignored, as they are /// often less important than the result. However, when this method is /// called, those errors will be returned as well. /// /// [`wait`]: Self::wait #[must_use] fn strict_errors(self) -> Self; /// Causes the process to be terminated if it exceeds the time limit. /// /// Process identifier reuse by the system will be mitigated. There should /// never be a scenario that causes an unintended process to be terminated. #[must_use] fn terminate_for_timeout(self) -> Self; /// Calls a filter function for each write to [stdout]. /// /// For more information, see [`PipeFilter`]. /// /// # Panics /// /// Panics if [`Command::stdout`] has not been set to [`Stdio::piped`]. /// /// [`Command::stdout`]: ::std::process::Command::stdout /// [`Stdio::piped`]: ::std::process::Stdio::piped /// [stdout]: Output::stdout #[must_use] fn stdout_filter(self, listener: T) -> Self where Self: Control, T: PipeFilter; /// Calls a filter function for each write to [stderr]. /// /// For more information, see [`PipeFilter`]. /// /// # Panics /// /// Panics if [`Command::stderr`] has not been set to [`Stdio::piped`]. /// /// [`Command::stderr`]: ::std::process::Command::stderr /// [`Stdio::piped`]: ::std::process::Stdio::piped /// [stderr]: Output::stderr #[must_use] fn stderr_filter(self, listener: T) -> Self where Self: Control, T: PipeFilter; /// Runs the process to completion, aborting if it exceeds the time limit. /// /// At least one additional thread might be created to wait on the process /// without blocking the current thread. /// /// If the time limit is exceeded before the process finishes, `Ok(None)` /// will be returned. However, the process will not be terminated in that /// case unless [`terminate_for_timeout`] is called beforehand. It is /// recommended to always call that method to allow system resources to be /// freed. /// /// The stdin handle to the process, if it exists, will be closed before /// waiting. Otherwise, the process would assuredly time out when reading /// from that pipe. /// /// This method cannot guarantee that the same [`io::ErrorKind`] variants /// will be returned in the future for the same types of failures. Allowing /// these breakages is required to enable calling [`Child::kill`] /// internally. /// /// [`terminate_for_timeout`]: Self::terminate_for_timeout fn wait(self) -> WaitResult; } /// Extensions to [`Child`] for easily terminating processes. /// /// For more information, see [the module-level documentation][module]. /// /// [module]: self pub trait ChildExt: private::Sealed { /// Creates an instance of [`Control`] that yields [`ExitStatus`] for this /// process. /// /// This method parallels [`Child::wait`] but allows setting limits on the /// process. /// /// # Examples /// /// ``` /// # use std::io; /// use std::process::Command; /// use std::time::Duration; /// /// use process_control::ChildExt; /// use process_control::Control; /// /// let exit_status = Command::new("echo") /// .spawn()? /// .controlled() /// .time_limit(Duration::from_secs(1)) /// .terminate_for_timeout() /// .wait()? /// .expect("process timed out"); /// assert!(exit_status.success()); /// # /// # Ok::<_, io::Error>(()) /// ``` #[must_use] fn controlled(&mut self) -> impl Control + Debug; /// Creates an instance of [`Control`] that yields [`Output`] for this /// process. /// /// This method parallels [`Child::wait_with_output`] but allows setting /// limits on the process. /// /// # Examples /// /// ``` /// # use std::io; /// use std::process::Command; /// use std::process::Stdio; /// use std::time::Duration; /// /// use process_control::ChildExt; /// use process_control::Control; /// /// let message = "foobar"; /// let output = Command::new("echo") /// .arg(message) /// .stdout(Stdio::piped()) /// .spawn()? /// .controlled_with_output() /// .time_limit(Duration::from_secs(1)) /// .terminate_for_timeout() /// .wait()? /// .expect("process timed out"); /// assert!(output.status.success()); /// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]); /// # /// # Ok::<_, io::Error>(()) /// ``` #[must_use] fn controlled_with_output(self) -> impl Control + Debug; } impl ChildExt for Child { #[inline] fn controlled(&mut self) -> impl Control + Debug { control::Buffer::new(self) } #[inline] fn controlled_with_output(self) -> impl Control + Debug { control::Buffer::new(self) } } mod private { use std::process::Child; use super::control; pub trait Sealed {} impl Sealed for Child {} impl

Sealed for control::Buffer

where P: control::Process {} } process_control-5.0.0/src/unix/exit_status.rs000064400000000000000000000077201046102023000175460ustar 00000000000000use std::fmt; use std::fmt::Display; use std::fmt::Formatter; use std::os::raw::c_int; use std::os::unix::process::ExitStatusExt; use std::process; use libc::EXIT_SUCCESS; if_waitid! { use libc::siginfo_t; use libc::CLD_CONTINUED; use libc::CLD_DUMPED; use libc::CLD_EXITED; use libc::CLD_KILLED; use libc::CLD_STOPPED; use libc::CLD_TRAPPED; } #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum ExitStatusKind { Continued, Dumped, Exited, Killed, Stopped, #[cfg(process_control_unix_waitid)] Trapped, Uncategorized, } impl ExitStatusKind { if_waitid! { const fn new(raw: c_int) -> Self { match raw { CLD_CONTINUED => Self::Continued, CLD_DUMPED => Self::Dumped, CLD_EXITED => Self::Exited, CLD_KILLED => Self::Killed, CLD_STOPPED => Self::Stopped, CLD_TRAPPED => Self::Trapped, _ => Self::Uncategorized, } } } } macro_rules! code_method { ( $method:ident , $($kind_token:tt)+ ) => { pub(crate) fn $method(self) -> Option { matches!(self.kind, $($kind_token)+).then_some(self.value) } }; } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) struct ExitStatus { value: c_int, kind: ExitStatusKind, } impl ExitStatus { if_waitid! { pub(super) unsafe fn new(process_info: siginfo_t) -> Self { Self { value: unsafe { process_info.si_status() }, kind: ExitStatusKind::new(process_info.si_code), } } } pub(crate) fn success(self) -> bool { self.code() == Some(EXIT_SUCCESS) } pub(crate) fn continued(self) -> bool { self.kind == ExitStatusKind::Continued } pub(crate) fn core_dumped(self) -> bool { self.kind == ExitStatusKind::Dumped } code_method!(code, ExitStatusKind::Exited); code_method!(signal, ExitStatusKind::Dumped | ExitStatusKind::Killed); code_method!(stopped_signal, ExitStatusKind::Stopped); } impl Display for ExitStatus { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self.kind { ExitStatusKind::Continued => { f.write_str("continued (WIFCONTINUED)") } ExitStatusKind::Dumped => { write!(f, "signal: {} (core dumped)", self.value) } ExitStatusKind::Exited => write!(f, "exit code: {}", self.value), ExitStatusKind::Killed => write!(f, "signal: {}", self.value), ExitStatusKind::Stopped => { write!(f, "stopped (not terminated) by signal: {}", self.value) } #[cfg(process_control_unix_waitid)] ExitStatusKind::Trapped => f.write_str("trapped"), ExitStatusKind::Uncategorized => { write!(f, "uncategorized wait status: {}", self.value) } } } } impl From for ExitStatus { fn from(value: process::ExitStatus) -> Self { if let Some(exit_code) = value.code() { Self { value: exit_code, kind: ExitStatusKind::Exited, } } else if let Some(signal) = value.signal() { Self { value: signal, kind: if value.core_dumped() { ExitStatusKind::Dumped } else { ExitStatusKind::Killed }, } } else if let Some(signal) = value.stopped_signal() { Self { value: signal, kind: ExitStatusKind::Stopped, } } else { Self { value: value.into_raw(), kind: if value.continued() { ExitStatusKind::Continued } else { ExitStatusKind::Uncategorized }, } } } } process_control-5.0.0/src/unix/mod.rs000064400000000000000000000102041046102023000157400ustar 00000000000000use std::io; use std::marker::PhantomData; pub(super) use std::os::fd::OwnedFd; use std::os::raw::c_int; use std::process::Child; use std::time::Duration; #[cfg(all(target_env = "gnu", target_os = "linux"))] use libc::__rlimit_resource_t; use super::WaitResult; macro_rules! if_waitid { ( $($item:item)+ ) => { $( #[cfg(process_control_unix_waitid)] $item )+ }; } mod exit_status; pub(super) use exit_status::ExitStatus; mod read; pub(super) use read::read2; mod wait; macro_rules! if_memory_limit { ( $($item:item)+ ) => { $( #[cfg(process_control_memory_limit)] $item )+ }; } if_memory_limit! { use std::ptr; use libc::rlimit; use libc::RLIMIT_AS; } macro_rules! if_raw_pid { ( $($item:item)+ ) => { $( #[cfg(any(process_control_memory_limit, process_control_unix_waitid))] $item )+ }; } if_raw_pid! { use libc::pid_t; } if_waitid! { use std::mem; use libc::id_t; } if_waitid! { macro_rules! static_assert { ( $condition:expr $(,)? ) => { const _: () = assert!($condition, "static assertion failed"); }; } } #[cfg(any( all(target_env = "musl", target_os = "linux"), target_os = "android", ))] type LimitResource = c_int; #[cfg(all(target_env = "gnu", target_os = "linux"))] type LimitResource = __rlimit_resource_t; fn check_syscall(result: c_int) -> io::Result<()> { if result >= 0 { Ok(()) } else { Err(io::Error::last_os_error()) } } if_raw_pid! { #[derive(Debug)] struct RawPid(pid_t); impl RawPid { fn new(process: &Child) -> Self { let pid: u32 = process.id(); Self(pid.try_into().expect("process identifier is invalid")) } if_waitid! { const fn as_id(&self) -> id_t { static_assert!(pid_t::MAX == i32::MAX); static_assert!( mem::size_of::() <= mem::size_of::(), ); self.0 as _ } } } } #[derive(Debug)] pub(super) struct Process<'a> { #[cfg(not(process_control_unix_waitid))] inner: &'a mut Child, #[cfg(any(process_control_memory_limit, process_control_unix_waitid))] pid: RawPid, _marker: PhantomData<&'a ()>, } impl<'a> Process<'a> { pub(super) fn new(process: &'a mut Child) -> Self { Self { #[cfg(any( process_control_memory_limit, process_control_unix_waitid, ))] pid: RawPid::new(process), #[cfg(not(process_control_unix_waitid))] inner: process, _marker: PhantomData, } } if_memory_limit! { fn set_limit( &mut self, resource: LimitResource, limit: usize, ) -> io::Result<()> { #[cfg(target_pointer_width = "32")] type PointerWidth = u32; #[cfg(target_pointer_width = "64")] type PointerWidth = u64; #[cfg(not(any( target_pointer_width = "32", target_pointer_width = "64", )))] compile_error!("unsupported pointer width"); #[cfg_attr( not(target_os = "freebsd"), allow(clippy::useless_conversion) )] let limit = PointerWidth::try_from(limit) .expect("`usize` too large for pointer width") .into(); check_syscall(unsafe { libc::prlimit( self.pid.0, resource, &rlimit { rlim_cur: limit, rlim_max: limit, }, ptr::null_mut(), ) }) } pub(super) fn set_memory_limit( &mut self, limit: usize, ) -> io::Result<()> { self.set_limit(RLIMIT_AS, limit) } } pub(super) fn wait( &mut self, time_limit: Option, ) -> WaitResult { wait::wait(self, time_limit) } } process_control-5.0.0/src/unix/read.rs000064400000000000000000000066731046102023000161130ustar 00000000000000//! Implementations copied and modified from The Rust Programming Language. //! //! Sources: //! - //! //! Copyrights: //! - Copyrights in the Rust project are retained by their contributors. No //! copyright assignment is required to contribute to the Rust project. //! //! Some files include explicit copyright notices and/or license notices. //! For full authorship information, see the version control history or //! //! //! //! - Modifications copyright (c) 2024 dylni ()
//! use std::io; use std::io::Read; use std::os::fd::AsRawFd; use std::os::fd::RawFd; use libc::fcntl; use libc::pollfd; use libc::F_GETFL; use libc::F_SETFL; use libc::O_NONBLOCK; use libc::POLLIN; use crate::control::Pipe; impl Pipe { fn set_nonblocking(&mut self) -> io::Result<()> { let fd = self.as_raw_fd(); let flags = unsafe { fcntl(fd, F_GETFL) }; super::check_syscall(flags)?; super::check_syscall(unsafe { fcntl(fd, F_SETFL, flags | O_NONBLOCK) }) } } impl AsRawFd for Pipe { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } struct AsyncPipe<'a> { inner: Pipe, buffer: &'a mut Vec, } impl<'a> AsyncPipe<'a> { fn new(mut pipe: Pipe, buffer: &'a mut Vec) -> io::Result { pipe.set_nonblocking()?; Ok(Self { inner: pipe, buffer, }) } fn next_result(&mut self) -> io::Result { let index = self.buffer.len(); let result = self .inner .inner .read_to_end(self.buffer) .map(|_| false) .or_else(|error| { if error.kind() == io::ErrorKind::WouldBlock { Ok(true) } else { Err(error) } }); if result.is_ok() && self.buffer.len() != index { self.inner.run_filter(self.buffer, index)?; } result } } pub(crate) fn read2(pipes: [Option; 2]) -> io::Result<[Vec; 2]> { const EMPTY_BUFFER: Vec = Vec::new(); let mut buffers = [EMPTY_BUFFER; 2]; let mut pipes: Vec<_> = pipes .into_iter() .zip(&mut buffers) .filter_map(|(pipe, buffer)| pipe.map(|x| AsyncPipe::new(x, buffer))) .collect::>()?; let mut fds: Vec<_> = pipes .iter_mut() .map(|pipe| pollfd { fd: pipe.inner.as_raw_fd(), events: POLLIN, revents: 0, }) .collect(); let mut start = 0; debug_assert!(fds.len() <= 2); let mut length: u8 = fds.len() as _; while length != 0 { let result = super::check_syscall(unsafe { libc::poll(fds.as_mut_ptr().add(start), length.into(), -1) }); if let Err(error) = result { if error.kind() != io::ErrorKind::Interrupted { return Err(error); } continue; } for i in (start..).take(length.into()) { if fds[i].revents != 0 && !pipes[i].next_result()? { start = i ^ 1; length -= 1; } } } Ok(buffers) } process_control-5.0.0/src/unix/wait/common.rs000064400000000000000000000064111046102023000174220ustar 00000000000000use std::io; use std::mem; use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; use std::sync::Arc; #[cfg(not(feature = "parking_lot"))] use std::sync::PoisonError; use std::time::Duration; #[cfg(feature = "parking_lot")] use parking_lot as sync; #[cfg(not(feature = "parking_lot"))] use std::sync; use sync::Mutex; use signal_hook::consts::SIGCHLD; use signal_hook::iterator::Signals; use crate::WaitResult; use super::super::ExitStatus; use super::super::Process; unsafe fn transmute_lifetime_mut<'a, T>(value: &mut T) -> &'a mut T where T: ?Sized, { unsafe { mem::transmute(value) } } struct MutexGuard<'a, T> { guard: ManuallyDrop>, #[cfg(feature = "parking_lot")] fair: bool, } impl<'a, T> MutexGuard<'a, T> { #[cfg_attr(not(feature = "parking_lot"), allow(unused_variables))] fn lock(mutex: &'a Mutex, fair: bool) -> Self { let guard = mutex.lock(); #[cfg(not(feature = "parking_lot"))] let guard = guard.unwrap_or_else(PoisonError::into_inner); Self { guard: ManuallyDrop::new(guard), #[cfg(feature = "parking_lot")] fair, } } } impl Deref for MutexGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { &self.guard } } impl DerefMut for MutexGuard<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.guard } } impl Drop for MutexGuard<'_, T> { fn drop(&mut self) { #[cfg_attr(not(feature = "parking_lot"), allow(unused_variables))] let guard = unsafe { ManuallyDrop::take(&mut self.guard) }; #[cfg(feature = "parking_lot")] if self.fair { sync::MutexGuard::unlock_fair(guard); } } } fn run_on_drop(drop_fn: F) -> impl Drop where F: FnOnce(), { struct DropBuffer(ManuallyDrop) where F: FnOnce(); impl Drop for DropBuffer where F: FnOnce(), { fn drop(&mut self) { (unsafe { ManuallyDrop::take(&mut self.0) })(); } } DropBuffer(ManuallyDrop::new(drop_fn)) } pub(in super::super) fn wait( process: &mut Process<'_>, time_limit: Option, ) -> WaitResult { // SAFETY: The process is removed by [_guard] before this function returns. let process = Arc::new(Mutex::new(Some(unsafe { transmute_lifetime_mut(process.inner) }))); let _guard = run_on_drop(|| { let _ = MutexGuard::lock(&process, false).take(); }); let process = Arc::clone(&process); super::run_with_time_limit( move || { let mut signals = Signals::new([SIGCHLD])?; loop { if let Some(process) = &mut *MutexGuard::lock(&process, true) { let result = check_result!(process.try_wait()); if let Some(result) = result { break Ok(result.into()); } } else { break Err(io::Error::new( io::ErrorKind::TimedOut, "Process timed out", )); }; while signals.wait().count() == 0 {} } }, time_limit, )? .transpose() } process_control-5.0.0/src/unix/wait/mod.rs000064400000000000000000000022571046102023000167150ustar 00000000000000use std::sync::mpsc; use std::thread; use std::time::Duration; use crate::WaitResult; macro_rules! check_result { ( $result:expr ) => {{ use libc::EINTR; // https://github.com/rust-lang/rust/blob/49c68bd53f90e375bfb3cbba8c1c67a9e0adb9c0/src/libstd/sys/unix/process/process_unix.rs#L432-L441 match $result { Ok(result) => result, Err(error) => { if error.raw_os_error() != Some(EINTR) { break Err(error); } continue; } } }}; } #[cfg_attr(process_control_unix_waitid, path = "waitid.rs")] #[cfg_attr(not(process_control_unix_waitid), path = "common.rs")] mod imp; pub(super) use imp::wait; fn run_with_time_limit( run_fn: F, time_limit: Option, ) -> WaitResult where F: 'static + FnOnce() -> R + Send, R: 'static + Send, { let Some(time_limit) = time_limit else { return Ok(Some(run_fn())); }; let (result_sender, result_receiver) = mpsc::channel(); thread::Builder::new() .spawn(move || result_sender.send(run_fn())) .map(|_| result_receiver.recv_timeout(time_limit).ok()) } process_control-5.0.0/src/unix/wait/waitid.rs000064400000000000000000000016231046102023000174130ustar 00000000000000use std::mem::MaybeUninit; use std::time::Duration; use libc::P_PID; use libc::WEXITED; use libc::WNOWAIT; use libc::WSTOPPED; use crate::WaitResult; use super::super::check_syscall; use super::super::ExitStatus; use super::super::Process; pub(in super::super) fn wait( process: &mut Process<'_>, time_limit: Option, ) -> WaitResult { let pid = process.pid.as_id(); super::run_with_time_limit( move || loop { let mut process_info = MaybeUninit::uninit(); check_result!(check_syscall(unsafe { libc::waitid( P_PID, pid, process_info.as_mut_ptr(), WEXITED | WNOWAIT | WSTOPPED, ) })); break Ok(unsafe { ExitStatus::new(process_info.assume_init()) }); }, time_limit, )? .transpose() } process_control-5.0.0/src/windows/exit_status.rs000064400000000000000000000014621046102023000202520ustar 00000000000000use std::fmt; use std::fmt::Display; use std::fmt::Formatter; use std::os::windows::process::ExitStatusExt; use std::process; use super::EXIT_SUCCESS; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) struct ExitStatus(u32); impl ExitStatus { pub(super) const fn new(exit_code: u32) -> Self { Self(exit_code) } pub(crate) fn success(self) -> bool { self.0 == EXIT_SUCCESS } pub(crate) fn code(self) -> Option { Some(self.0) } } impl Display for ExitStatus { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { process::ExitStatus::from_raw(self.0).fmt(f) } } impl From for ExitStatus { fn from(value: process::ExitStatus) -> Self { Self(value.code().expect("process has no exit code") as u32) } } process_control-5.0.0/src/windows/mod.rs000064400000000000000000000164271046102023000164640ustar 00000000000000use std::io; use std::iter::FusedIterator; use std::mem; use std::num::NonZeroU32; use std::os::windows::io::AsRawHandle; use std::os::windows::io::OwnedHandle; use std::process::Child; use std::ptr; use std::time::Duration; use std::time::Instant; use windows_sys::Win32::Foundation::CloseHandle; use windows_sys::Win32::Foundation::BOOL; use windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::WAIT_OBJECT_0; use windows_sys::Win32::Foundation::WAIT_TIMEOUT; use windows_sys::Win32::System::JobObjects::AssignProcessToJobObject; use windows_sys::Win32::System::JobObjects::CreateJobObjectW; use windows_sys::Win32::System::JobObjects::JobObjectExtendedLimitInformation; use windows_sys::Win32::System::JobObjects::SetInformationJobObject; use windows_sys::Win32::System::JobObjects::JOBOBJECT_BASIC_LIMIT_INFORMATION; use windows_sys::Win32::System::JobObjects::JOBOBJECT_EXTENDED_LIMIT_INFORMATION; use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_JOB_MEMORY; use windows_sys::Win32::System::Threading::GetExitCodeProcess; use windows_sys::Win32::System::Threading::WaitForSingleObject; use windows_sys::Win32::System::Threading::INFINITE; use windows_sys::Win32::System::Threading::IO_COUNTERS; use super::WaitResult; mod exit_status; pub(super) use exit_status::ExitStatus; mod read; pub(super) use read::read2; macro_rules! assert_matches { ( $result:expr , $expected_result:pat ) => {{ let result = $result; #[allow(clippy::redundant_pattern_matching)] if !matches!(result, $expected_result) { panic!( "assertion failed: `(left matches right)` left: `{:?}` right: `{:?}`", result, stringify!($expected_result), ); } }}; } pub(super) type OwnedFd = OwnedHandle; const EXIT_SUCCESS: u32 = 0; const TRUE: BOOL = 1; fn raw_os_error(error: &io::Error) -> Option { error.raw_os_error().and_then(|x| x.try_into().ok()) } fn check_syscall(result: BOOL) -> io::Result<()> { if result == TRUE { Ok(()) } else { Err(io::Error::last_os_error()) } } const fn size_of_val_raw(_: *const T) -> usize { mem::size_of::() } #[derive(Debug)] struct RawHandle(HANDLE); impl RawHandle { fn new(process: &Child) -> Self { Self(process.as_raw_handle()) } } #[derive(Debug)] struct JobHandle(Option); impl JobHandle { fn init(&mut self) -> io::Result<&RawHandle> { assert_matches!(&self.0, None); let handle = unsafe { CreateJobObjectW(ptr::null(), ptr::null_mut()) }; if handle.is_null() { return Err(io::Error::last_os_error()); } Ok(self.0.insert(RawHandle(handle))) } fn close(&mut self) -> io::Result<()> { if let Some(handle) = self.0.take() { check_syscall(unsafe { CloseHandle(handle.0) })?; } Ok(()) } } impl Drop for JobHandle { fn drop(&mut self) { let _ = self.close(); } } struct TimeLimits { time_limit: Option, start: Instant, } impl TimeLimits { fn new(time_limit: Option) -> Self { Self { time_limit, start: Instant::now(), } } } impl FusedIterator for TimeLimits {} impl Iterator for TimeLimits { type Item = NonZeroU32; fn next(&mut self) -> Option { let Some(time_limit) = self.time_limit else { const NON_ZERO_INFINITE: NonZeroU32 = if let Some(result) = NonZeroU32::new(INFINITE) { result } else { unreachable!(); }; return Some(NON_ZERO_INFINITE); }; let mut time_limit = time_limit .saturating_sub(self.start.elapsed()) .as_millis() .try_into() .unwrap_or(u32::MAX); if time_limit == INFINITE { time_limit -= 1; } NonZeroU32::new(time_limit) } } #[derive(Debug)] pub(super) struct Process<'a> { inner: &'a mut Child, handle: RawHandle, job_handle: JobHandle, } impl<'a> Process<'a> { pub(super) fn new(process: &'a mut Child) -> Self { Self { handle: RawHandle::new(process), inner: process, job_handle: JobHandle(None), } } fn get_exit_code(&self) -> io::Result { let mut exit_code = 0; check_syscall(unsafe { GetExitCodeProcess(self.handle.0, &mut exit_code) })?; Ok(exit_code) } pub(super) fn set_memory_limit(&mut self, limit: usize) -> io::Result<()> { self.job_handle.close()?; let job_handle = self.job_handle.init()?; let job_information: *const _ = &JOBOBJECT_EXTENDED_LIMIT_INFORMATION { BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION { PerProcessUserTimeLimit: 0, PerJobUserTimeLimit: 0, LimitFlags: JOB_OBJECT_LIMIT_JOB_MEMORY, MinimumWorkingSetSize: 0, MaximumWorkingSetSize: 0, ActiveProcessLimit: 0, Affinity: 0, PriorityClass: 0, SchedulingClass: 0, }, IoInfo: IO_COUNTERS { ReadOperationCount: 0, WriteOperationCount: 0, OtherOperationCount: 0, ReadTransferCount: 0, WriteTransferCount: 0, OtherTransferCount: 0, }, ProcessMemoryLimit: 0, JobMemoryLimit: limit, PeakProcessMemoryUsed: 0, PeakJobMemoryUsed: 0, }; let result = check_syscall(unsafe { SetInformationJobObject( job_handle.0, JobObjectExtendedLimitInformation, job_information.cast(), size_of_val_raw(job_information) .try_into() .expect("job information too large for WinAPI"), ) }); if let Err(error) = &result { // This error will occur when the job has a low memory limit. return if raw_os_error(error) == Some(ERROR_INVALID_PARAMETER) { self.inner.kill() } else { result }; } check_syscall(unsafe { AssignProcessToJobObject(job_handle.0, self.handle.0) }) } pub(super) fn wait( &mut self, time_limit: Option, ) -> WaitResult { // https://github.com/rust-lang/rust/blob/49c68bd53f90e375bfb3cbba8c1c67a9e0adb9c0/src/libstd/sys/windows/process.rs#L334-L344 for time_limit in TimeLimits::new(time_limit) { match unsafe { WaitForSingleObject(self.handle.0, time_limit.get()) } { WAIT_OBJECT_0 => { return self .get_exit_code() .map(|x| Some(ExitStatus::new(x))); } WAIT_TIMEOUT => {} _ => return Err(io::Error::last_os_error()), } } Ok(None) } } process_control-5.0.0/src/windows/read.rs000064400000000000000000000201351046102023000166070ustar 00000000000000//! Implementations copied and modified from The Rust Programming Language. //! //! Sources: //! - //! - //! //! Copyrights: //! - Copyrights in the Rust project are retained by their contributors. No //! copyright assignment is required to contribute to the Rust project. //! //! Some files include explicit copyright notices and/or license notices. //! For full authorship information, see the version control history or //! //! //! //! - Modifications copyright (c) 2024 dylni ()
//! use std::io; use std::mem; use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; use std::os::windows::io::AsRawHandle; use std::os::windows::io::FromRawHandle; use std::os::windows::io::OwnedHandle; use std::ptr; use windows_sys::Win32::Foundation::BOOL; use windows_sys::Win32::Foundation::ERROR_BROKEN_PIPE; use windows_sys::Win32::Foundation::ERROR_HANDLE_EOF; use windows_sys::Win32::Foundation::ERROR_IO_PENDING; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::WAIT_OBJECT_0; use windows_sys::Win32::Storage::FileSystem::ReadFile; use windows_sys::Win32::System::Threading::CreateEventW; use windows_sys::Win32::System::Threading::WaitForMultipleObjects; use windows_sys::Win32::System::Threading::INFINITE; use windows_sys::Win32::System::IO::CancelIo; use windows_sys::Win32::System::IO::GetOverlappedResult; use windows_sys::Win32::System::IO::OVERLAPPED; use windows_sys::Win32::System::IO::OVERLAPPED_0; use crate::control::Pipe; macro_rules! static_assert { ( $condition:expr ) => { const _: () = assert!($condition, "static assertion failed"); }; } const FALSE: BOOL = 0; const TRUE: BOOL = 1; struct Event { inner: Box, _handle: OwnedHandle, } impl Event { fn new(manual_reset: bool, initial_state: bool) -> io::Result { let event = unsafe { CreateEventW( ptr::null_mut(), manual_reset.into(), initial_state.into(), ptr::null(), ) }; if event.is_null() { Err(io::Error::last_os_error()) } else { Ok(Self { inner: Box::new(OVERLAPPED { Internal: 0, InternalHigh: 0, Anonymous: OVERLAPPED_0 { Pointer: ptr::null_mut(), }, hEvent: event, }), _handle: unsafe { OwnedHandle::from_raw_handle(event) }, }) } } } impl Deref for Event { type Target = OVERLAPPED; fn deref(&self) -> &Self::Target { &self.inner } } impl DerefMut for Event { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } impl Pipe { fn raw(&self) -> HANDLE { self.inner.as_raw_handle() } fn overlapped_result(&self, event: &Event) -> io::Result { // This assertion should never fail. static_assert!(mem::size_of::() <= mem::size_of::()); let mut read_length: u32 = 0; super::check_syscall(unsafe { GetOverlappedResult(self.raw(), &**event, &mut read_length, TRUE) }) .map(|()| read_length as usize) .or_else(|error| { if matches!( super::raw_os_error(&error), Some(ERROR_HANDLE_EOF | ERROR_BROKEN_PIPE) ) { Ok(0) } else { Err(error) } }) } fn cancel_io(&self) -> io::Result<()> { super::check_syscall(unsafe { CancelIo(self.raw()) }).map(drop) } } struct AsyncPipe<'a> { inner: Pipe, event: ManuallyDrop, buffer: &'a mut Vec, reading: bool, } impl<'a> AsyncPipe<'a> { fn new(pipe: Pipe, buffer: &'a mut Vec) -> io::Result { debug_assert!(buffer.is_empty()); Ok(Self { inner: pipe, event: ManuallyDrop::new(Event::new(true, true)?), buffer, reading: false, }) } unsafe fn finish_read(&mut self, read_length: usize) -> io::Result { debug_assert!(read_length <= self.buffer.spare_capacity_mut().len()); let index = self.buffer.len(); unsafe { self.buffer.set_len(index + read_length); } let eof = read_length == 0; if !eof { self.buffer.reserve(1); self.inner.run_filter(self.buffer, index)?; } self.reading = false; Ok(!eof) } fn result(&mut self) -> io::Result { if !self.reading { return Ok(true); } self.inner .overlapped_result(&self.event) .and_then(|x| unsafe { self.finish_read(x) }) } fn read_overlapped(&mut self) -> io::Result> { debug_assert!(!self.reading); // This assertion should never fail. static_assert!(mem::size_of::() <= mem::size_of::()); let buffer = self.buffer.spare_capacity_mut(); let max_length = buffer.len().min(u32::MAX as usize) as u32; let mut length = 0; super::check_syscall(unsafe { ReadFile( self.inner.raw(), buffer.as_mut_ptr().cast(), max_length, &mut length, &mut **self.event, ) }) .map(|()| Some(length as usize)) .or_else(|error| match super::raw_os_error(&error) { Some(ERROR_IO_PENDING) => Ok(None), Some(ERROR_BROKEN_PIPE) => Ok(Some(0)), _ => Err(error), }) } fn next_result(&mut self) -> io::Result { macro_rules! continue_if_idle { ( $result:expr ) => {{ let result = $result; if !matches!(result, Ok(true)) { return result; } }}; } continue_if_idle!(self.result()); while let Some(read_length) = self.read_overlapped()? { continue_if_idle!(unsafe { self.finish_read(read_length) }); } self.reading = true; Ok(true) } } impl Drop for AsyncPipe<'_> { fn drop(&mut self) { if self.reading && (self.inner.cancel_io().is_err() || self.result().is_err()) { // Upon failure, overlapped IO operations may still be in progress, // so leaking memory is required to ensure that pointers remain // valid. mem::forget(mem::take(self.buffer)); } else { unsafe { ManuallyDrop::drop(&mut self.event); } } } } pub(crate) fn read2(pipes: [Option; 2]) -> io::Result<[Vec; 2]> { let mut buffers = [(); 2].map(|()| Vec::with_capacity(32)); let mut pipes: Vec<_> = pipes .into_iter() .zip(&mut buffers) .filter_map(|(pipe, buffer)| pipe.map(|x| AsyncPipe::new(x, buffer))) .collect::>()?; let events: Vec<_> = pipes.iter().map(|x| x.event.hEvent).collect(); let mut start = 0; debug_assert!(events.len() <= 2); let mut length = events.len() as _; while length != 0 { let mut index = unsafe { WaitForMultipleObjects( length, events.as_ptr().add(start), FALSE, INFINITE, ) } .checked_sub(WAIT_OBJECT_0) .filter(|&x| x < length) .map(|x| x as usize) .ok_or_else(io::Error::last_os_error)?; index += start; if !pipes[index].next_result()? { start = index ^ 1; length -= 1; } } drop(pipes); Ok(buffers) }