1//! libc syscalls supporting `rustix::io`.
2
3use super::super::c;
4#[cfg(any(target_os = "android", target_os = "linux"))]
5use super::super::conv::syscall_ret_owned_fd;
6use super::super::conv::{
7    borrowed_fd, ret, ret_c_int, ret_discarded_fd, ret_owned_fd, ret_ssize_t,
8};
9use super::super::offset::{libc_pread, libc_pwrite};
10#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "solaris")))]
11use super::super::offset::{libc_preadv, libc_pwritev};
12#[cfg(all(target_os = "linux", target_env = "gnu"))]
13use super::super::offset::{libc_preadv2, libc_pwritev2};
14use crate::fd::{AsFd, BorrowedFd, OwnedFd, RawFd};
15#[cfg(not(any(target_os = "aix", target_os = "wasi")))]
16use crate::io::DupFlags;
17#[cfg(not(any(
18    target_os = "aix",
19    target_os = "haiku",
20    target_os = "ios",
21    target_os = "macos",
22    target_os = "wasi"
23)))]
24use crate::io::PipeFlags;
25use crate::io::{self, FdFlags, IoSlice, IoSliceMut, PollFd};
26#[cfg(any(target_os = "android", target_os = "linux"))]
27use crate::io::{EventfdFlags, IoSliceRaw, ReadWriteFlags, SpliceFlags};
28use core::cmp::min;
29use core::convert::TryInto;
30use core::mem::MaybeUninit;
31#[cfg(any(target_os = "android", target_os = "linux"))]
32use core::ptr;
33#[cfg(all(feature = "fs", feature = "net"))]
34use libc_errno::errno;
35
36pub(crate) fn read(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result<usize> {
37    let nread = unsafe {
38        ret_ssize_t(c::read(
39            borrowed_fd(fd),
40            buf.as_mut_ptr().cast(),
41            min(buf.len(), READ_LIMIT),
42        ))?
43    };
44    Ok(nread as usize)
45}
46
47pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
48    let nwritten = unsafe {
49        ret_ssize_t(c::write(
50            borrowed_fd(fd),
51            buf.as_ptr().cast(),
52            min(buf.len(), READ_LIMIT),
53        ))?
54    };
55    Ok(nwritten as usize)
56}
57
58pub(crate) fn pread(fd: BorrowedFd<'_>, buf: &mut [u8], offset: u64) -> io::Result<usize> {
59    let len = min(buf.len(), READ_LIMIT);
60
61    // Silently cast; we'll get `EINVAL` if the value is negative.
62    let offset = offset as i64;
63
64    let nread = unsafe {
65        ret_ssize_t(libc_pread(
66            borrowed_fd(fd),
67            buf.as_mut_ptr().cast(),
68            len,
69            offset,
70        ))?
71    };
72    Ok(nread as usize)
73}
74
75pub(crate) fn pwrite(fd: BorrowedFd<'_>, buf: &[u8], offset: u64) -> io::Result<usize> {
76    let len = min(buf.len(), READ_LIMIT);
77
78    // Silently cast; we'll get `EINVAL` if the value is negative.
79    let offset = offset as i64;
80
81    let nwritten = unsafe {
82        ret_ssize_t(libc_pwrite(
83            borrowed_fd(fd),
84            buf.as_ptr().cast(),
85            len,
86            offset,
87        ))?
88    };
89    Ok(nwritten as usize)
90}
91
92pub(crate) fn readv(fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut]) -> io::Result<usize> {
93    let nread = unsafe {
94        ret_ssize_t(c::readv(
95            borrowed_fd(fd),
96            bufs.as_ptr().cast::<c::iovec>(),
97            min(bufs.len(), max_iov()) as c::c_int,
98        ))?
99    };
100    Ok(nread as usize)
101}
102
103pub(crate) fn writev(fd: BorrowedFd<'_>, bufs: &[IoSlice]) -> io::Result<usize> {
104    let nwritten = unsafe {
105        ret_ssize_t(c::writev(
106            borrowed_fd(fd),
107            bufs.as_ptr().cast::<c::iovec>(),
108            min(bufs.len(), max_iov()) as c::c_int,
109        ))?
110    };
111    Ok(nwritten as usize)
112}
113
114#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "solaris")))]
115pub(crate) fn preadv(
116    fd: BorrowedFd<'_>,
117    bufs: &mut [IoSliceMut],
118    offset: u64,
119) -> io::Result<usize> {
120    // Silently cast; we'll get `EINVAL` if the value is negative.
121    let offset = offset as i64;
122    let nread = unsafe {
123        ret_ssize_t(libc_preadv(
124            borrowed_fd(fd),
125            bufs.as_ptr().cast::<c::iovec>(),
126            min(bufs.len(), max_iov()) as c::c_int,
127            offset,
128        ))?
129    };
130    Ok(nread as usize)
131}
132
133#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "solaris")))]
134pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice], offset: u64) -> io::Result<usize> {
135    // Silently cast; we'll get `EINVAL` if the value is negative.
136    let offset = offset as i64;
137    let nwritten = unsafe {
138        ret_ssize_t(libc_pwritev(
139            borrowed_fd(fd),
140            bufs.as_ptr().cast::<c::iovec>(),
141            min(bufs.len(), max_iov()) as c::c_int,
142            offset,
143        ))?
144    };
145    Ok(nwritten as usize)
146}
147
148#[cfg(all(target_os = "linux", target_env = "gnu"))]
149pub(crate) fn preadv2(
150    fd: BorrowedFd<'_>,
151    bufs: &mut [IoSliceMut],
152    offset: u64,
153    flags: ReadWriteFlags,
154) -> io::Result<usize> {
155    // Silently cast; we'll get `EINVAL` if the value is negative.
156    let offset = offset as i64;
157    let nread = unsafe {
158        ret_ssize_t(libc_preadv2(
159            borrowed_fd(fd),
160            bufs.as_ptr().cast::<c::iovec>(),
161            min(bufs.len(), max_iov()) as c::c_int,
162            offset,
163            flags.bits(),
164        ))?
165    };
166    Ok(nread as usize)
167}
168
169/// At present, `libc` only has `preadv2` defined for glibc. On other
170/// ABIs, use `libc::syscall`.
171#[cfg(any(
172    target_os = "android",
173    all(target_os = "linux", not(target_env = "gnu")),
174))]
175#[inline]
176pub(crate) fn preadv2(
177    fd: BorrowedFd<'_>,
178    bufs: &mut [IoSliceMut],
179    offset: u64,
180    flags: ReadWriteFlags,
181) -> io::Result<usize> {
182    // Silently cast; we'll get `EINVAL` if the value is negative.
183    let offset = offset as i64;
184    let nread = unsafe {
185        ret_ssize_t(libc::syscall(
186            libc::SYS_preadv2,
187            borrowed_fd(fd),
188            bufs.as_ptr().cast::<c::iovec>(),
189            min(bufs.len(), max_iov()) as c::c_int,
190            offset,
191            flags.bits(),
192        ) as c::ssize_t)?
193    };
194    Ok(nread as usize)
195}
196
197#[cfg(all(target_os = "linux", target_env = "gnu"))]
198pub(crate) fn pwritev2(
199    fd: BorrowedFd<'_>,
200    bufs: &[IoSlice],
201    offset: u64,
202    flags: ReadWriteFlags,
203) -> io::Result<usize> {
204    // Silently cast; we'll get `EINVAL` if the value is negative.
205    let offset = offset as i64;
206    let nwritten = unsafe {
207        ret_ssize_t(libc_pwritev2(
208            borrowed_fd(fd),
209            bufs.as_ptr().cast::<c::iovec>(),
210            min(bufs.len(), max_iov()) as c::c_int,
211            offset,
212            flags.bits(),
213        ))?
214    };
215    Ok(nwritten as usize)
216}
217
218/// At present, `libc` only has `pwritev2` defined for glibc. On other
219/// ABIs, use `libc::syscall`.
220#[cfg(any(
221    target_os = "android",
222    all(target_os = "linux", not(target_env = "gnu")),
223))]
224#[inline]
225pub(crate) fn pwritev2(
226    fd: BorrowedFd<'_>,
227    bufs: &[IoSlice],
228    offset: u64,
229    flags: ReadWriteFlags,
230) -> io::Result<usize> {
231    // Silently cast; we'll get `EINVAL` if the value is negative.
232    let offset = offset as i64;
233    let nwritten = unsafe {
234        ret_ssize_t(libc::syscall(
235            libc::SYS_pwritev2,
236            borrowed_fd(fd),
237            bufs.as_ptr().cast::<c::iovec>(),
238            min(bufs.len(), max_iov()) as c::c_int,
239            offset,
240            flags.bits(),
241        ) as c::ssize_t)?
242    };
243    Ok(nwritten as usize)
244}
245
246// These functions are derived from Rust's library/std/src/sys/unix/fd.rs at
247// revision a77da2d454e6caa227a85b16410b95f93495e7e0.
248
249// The maximum read limit on most POSIX-like systems is `SSIZE_MAX`, with the
250// man page quoting that if the count of bytes to read is greater than
251// `SSIZE_MAX` the result is "unspecified".
252//
253// On macOS, however, apparently the 64-bit libc is either buggy or
254// intentionally showing odd behavior by rejecting any read with a size larger
255// than or equal to `INT_MAX`. To handle both of these the read size is capped
256// on both platforms.
257#[cfg(target_os = "macos")]
258const READ_LIMIT: usize = c::c_int::MAX as usize - 1;
259#[cfg(not(target_os = "macos"))]
260const READ_LIMIT: usize = c::ssize_t::MAX as usize;
261
262#[cfg(any(
263    target_os = "dragonfly",
264    target_os = "freebsd",
265    target_os = "ios",
266    target_os = "macos",
267    target_os = "netbsd",
268    target_os = "openbsd",
269))]
270const fn max_iov() -> usize {
271    c::IOV_MAX as usize
272}
273
274#[cfg(any(target_os = "android", target_os = "emscripten", target_os = "linux"))]
275const fn max_iov() -> usize {
276    c::UIO_MAXIOV as usize
277}
278
279#[cfg(not(any(
280    target_os = "android",
281    target_os = "dragonfly",
282    target_os = "emscripten",
283    target_os = "freebsd",
284    target_os = "ios",
285    target_os = "linux",
286    target_os = "macos",
287    target_os = "netbsd",
288    target_os = "openbsd",
289)))]
290const fn max_iov() -> usize {
291    16 // The minimum value required by POSIX.
292}
293
294pub(crate) unsafe fn close(raw_fd: RawFd) {
295    let _ = c::close(raw_fd as c::c_int);
296}
297
298#[cfg(any(target_os = "android", target_os = "linux"))]
299pub(crate) fn eventfd(initval: u32, flags: EventfdFlags) -> io::Result<OwnedFd> {
300    unsafe { syscall_ret_owned_fd(c::syscall(c::SYS_eventfd2, initval, flags.bits())) }
301}
302
303#[cfg(any(target_os = "android", target_os = "linux"))]
304#[inline]
305pub(crate) fn ioctl_blksszget(fd: BorrowedFd) -> io::Result<u32> {
306    let mut result = MaybeUninit::<c::c_uint>::uninit();
307    unsafe {
308        ret(c::ioctl(borrowed_fd(fd), c::BLKSSZGET, result.as_mut_ptr()))?;
309        Ok(result.assume_init() as u32)
310    }
311}
312
313#[cfg(any(target_os = "android", target_os = "linux"))]
314#[inline]
315pub(crate) fn ioctl_blkpbszget(fd: BorrowedFd) -> io::Result<u32> {
316    let mut result = MaybeUninit::<c::c_uint>::uninit();
317    unsafe {
318        ret(c::ioctl(
319            borrowed_fd(fd),
320            c::BLKPBSZGET,
321            result.as_mut_ptr(),
322        ))?;
323        Ok(result.assume_init() as u32)
324    }
325}
326
327#[cfg(not(target_os = "redox"))]
328pub(crate) fn ioctl_fionread(fd: BorrowedFd<'_>) -> io::Result<u64> {
329    let mut nread = MaybeUninit::<c::c_int>::uninit();
330    unsafe {
331        ret(c::ioctl(borrowed_fd(fd), c::FIONREAD, nread.as_mut_ptr()))?;
332        // `FIONREAD` returns the number of bytes silently casted to a `c_int`,
333        // even when this is lossy. The best we can do is convert it back to a
334        // `u64` without sign-extending it back first.
335        Ok(u64::from(nread.assume_init() as c::c_uint))
336    }
337}
338
339pub(crate) fn ioctl_fionbio(fd: BorrowedFd<'_>, value: bool) -> io::Result<()> {
340    unsafe {
341        let data = value as c::c_int;
342        ret(c::ioctl(borrowed_fd(fd), c::FIONBIO, &data))
343    }
344}
345
346#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
347#[cfg(all(feature = "fs", feature = "net"))]
348pub(crate) fn is_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> {
349    let (mut read, mut write) = crate::fs::fd::_is_file_read_write(fd)?;
350    let mut not_socket = false;
351    if read {
352        // Do a `recv` with `PEEK` and `DONTWAIT` for 1 byte. A 0 indicates
353        // the read side is shut down; an `EWOULDBLOCK` indicates the read
354        // side is still open.
355        match unsafe {
356            c::recv(
357                borrowed_fd(fd),
358                MaybeUninit::<[u8; 1]>::uninit()
359                    .as_mut_ptr()
360                    .cast::<c::c_void>(),
361                1,
362                c::MSG_PEEK | c::MSG_DONTWAIT,
363            )
364        } {
365            0 => read = false,
366            -1 => {
367                #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK`
368                match errno().0 {
369                    c::EAGAIN | c::EWOULDBLOCK => (),
370                    c::ENOTSOCK => not_socket = true,
371                    err => return Err(io::Errno(err)),
372                }
373            }
374            _ => (),
375        }
376    }
377    if write && !not_socket {
378        // Do a `send` with `DONTWAIT` for 0 bytes. An `EPIPE` indicates
379        // the write side is shut down.
380        if unsafe { c::send(borrowed_fd(fd), [].as_ptr(), 0, c::MSG_DONTWAIT) } == -1 {
381            #[allow(unreachable_patterns)] // `EAGAIN` may equal `EWOULDBLOCK`
382            match errno().0 {
383                c::EAGAIN | c::EWOULDBLOCK => (),
384                c::ENOTSOCK => (),
385                c::EPIPE => write = false,
386                err => return Err(io::Errno(err)),
387            }
388        }
389    }
390    Ok((read, write))
391}
392
393#[cfg(target_os = "wasi")]
394#[cfg(all(feature = "fs", feature = "net"))]
395pub(crate) fn is_read_write(_fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> {
396    todo!("Implement is_read_write for WASI in terms of fd_fdstat_get");
397}
398
399pub(crate) fn fcntl_getfd(fd: BorrowedFd<'_>) -> io::Result<FdFlags> {
400    unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFD)).map(FdFlags::from_bits_truncate) }
401}
402
403pub(crate) fn fcntl_setfd(fd: BorrowedFd<'_>, flags: FdFlags) -> io::Result<()> {
404    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFD, flags.bits())) }
405}
406
407#[cfg(not(target_os = "wasi"))]
408pub(crate) fn fcntl_dupfd_cloexec(fd: BorrowedFd<'_>, min: RawFd) -> io::Result<OwnedFd> {
409    unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD_CLOEXEC, min)) }
410}
411
412#[cfg(not(target_os = "wasi"))]
413pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
414    unsafe { ret_owned_fd(c::dup(borrowed_fd(fd))) }
415}
416
417#[cfg(not(target_os = "wasi"))]
418pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> {
419    unsafe { ret_discarded_fd(c::dup2(borrowed_fd(fd), borrowed_fd(new.as_fd()))) }
420}
421
422#[cfg(not(any(
423    target_os = "aix",
424    target_os = "android",
425    target_os = "dragonfly",
426    target_os = "haiku",
427    target_os = "ios",
428    target_os = "macos",
429    target_os = "redox",
430    target_os = "wasi",
431)))]
432pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
433    unsafe {
434        ret_discarded_fd(c::dup3(
435            borrowed_fd(fd),
436            borrowed_fd(new.as_fd()),
437            flags.bits(),
438        ))
439    }
440}
441
442#[cfg(any(
443    target_os = "android",
444    target_os = "dragonfly",
445    target_os = "haiku",
446    target_os = "ios",
447    target_os = "macos",
448    target_os = "redox",
449))]
450pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, _flags: DupFlags) -> io::Result<()> {
451    // Android 5.0 has `dup3`, but libc doesn't have bindings. Emulate it
452    // using `dup2`. We don't need to worry about the difference between
453    // `dup2` and `dup3` when the file descriptors are equal because we
454    // have an `&mut OwnedFd` which means `fd` doesn't alias it.
455    dup2(fd, new)
456}
457
458#[cfg(any(target_os = "ios", target_os = "macos"))]
459pub(crate) fn ioctl_fioclex(fd: BorrowedFd<'_>) -> io::Result<()> {
460    unsafe { ret(c::ioctl(borrowed_fd(fd), c::FIOCLEX)) }
461}
462
463#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
464pub(crate) fn ioctl_tiocexcl(fd: BorrowedFd) -> io::Result<()> {
465    unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCEXCL as _)) }
466}
467
468#[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))]
469pub(crate) fn ioctl_tiocnxcl(fd: BorrowedFd) -> io::Result<()> {
470    unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCNXCL as _)) }
471}
472
473#[cfg(not(target_os = "wasi"))]
474pub(crate) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> {
475    unsafe {
476        let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
477        ret(c::pipe(result.as_mut_ptr().cast::<i32>()))?;
478        let [p0, p1] = result.assume_init();
479        Ok((p0, p1))
480    }
481}
482
483#[cfg(not(any(
484    target_os = "aix",
485    target_os = "haiku",
486    target_os = "ios",
487    target_os = "macos",
488    target_os = "wasi"
489)))]
490pub(crate) fn pipe_with(flags: PipeFlags) -> io::Result<(OwnedFd, OwnedFd)> {
491    unsafe {
492        let mut result = MaybeUninit::<[OwnedFd; 2]>::uninit();
493        ret(c::pipe2(result.as_mut_ptr().cast::<i32>(), flags.bits()))?;
494        let [p0, p1] = result.assume_init();
495        Ok((p0, p1))
496    }
497}
498
499#[inline]
500pub(crate) fn poll(fds: &mut [PollFd<'_>], timeout: c::c_int) -> io::Result<usize> {
501    let nfds = fds
502        .len()
503        .try_into()
504        .map_err(|_convert_err| io::Errno::INVAL)?;
505
506    ret_c_int(unsafe { c::poll(fds.as_mut_ptr().cast(), nfds, timeout) })
507        .map(|nready| nready as usize)
508}
509
510#[cfg(any(target_os = "android", target_os = "linux"))]
511#[inline]
512pub fn splice(
513    fd_in: BorrowedFd,
514    off_in: Option<&mut u64>,
515    fd_out: BorrowedFd,
516    off_out: Option<&mut u64>,
517    len: usize,
518    flags: SpliceFlags,
519) -> io::Result<usize> {
520    let off_in = off_in
521        .map(|off| (off as *mut u64).cast())
522        .unwrap_or(ptr::null_mut());
523
524    let off_out = off_out
525        .map(|off| (off as *mut u64).cast())
526        .unwrap_or(ptr::null_mut());
527
528    ret_ssize_t(unsafe {
529        c::splice(
530            borrowed_fd(fd_in),
531            off_in,
532            borrowed_fd(fd_out),
533            off_out,
534            len,
535            flags.bits(),
536        )
537    })
538    .map(|spliced| spliced as usize)
539}
540
541#[cfg(any(target_os = "android", target_os = "linux"))]
542#[inline]
543pub unsafe fn vmsplice(
544    fd: BorrowedFd,
545    bufs: &[IoSliceRaw],
546    flags: SpliceFlags,
547) -> io::Result<usize> {
548    ret_ssize_t(c::vmsplice(
549        borrowed_fd(fd),
550        bufs.as_ptr().cast::<c::iovec>(),
551        min(bufs.len(), max_iov()),
552        flags.bits(),
553    ))
554    .map(|spliced| spliced as usize)
555}
556