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