]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/fd.rs
Reduce synchronization overhead of I/O vector count memoization
[rust.git] / library / std / src / sys / unix / fd.rs
1 #![unstable(reason = "not public", issue = "none", feature = "fd")]
2
3 use crate::cmp;
4 use crate::io::{self, Initializer, IoSlice, IoSliceMut, Read};
5 use crate::mem;
6 use crate::sync::atomic::{AtomicUsize, Ordering};
7 use crate::sys::cvt;
8 use crate::sys_common::AsInner;
9
10 use libc::{c_int, c_void};
11
12 #[derive(Debug)]
13 pub struct FileDesc {
14     fd: c_int,
15 }
16
17 // The maximum read limit on most POSIX-like systems is `SSIZE_MAX`,
18 // with the man page quoting that if the count of bytes to read is
19 // greater than `SSIZE_MAX` the result is "unspecified".
20 //
21 // On macOS, however, apparently the 64-bit libc is either buggy or
22 // intentionally showing odd behavior by rejecting any read with a size
23 // larger than or equal to INT_MAX. To handle both of these the read
24 // size is capped on both platforms.
25 #[cfg(target_os = "macos")]
26 const READ_LIMIT: usize = c_int::MAX as usize - 1;
27 #[cfg(not(target_os = "macos"))]
28 const READ_LIMIT: usize = libc::ssize_t::MAX as usize;
29
30 #[cfg(any(target_os = "linux", target_os = "macos"))]
31 fn max_iov() -> usize {
32     static LIM: AtomicUsize = AtomicUsize::new(0);
33
34     let mut lim = LIM.load(Ordering::Relaxed);
35     if lim == 0 {
36         let ret = unsafe {
37             libc::sysconf(
38                 #[cfg(target_os = "linux")]
39                 libc::_SC_IOV_MAX,
40                 #[cfg(target_os = "macos")]
41                 libc::_SC_UIO_MAXIOV,
42             )
43         };
44
45         // 1024 is the default value on modern Linux systems
46         // and hopefully more useful than `c_int::MAX`.
47         lim = if ret > 0 { ret as usize } else { 1024 };
48         LIM.store(lim, Ordering::Relaxed);
49     }
50
51     lim
52 }
53
54 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
55 fn max_iov() -> usize {
56     c_int::MAX as usize
57 }
58
59 impl FileDesc {
60     pub fn new(fd: c_int) -> FileDesc {
61         FileDesc { fd }
62     }
63
64     pub fn raw(&self) -> c_int {
65         self.fd
66     }
67
68     /// Extracts the actual file descriptor without closing it.
69     pub fn into_raw(self) -> c_int {
70         let fd = self.fd;
71         mem::forget(self);
72         fd
73     }
74
75     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
76         let ret = cvt(unsafe {
77             libc::read(self.fd, buf.as_mut_ptr() as *mut c_void, cmp::min(buf.len(), READ_LIMIT))
78         })?;
79         Ok(ret as usize)
80     }
81
82     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
83         let ret = cvt(unsafe {
84             libc::readv(
85                 self.fd,
86                 bufs.as_ptr() as *const libc::iovec,
87                 cmp::min(bufs.len(), max_iov()) as c_int,
88             )
89         })?;
90         Ok(ret as usize)
91     }
92
93     #[inline]
94     pub fn is_read_vectored(&self) -> bool {
95         true
96     }
97
98     pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
99         let mut me = self;
100         (&mut me).read_to_end(buf)
101     }
102
103     pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
104         #[cfg(target_os = "android")]
105         use super::android::cvt_pread64;
106
107         #[cfg(not(target_os = "android"))]
108         unsafe fn cvt_pread64(
109             fd: c_int,
110             buf: *mut c_void,
111             count: usize,
112             offset: i64,
113         ) -> io::Result<isize> {
114             #[cfg(not(target_os = "linux"))]
115             use libc::pread as pread64;
116             #[cfg(target_os = "linux")]
117             use libc::pread64;
118             cvt(pread64(fd, buf, count, offset))
119         }
120
121         unsafe {
122             cvt_pread64(
123                 self.fd,
124                 buf.as_mut_ptr() as *mut c_void,
125                 cmp::min(buf.len(), READ_LIMIT),
126                 offset as i64,
127             )
128             .map(|n| n as usize)
129         }
130     }
131
132     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
133         let ret = cvt(unsafe {
134             libc::write(self.fd, buf.as_ptr() as *const c_void, cmp::min(buf.len(), READ_LIMIT))
135         })?;
136         Ok(ret as usize)
137     }
138
139     pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
140         let ret = cvt(unsafe {
141             libc::writev(
142                 self.fd,
143                 bufs.as_ptr() as *const libc::iovec,
144                 cmp::min(bufs.len(), max_iov()) as c_int,
145             )
146         })?;
147         Ok(ret as usize)
148     }
149
150     #[inline]
151     pub fn is_write_vectored(&self) -> bool {
152         true
153     }
154
155     pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
156         #[cfg(target_os = "android")]
157         use super::android::cvt_pwrite64;
158
159         #[cfg(not(target_os = "android"))]
160         unsafe fn cvt_pwrite64(
161             fd: c_int,
162             buf: *const c_void,
163             count: usize,
164             offset: i64,
165         ) -> io::Result<isize> {
166             #[cfg(not(target_os = "linux"))]
167             use libc::pwrite as pwrite64;
168             #[cfg(target_os = "linux")]
169             use libc::pwrite64;
170             cvt(pwrite64(fd, buf, count, offset))
171         }
172
173         unsafe {
174             cvt_pwrite64(
175                 self.fd,
176                 buf.as_ptr() as *const c_void,
177                 cmp::min(buf.len(), READ_LIMIT),
178                 offset as i64,
179             )
180             .map(|n| n as usize)
181         }
182     }
183
184     #[cfg(target_os = "linux")]
185     pub fn get_cloexec(&self) -> io::Result<bool> {
186         unsafe { Ok((cvt(libc::fcntl(self.fd, libc::F_GETFD))? & libc::FD_CLOEXEC) != 0) }
187     }
188
189     #[cfg(not(any(
190         target_env = "newlib",
191         target_os = "solaris",
192         target_os = "illumos",
193         target_os = "emscripten",
194         target_os = "fuchsia",
195         target_os = "l4re",
196         target_os = "linux",
197         target_os = "haiku",
198         target_os = "redox"
199     )))]
200     pub fn set_cloexec(&self) -> io::Result<()> {
201         unsafe {
202             cvt(libc::ioctl(self.fd, libc::FIOCLEX))?;
203             Ok(())
204         }
205     }
206     #[cfg(any(
207         target_env = "newlib",
208         target_os = "solaris",
209         target_os = "illumos",
210         target_os = "emscripten",
211         target_os = "fuchsia",
212         target_os = "l4re",
213         target_os = "linux",
214         target_os = "haiku",
215         target_os = "redox"
216     ))]
217     pub fn set_cloexec(&self) -> io::Result<()> {
218         unsafe {
219             let previous = cvt(libc::fcntl(self.fd, libc::F_GETFD))?;
220             let new = previous | libc::FD_CLOEXEC;
221             if new != previous {
222                 cvt(libc::fcntl(self.fd, libc::F_SETFD, new))?;
223             }
224             Ok(())
225         }
226     }
227
228     #[cfg(target_os = "linux")]
229     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
230         unsafe {
231             let v = nonblocking as c_int;
232             cvt(libc::ioctl(self.fd, libc::FIONBIO, &v))?;
233             Ok(())
234         }
235     }
236
237     #[cfg(not(target_os = "linux"))]
238     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
239         unsafe {
240             let previous = cvt(libc::fcntl(self.fd, libc::F_GETFL))?;
241             let new = if nonblocking {
242                 previous | libc::O_NONBLOCK
243             } else {
244                 previous & !libc::O_NONBLOCK
245             };
246             if new != previous {
247                 cvt(libc::fcntl(self.fd, libc::F_SETFL, new))?;
248             }
249             Ok(())
250         }
251     }
252
253     pub fn duplicate(&self) -> io::Result<FileDesc> {
254         // We want to atomically duplicate this file descriptor and set the
255         // CLOEXEC flag, and currently that's done via F_DUPFD_CLOEXEC. This
256         // is a POSIX flag that was added to Linux in 2.6.24.
257         let fd = cvt(unsafe { libc::fcntl(self.raw(), libc::F_DUPFD_CLOEXEC, 0) })?;
258         Ok(FileDesc::new(fd))
259     }
260 }
261
262 impl<'a> Read for &'a FileDesc {
263     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
264         (**self).read(buf)
265     }
266
267     #[inline]
268     unsafe fn initializer(&self) -> Initializer {
269         Initializer::nop()
270     }
271 }
272
273 impl AsInner<c_int> for FileDesc {
274     fn as_inner(&self) -> &c_int {
275         &self.fd
276     }
277 }
278
279 impl Drop for FileDesc {
280     fn drop(&mut self) {
281         // Note that errors are ignored when closing a file descriptor. The
282         // reason for this is that if an error occurs we don't actually know if
283         // the file descriptor was closed or not, and if we retried (for
284         // something like EINTR), we might close another valid file descriptor
285         // opened after we closed ours.
286         let _ = unsafe { libc::close(self.fd) };
287     }
288 }
289
290 #[cfg(test)]
291 mod tests {
292     use super::{FileDesc, IoSlice};
293
294     #[test]
295     fn limit_vector_count() {
296         let stdout = FileDesc { fd: 1 };
297         let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::<Vec<_>>();
298
299         assert!(stdout.write_vectored(&bufs).is_ok());
300     }
301 }