tokio_file_unix/
lib.rs

1//! A utility library that adds asynchronous support to file-like objects on
2//! Unix-like platforms.
3//!
4//! This crate is primarily intended for pipes and other files that support
5//! nonblocking I/O.  Regular files do not support nonblocking I/O, so this
6//! crate has no effect on them.
7//!
8//! See [`File`](struct.File.html) for an example of how a file can be made
9//! suitable for asynchronous I/O.
10
11use std::cell::RefCell;
12use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
13use std::{fs, io};
14use tokio::io::PollEvented;
15
16unsafe fn dupe_file_from_fd(old_fd: RawFd) -> io::Result<fs::File> {
17    let fd = libc::fcntl(old_fd, libc::F_DUPFD_CLOEXEC, 0);
18    if fd < 0 {
19        return Err(io::Error::last_os_error());
20    }
21    Ok(fs::File::from_raw_fd(fd))
22}
23
24/// Duplicate the standard input file.
25///
26/// Unlike `std::io::Stdin`, this file is not buffered.
27pub fn raw_stdin() -> io::Result<fs::File> {
28    unsafe { dupe_file_from_fd(libc::STDIN_FILENO) }
29}
30
31/// Duplicate the standard output file.
32///
33/// Unlike `std::io::Stdout`, this file is not buffered.
34pub fn raw_stdout() -> io::Result<fs::File> {
35    unsafe { dupe_file_from_fd(libc::STDOUT_FILENO) }
36}
37
38/// Duplicate the standard error file.
39///
40/// Unlike `std::io::Stderr`, this file is not buffered.
41pub fn raw_stderr() -> io::Result<fs::File> {
42    unsafe { dupe_file_from_fd(libc::STDERR_FILENO) }
43}
44
45/// Gets the nonblocking mode of the underlying file descriptor.
46///
47/// Implementation detail: uses `fcntl` to retrieve `O_NONBLOCK`.
48pub fn get_nonblocking<F: AsRawFd>(file: &F) -> io::Result<bool> {
49    unsafe {
50        let flags = libc::fcntl(file.as_raw_fd(), libc::F_GETFL);
51        if flags < 0 {
52            return Err(io::Error::last_os_error());
53        }
54        Ok(flags & libc::O_NONBLOCK != 0)
55    }
56}
57
58/// Sets the nonblocking mode of the underlying file descriptor to either on
59/// (`true`) or off (`false`).  If `File::new_nb` was previously used to
60/// construct the `File`, then nonblocking mode has already been turned on.
61///
62/// This function is not atomic. It should only called if you have exclusive
63/// control of the underlying file descriptor.
64///
65/// Implementation detail: uses `fcntl` to query the flags and set
66/// `O_NONBLOCK`.
67pub fn set_nonblocking<F: AsRawFd>(file: &mut F, nonblocking: bool) -> io::Result<()> {
68    unsafe {
69        let fd = file.as_raw_fd();
70        // shamelessly copied from libstd/sys/unix/fd.rs
71        let previous = libc::fcntl(fd, libc::F_GETFL);
72        if previous < 0 {
73            return Err(io::Error::last_os_error());
74        }
75        let new = if nonblocking {
76            previous | libc::O_NONBLOCK
77        } else {
78            previous & !libc::O_NONBLOCK
79        };
80        if libc::fcntl(fd, libc::F_SETFL, new) < 0 {
81            return Err(io::Error::last_os_error());
82        }
83        Ok(())
84    }
85}
86
87/// Wraps file-like objects for asynchronous I/O.
88///
89/// Normally, you should use `File::new_nb` rather than `File::raw_new` unless
90/// the underlying file descriptor has already been set to nonblocking mode.
91/// Using a file descriptor that is not in nonblocking mode for asynchronous
92/// I/O will lead to subtle and confusing bugs.
93///
94/// Wrapping regular files has no effect because they do not support
95/// nonblocking mode.
96///
97/// The most common instantiation of this type is `File<std::fs::File>`, which
98/// indirectly provides the following trait implementation:
99///
100/// ```ignore
101/// impl AsyncRead + AsyncWrite for PollEvented<File<std::fs::File>>;
102/// ```
103///
104/// ## Example: read standard input line by line
105///
106/// ```
107/// use tokio::stream::StreamExt;
108/// use tokio_util::codec::FramedRead;
109/// use tokio_util::codec::LinesCodec;
110///
111/// #[tokio::main]
112/// async fn main() -> std::io::Result<()> {
113///     // convert stdin into a nonblocking file;
114///     // this is the only part that makes use of tokio_file_unix
115///     let file = tokio_file_unix::raw_stdin()?;
116///     let file = tokio_file_unix::File::new_nb(file)?;
117///
118///     let mut framed = FramedRead::new(file, LinesCodec::new());
119///
120///     while let Some(got) = framed.next().await {
121///         println!("Got this: {:?}", got);
122///     }
123///
124///     println!("Received None, lol");
125///     Ok(())
126/// }
127/// ```
128///
129/// ## Example: unsafe creation from raw file descriptor
130///
131/// To unsafely create `File<F>` from a raw file descriptor `fd`, you can do
132/// something like:
133///
134/// ```
135/// # use std::os::unix::io::{AsRawFd, RawFd};
136/// use std::os::unix::io::FromRawFd;
137///
138/// # unsafe fn test<F: AsRawFd + FromRawFd>(fd: RawFd) -> std::io::Result<()> {
139/// let file = tokio_file_unix::File::new_nb(F::from_raw_fd(fd))?;
140/// # Ok(())
141/// # }
142/// ```
143///
144/// which will enable nonblocking mode upon creation.  The choice of `F` is
145/// critical: it determines the ownership semantics of the file descriptor.
146/// For example, if you choose `F = std::fs::File`, the file descriptor will
147/// be closed when the `File` is dropped.
148#[derive(Debug)]
149pub struct File<F> {
150    file: F,
151    evented: RefCell<Option<mio::Registration>>,
152}
153
154impl<F: AsRawFd> File<F> {
155    /// Wraps a file-like object into a pollable object that supports
156    /// `tokio::io::AsyncRead` and `tokio::io::AsyncWrite`, and also *enables
157    /// nonblocking mode* on the underlying file descriptor.
158    pub fn new_nb(mut file: F) -> io::Result<PollEvented<Self>> {
159        set_nonblocking(&mut file, true)?;
160        File::raw_new(file)
161    }
162
163    /// Raw constructor that **does not enable nonblocking mode** on the
164    /// underlying file descriptor.  This constructor should only be used if
165    /// you are certain that the underlying file descriptor is already in
166    /// nonblocking mode.
167    pub fn raw_new(file: F) -> io::Result<PollEvented<Self>> {
168        PollEvented::new(File {
169            file: file,
170            evented: Default::default(),
171        })
172    }
173}
174
175impl<F: AsRawFd> AsRawFd for File<F> {
176    fn as_raw_fd(&self) -> RawFd {
177        self.file.as_raw_fd()
178    }
179}
180
181impl<F: AsRawFd> mio::Evented for File<F> {
182    fn register(
183        &self,
184        poll: &mio::Poll,
185        token: mio::Token,
186        interest: mio::Ready,
187        opts: mio::PollOpt,
188    ) -> io::Result<()> {
189        match mio::unix::EventedFd(&self.as_raw_fd()).register(poll, token, interest, opts) {
190            // this is a workaround for regular files, which are not supported
191            // by epoll; they would instead cause EPERM upon registration
192            Err(ref e) if e.raw_os_error() == Some(libc::EPERM) => {
193                set_nonblocking(&mut self.as_raw_fd(), false)?;
194                // workaround: PollEvented/IoToken always starts off in the
195                // "not ready" state so we have to use a real Evented object
196                // to set its readiness state
197                let (r, s) = mio::Registration::new2();
198                r.register(poll, token, interest, opts)?;
199                s.set_readiness(mio::Ready::readable() | mio::Ready::writable())?;
200                *self.evented.borrow_mut() = Some(r);
201                Ok(())
202            }
203            e => e,
204        }
205    }
206
207    fn reregister(
208        &self,
209        poll: &mio::Poll,
210        token: mio::Token,
211        interest: mio::Ready,
212        opts: mio::PollOpt,
213    ) -> io::Result<()> {
214        match *self.evented.borrow() {
215            None => mio::unix::EventedFd(&self.as_raw_fd()).reregister(poll, token, interest, opts),
216            Some(ref r) => r.reregister(poll, token, interest, opts),
217        }
218    }
219
220    fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
221        match *self.evented.borrow() {
222            None => mio::unix::EventedFd(&self.as_raw_fd()).deregister(poll),
223            Some(ref r) => mio::Evented::deregister(r, poll),
224        }
225    }
226}
227
228impl<F: io::Read> io::Read for File<F> {
229    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
230        self.file.read(buf)
231    }
232}
233
234impl<F: io::Write> io::Write for File<F> {
235    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
236        self.file.write(buf)
237    }
238
239    fn flush(&mut self) -> io::Result<()> {
240        self.file.flush()
241    }
242}
243
244impl<F: io::Seek> io::Seek for File<F> {
245    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
246        self.file.seek(pos)
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use std::os::unix::net::UnixStream;
254
255    #[test]
256    fn test_nonblocking() -> io::Result<()> {
257        let (sock, _) = UnixStream::pair()?;
258        let mut fd = sock.as_raw_fd();
259        set_nonblocking(&mut fd, false)?;
260        assert!(!get_nonblocking(&fd)?);
261        set_nonblocking(&mut fd, true)?;
262        assert!(get_nonblocking(&fd)?);
263        set_nonblocking(&mut fd, false)?;
264        assert!(!get_nonblocking(&fd)?);
265        Ok(())
266    }
267}