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