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 [![Actions Status](https://github.com/oconnor663/os_pipe.rs/workflows/tests/badge.svg)](https://github.com/oconnor663/os_pipe.rs/actions) [![crates.io](https://img.shields.io/crates/v/os_pipe.svg)](https://crates.io/crates/os_pipe) [![docs.rs](https://docs.rs/os_pipe/badge.svg)](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 [![Actions Status](https://github.com/oconnor663/os_pipe.rs/workflows/tests/badge.svg)](https://github.com/oconnor663/os_pipe.rs/actions) [![crates.io](https://img.shields.io/crates/v/os_pipe.svg)](https://crates.io/crates/os_pipe) [![docs.rs](https://docs.rs/os_pipe/badge.svg)](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