unix_named_pipe/
lib.rs

1//! Provides utilities for working with Unix named pipes / FIFOs.
2extern crate errno;
3extern crate libc;
4
5use libc::{c_int, mkfifo, mode_t, EACCES, EEXIST, ENOENT};
6use std::ffi::CString;
7use std::fs::{File, OpenOptions};
8use std::io;
9use std::os::unix::fs::OpenOptionsExt;
10use std::path::Path;
11
12mod ext;
13pub use self::ext::*;
14
15/// Creates a new named pipe at the path given as `path`.
16/// Pipe will be created with mode `mode` if given, else `0o644` will be used.
17///
18/// # Examples
19///
20/// Without an explicit mode:
21///
22/// ```
23/// # extern crate unix_named_pipe;
24/// # use std::fs;
25/// # let file_name = "/tmp/fifo.0";
26/// unix_named_pipe::create(file_name, None).expect("could not create fifo");
27/// # fs::remove_file(file_name).expect("could not remove fifo");
28/// ```
29///
30/// With an explicit mode:
31///
32/// ```
33/// # extern crate unix_named_pipe;
34/// # use std::fs;
35/// # let file_name = "/tmp/fifo.1";
36/// unix_named_pipe::create(file_name, Some(0o740)).expect("could not create fifo");
37/// # fs::remove_file(file_name).unwrap();
38/// ```
39pub fn create<P: AsRef<Path>>(path: P, mode: Option<u32>) -> io::Result<()> {
40    let path = CString::new(path.as_ref().to_str().unwrap())?;
41    let mode = mode.unwrap_or(0o644);
42    let result: c_int = unsafe { mkfifo(path.as_ptr(), mode as mode_t) };
43
44    let result: i32 = result.into();
45    if result == 0 {
46        return Ok(());
47    }
48
49    let error = errno::errno();
50    match error.0 {
51        EACCES => {
52            return Err(io::Error::new(
53                io::ErrorKind::PermissionDenied,
54                format!("could not open {:?}: {}", path, error),
55            ));
56        }
57        EEXIST => {
58            return Err(io::Error::new(
59                io::ErrorKind::AlreadyExists,
60                format!("could not open {:?}: {}", path, error),
61            ));
62        }
63        ENOENT => {
64            return Err(io::Error::new(
65                io::ErrorKind::NotFound,
66                format!("could not open {:?}: {}", path, error),
67            ));
68        }
69        _ => {
70            return Err(io::Error::new(
71                io::ErrorKind::Other,
72                format!("could not open {:?}: {}", path, error),
73            ));
74        }
75    }
76}
77
78/// Opens a named pipe for reading. The file is opened for non-blocking reads
79/// a la `libc`'s `O_NONBLOCK`.
80///
81/// # Examples
82///
83/// ```
84/// # extern crate unix_named_pipe;
85/// # use std::fs;
86/// # let file_name = "/tmp/fifo.2";
87/// # unix_named_pipe::create(file_name, None).unwrap();
88/// let file = unix_named_pipe::open_read(file_name).expect("could not open fifo for reading");
89/// # fs::remove_file(file_name).unwrap();
90/// ```
91pub fn open_read<P: AsRef<Path>>(path: P) -> io::Result<File> {
92    OpenOptions::new()
93        .read(true)
94        .custom_flags(libc::O_NONBLOCK)
95        .open(path)
96}
97
98/// Opens a named pipe for writing. The file is opened for non-blocking writes
99/// a la `libc`'s `O_NONBLOCK`.
100///
101/// # Examples
102///
103/// ```
104/// # extern crate unix_named_pipe;
105/// # use std::fs;
106/// # let file_name = "/tmp/fifo.3";
107/// # unix_named_pipe::create(file_name, Some(0o777)).unwrap();;
108/// # let read = unix_named_pipe::open_read(file_name).unwrap();
109/// let file = unix_named_pipe::open_write(file_name).expect("could not open fifo for writing");
110/// # fs::remove_file(file_name).unwrap();
111/// ```
112///
113/// # Errors
114///
115/// - If there is no pipe receiver configured when `open_write` is called,
116///   `Err(io::ErrorKind::Other)` will be returned with
117///   `code = 6, message = "Device not configured"`.
118pub fn open_write<P: AsRef<Path>>(path: P) -> io::Result<File> {
119    OpenOptions::new()
120        .write(true)
121        .append(true)
122        .custom_flags(libc::O_NONBLOCK)
123        .open(path)
124}
125
126#[cfg(test)]
127mod tests {
128    extern crate fs2;
129
130    use super::*;
131    use fs2::FileExt;
132    use std::fs;
133    use std::io::{self, Error, ErrorKind, Read, Write};
134
135    fn lock_active_test() -> io::Result<fs::File> {
136        let file = File::create("/tmp/unix-named-pipe_tests.lock")?;
137        file.lock_exclusive()?;
138
139        Ok(file)
140    }
141
142    #[test]
143    fn create_new_pipe() {
144        let lock = lock_active_test().unwrap();
145
146        let filename = "/tmp/pipe";
147        let _ = create(filename, None).expect("could not create pipe");
148
149        fs::remove_file(filename).expect("could not remove test pipe");
150        lock.unlock().unwrap();
151    }
152
153    #[test]
154    fn create_pipe_eexists() {
155        let lock = lock_active_test().unwrap();
156
157        let filename = "/tmp/pipe";
158        fs::write(filename, "").expect("could not write test file");
159
160        let pipe = create(filename, None);
161        assert_eq!(pipe.is_err(), true);
162
163        let err: Error = pipe.unwrap_err();
164        assert_eq!(err.kind(), ErrorKind::AlreadyExists);
165
166        fs::remove_file(filename).expect("could not remove test file");
167        lock.unlock().unwrap();
168    }
169
170    #[test]
171    fn create_pipe_enoent() {
172        let filename = "/notadir/pipe";
173        let pipe = create(filename, None);
174        assert_eq!(pipe.is_err(), true);
175
176        let err: Error = pipe.unwrap_err();
177        assert_eq!(err.kind(), ErrorKind::NotFound);
178    }
179
180    #[test]
181    fn open_pipe_read() {
182        let lock = lock_active_test().unwrap();
183
184        let filename = "/tmp/test.pipe";
185        let _ = create(filename, None).expect("could not make test pipe");
186
187        let contents: [u8; 4] = [0xca, 0xfe, 0xba, 0xbe];
188        let mut actual: [u8; 4] = [0; 4];
189
190        // Create a reader first
191        let mut read_file = open_read(filename).expect("could not open test pipe for reading");
192
193        // Write some data to the pipe
194        {
195            let mut write_file =
196                open_write(filename).expect("could not open test pipe for writing");
197            write_file
198                .write(&contents)
199                .expect("could not write test data to pipe");
200            write_file.flush().expect("could not flush test pipe");
201        }
202
203        // Read some data from the pipe
204        read_file
205            .read_exact(&mut actual)
206            .expect("could not read test data from pipe");
207        assert_eq!(contents, actual);
208
209        fs::remove_file(filename).expect("could not remove test file");
210        lock.unlock().unwrap();
211    }
212}