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