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