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