Import 'os_pipe' crate
Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
Bug: 402757376
Test: m libos_pipe
Change-Id: Ide79491a4537d9d229c4f2471e7ac5e71c149e12
diff --git a/crates/os_pipe/.android-checksum.json b/crates/os_pipe/.android-checksum.json
new file mode 100644
index 0000000..4b607fd
--- /dev/null
+++ b/crates/os_pipe/.android-checksum.json
@@ -0,0 +1 @@
+{"package":null,"files":{".cargo-checksum.json":"332966fa9166deb40188173f331efbd8b8ec8d4e7ef2a36bf06f2d38cee74a7c","Android.bp":"c094d53c1c5ae7804af8b29766c83b810ab3d601650dda44d06248da1ad73d96","Cargo.lock":"c98478728e0cfd51119216578f099f5720717a4f2473e9993a40b84d73f51f10","Cargo.toml":"4dae1297d50ba42ecc996b9b4c188ac81b0cfded7a99df3aa28c2f0db6835ef3","LICENSE":"29856856b1e210364711a1394960b7ffc52417a1ffa353f4b498400205e190fa","METADATA":"fb89c4a453c4585efe06426155ce03385d015a71aa472c779277a072f1de9748","MODULE_LICENSE_MIT":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","README.md":"85a1fbee4a0c0b9faede24b11a6bb09d43acec3962835208f3fdf5a15b43ef4b","README.tpl":"437e5a10c286870cb80bd772fba3bffae14371068fd4b524db6411e9a351abec","cargo_embargo.json":"b1692e5e5d61e6b165d64e7b74cbbf4017a5b158c08b646eb1ebe9ba5c704235","src/bin/cat.rs":"9119bbddf5f21022afc4718b4c2e1e52d9d114877a432e4d47342319b907c24e","src/bin/cat_both.rs":"a794eb7b55bba5eb130b4ae0f20d11a833259d40bc2075ad6f7c34bf00454f00","src/bin/swap.rs":"7bafe566b974144f42a1aca3df541e5c40533cd11b9244f4c777df317a94ae46","src/lib.rs":"7f33e0b9946c4bcb4152766d3350d74207a03596262ecf965ac8da136f7ca01f","src/unix.rs":"f7caadaf9ee0fbbec3bb7ac52893bb900386fc4a28857b61c90c127b8fb75d7d","src/windows.rs":"7d9ebb2118feeed8370e830f7e7266774d5b950b9223a29da7b30a07424d5538"}}
\ No newline at end of file
diff --git a/crates/os_pipe/.cargo-checksum.json b/crates/os_pipe/.cargo-checksum.json
new file mode 100644
index 0000000..9063dec
--- /dev/null
+++ b/crates/os_pipe/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.lock":"4f24f0272f01ebe33055ea8561b1f9c566721cf505025b7f276678a72e3a03c6","Cargo.toml":"46db76edca2d0358992750701d9c309b032a13e5abaf2ffa65f7df83746915f1","LICENSE":"8f71659370c5268d9a1dc962a46232540e8fca63462586d8efaa95aab492a208","README.md":"2a063cdd2b7dfaf77587d7ad41fe762295ca791e34d36fa7af7db21b2b702cac","README.tpl":"dbb27f7af2c738afc6954312d46186d64bcd5540cc577078e57d18b9aff52d88","src/bin/cat.rs":"ec54480a73634148b0ee26f6a1a3c912ebfb85ecb08cdab17a325dfd21881a64","src/bin/cat_both.rs":"667e0d0df192399009a33f9f573220ec86c80cd4090e99809d1d7e4bfef8e211","src/bin/swap.rs":"69eca65dd6e94e8c530160108f20ab7ccb1dcf41392edafabbca470ee814206b","src/lib.rs":"99a066f2083bf0656394d6a1876d43a543603d8ea2c36e748f3dd5d72ef1c68e","src/unix.rs":"8a7ef11569aadf8ed610cb2411fa88a583a5973db3978c6e624e9c0e99b470a6","src/windows.rs":"cef262c40e365c12c6c52c60f02e1de350c190f85d8049f64c79880a42f876a3"},"package":"5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"}
\ No newline at end of file
diff --git a/crates/os_pipe/Android.bp b/crates/os_pipe/Android.bp
new file mode 100644
index 0000000..bb05c73
--- /dev/null
+++ b/crates/os_pipe/Android.bp
@@ -0,0 +1,95 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file because the changes will be overridden on upgrade.
+
+package {
+ default_applicable_licenses: ["external_rust_crates_os_pipe_license"],
+ default_team: "trendy_team_android_rust",
+}
+
+license {
+ name: "external_rust_crates_os_pipe_license",
+ visibility: [":__subpackages__"],
+ license_kinds: ["SPDX-license-identifier-MIT"],
+ license_text: ["LICENSE"],
+}
+
+rust_binary {
+ name: "cat_both",
+ host_supported: true,
+ crate_name: "cat_both",
+ cargo_env_compat: true,
+ cargo_pkg_version: "1.2.1",
+ crate_root: "src/bin/cat_both.rs",
+ edition: "2021",
+ rustlibs: [
+ "liblibc",
+ "libos_pipe",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+ min_sdk_version: "29",
+}
+
+rust_library {
+ name: "libos_pipe",
+ host_supported: true,
+ crate_name: "os_pipe",
+ cargo_env_compat: true,
+ cargo_pkg_version: "1.2.1",
+ crate_root: "src/lib.rs",
+ edition: "2021",
+ rustlibs: ["liblibc"],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+ min_sdk_version: "29",
+}
+
+rust_binary {
+ name: "os_pipe_cat",
+ host_supported: true,
+ crate_name: "cat",
+ cargo_env_compat: true,
+ cargo_pkg_version: "1.2.1",
+ crate_root: "src/bin/cat.rs",
+ edition: "2021",
+ rustlibs: [
+ "liblibc",
+ "libos_pipe",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+ min_sdk_version: "29",
+}
+
+rust_binary {
+ name: "swap",
+ host_supported: true,
+ crate_name: "swap",
+ cargo_env_compat: true,
+ cargo_pkg_version: "1.2.1",
+ crate_root: "src/bin/swap.rs",
+ edition: "2021",
+ rustlibs: [
+ "liblibc",
+ "libos_pipe",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+ product_available: true,
+ vendor_available: true,
+ min_sdk_version: "29",
+}
diff --git a/crates/os_pipe/Cargo.lock b/crates/os_pipe/Cargo.lock
new file mode 100644
index 0000000..b51b128
--- /dev/null
+++ b/crates/os_pipe/Cargo.lock
@@ -0,0 +1,90 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "os_pipe"
+version = "1.2.1"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/crates/os_pipe/Cargo.toml b/crates/os_pipe/Cargo.toml
new file mode 100644
index 0000000..fffb64e
--- /dev/null
+++ b/crates/os_pipe/Cargo.toml
@@ -0,0 +1,63 @@
+# 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.63"
+name = "os_pipe"
+version = "1.2.1"
+authors = ["Jack O'Connor"]
+build = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "a cross-platform library for opening OS pipes"
+documentation = "https://docs.rs/os_pipe"
+readme = "README.md"
+keywords = [
+ "pipe",
+ "pipe2",
+ "createpipe",
+ "dup",
+]
+license = "MIT"
+repository = "https://github.com/oconnor663/os_pipe.rs"
+
+[lib]
+name = "os_pipe"
+path = "src/lib.rs"
+
+[[bin]]
+name = "cat"
+path = "src/bin/cat.rs"
+
+[[bin]]
+name = "cat_both"
+path = "src/bin/cat_both.rs"
+
+[[bin]]
+name = "swap"
+path = "src/bin/swap.rs"
+
+[features]
+io_safety = []
+
+[target."cfg(not(windows))".dependencies.libc]
+version = "0.2.62"
+
+[target."cfg(windows)".dependencies.windows-sys]
+version = "0.59.0"
+features = [
+ "Win32_Foundation",
+ "Win32_System_Pipes",
+ "Win32_Security",
+]
diff --git a/crates/os_pipe/LICENSE b/crates/os_pipe/LICENSE
new file mode 100644
index 0000000..72dc60d
--- /dev/null
+++ b/crates/os_pipe/LICENSE
@@ -0,0 +1,19 @@
+The MIT 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.
diff --git a/crates/os_pipe/METADATA b/crates/os_pipe/METADATA
new file mode 100644
index 0000000..27dc4d8
--- /dev/null
+++ b/crates/os_pipe/METADATA
@@ -0,0 +1,17 @@
+name: "os_pipe"
+description: "a cross-platform library for opening OS pipes"
+third_party {
+ version: "1.2.1"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2025
+ month: 3
+ day: 21
+ }
+ homepage: "https://crates.io/crates/os_pipe"
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/os_pipe/os_pipe-1.2.1.crate"
+ version: "1.2.1"
+ }
+}
diff --git a/crates/os_pipe/MODULE_LICENSE_MIT b/crates/os_pipe/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/crates/os_pipe/MODULE_LICENSE_MIT
diff --git a/crates/os_pipe/README.md b/crates/os_pipe/README.md
new file mode 100644
index 0000000..ce3ba93
--- /dev/null
+++ b/crates/os_pipe/README.md
@@ -0,0 +1,113 @@
+# os_pipe.rs [](https://github.com/oconnor663/os_pipe.rs/actions) [](https://crates.io/crates/os_pipe) [](https://docs.rs/os_pipe)
+
+A cross-platform library for opening OS pipes, like those from
+[`pipe`](https://man7.org/linux/man-pages/man2/pipe.2.html) on Linux
+or
+[`CreatePipe`](https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe)
+on Windows. The Rust standard library provides
+[`Stdio::piped`](https://doc.rust-lang.org/std/process/struct.Stdio.html#method.piped)
+for simple use cases involving child processes, but it doesn't
+support creating pipes directly. This crate fills that gap.
+
+- [Docs](https://docs.rs/os_pipe)
+- [Crate](https://crates.io/crates/os_pipe)
+- [Repo](https://github.com/oconnor663/os_pipe.rs)
+
+## Common deadlocks related to pipes
+
+When you work with pipes, you often end up debugging a deadlock at
+some point. These can be confusing if you don't know why they
+happen. Here are two things you need to know:
+
+1. Pipe reads will block waiting for input as long as there's at
+ least one writer still open. **If you forget to close a writer,
+ reads will block forever.** This includes writers that you give
+ to child processes.
+2. Pipes have an internal buffer of some fixed size. On Linux for
+ example, pipe buffers are 64 KiB by default. When the buffer is
+ full, writes will block waiting for space. **If the buffer is
+ full and there aren't any readers, writes will block forever.**
+
+Deadlocks caused by a forgotten writer usually show up immediately,
+which makes them relatively easy to fix once you know what to look
+for. (See "Avoid a deadlock!" in the example code below.) However,
+deadlocks caused by full pipe buffers are trickier. These might only
+show up for larger inputs, and they might be timing-dependent or
+platform-dependent. If you find that writing to a pipe deadlocks
+sometimes, think about who's supposed to be reading from that pipe,
+and whether that thread or process might be blocked on something
+else. For more on this, see the [Gotchas
+Doc](https://github.com/oconnor663/duct.py/blob/master/gotchas.md#using-io-threads-to-avoid-blocking-children)
+from the [`duct`](https://github.com/oconnor663/duct.rs) crate. (And
+consider whether [`duct`](https://github.com/oconnor663/duct.rs)
+might be a good fit for your use case.)
+
+## Examples
+
+Here we write a single byte into a pipe and read it back out:
+
+```rust
+use std::io::prelude::*;
+
+let (mut reader, mut writer) = os_pipe::pipe()?;
+// XXX: If this write blocks, we'll never get to the read.
+writer.write_all(b"x")?;
+let mut output = [0];
+reader.read_exact(&mut output)?;
+assert_eq!(b"x", &output);
+```
+
+This is a minimal working example, but as discussed in the section
+above, reading and writing on the same thread like this is
+deadlock-prone. If we wrote 100 KB instead of just one byte, this
+example would block on `write_all`, it would never make it to
+`read_exact`, and that would be a deadlock. Doing the read and write
+from different threads or different processes would fix the
+deadlock.
+
+For a more complex example, here we join the stdout and stderr of a
+child process into a single pipe. To do that we open a pipe, clone
+its writer, and set that pair of writers as the child's stdout and
+stderr. (This is possible because `PipeWriter` implements
+`Into<Stdio>`.) Then we can read interleaved output from the pipe
+reader. This example is deadlock-free, but note the comment about
+closing the writers.
+
+```rust
+// We're going to spawn a child process that prints "foo" to stdout
+// and "bar" to stderr, and we'll combine these into a single pipe.
+let mut command = std::process::Command::new("python");
+command.args(&["-c", r#"
+import sys
+sys.stdout.write("foo")
+sys.stdout.flush()
+sys.stderr.write("bar")
+sys.stderr.flush()
+"#]);
+
+// Here's the interesting part. Open a pipe, clone its writer, and
+// set that pair of writers as the child's stdout and stderr.
+let (mut reader, writer) = os_pipe::pipe()?;
+let writer_clone = writer.try_clone()?;
+command.stdout(writer);
+command.stderr(writer_clone);
+
+// Now start the child process running.
+let mut handle = command.spawn()?;
+
+// Avoid a deadlock! This parent process is still holding open pipe
+// writers inside the Command object, and we have to close those
+// before we read. Here we do this by dropping the Command object.
+drop(command);
+
+// Finally we can read all the output and clean up the child.
+let mut output = String::new();
+reader.read_to_string(&mut output)?;
+handle.wait()?;
+assert_eq!(output, "foobar");
+```
+
+Note that the [`duct`](https://github.com/oconnor663/duct.rs) crate
+can reproduce the example above in a single line of code, with no
+risk of deadlocks and no risk of leaking [zombie
+children](https://en.wikipedia.org/wiki/Zombie_process).
diff --git a/crates/os_pipe/README.tpl b/crates/os_pipe/README.tpl
new file mode 100644
index 0000000..97edba8
--- /dev/null
+++ b/crates/os_pipe/README.tpl
@@ -0,0 +1,3 @@
+# {{crate}}.rs [](https://github.com/oconnor663/os_pipe.rs/actions) [](https://crates.io/crates/os_pipe) [](https://docs.rs/os_pipe)
+
+{{readme}}
diff --git a/crates/os_pipe/cargo_embargo.json b/crates/os_pipe/cargo_embargo.json
new file mode 100644
index 0000000..9572847
--- /dev/null
+++ b/crates/os_pipe/cargo_embargo.json
@@ -0,0 +1,5 @@
+{
+ "run_cargo": false,
+ "min_sdk_version": "29",
+ "module_name_overrides": {"cat": "os_pipe_cat"}
+}
diff --git a/crates/os_pipe/src/bin/cat.rs b/crates/os_pipe/src/bin/cat.rs
new file mode 100644
index 0000000..bc1531b
--- /dev/null
+++ b/crates/os_pipe/src/bin/cat.rs
@@ -0,0 +1,11 @@
+#![deny(warnings)]
+
+/// Windows doesn't have a native equivalent for cat, so we use this little
+/// Rust implementation instead.
+use std::io::{copy, stdin, stdout};
+
+fn main() {
+ let stdin_handle = stdin();
+ let stdout_handle = stdout();
+ copy(&mut stdin_handle.lock(), &mut stdout_handle.lock()).unwrap();
+}
diff --git a/crates/os_pipe/src/bin/cat_both.rs b/crates/os_pipe/src/bin/cat_both.rs
new file mode 100644
index 0000000..cff2f56
--- /dev/null
+++ b/crates/os_pipe/src/bin/cat_both.rs
@@ -0,0 +1,19 @@
+//! This little test binary reads stdin, and then writes what it read to both
+//! stdout and stderr, with a little tag to differentiate them. We use it to
+//! test duping the standard file descriptors.
+
+#![deny(warnings)]
+
+use std::io;
+use std::io::prelude::*;
+
+fn main() {
+ let mut input = Vec::new();
+ io::stdin().read_to_end(&mut input).unwrap();
+
+ print!("stdout: ");
+ io::stdout().write_all(&input).unwrap();
+
+ eprint!("stderr: ");
+ io::stderr().write_all(&input).unwrap();
+}
diff --git a/crates/os_pipe/src/bin/swap.rs b/crates/os_pipe/src/bin/swap.rs
new file mode 100644
index 0000000..eab016d
--- /dev/null
+++ b/crates/os_pipe/src/bin/swap.rs
@@ -0,0 +1,27 @@
+#![deny(warnings)]
+
+/// This little test binary reads stdin and write what it reads to both
+/// stdout and stderr. It depends on os_pipe's parent_* functions, and
+/// we use it to test them.
+use std::env::args_os;
+use std::ffi::OsString;
+use std::process::Command;
+
+fn main() {
+ let stdin = os_pipe::dup_stdin().unwrap();
+ let stdout = os_pipe::dup_stdout().unwrap();
+ let stderr = os_pipe::dup_stderr().unwrap();
+
+ let args: Vec<OsString> = args_os().collect();
+ let mut child = Command::new(&args[1]);
+ child.args(&args[2..]);
+
+ // Swap stdout and stderr in the child. Set stdin too, just for testing,
+ // though this should be the same as the default behavior.
+ child.stdin(stdin);
+ child.stdout(stderr);
+ child.stderr(stdout);
+
+ // Run the child. This method is kind of confusingly named...
+ child.status().unwrap();
+}
diff --git a/crates/os_pipe/src/lib.rs b/crates/os_pipe/src/lib.rs
new file mode 100644
index 0000000..f207d33
--- /dev/null
+++ b/crates/os_pipe/src/lib.rs
@@ -0,0 +1,490 @@
+//! A cross-platform library for opening OS pipes, like those from
+//! [`pipe`](https://man7.org/linux/man-pages/man2/pipe.2.html) on Linux
+//! or
+//! [`CreatePipe`](https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe)
+//! on Windows. The Rust standard library provides
+//! [`Stdio::piped`](https://doc.rust-lang.org/std/process/struct.Stdio.html#method.piped)
+//! for simple use cases involving child processes, but it doesn't
+//! support creating pipes directly. This crate fills that gap.
+//!
+//! - [Docs](https://docs.rs/os_pipe)
+//! - [Crate](https://crates.io/crates/os_pipe)
+//! - [Repo](https://github.com/oconnor663/os_pipe.rs)
+//!
+//! # Common deadlocks related to pipes
+//!
+//! When you work with pipes, you often end up debugging a deadlock at
+//! some point. These can be confusing if you don't know why they
+//! happen. Here are two things you need to know:
+//!
+//! 1. Pipe reads will block waiting for input as long as there's at
+//! least one writer still open. **If you forget to close a writer,
+//! reads will block forever.** This includes writers that you give
+//! to child processes.
+//! 2. Pipes have an internal buffer of some fixed size. On Linux for
+//! example, pipe buffers are 64 KiB by default. When the buffer is
+//! full, writes will block waiting for space. **If the buffer is
+//! full and there aren't any readers, writes will block forever.**
+//!
+//! Deadlocks caused by a forgotten writer usually show up immediately,
+//! which makes them relatively easy to fix once you know what to look
+//! for. (See "Avoid a deadlock!" in the example code below.) However,
+//! deadlocks caused by full pipe buffers are trickier. These might only
+//! show up for larger inputs, and they might be timing-dependent or
+//! platform-dependent. If you find that writing to a pipe deadlocks
+//! sometimes, think about who's supposed to be reading from that pipe,
+//! and whether that thread or process might be blocked on something
+//! else. For more on this, see the [Gotchas
+//! Doc](https://github.com/oconnor663/duct.py/blob/master/gotchas.md#using-io-threads-to-avoid-blocking-children)
+//! from the [`duct`](https://github.com/oconnor663/duct.rs) crate. (And
+//! consider whether [`duct`](https://github.com/oconnor663/duct.rs)
+//! might be a good fit for your use case.)
+//!
+//! # Examples
+//!
+//! Here we write a single byte into a pipe and read it back out:
+//!
+//! ```rust
+//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! use std::io::prelude::*;
+//!
+//! let (mut reader, mut writer) = os_pipe::pipe()?;
+//! // XXX: If this write blocks, we'll never get to the read.
+//! writer.write_all(b"x")?;
+//! let mut output = [0];
+//! reader.read_exact(&mut output)?;
+//! assert_eq!(b"x", &output);
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! This is a minimal working example, but as discussed in the section
+//! above, reading and writing on the same thread like this is
+//! deadlock-prone. If we wrote 100 KB instead of just one byte, this
+//! example would block on `write_all`, it would never make it to
+//! `read_exact`, and that would be a deadlock. Doing the read and write
+//! from different threads or different processes would fix the
+//! deadlock.
+//!
+//! For a more complex example, here we join the stdout and stderr of a
+//! child process into a single pipe. To do that we open a pipe, clone
+//! its writer, and set that pair of writers as the child's stdout and
+//! stderr. (This is possible because `PipeWriter` implements
+//! `Into<Stdio>`.) Then we can read interleaved output from the pipe
+//! reader. This example is deadlock-free, but note the comment about
+//! closing the writers.
+//!
+//! ```rust
+//! # use std::io::prelude::*;
+//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! // We're going to spawn a child process that prints "foo" to stdout
+//! // and "bar" to stderr, and we'll combine these into a single pipe.
+//! let mut command = std::process::Command::new("python");
+//! command.args(&["-c", r#"
+//! import sys
+//! sys.stdout.write("foo")
+//! sys.stdout.flush()
+//! sys.stderr.write("bar")
+//! sys.stderr.flush()
+//! "#]);
+//!
+//! // Here's the interesting part. Open a pipe, clone its writer, and
+//! // set that pair of writers as the child's stdout and stderr.
+//! let (mut reader, writer) = os_pipe::pipe()?;
+//! let writer_clone = writer.try_clone()?;
+//! command.stdout(writer);
+//! command.stderr(writer_clone);
+//!
+//! // Now start the child process running.
+//! let mut handle = command.spawn()?;
+//!
+//! // Avoid a deadlock! This parent process is still holding open pipe
+//! // writers inside the Command object, and we have to close those
+//! // before we read. Here we do this by dropping the Command object.
+//! drop(command);
+//!
+//! // Finally we can read all the output and clean up the child.
+//! let mut output = String::new();
+//! reader.read_to_string(&mut output)?;
+//! handle.wait()?;
+//! assert_eq!(output, "foobar");
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! Note that the [`duct`](https://github.com/oconnor663/duct.rs) crate
+//! can reproduce the example above in a single line of code, with no
+//! risk of deadlocks and no risk of leaking [zombie
+//! children](https://en.wikipedia.org/wiki/Zombie_process).
+
+use std::fs::File;
+use std::io;
+use std::process::Stdio;
+
+#[cfg(not(windows))]
+#[path = "unix.rs"]
+mod sys;
+#[cfg(windows)]
+#[path = "windows.rs"]
+mod sys;
+
+/// The reading end of a pipe, returned by [`pipe`](fn.pipe.html).
+///
+/// `PipeReader` implements `Into<Stdio>`, so you can pass it as an argument to
+/// `Command::stdin` to spawn a child process that reads from the pipe.
+#[derive(Debug)]
+pub struct PipeReader(
+ // We use std::fs::File here for two reasons: OwnedFd and OwnedHandle are platform-specific,
+ // and this gives us read/write/flush for free.
+ File,
+);
+
+impl PipeReader {
+ pub fn try_clone(&self) -> io::Result<PipeReader> {
+ self.0.try_clone().map(PipeReader)
+ }
+}
+
+impl io::Read for PipeReader {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.0.read(buf)
+ }
+}
+
+impl<'a> io::Read for &'a PipeReader {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ (&self.0).read(buf)
+ }
+}
+
+impl From<PipeReader> for Stdio {
+ fn from(p: PipeReader) -> Stdio {
+ p.0.into()
+ }
+}
+
+/// The writing end of a pipe, returned by [`pipe`](fn.pipe.html).
+///
+/// `PipeWriter` implements `Into<Stdio>`, so you can pass it as an argument to
+/// `Command::stdout` or `Command::stderr` to spawn a child process that writes
+/// to the pipe.
+#[derive(Debug)]
+pub struct PipeWriter(File);
+
+impl PipeWriter {
+ pub fn try_clone(&self) -> io::Result<PipeWriter> {
+ self.0.try_clone().map(PipeWriter)
+ }
+}
+
+impl io::Write for PipeWriter {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.0.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.0.flush()
+ }
+}
+
+impl<'a> io::Write for &'a PipeWriter {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ (&self.0).write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ (&self.0).flush()
+ }
+}
+
+impl From<PipeWriter> for Stdio {
+ fn from(p: PipeWriter) -> Stdio {
+ p.0.into()
+ }
+}
+
+/// Open a new pipe and return a [`PipeReader`] and [`PipeWriter`] pair.
+///
+/// This corresponds to the `pipe2` library call on Posix and the
+/// `CreatePipe` library call on Windows (though these implementation
+/// details might change). These pipes are non-inheritable, so new child
+/// processes won't receive a copy of them unless they're explicitly
+/// passed as stdin/stdout/stderr.
+///
+/// [`PipeReader`]: struct.PipeReader.html
+/// [`PipeWriter`]: struct.PipeWriter.html
+pub fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
+ sys::pipe()
+}
+
+/// Get a duplicated copy of the current process's standard input, as a
+/// [`PipeReader`].
+///
+/// Reading directly from this pipe isn't recommended, because it's not
+/// synchronized with [`std::io::stdin`]. [`PipeReader`] implements
+/// [`Into<Stdio>`], so it can be passed directly to [`Command::stdin`]. This is
+/// equivalent to [`Stdio::inherit`], though, so it's usually not necessary
+/// unless you need a collection of different pipes.
+///
+/// [`std::io::stdin`]: https://doc.rust-lang.org/std/io/fn.stdin.html
+/// [`PipeReader`]: struct.PipeReader.html
+/// [`Into<Stdio>`]: https://doc.rust-lang.org/std/process/struct.Stdio.html
+/// [`Command::stdin`]: https://doc.rust-lang.org/std/process/struct.Command.html#method.stdin
+/// [`Stdio::inherit`]: https://doc.rust-lang.org/std/process/struct.Stdio.html#method.inherit
+pub fn dup_stdin() -> io::Result<PipeReader> {
+ sys::dup(io::stdin()).map(PipeReader::from)
+}
+
+/// Get a duplicated copy of the current process's standard output, as a
+/// [`PipeWriter`](struct.PipeWriter.html).
+///
+/// Writing directly to this pipe isn't recommended, because it's not
+/// synchronized with [`std::io::stdout`]. [`PipeWriter`] implements
+/// [`Into<Stdio>`], so it can be passed directly to [`Command::stdout`] or
+/// [`Command::stderr`]. This can be useful if you want the child's stderr to go
+/// to the parent's stdout.
+///
+/// [`std::io::stdout`]: https://doc.rust-lang.org/std/io/fn.stdout.html
+/// [`PipeWriter`]: struct.PipeWriter.html
+/// [`Into<Stdio>`]: https://doc.rust-lang.org/std/process/struct.Stdio.html
+/// [`Command::stdout`]: https://doc.rust-lang.org/std/process/struct.Command.html#method.stdout
+/// [`Command::stderr`]: https://doc.rust-lang.org/std/process/struct.Command.html#method.stderr
+/// [`Stdio::inherit`]: https://doc.rust-lang.org/std/process/struct.Stdio.html#method.inherit
+pub fn dup_stdout() -> io::Result<PipeWriter> {
+ sys::dup(io::stdout()).map(PipeWriter::from)
+}
+
+/// Get a duplicated copy of the current process's standard error, as a
+/// [`PipeWriter`](struct.PipeWriter.html).
+///
+/// Writing directly to this pipe isn't recommended, because it's not
+/// synchronized with [`std::io::stderr`]. [`PipeWriter`] implements
+/// [`Into<Stdio>`], so it can be passed directly to [`Command::stdout`] or
+/// [`Command::stderr`]. This can be useful if you want the child's stdout to go
+/// to the parent's stderr.
+///
+/// [`std::io::stderr`]: https://doc.rust-lang.org/std/io/fn.stderr.html
+/// [`PipeWriter`]: struct.PipeWriter.html
+/// [`Into<Stdio>`]: https://doc.rust-lang.org/std/process/struct.Stdio.html
+/// [`Command::stdout`]: https://doc.rust-lang.org/std/process/struct.Command.html#method.stdout
+/// [`Command::stderr`]: https://doc.rust-lang.org/std/process/struct.Command.html#method.stderr
+/// [`Stdio::inherit`]: https://doc.rust-lang.org/std/process/struct.Stdio.html#method.inherit
+pub fn dup_stderr() -> io::Result<PipeWriter> {
+ sys::dup(io::stderr()).map(PipeWriter::from)
+}
+
+#[cfg(test)]
+mod tests {
+ use std::env::consts::EXE_EXTENSION;
+ use std::io::prelude::*;
+ use std::path::{Path, PathBuf};
+ use std::process::Command;
+ use std::sync::Once;
+ use std::thread;
+
+ fn path_to_exe(name: &str) -> PathBuf {
+ // This project defines some associated binaries for testing, and we shell out to them in
+ // these tests. `cargo test` doesn't automatically build associated binaries, so this
+ // function takes care of building them explicitly, with the right debug/release flavor.
+ static CARGO_BUILD_ONCE: Once = Once::new();
+ CARGO_BUILD_ONCE.call_once(|| {
+ let mut build_command = Command::new("cargo");
+ build_command.args(&["build", "--quiet"]);
+ if !cfg!(debug_assertions) {
+ build_command.arg("--release");
+ }
+ let build_status = build_command.status().unwrap();
+ assert!(
+ build_status.success(),
+ "Cargo failed to build associated binaries."
+ );
+ });
+ let flavor = if cfg!(debug_assertions) {
+ "debug"
+ } else {
+ "release"
+ };
+ Path::new("target")
+ .join(flavor)
+ .join(name)
+ .with_extension(EXE_EXTENSION)
+ }
+
+ #[test]
+ fn test_pipe_some_data() {
+ let (mut reader, mut writer) = crate::pipe().unwrap();
+ // A small write won't fill the pipe buffer, so it won't block this thread.
+ writer.write_all(b"some stuff").unwrap();
+ drop(writer);
+ let mut out = String::new();
+ reader.read_to_string(&mut out).unwrap();
+ assert_eq!(out, "some stuff");
+ }
+
+ #[test]
+ fn test_pipe_some_data_with_refs() {
+ // As with `File`, there's a second set of impls for shared
+ // refs. Test those.
+ let (reader, writer) = crate::pipe().unwrap();
+ let mut reader_ref = &reader;
+ {
+ let mut writer_ref = &writer;
+ // A small write won't fill the pipe buffer, so it won't block this thread.
+ writer_ref.write_all(b"some stuff").unwrap();
+ }
+ drop(writer);
+ let mut out = String::new();
+ reader_ref.read_to_string(&mut out).unwrap();
+ assert_eq!(out, "some stuff");
+ }
+
+ #[test]
+ fn test_pipe_no_data() {
+ let (mut reader, writer) = crate::pipe().unwrap();
+ drop(writer);
+ let mut out = String::new();
+ reader.read_to_string(&mut out).unwrap();
+ assert_eq!(out, "");
+ }
+
+ #[test]
+ fn test_pipe_a_megabyte_of_data_from_another_thread() {
+ let data = vec![0xff; 1_000_000];
+ let data_copy = data.clone();
+ let (mut reader, mut writer) = crate::pipe().unwrap();
+ let joiner = thread::spawn(move || {
+ writer.write_all(&data_copy).unwrap();
+ // This drop happens automatically, so writing it out here is mostly
+ // just for clarity. For what it's worth, it also guards against
+ // accidentally forgetting to drop if we switch to scoped threads or
+ // something like that and change this to a non-moving closure. The
+ // explicit drop forces `writer` to move.
+ drop(writer);
+ });
+ let mut out = Vec::new();
+ reader.read_to_end(&mut out).unwrap();
+ joiner.join().unwrap();
+ assert_eq!(out, data);
+ }
+
+ #[test]
+ fn test_pipes_are_not_inheritable() {
+ // Create pipes for a child process.
+ let (input_reader, mut input_writer) = crate::pipe().unwrap();
+ let (mut output_reader, output_writer) = crate::pipe().unwrap();
+
+ // Create a bunch of duplicated copies, which we'll close later. This
+ // tests that duplication preserves non-inheritability.
+ let ir_dup = input_reader.try_clone().unwrap();
+ let iw_dup = input_writer.try_clone().unwrap();
+ let or_dup = output_reader.try_clone().unwrap();
+ let ow_dup = output_writer.try_clone().unwrap();
+
+ // Spawn the child. Note that this temporary Command object takes
+ // ownership of our copies of the child's stdin and stdout, and then
+ // closes them immediately when it drops. That stops us from blocking
+ // our own read below. We use our own simple implementation of cat for
+ // compatibility with Windows.
+ let mut child = Command::new(path_to_exe("cat"))
+ .stdin(input_reader)
+ .stdout(output_writer)
+ .spawn()
+ .unwrap();
+
+ // Drop all the dups now that the child is spawned.
+ drop(ir_dup);
+ drop(iw_dup);
+ drop(or_dup);
+ drop(ow_dup);
+
+ // Write to the child's stdin. This is a small write, so it shouldn't
+ // block.
+ input_writer.write_all(b"hello").unwrap();
+ drop(input_writer);
+
+ // Read from the child's stdout. If this child has accidentally
+ // inherited the write end of its own stdin, then it will never exit,
+ // and this read will block forever. That's what this test is all
+ // about.
+ let mut output = Vec::new();
+ output_reader.read_to_end(&mut output).unwrap();
+ child.wait().unwrap();
+
+ // Confirm that we got the right bytes.
+ assert_eq!(b"hello", &*output);
+ }
+
+ #[test]
+ fn test_parent_handles() {
+ // This test invokes the `swap` test program, which uses parent_stdout() and
+ // parent_stderr() to swap the outputs for another child that it spawns.
+
+ // Create pipes for a child process.
+ let (reader, mut writer) = crate::pipe().unwrap();
+
+ // Write input. This shouldn't block because it's small. Then close the write end, or else
+ // the child will hang.
+ writer.write_all(b"quack").unwrap();
+ drop(writer);
+
+ // Use `swap` to run `cat_both`. `cat_both will read "quack" from stdin
+ // and write it to stdout and stderr with different tags. But because we
+ // run it inside `swap`, the tags in the output should be backwards.
+ let output = Command::new(path_to_exe("swap"))
+ .arg(path_to_exe("cat_both"))
+ .stdin(reader)
+ .output()
+ .unwrap();
+
+ // Check for a clean exit.
+ assert!(
+ output.status.success(),
+ "child process returned {:#?}",
+ output
+ );
+
+ // Confirm that we got the right bytes.
+ assert_eq!(b"stderr: quack", &*output.stdout);
+ assert_eq!(b"stdout: quack", &*output.stderr);
+ }
+
+ #[test]
+ fn test_parent_handles_dont_close() {
+ // Open and close each parent pipe multiple times. If this closes the
+ // original, subsequent opens should fail.
+ let stdin = crate::dup_stdin().unwrap();
+ drop(stdin);
+ let stdin = crate::dup_stdin().unwrap();
+ drop(stdin);
+
+ let stdout = crate::dup_stdout().unwrap();
+ drop(stdout);
+ let stdout = crate::dup_stdout().unwrap();
+ drop(stdout);
+
+ let stderr = crate::dup_stderr().unwrap();
+ drop(stderr);
+ let stderr = crate::dup_stderr().unwrap();
+ drop(stderr);
+ }
+
+ #[test]
+ fn test_try_clone() {
+ let (reader, writer) = crate::pipe().unwrap();
+ let mut reader_clone = reader.try_clone().unwrap();
+ let mut writer_clone = writer.try_clone().unwrap();
+ // A small write won't fill the pipe buffer, so it won't block this thread.
+ writer_clone.write_all(b"some stuff").unwrap();
+ drop(writer);
+ drop(writer_clone);
+ let mut out = String::new();
+ reader_clone.read_to_string(&mut out).unwrap();
+ assert_eq!(out, "some stuff");
+ }
+
+ #[test]
+ fn test_debug() {
+ let (reader, writer) = crate::pipe().unwrap();
+ _ = format!("{:?} {:?}", reader, writer);
+ }
+}
diff --git a/crates/os_pipe/src/unix.rs b/crates/os_pipe/src/unix.rs
new file mode 100644
index 0000000..e9b68be
--- /dev/null
+++ b/crates/os_pipe/src/unix.rs
@@ -0,0 +1,135 @@
+use crate::PipeReader;
+use crate::PipeWriter;
+use libc::c_int;
+use std::fs::File;
+use std::io;
+use std::os::unix::prelude::*;
+
+// We need to atomically create pipes and set the CLOEXEC flag on them. This is
+// done with the pipe2() API. However, macOS doesn't support pipe2. There, all
+// we can do is call pipe() followed by fcntl(), and hope that no other threads
+// fork() in between. The following code is copied from the nix crate, where it
+// works but is deprecated.
+#[cfg(not(any(
+ target_os = "aix",
+ target_os = "ios",
+ target_os = "visionos",
+ target_os = "macos",
+ target_os = "haiku"
+)))]
+fn pipe2_cloexec() -> io::Result<(OwnedFd, OwnedFd)> {
+ let mut fds: [c_int; 2] = [0; 2];
+ let res = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) };
+ if res != 0 {
+ return Err(io::Error::last_os_error());
+ }
+ unsafe { Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1]))) }
+}
+
+#[cfg(any(
+ target_os = "aix",
+ target_os = "ios",
+ target_os = "visionos",
+ target_os = "macos",
+ target_os = "haiku"
+))]
+fn pipe2_cloexec() -> io::Result<(OwnedFd, OwnedFd)> {
+ let mut fds: [c_int; 2] = [0; 2];
+ let res = unsafe { libc::pipe(fds.as_mut_ptr()) };
+ if res != 0 {
+ return Err(io::Error::last_os_error());
+ }
+ // Wrap the fds immediately, so that we'll drop them and close them in the unlikely event that
+ // any of the following fcntls fails.
+ let owned_fds = unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) };
+ let res = unsafe { libc::fcntl(fds[0], libc::F_SETFD, libc::FD_CLOEXEC) };
+ if res != 0 {
+ return Err(io::Error::last_os_error());
+ }
+ let res = unsafe { libc::fcntl(fds[1], libc::F_SETFD, libc::FD_CLOEXEC) };
+ if res != 0 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(owned_fds)
+}
+
+pub(crate) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
+ let (read_fd, write_fd) = pipe2_cloexec()?;
+ Ok((read_fd.into(), write_fd.into()))
+}
+
+pub(crate) fn dup(handle: impl AsFd) -> io::Result<OwnedFd> {
+ handle.as_fd().try_clone_to_owned()
+}
+
+impl IntoRawFd for PipeReader {
+ fn into_raw_fd(self) -> RawFd {
+ self.0.into_raw_fd()
+ }
+}
+
+impl AsRawFd for PipeReader {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
+
+impl FromRawFd for PipeReader {
+ unsafe fn from_raw_fd(fd: RawFd) -> PipeReader {
+ PipeReader(File::from_raw_fd(fd))
+ }
+}
+
+impl From<PipeReader> for OwnedFd {
+ fn from(pr: PipeReader) -> Self {
+ pr.0.into()
+ }
+}
+
+impl AsFd for PipeReader {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.0.as_fd()
+ }
+}
+
+impl From<OwnedFd> for PipeReader {
+ fn from(fd: OwnedFd) -> Self {
+ PipeReader(fd.into())
+ }
+}
+
+impl IntoRawFd for PipeWriter {
+ fn into_raw_fd(self) -> RawFd {
+ self.0.into_raw_fd()
+ }
+}
+
+impl AsRawFd for PipeWriter {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
+
+impl FromRawFd for PipeWriter {
+ unsafe fn from_raw_fd(fd: RawFd) -> PipeWriter {
+ PipeWriter(File::from_raw_fd(fd))
+ }
+}
+
+impl From<PipeWriter> for OwnedFd {
+ fn from(pw: PipeWriter) -> Self {
+ pw.0.into()
+ }
+}
+
+impl AsFd for PipeWriter {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.0.as_fd()
+ }
+}
+
+impl From<OwnedFd> for PipeWriter {
+ fn from(fd: OwnedFd) -> Self {
+ PipeWriter(fd.into())
+ }
+}
diff --git a/crates/os_pipe/src/windows.rs b/crates/os_pipe/src/windows.rs
new file mode 100644
index 0000000..a26c4bc
--- /dev/null
+++ b/crates/os_pipe/src/windows.rs
@@ -0,0 +1,106 @@
+use crate::PipeReader;
+use crate::PipeWriter;
+use std::fs::File;
+use std::io;
+use std::os::windows::prelude::*;
+use std::ptr;
+use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
+use windows_sys::Win32::System::Pipes::CreatePipe;
+
+pub(crate) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
+ let mut read_pipe = INVALID_HANDLE_VALUE;
+ let mut write_pipe = INVALID_HANDLE_VALUE;
+
+ let ret = unsafe {
+ // NOTE: These pipes do not support IOCP. We might want to emulate
+ // anonymous pipes with CreateNamedPipe, as Rust's stdlib does.
+ CreatePipe(&mut read_pipe, &mut write_pipe, ptr::null_mut(), 0)
+ };
+
+ if ret == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ unsafe {
+ Ok((
+ PipeReader::from_raw_handle(read_pipe as _),
+ PipeWriter::from_raw_handle(write_pipe as _),
+ ))
+ }
+ }
+}
+
+pub(crate) fn dup(handle: impl AsHandle) -> io::Result<OwnedHandle> {
+ handle.as_handle().try_clone_to_owned()
+}
+
+impl IntoRawHandle for PipeReader {
+ fn into_raw_handle(self) -> RawHandle {
+ self.0.into_raw_handle()
+ }
+}
+
+impl AsRawHandle for PipeReader {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.0.as_raw_handle()
+ }
+}
+
+impl FromRawHandle for PipeReader {
+ unsafe fn from_raw_handle(handle: RawHandle) -> PipeReader {
+ PipeReader(File::from_raw_handle(handle))
+ }
+}
+
+impl From<PipeReader> for OwnedHandle {
+ fn from(reader: PipeReader) -> Self {
+ reader.0.into()
+ }
+}
+
+impl AsHandle for PipeReader {
+ fn as_handle(&self) -> BorrowedHandle<'_> {
+ self.0.as_handle()
+ }
+}
+
+impl From<OwnedHandle> for PipeReader {
+ fn from(handle: OwnedHandle) -> Self {
+ PipeReader(handle.into())
+ }
+}
+
+impl IntoRawHandle for PipeWriter {
+ fn into_raw_handle(self) -> RawHandle {
+ self.0.into_raw_handle()
+ }
+}
+
+impl AsRawHandle for PipeWriter {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.0.as_raw_handle()
+ }
+}
+
+impl FromRawHandle for PipeWriter {
+ unsafe fn from_raw_handle(handle: RawHandle) -> PipeWriter {
+ PipeWriter(File::from_raw_handle(handle))
+ }
+}
+
+impl From<PipeWriter> for OwnedHandle {
+ fn from(writer: PipeWriter) -> Self {
+ writer.0.into()
+ }
+}
+
+impl AsHandle for PipeWriter {
+ fn as_handle(&self) -> BorrowedHandle<'_> {
+ self.0.as_handle()
+ }
+}
+
+impl From<OwnedHandle> for PipeWriter {
+ fn from(handle: OwnedHandle) -> Self {
+ PipeWriter(handle.into())
+ }
+}
diff --git a/pseudo_crate/Cargo.lock b/pseudo_crate/Cargo.lock
index 3064a14..69f824e 100644
--- a/pseudo_crate/Cargo.lock
+++ b/pseudo_crate/Cargo.lock
@@ -344,6 +344,7 @@
"open-enum",
"open-enum-derive",
"openssl-macros",
+ "os_pipe",
"os_str_bytes",
"p9",
"p9_wire_format_derive",
@@ -4548,6 +4549,16 @@
]
[[package]]
+name = "os_pipe"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
+dependencies = [
+ "libc 0.2.161",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "os_str_bytes"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/pseudo_crate/Cargo.toml b/pseudo_crate/Cargo.toml
index 951c8de..2b5b4c4 100644
--- a/pseudo_crate/Cargo.toml
+++ b/pseudo_crate/Cargo.toml
@@ -254,6 +254,7 @@
open-enum = "=0.5.2"
open-enum-derive = "=0.5.2"
openssl-macros = "=0.1.1"
+os_pipe = "=1.2.1"
os_str_bytes = "=6.6.1"
p9 = "=0.2.3"
p9_wire_format_derive = "=0.2.3"
diff --git a/pseudo_crate/crate-list.txt b/pseudo_crate/crate-list.txt
index 5b62edb..4c1a927 100644
--- a/pseudo_crate/crate-list.txt
+++ b/pseudo_crate/crate-list.txt
@@ -248,6 +248,7 @@
open-enum
open-enum-derive
openssl-macros
+os_pipe
os_str_bytes
p9
p9_wire_format_derive