Skip to main content

nuclei/net/unix/
pipe.rs

1//! Unix pipes: an anonymous pair ([`pipe`]) and FIFOs opened by path
2//! ([`OpenOptions`]), as a [`Sender`] (the write end) and a [`Receiver`]
3//! (the read end). Each is a [`Handle`] of std's pipe end, read or written by
4//! the driver as any byte stream, with the calls of its one direction.
5//!
6//! ```
7//! use nuclei::io::{AsyncReadExt, AsyncWriteExt};
8//! use nuclei::net::unix::pipe;
9//!
10//! # nuclei::block_on(async {
11//! let (mut tx, mut rx) = pipe::pipe()?;
12//! tx.write_all(b"through").await?;
13//! tx.flush().await?;
14//! let mut buf = [0u8; 7];
15//! rx.read_exact(&mut buf).await?;
16//! assert_eq!(&buf, b"through");
17//! # std::io::Result::Ok(())
18//! # }).unwrap();
19//! ```
20
21use std::fs::File;
22use std::io;
23use std::os::fd::OwnedFd;
24use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
25use std::path::Path;
26
27use crate::io::Handle;
28
29/// A pipe's write end.
30pub type Sender = Handle<std::io::PipeWriter>;
31/// A pipe's read end.
32pub type Receiver = Handle<std::io::PipeReader>;
33
34/// A new anonymous pipe: what is written to the sender is read from the
35/// receiver.
36///
37/// # Errors
38///
39/// When the process has no descriptors left.
40pub fn pipe() -> io::Result<(Sender, Receiver)> {
41    let (rx, tx) = std::io::pipe()?;
42    Ok((Handle::new(tx)?, Handle::new(rx)?))
43}
44
45/// How a FIFO is opened by path.
46#[derive(Clone, Debug, Default)]
47pub struct OpenOptions {
48    read_write: bool,
49    unchecked: bool,
50}
51
52impl OpenOptions {
53    /// Options with each set to its default.
54    pub fn new() -> OpenOptions {
55        OpenOptions::default()
56    }
57
58    /// Opens the FIFO for reading and writing both (Linux): a receiver
59    /// sees no end while it holds the write side itself, and a sender
60    /// opens with no reader there yet. Default false.
61    #[cfg(any(target_os = "linux", target_os = "android"))]
62    pub fn read_write(&mut self, value: bool) -> &mut Self {
63        self.read_write = value;
64        self
65    }
66
67    /// Opens the path without checking it is a FIFO. Default false.
68    pub fn unchecked(&mut self, value: bool) -> &mut Self {
69        self.unchecked = value;
70        self
71    }
72
73    /// Opens the FIFO at `path` as a receiver.
74    ///
75    /// # Errors
76    ///
77    /// When the path cannot be opened, or (unless unchecked) is not a FIFO.
78    pub fn open_receiver<P: AsRef<Path>>(&self, path: P) -> io::Result<Receiver> {
79        let fd = self.open(path.as_ref(), false)?;
80        Handle::new(std::io::PipeReader::from(fd))
81    }
82
83    /// Opens the FIFO at `path` as a sender.
84    ///
85    /// # Errors
86    ///
87    /// When the path cannot be opened (`ENXIO` when nothing reads it yet,
88    /// unless opened for reading and writing), or (unless unchecked) is not
89    /// a FIFO.
90    pub fn open_sender<P: AsRef<Path>>(&self, path: P) -> io::Result<Sender> {
91        let fd = self.open(path.as_ref(), true)?;
92        Handle::new(std::io::PipeWriter::from(fd))
93    }
94
95    fn open(&self, path: &Path, write: bool) -> io::Result<OwnedFd> {
96        let mut options = std::fs::OpenOptions::new();
97        // Non-blocking from the open: a FIFO's open waits for its other end
98        // otherwise.
99        options.custom_flags(libc::O_NONBLOCK);
100        if self.read_write {
101            options.read(true).write(true);
102        } else if write {
103            options.write(true);
104        } else {
105            options.read(true);
106        }
107        let file: File = options.open(path)?;
108        if !self.unchecked && !file.metadata()?.file_type().is_fifo() {
109            return Err(io::Error::new(
110                io::ErrorKind::InvalidInput,
111                "the path is not a FIFO",
112            ));
113        }
114        Ok(file.into())
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::io::{AsyncReadExt, AsyncWriteExt};
122
123    fn fifo(name: &str) -> std::path::PathBuf {
124        let path = std::env::temp_dir().join(format!("nuclei-fifo-{}-{name}", std::process::id()));
125        let _ = std::fs::remove_file(&path);
126        let c = std::ffi::CString::new(path.as_os_str().as_encoded_bytes()).unwrap();
127        // SAFETY: a plain call with a valid path.
128        assert_eq!(unsafe { libc::mkfifo(c.as_ptr(), 0o600) }, 0);
129        path
130    }
131
132    #[test]
133    fn an_anonymous_pipe_carries_bytes_to_its_end() {
134        crate::block_on(async {
135            let (mut tx, mut rx) = pipe().unwrap();
136            let mut buf = [0u8; 8];
137            assert_eq!(
138                rx.try_read(&mut buf).unwrap_err().kind(),
139                io::ErrorKind::WouldBlock
140            );
141            tx.write_all(b"bytes").await.unwrap();
142            tx.flush().await.unwrap();
143            rx.readable().await.unwrap();
144            assert_eq!(rx.try_read(&mut buf).unwrap(), 5);
145            drop(tx);
146            assert_eq!(
147                rx.read(&mut buf).await.unwrap(),
148                0,
149                "the end, once the sender went"
150            );
151        });
152    }
153
154    #[test]
155    fn a_fifo_opens_by_path_and_is_checked() {
156        let path = fifo("checked");
157        crate::block_on(async {
158            let mut rx = OpenOptions::new().open_receiver(&path).unwrap();
159            let mut tx = OpenOptions::new().open_sender(&path).unwrap();
160            tx.write_all(b"fifo").await.unwrap();
161            tx.flush().await.unwrap();
162            let mut buf = [0u8; 4];
163            rx.read_exact(&mut buf).await.unwrap();
164            assert_eq!(&buf, b"fifo");
165        });
166        let _ = std::fs::remove_file(&path);
167        let plain = std::env::temp_dir().join(format!("nuclei-not-fifo-{}", std::process::id()));
168        std::fs::write(&plain, b"x").unwrap();
169        let err = OpenOptions::new().open_receiver(&plain).unwrap_err();
170        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
171        assert!(
172            OpenOptions::new()
173                .unchecked(true)
174                .open_receiver(&plain)
175                .is_ok()
176        );
177        let _ = std::fs::remove_file(&plain);
178    }
179
180    #[test]
181    fn a_sender_with_no_reader_is_refused_unless_it_reads_too() {
182        let path = fifo("lonely");
183        let err = OpenOptions::new().open_sender(&path).unwrap_err();
184        assert_eq!(err.raw_os_error(), Some(libc::ENXIO));
185        #[cfg(any(target_os = "linux", target_os = "android"))]
186        assert!(
187            OpenOptions::new()
188                .read_write(true)
189                .open_sender(&path)
190                .is_ok()
191        );
192        let _ = std::fs::remove_file(&path);
193    }
194}