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