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}