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