1use 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
29pub type Sender = Handle<std::io::PipeWriter>;
31pub type Receiver = Handle<std::io::PipeReader>;
33
34pub fn pipe() -> io::Result<(Sender, Receiver)> {
41 let (rx, tx) = std::io::pipe()?;
42 Ok((Handle::new(tx)?, Handle::new(rx)?))
43}
44
45#[derive(Clone, Debug, Default)]
47pub struct OpenOptions {
48 read_write: bool,
49 unchecked: bool,
50}
51
52impl OpenOptions {
53 pub fn new() -> OpenOptions {
55 OpenOptions::default()
56 }
57
58 #[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 pub fn unchecked(&mut self, value: bool) -> &mut Self {
69 self.unchecked = value;
70 self
71 }
72
73 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 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 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 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}