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::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
39#[cfg(all(target_os = "android", target_pointer_width = "64"))]
40use crate::sys::pal::weak::syscall;
41#[cfg(any(all(target_os = "android", target_pointer_width = "32"), target_vendor = "apple"))]
42use crate::sys::pal::weak::weak;
43use crate::sys::{AsInner, FromInner, IntoInner, cvt};
44
45#[derive(Debug)]
46pub struct FileDesc(OwnedFd);
47
48// The maximum read limit on most POSIX-like systems is `SSIZE_MAX`,
49// with the man page quoting that if the count of bytes to read is
50// greater than `SSIZE_MAX` the result is "unspecified".
51//
52// On Apple targets however, apparently the 64-bit libc is either buggy or
53// intentionally showing odd behavior by rejecting any read with a size
54// larger than INT_MAX. To handle both of these the read size is capped on
55// both platforms.
56const READ_LIMIT: usize = if cfg!(target_vendor = "apple") {
57    libc::c_int::MAX as usize
58} else {
59    libc::ssize_t::MAX as usize
60};
61
62#[cfg(any(
63    target_os = "dragonfly",
64    target_os = "freebsd",
65    target_os = "netbsd",
66    target_os = "openbsd",
67    target_vendor = "apple",
68    target_os = "cygwin",
69))]
70const fn max_iov() -> usize {
71    libc::IOV_MAX as usize
72}
73
74#[cfg(any(
75    target_os = "android",
76    target_os = "emscripten",
77    target_os = "linux",
78    target_os = "nto",
79))]
80const fn max_iov() -> usize {
81    libc::UIO_MAXIOV as usize
82}
83
84#[cfg(not(any(
85    target_os = "android",
86    target_os = "dragonfly",
87    target_os = "emscripten",
88    target_os = "espidf",
89    target_os = "freebsd",
90    target_os = "linux",
91    target_os = "netbsd",
92    target_os = "nuttx",
93    target_os = "nto",
94    target_os = "openbsd",
95    target_os = "horizon",
96    target_os = "vita",
97    target_vendor = "apple",
98    target_os = "cygwin",
99)))]
100const fn max_iov() -> usize {
101    16 // The minimum value required by POSIX.
102}
103
104impl FileDesc {
105    #[inline]
106    pub fn try_clone(&self) -> io::Result<Self> {
107        self.duplicate()
108    }
109
110    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
111        let ret = cvt(unsafe {
112            libc::read(
113                self.as_raw_fd(),
114                buf.as_mut_ptr() as *mut libc::c_void,
115                cmp::min(buf.len(), READ_LIMIT),
116            )
117        })?;
118        Ok(ret as usize)
119    }
120
121    #[cfg(not(any(
122        target_os = "espidf",
123        target_os = "horizon",
124        target_os = "vita",
125        target_os = "nuttx"
126    )))]
127    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
128        let ret = cvt(unsafe {
129            libc::readv(
130                self.as_raw_fd(),
131                bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
132                cmp::min(bufs.len(), max_iov()) as libc::c_int,
133            )
134        })?;
135        Ok(ret as usize)
136    }
137
138    #[cfg(any(
139        target_os = "espidf",
140        target_os = "horizon",
141        target_os = "vita",
142        target_os = "nuttx"
143    ))]
144    pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
145        io::default_read_vectored(|b| self.read(b), bufs)
146    }
147
148    #[inline]
149    pub fn is_read_vectored(&self) -> bool {
150        cfg!(not(any(
151            target_os = "espidf",
152            target_os = "horizon",
153            target_os = "vita",
154            target_os = "nuttx",
155            target_os = "wasi",
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(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(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            target_os = "wasi",
390        )))
391    }
392
393    pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
394        #[cfg(not(any(
395            all(target_os = "linux", not(target_env = "musl")),
396            target_os = "android",
397            target_os = "hurd"
398        )))]
399        use libc::pwrite as pwrite64;
400        #[cfg(any(
401            all(target_os = "linux", not(target_env = "musl")),
402            target_os = "android",
403            target_os = "hurd"
404        ))]
405        use libc::pwrite64;
406
407        unsafe {
408            cvt(pwrite64(
409                self.as_raw_fd(),
410                buf.as_ptr() as *const libc::c_void,
411                cmp::min(buf.len(), READ_LIMIT),
412                offset as off64_t,
413            ))
414            .map(|n| n as usize)
415        }
416    }
417
418    #[cfg(any(
419        target_os = "aix",
420        target_os = "dragonfly", // DragonFly 1.5
421        target_os = "emscripten",
422        target_os = "freebsd",
423        target_os = "fuchsia",
424        target_os = "hurd",
425        target_os = "illumos",
426        target_os = "linux",
427        target_os = "netbsd",
428        target_os = "openbsd", // OpenBSD 2.7
429    ))]
430    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
431        let ret = cvt(unsafe {
432            libc::pwritev(
433                self.as_raw_fd(),
434                bufs.as_ptr() as *const libc::iovec,
435                cmp::min(bufs.len(), max_iov()) as libc::c_int,
436                offset as _,
437            )
438        })?;
439        Ok(ret as usize)
440    }
441
442    #[cfg(not(any(
443        target_os = "aix",
444        target_os = "android",
445        target_os = "dragonfly",
446        target_os = "emscripten",
447        target_os = "freebsd",
448        target_os = "fuchsia",
449        target_os = "hurd",
450        target_os = "illumos",
451        target_os = "linux",
452        target_os = "netbsd",
453        target_os = "openbsd",
454        target_vendor = "apple",
455    )))]
456    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
457        io::default_write_vectored(|b| self.write_at(b, offset), bufs)
458    }
459
460    // We support some old Android versions that do not have `pwritev` in libc,
461    // so we use weak linkage and fallback to a direct syscall if not available.
462    //
463    // On 32-bit targets, we don't want to deal with weird ABI issues around
464    // passing 64-bits parameters to syscalls, so we fallback to the default
465    // implementation if `pwritev` is not available.
466    #[cfg(all(target_os = "android", target_pointer_width = "64"))]
467    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
468        syscall!(
469            fn pwritev(
470                fd: libc::c_int,
471                iovec: *const libc::iovec,
472                n_iovec: libc::c_int,
473                offset: off64_t,
474            ) -> isize;
475        );
476
477        let ret = cvt(unsafe {
478            pwritev(
479                self.as_raw_fd(),
480                bufs.as_ptr() as *const libc::iovec,
481                cmp::min(bufs.len(), max_iov()) as libc::c_int,
482                offset as _,
483            )
484        })?;
485        Ok(ret as usize)
486    }
487
488    #[cfg(all(target_os = "android", target_pointer_width = "32"))]
489    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
490        weak!(
491            fn pwritev64(
492                fd: libc::c_int,
493                iovec: *const libc::iovec,
494                n_iovec: libc::c_int,
495                offset: off64_t,
496            ) -> isize;
497        );
498
499        match pwritev64.get() {
500            Some(pwritev) => {
501                let ret = cvt(unsafe {
502                    pwritev(
503                        self.as_raw_fd(),
504                        bufs.as_ptr() as *const libc::iovec,
505                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
506                        offset as _,
507                    )
508                })?;
509                Ok(ret as usize)
510            }
511            None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
512        }
513    }
514
515    // We support old MacOS, iOS, watchOS, tvOS and visionOS. `pwritev` was added in the following
516    // Apple OS versions:
517    // ios 14.0
518    // tvos 14.0
519    // macos 11.0
520    // watchos 7.0
521    //
522    // These versions may be newer than the minimum supported versions of OS's we support so we must
523    // use "weak" linking.
524    #[cfg(target_vendor = "apple")]
525    pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
526        weak!(
527            fn pwritev(
528                fd: libc::c_int,
529                iovec: *const libc::iovec,
530                n_iovec: libc::c_int,
531                offset: off64_t,
532            ) -> isize;
533        );
534
535        match pwritev.get() {
536            Some(pwritev) => {
537                let ret = cvt(unsafe {
538                    pwritev(
539                        self.as_raw_fd(),
540                        bufs.as_ptr() as *const libc::iovec,
541                        cmp::min(bufs.len(), max_iov()) as libc::c_int,
542                        offset as _,
543                    )
544                })?;
545                Ok(ret as usize)
546            }
547            None => io::default_write_vectored(|b| self.write_at(b, offset), bufs),
548        }
549    }
550
551    #[cfg(not(any(
552        target_env = "newlib",
553        target_os = "solaris",
554        target_os = "illumos",
555        target_os = "emscripten",
556        target_os = "fuchsia",
557        target_os = "l4re",
558        target_os = "linux",
559        target_os = "cygwin",
560        target_os = "haiku",
561        target_os = "redox",
562        target_os = "vxworks",
563        target_os = "nto",
564        target_os = "wasi",
565    )))]
566    pub fn set_cloexec(&self) -> io::Result<()> {
567        unsafe {
568            cvt(libc::ioctl(self.as_raw_fd(), libc::FIOCLEX))?;
569            Ok(())
570        }
571    }
572    #[cfg(any(
573        all(
574            target_env = "newlib",
575            not(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))
576        ),
577        target_os = "solaris",
578        target_os = "illumos",
579        target_os = "emscripten",
580        target_os = "fuchsia",
581        target_os = "l4re",
582        target_os = "linux",
583        target_os = "cygwin",
584        target_os = "haiku",
585        target_os = "redox",
586        target_os = "vxworks",
587        target_os = "nto",
588        target_os = "wasi",
589    ))]
590    pub fn set_cloexec(&self) -> io::Result<()> {
591        unsafe {
592            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?;
593            let new = previous | libc::FD_CLOEXEC;
594            if new != previous {
595                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?;
596            }
597            Ok(())
598        }
599    }
600    #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))]
601    pub fn set_cloexec(&self) -> io::Result<()> {
602        // FD_CLOEXEC is not supported in ESP-IDF, Horizon OS and Vita but there's no need to,
603        // because none of them supports spawning processes.
604        Ok(())
605    }
606
607    #[cfg(target_os = "linux")]
608    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
609        unsafe {
610            let v = nonblocking as libc::c_int;
611            cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?;
612            Ok(())
613        }
614    }
615
616    #[cfg(not(target_os = "linux"))]
617    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
618        unsafe {
619            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFL))?;
620            let new = if nonblocking {
621                previous | libc::O_NONBLOCK
622            } else {
623                previous & !libc::O_NONBLOCK
624            };
625            if new != previous {
626                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFL, new))?;
627            }
628            Ok(())
629        }
630    }
631
632    #[inline]
633    pub fn duplicate(&self) -> io::Result<FileDesc> {
634        Ok(Self(self.0.try_clone()?))
635    }
636}
637
638impl<'a> Read for &'a FileDesc {
639    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
640        (**self).read(buf)
641    }
642
643    fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> {
644        (**self).read_buf(cursor)
645    }
646
647    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
648        (**self).read_vectored(bufs)
649    }
650
651    #[inline]
652    fn is_read_vectored(&self) -> bool {
653        (**self).is_read_vectored()
654    }
655}
656
657impl AsInner<OwnedFd> for FileDesc {
658    #[inline]
659    fn as_inner(&self) -> &OwnedFd {
660        &self.0
661    }
662}
663
664impl IntoInner<OwnedFd> for FileDesc {
665    fn into_inner(self) -> OwnedFd {
666        self.0
667    }
668}
669
670impl FromInner<OwnedFd> for FileDesc {
671    fn from_inner(owned_fd: OwnedFd) -> Self {
672        Self(owned_fd)
673    }
674}
675
676impl AsFd for FileDesc {
677    fn as_fd(&self) -> BorrowedFd<'_> {
678        self.0.as_fd()
679    }
680}
681
682impl AsRawFd for FileDesc {
683    #[inline]
684    fn as_raw_fd(&self) -> RawFd {
685        self.0.as_raw_fd()
686    }
687}
688
689impl IntoRawFd for FileDesc {
690    fn into_raw_fd(self) -> RawFd {
691        self.0.into_raw_fd()
692    }
693}
694
695impl FromRawFd for FileDesc {
696    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
697        Self(unsafe { FromRawFd::from_raw_fd(raw_fd) })
698    }
699}