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