Skip to main content

std/sys/fd/
unix.rs

1#![unstable(reason = "not public", issue = "none", feature = "fd")]
2
3#[cfg(test)]
4mod tests;
5
6#[cfg(not(any(
7    target_os = "linux",
8    target_os = "l4re",
9    target_os = "android",
10    target_os = "hurd",
11)))]
12use libc::off_t as off64_t;
13#[cfg(any(
14    target_os = "android",
15    target_os = "linux",
16    target_os = "l4re",
17    target_os = "hurd",
18))]
19use libc::off64_t;
20
21cfg_select! {
22    target_os = "vxworks" => {
23        // VxWorks does not have pread/pwrite.
24        // See <https://github.com/rust-lang/libc/issues/5328>.
25        pub unsafe fn pread64(
26            _fd: libc::c_int,
27            _buf: *mut libc::c_void,
28            _count: libc::size_t,
29            _offset: off64_t,
30        ) -> libc::ssize_t {
31            -1
32        }
33
34        pub unsafe fn pwrite64(
35            _fd: libc::c_int,
36            _buf: *const libc::c_void,
37            _count: libc::size_t,
38            _offset: off64_t,
39        ) -> libc::ssize_t {
40            -1
41        }
42    }
43    any(
44        all(target_os = "linux", not(target_env = "musl")),
45        target_os = "android",
46        target_os = "hurd",
47    ) => {
48        // Prefer explicit pread64 for 64-bit offset independently of libc
49        // #[cfg(gnu_file_offset_bits64)].
50        use libc::pread64;
51        use libc::pwrite64;
52    }
53    _ => {
54        use libc::pread as pread64;
55        use libc::pwrite as pwrite64;
56    }
57}
58
59use crate::cmp;
60use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read};
61use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
62#[cfg(all(target_os = "android", target_pointer_width = "64"))]
63use crate::sys::pal::weak::syscall;
64#[cfg(any(
65    all(target_os = "android", target_pointer_width = "32"),
66    all(target_vendor = "apple", not(all(target_os = "macos", target_arch = "aarch64")))
67))]
68use crate::sys::pal::weak::weak;
69use crate::sys::{AsInner, FromInner, IntoInner, cvt};
70
71#[derive(Debug)]
72pub struct FileDesc(OwnedFd);
73
74// The maximum read limit on most POSIX-like systems is `SSIZE_MAX`,
75// with the man page quoting that if the count of bytes to read is
76// greater than `SSIZE_MAX` the result is "unspecified".
77//
78// On Apple targets however, apparently the 64-bit libc is either buggy or
79// intentionally showing odd behavior by rejecting any read with a size
80// larger than INT_MAX. To handle both of these the read size is capped on
81// both platforms.
82const READ_LIMIT: usize = if cfg!(target_vendor = "apple") {
83    libc::c_int::MAX as usize
84} else {
85    libc::ssize_t::MAX as usize
86};
87
88#[cfg(any(
89    target_os = "dragonfly",
90    target_os = "freebsd",
91    target_os = "netbsd",
92    target_os = "openbsd",
93    target_vendor = "apple",
94    target_os = "cygwin",
95))]
96const fn max_iov() -> usize {
97    libc::IOV_MAX as usize
98}
99
100#[cfg(any(
101    target_os = "android",
102    target_os = "emscripten",
103    target_os = "linux",
104    target_os = "nto",
105    target_os = "qnx",
106))]
107const fn max_iov() -> usize {
108    libc::UIO_MAXIOV as usize
109}
110
111#[cfg(not(any(
112    target_os = "android",
113    target_os = "dragonfly",
114    target_os = "emscripten",
115    target_os = "espidf",
116    target_os = "freebsd",
117    target_os = "linux",
118    target_os = "netbsd",
119    target_os = "nuttx",
120    target_os = "nto",
121    target_os = "qnx",
122    target_os = "openbsd",
123    target_os = "horizon",
124    target_os = "vita",
125    target_vendor = "apple",
126    target_os = "cygwin",
127)))]
128const fn max_iov() -> usize {
129    16 // The minimum value required by POSIX.
130}
131
132impl FileDesc {
133    #[inline]
134    pub fn try_clone(&self) -> io::Result<Self> {
135        self.duplicate()
136    }
137
138    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
139        let ret = cvt(unsafe {
140            libc::read(
141                self.as_raw_fd(),
142                buf.as_mut_ptr() as *mut libc::c_void,
143                cmp::min(buf.len(), READ_LIMIT),
144            )
145        })?;
146        Ok(ret as usize)
147    }
148
149    #[cfg(not(any(
150        target_os = "espidf",
151        target_os = "horizon",
152        target_os = "vita",
153        target_os = "nuttx"
154    )))]
155    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
156        let ret = cvt(unsafe {
157            libc::readv(
158                self.as_raw_fd(),
159                bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
160                cmp::min(bufs.len(), max_iov()) as libc::c_int,
161            )
162        })?;
163        Ok(ret as usize)
164    }
165
166    #[cfg(any(
167        target_os = "espidf",
168        target_os = "horizon",
169        target_os = "vita",
170        target_os = "nuttx"
171    ))]
172    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
173        io::default_read_vectored(|b| self.read(b), bufs)
174    }
175
176    #[inline]
177    pub fn is_read_vectored(&self) -> bool {
178        cfg!(not(any(
179            target_os = "espidf",
180            target_os = "horizon",
181            target_os = "vita",
182            target_os = "nuttx",
183            target_os = "wasi",
184        )))
185    }
186
187    pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
188        let mut me = self;
189        (&mut me).read_to_end(buf)
190    }
191
192    pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
193        cvt(unsafe {
194            pread64(
195                self.as_raw_fd(),
196                buf.as_mut_ptr() as *mut libc::c_void,
197                cmp::min(buf.len(), READ_LIMIT),
198                offset as off64_t, // EINVAL if offset + count overflows
199            )
200        })
201        .map(|n| n as usize)
202    }
203
204    pub fn read_buf(&self, mut cursor: BorrowedCursor<'_, u8>) -> io::Result<()> {
205        // SAFETY: `cursor.as_mut()` starts with `cursor.capacity()` writable bytes
206        let ret = cvt(unsafe {
207            libc::read(
208                self.as_raw_fd(),
209                cursor.as_mut().as_mut_ptr().cast::<libc::c_void>(),
210                cmp::min(cursor.capacity(), READ_LIMIT),
211            )
212        })?;
213
214        // SAFETY: `ret` bytes were written to the initialized portion of the buffer
215        unsafe {
216            cursor.advance(ret as usize);
217        }
218        Ok(())
219    }
220
221    pub fn read_buf_at(&self, mut cursor: BorrowedCursor<'_, u8>, offset: u64) -> io::Result<()> {
222        // SAFETY: `cursor.as_mut()` starts with `cursor.capacity()` writable bytes
223        let ret = cvt(unsafe {
224            pread64(
225                self.as_raw_fd(),
226                cursor.as_mut().as_mut_ptr().cast::<libc::c_void>(),
227                cmp::min(cursor.capacity(), READ_LIMIT),
228                offset as off64_t, // EINVAL if offset + count overflows
229            )
230        })?;
231
232        // SAFETY: `ret` bytes were written to the initialized portion of the buffer
233        unsafe {
234            cursor.advance(ret as usize);
235        }
236        Ok(())
237    }
238
239    #[cfg(any(
240        target_os = "aix",
241        target_os = "dragonfly", // DragonFly 1.5
242        target_os = "emscripten",
243        target_os = "freebsd",
244        target_os = "fuchsia",
245        target_os = "hurd",
246        target_os = "illumos",
247        target_os = "linux",
248        target_os = "netbsd",
249        target_os = "openbsd", // OpenBSD 2.7
250        all(target_os = "macos", target_arch = "aarch64"),
251    ))]
252    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
253        let ret = cvt(unsafe {
254            libc::preadv(
255                self.as_raw_fd(),
256                bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
257                cmp::min(bufs.len(), max_iov()) as libc::c_int,
258                offset as _,
259            )
260        })?;
261        Ok(ret as usize)
262    }
263
264    #[cfg(not(any(
265        target_os = "aix",
266        target_os = "android",
267        target_os = "dragonfly",
268        target_os = "emscripten",
269        target_os = "freebsd",
270        target_os = "fuchsia",
271        target_os = "hurd",
272        target_os = "illumos",
273        target_os = "linux",
274        target_os = "netbsd",
275        target_os = "openbsd",
276        target_vendor = "apple",
277    )))]
278    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
279        io::default_read_vectored(|b| self.read_at(b, offset), bufs)
280    }
281
282    // We support some old Android versions that do not have `preadv` in libc,
283    // so we use weak linkage and fallback to a direct syscall if not available.
284    //
285    // On 32-bit targets, we don't want to deal with weird ABI issues around
286    // passing 64-bits parameters to syscalls, so we fallback to the default
287    // implementation if `preadv` is not available.
288    #[cfg(all(target_os = "android", target_pointer_width = "64"))]
289    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
290        syscall!(
291            fn preadv(
292                fd: libc::c_int,
293                iovec: *const libc::iovec,
294                n_iovec: libc::c_int,
295                offset: off64_t,
296            ) -> isize;
297        );
298
299        let ret = cvt(unsafe {
300            preadv(
301                self.as_raw_fd(),
302                bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
303                cmp::min(bufs.len(), max_iov()) as libc::c_int,
304                offset as _,
305            )
306        })?;
307        Ok(ret as usize)
308    }
309
310    #[cfg(all(target_os = "android", target_pointer_width = "32"))]
311    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
312        weak!(
313            fn preadv64(
314                fd: libc::c_int,
315                iovec: *const libc::iovec,
316                n_iovec: libc::c_int,
317                offset: off64_t,
318            ) -> isize;
319        );
320
321        match preadv64.get() {
322            Some(preadv) => {
323                let ret = cvt(unsafe {
324                    preadv(
325                        self.as_raw_fd(),
326                        bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
327                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
328                        offset as _,
329                    )
330                })?;
331                Ok(ret as usize)
332            }
333            None => io::default_read_vectored(|b| self.read_at(b, offset), bufs),
334        }
335    }
336
337    // We support old MacOS, iOS, watchOS, tvOS and visionOS. `preadv` was added in the following
338    // Apple OS versions:
339    // iOS 14.0
340    // tvOS 14.0
341    // macOS 11.0
342    // watchOS 7.0
343    //
344    // Since macOS 11.0 is also the first version with AArch64 support, we can
345    // `preadv` unconditionally there. But on all other targets we must use
346    // "weak" linking.
347    #[cfg(all(target_vendor = "apple", not(all(target_os = "macos", target_arch = "aarch64"))))]
348    pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
349        weak!(
350            fn preadv(
351                fd: libc::c_int,
352                iovec: *const libc::iovec,
353                n_iovec: libc::c_int,
354                offset: off64_t,
355            ) -> isize;
356        );
357
358        match preadv.get() {
359            Some(preadv) => {
360                let ret = cvt(unsafe {
361                    preadv(
362                        self.as_raw_fd(),
363                        bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
364                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
365                        offset as _,
366                    )
367                })?;
368                Ok(ret as usize)
369            }
370            None => io::default_read_vectored(|b| self.read_at(b, offset), bufs),
371        }
372    }
373
374    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
375        let ret = cvt(unsafe {
376            libc::write(
377                self.as_raw_fd(),
378                buf.as_ptr() as *const libc::c_void,
379                cmp::min(buf.len(), READ_LIMIT),
380            )
381        })?;
382        Ok(ret as usize)
383    }
384
385    #[cfg(not(any(
386        target_os = "espidf",
387        target_os = "horizon",
388        target_os = "vita",
389        target_os = "nuttx"
390    )))]
391    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
392        let ret = cvt(unsafe {
393            libc::writev(
394                self.as_raw_fd(),
395                bufs.as_ptr() as *const libc::iovec,
396                cmp::min(bufs.len(), max_iov()) as libc::c_int,
397            )
398        })?;
399        Ok(ret as usize)
400    }
401
402    #[cfg(any(
403        target_os = "espidf",
404        target_os = "horizon",
405        target_os = "vita",
406        target_os = "nuttx"
407    ))]
408    pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
409        io::default_write_vectored(|b| self.write(b), bufs)
410    }
411
412    #[inline]
413    pub fn is_write_vectored(&self) -> bool {
414        cfg!(not(any(
415            target_os = "espidf",
416            target_os = "horizon",
417            target_os = "vita",
418            target_os = "nuttx",
419            target_os = "wasi",
420        )))
421    }
422
423    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
424        unsafe {
425            cvt(pwrite64(
426                self.as_raw_fd(),
427                buf.as_ptr() as *const libc::c_void,
428                cmp::min(buf.len(), READ_LIMIT),
429                offset as off64_t,
430            ))
431            .map(|n| n as usize)
432        }
433    }
434
435    #[cfg(any(
436        target_os = "aix",
437        target_os = "dragonfly", // DragonFly 1.5
438        target_os = "emscripten",
439        target_os = "freebsd",
440        target_os = "fuchsia",
441        target_os = "hurd",
442        target_os = "illumos",
443        target_os = "linux",
444        target_os = "netbsd",
445        target_os = "openbsd", // OpenBSD 2.7
446        all(target_os = "macos", target_arch = "aarch64"),
447    ))]
448    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
449        let ret = cvt(unsafe {
450            libc::pwritev(
451                self.as_raw_fd(),
452                bufs.as_ptr() as *const libc::iovec,
453                cmp::min(bufs.len(), max_iov()) as libc::c_int,
454                offset as _,
455            )
456        })?;
457        Ok(ret as usize)
458    }
459
460    #[cfg(not(any(
461        target_os = "aix",
462        target_os = "android",
463        target_os = "dragonfly",
464        target_os = "emscripten",
465        target_os = "freebsd",
466        target_os = "fuchsia",
467        target_os = "hurd",
468        target_os = "illumos",
469        target_os = "linux",
470        target_os = "netbsd",
471        target_os = "openbsd",
472        target_vendor = "apple",
473    )))]
474    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
475        io::default_write_vectored(|b| self.write_at(b, offset), bufs)
476    }
477
478    // We support some old Android versions that do not have `pwritev` in libc,
479    // so we use weak linkage and fallback to a direct syscall if not available.
480    //
481    // On 32-bit targets, we don't want to deal with weird ABI issues around
482    // passing 64-bits parameters to syscalls, so we fallback to the default
483    // implementation if `pwritev` is not available.
484    #[cfg(all(target_os = "android", target_pointer_width = "64"))]
485    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
486        syscall!(
487            fn pwritev(
488                fd: libc::c_int,
489                iovec: *const libc::iovec,
490                n_iovec: libc::c_int,
491                offset: off64_t,
492            ) -> isize;
493        );
494
495        let ret = cvt(unsafe {
496            pwritev(
497                self.as_raw_fd(),
498                bufs.as_ptr() as *const libc::iovec,
499                cmp::min(bufs.len(), max_iov()) as libc::c_int,
500                offset as _,
501            )
502        })?;
503        Ok(ret as usize)
504    }
505
506    #[cfg(all(target_os = "android", target_pointer_width = "32"))]
507    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
508        weak!(
509            fn pwritev64(
510                fd: libc::c_int,
511                iovec: *const libc::iovec,
512                n_iovec: libc::c_int,
513                offset: off64_t,
514            ) -> isize;
515        );
516
517        match pwritev64.get() {
518            Some(pwritev) => {
519                let ret = cvt(unsafe {
520                    pwritev(
521                        self.as_raw_fd(),
522                        bufs.as_ptr() as *const libc::iovec,
523                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
524                        offset as _,
525                    )
526                })?;
527                Ok(ret as usize)
528            }
529            None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
530        }
531    }
532
533    // We support old MacOS, iOS, watchOS, tvOS and visionOS. `pwritev` was added in the following
534    // Apple OS versions:
535    // iOS 14.0
536    // tvOS 14.0
537    // macOS 11.0
538    // watchOS 7.0
539    //
540    // Since macOS 11.0 is also the first version with AArch64 support, we can
541    // `pwritev` unconditionally there. But on all other targets we must use
542    // "weak" linking.
543    #[cfg(all(target_vendor = "apple", not(all(target_os = "macos", target_arch = "aarch64")),))]
544    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
545        weak!(
546            fn pwritev(
547                fd: libc::c_int,
548                iovec: *const libc::iovec,
549                n_iovec: libc::c_int,
550                offset: off64_t,
551            ) -> isize;
552        );
553
554        match pwritev.get() {
555            Some(pwritev) => {
556                let ret = cvt(unsafe {
557                    pwritev(
558                        self.as_raw_fd(),
559                        bufs.as_ptr() as *const libc::iovec,
560                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
561                        offset as _,
562                    )
563                })?;
564                Ok(ret as usize)
565            }
566            None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
567        }
568    }
569
570    #[cfg(not(any(
571        target_env = "newlib",
572        target_os = "solaris",
573        target_os = "illumos",
574        target_os = "emscripten",
575        target_os = "fuchsia",
576        target_os = "l4re",
577        target_os = "linux",
578        target_os = "cygwin",
579        target_os = "haiku",
580        target_os = "redox",
581        target_os = "vxworks",
582        target_os = "nto",
583        target_os = "qnx",
584        target_os = "wasi",
585    )))]
586    pub fn set_cloexec(&self) -> io::Result<()> {
587        unsafe {
588            cvt(libc::ioctl(self.as_raw_fd(), libc::FIOCLEX))?;
589            Ok(())
590        }
591    }
592    #[cfg(any(
593        all(
594            target_env = "newlib",
595            not(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))
596        ),
597        target_os = "solaris",
598        target_os = "illumos",
599        target_os = "emscripten",
600        target_os = "fuchsia",
601        target_os = "l4re",
602        target_os = "linux",
603        target_os = "cygwin",
604        target_os = "haiku",
605        target_os = "redox",
606        target_os = "vxworks",
607        target_os = "nto",
608        target_os = "qnx",
609        target_os = "wasi",
610    ))]
611    pub fn set_cloexec(&self) -> io::Result<()> {
612        unsafe {
613            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?;
614            let new = previous | libc::FD_CLOEXEC;
615            if new != previous {
616                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?;
617            }
618            Ok(())
619        }
620    }
621    #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
622    pub fn set_cloexec(&self) -> io::Result<()> {
623        // FD_CLOEXEC is not supported in ESP-IDF, Horizon OS and Vita but there's no need to,
624        // because none of them supports spawning processes.
625        Ok(())
626    }
627
628    #[cfg(target_os = "linux")]
629    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
630        unsafe {
631            let v = nonblocking as libc::c_int;
632            cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?;
633            Ok(())
634        }
635    }
636
637    #[cfg(not(target_os = "linux"))]
638    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
639        unsafe {
640            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFL))?;
641            let new = if nonblocking {
642                previous | libc::O_NONBLOCK
643            } else {
644                previous & !libc::O_NONBLOCK
645            };
646            if new != previous {
647                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFL, new))?;
648            }
649            Ok(())
650        }
651    }
652
653    #[inline]
654    pub fn duplicate(&self) -> io::Result<FileDesc> {
655        Ok(Self(self.0.try_clone()?))
656    }
657}
658
659impl<'a> Read for &'a FileDesc {
660    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
661        (**self).read(buf)
662    }
663
664    fn read_buf(&mut self, cursor: BorrowedCursor<'_, u8>) -> io::Result<()> {
665        (**self).read_buf(cursor)
666    }
667
668    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
669        (**self).read_vectored(bufs)
670    }
671
672    #[inline]
673    fn is_read_vectored(&self) -> bool {
674        (**self).is_read_vectored()
675    }
676}
677
678impl AsInner<OwnedFd> for FileDesc {
679    #[inline]
680    fn as_inner(&self) -> &OwnedFd {
681        &self.0
682    }
683}
684
685impl IntoInner<OwnedFd> for FileDesc {
686    fn into_inner(self) -> OwnedFd {
687        self.0
688    }
689}
690
691impl FromInner<OwnedFd> for FileDesc {
692    fn from_inner(owned_fd: OwnedFd) -> Self {
693        Self(owned_fd)
694    }
695}
696
697impl AsFd for FileDesc {
698    fn as_fd(&self) -> BorrowedFd<'_> {
699        self.0.as_fd()
700    }
701}
702
703impl AsRawFd for FileDesc {
704    #[inline]
705    fn as_raw_fd(&self) -> RawFd {
706        self.0.as_raw_fd()
707    }
708}
709
710impl IntoRawFd for FileDesc {
711    fn into_raw_fd(self) -> RawFd {
712        self.0.into_raw_fd()
713    }
714}
715
716impl FromRawFd for FileDesc {
717    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
718        Self(unsafe { FromRawFd::from_raw_fd(raw_fd) })
719    }
720}