rustix/backend/libc/io/
syscalls.rs

1//! libc syscalls supporting `rustix::io`.
2
3use crate::backend::c;
4#[cfg(not(target_os = "wasi"))]
5use crate::backend::conv::ret_discarded_fd;
6use crate::backend::conv::{borrowed_fd, ret, ret_c_int, ret_owned_fd, ret_usize};
7use crate::fd::{AsFd as _, BorrowedFd, OwnedFd, RawFd};
8#[cfg(not(any(
9    target_os = "aix",
10    target_os = "espidf",
11    target_os = "nto",
12    target_os = "vita",
13    target_os = "wasi"
14)))]
15use crate::io::DupFlags;
16#[cfg(all(linux_kernel, not(target_os = "android")))]
17use crate::io::ReadWriteFlags;
18use crate::io::{self, FdFlags};
19use crate::ioctl::{IoctlOutput, Opcode};
20use core::cmp::min;
21#[cfg(not(any(target_os = "espidf", target_os = "horizon")))]
22use {
23    crate::backend::MAX_IOV,
24    crate::io::{IoSlice, IoSliceMut},
25};
26
27pub(crate) unsafe fn read(fd: BorrowedFd<'_>, buf: (*mut u8, usize)) -> io::Result<usize> {
28    ret_usize(c::read(
29        borrowed_fd(fd),
30        buf.0.cast(),
31        min(buf.1, READ_LIMIT),
32    ))
33}
34
35pub(crate) fn write(fd: BorrowedFd<'_>, buf: &[u8]) -> io::Result<usize> {
36    unsafe {
37        ret_usize(c::write(
38            borrowed_fd(fd),
39            buf.as_ptr().cast(),
40            min(buf.len(), READ_LIMIT),
41        ))
42    }
43}
44
45pub(crate) unsafe fn pread(
46    fd: BorrowedFd<'_>,
47    buf: (*mut u8, usize),
48    offset: u64,
49) -> io::Result<usize> {
50    let len = min(buf.1, READ_LIMIT);
51
52    // Silently cast; we'll get `EINVAL` if the value is negative.
53    let offset = offset as i64;
54
55    // ESP-IDF and Vita don't support 64-bit offsets, for example.
56    let offset = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
57
58    ret_usize(c::pread(borrowed_fd(fd), buf.0.cast(), len, offset))
59}
60
61pub(crate) fn pwrite(fd: BorrowedFd<'_>, buf: &[u8], offset: u64) -> io::Result<usize> {
62    let len = min(buf.len(), READ_LIMIT);
63
64    // Silently cast; we'll get `EINVAL` if the value is negative.
65    let offset = offset as i64;
66
67    // ESP-IDF and Vita don't support 64-bit offsets, for example.
68    let offset = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
69
70    unsafe { ret_usize(c::pwrite(borrowed_fd(fd), buf.as_ptr().cast(), len, offset)) }
71}
72
73#[cfg(not(any(target_os = "espidf", target_os = "horizon")))]
74pub(crate) fn readv(fd: BorrowedFd<'_>, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
75    unsafe {
76        ret_usize(c::readv(
77            borrowed_fd(fd),
78            bufs.as_ptr().cast::<c::iovec>(),
79            min(bufs.len(), MAX_IOV) as c::c_int,
80        ))
81    }
82}
83
84#[cfg(not(any(target_os = "espidf", target_os = "horizon")))]
85pub(crate) fn writev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
86    unsafe {
87        ret_usize(c::writev(
88            borrowed_fd(fd),
89            bufs.as_ptr().cast::<c::iovec>(),
90            min(bufs.len(), MAX_IOV) as c::c_int,
91        ))
92    }
93}
94
95#[cfg(not(any(
96    target_os = "cygwin",
97    target_os = "espidf",
98    target_os = "haiku",
99    target_os = "horizon",
100    target_os = "nto",
101    target_os = "redox",
102    target_os = "solaris",
103    target_os = "vita",
104)))]
105pub(crate) fn preadv(
106    fd: BorrowedFd<'_>,
107    bufs: &mut [IoSliceMut<'_>],
108    offset: u64,
109) -> io::Result<usize> {
110    // Silently cast; we'll get `EINVAL` if the value is negative.
111    let offset = offset as i64;
112
113    // ESP-IDF and Vita don't support 64-bit offsets, for example.
114    let offset = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
115
116    unsafe {
117        ret_usize(c::preadv(
118            borrowed_fd(fd),
119            bufs.as_ptr().cast::<c::iovec>(),
120            min(bufs.len(), MAX_IOV) as c::c_int,
121            offset,
122        ))
123    }
124}
125
126#[cfg(not(any(
127    target_os = "cygwin",
128    target_os = "espidf",
129    target_os = "haiku",
130    target_os = "nto",
131    target_os = "horizon",
132    target_os = "redox",
133    target_os = "solaris",
134    target_os = "vita",
135)))]
136pub(crate) fn pwritev(fd: BorrowedFd<'_>, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
137    // Silently cast; we'll get `EINVAL` if the value is negative.
138    let offset = offset as i64;
139
140    // ESP-IDF and Vita don't support 64-bit offsets, for example.
141    let offset = offset.try_into().map_err(|_| io::Errno::OVERFLOW)?;
142
143    unsafe {
144        ret_usize(c::pwritev(
145            borrowed_fd(fd),
146            bufs.as_ptr().cast::<c::iovec>(),
147            min(bufs.len(), MAX_IOV) as c::c_int,
148            offset,
149        ))
150    }
151}
152
153#[cfg(all(linux_kernel, not(target_os = "android")))]
154pub(crate) fn preadv2(
155    fd: BorrowedFd<'_>,
156    bufs: &mut [IoSliceMut<'_>],
157    offset: u64,
158    flags: ReadWriteFlags,
159) -> io::Result<usize> {
160    // Silently cast; we'll get `EINVAL` if the value is negative.
161    let offset = offset as i64;
162    unsafe {
163        ret_usize(c::preadv2(
164            borrowed_fd(fd),
165            bufs.as_ptr().cast::<c::iovec>(),
166            min(bufs.len(), MAX_IOV) as c::c_int,
167            offset,
168            bitflags_bits!(flags),
169        ))
170    }
171}
172
173#[cfg(all(linux_kernel, not(target_os = "android")))]
174pub(crate) fn pwritev2(
175    fd: BorrowedFd<'_>,
176    bufs: &[IoSlice<'_>],
177    offset: u64,
178    flags: ReadWriteFlags,
179) -> io::Result<usize> {
180    // Silently cast; we'll get `EINVAL` if the value is negative.
181    let offset = offset as i64;
182    unsafe {
183        ret_usize(c::pwritev2(
184            borrowed_fd(fd),
185            bufs.as_ptr().cast::<c::iovec>(),
186            min(bufs.len(), MAX_IOV) as c::c_int,
187            offset,
188            bitflags_bits!(flags),
189        ))
190    }
191}
192
193// These functions are derived from Rust's library/std/src/sys/unix/fd.rs at
194// revision 326ef470a8b379a180d6dc4bbef08990698a737a.
195
196// The maximum read limit on most POSIX-like systems is `SSIZE_MAX`, with the
197// manual page quoting that if the count of bytes to read is greater than
198// `SSIZE_MAX` the result is “unspecified”.
199//
200// On macOS, however, apparently the 64-bit libc is either buggy or
201// intentionally showing odd behavior by rejecting any read with a size larger
202// than or equal to `INT_MAX`. To handle both of these the read size is capped
203// on both platforms.
204#[cfg(target_os = "macos")]
205const READ_LIMIT: usize = c::c_int::MAX as usize - 1;
206#[cfg(not(target_os = "macos"))]
207const READ_LIMIT: usize = c::ssize_t::MAX as usize;
208
209pub(crate) unsafe fn close(raw_fd: RawFd) {
210    let _ = c::close(raw_fd as c::c_int);
211}
212
213#[cfg(feature = "try_close")]
214pub(crate) unsafe fn try_close(raw_fd: RawFd) -> io::Result<()> {
215    ret(c::close(raw_fd as c::c_int))
216}
217
218#[inline]
219pub(crate) unsafe fn ioctl(
220    fd: BorrowedFd<'_>,
221    request: Opcode,
222    arg: *mut c::c_void,
223) -> io::Result<IoctlOutput> {
224    ret_c_int(c::ioctl(borrowed_fd(fd), request, arg))
225}
226
227#[inline]
228pub(crate) unsafe fn ioctl_readonly(
229    fd: BorrowedFd<'_>,
230    request: Opcode,
231    arg: *mut c::c_void,
232) -> io::Result<IoctlOutput> {
233    ioctl(fd, request, arg)
234}
235
236pub(crate) fn fcntl_getfd(fd: BorrowedFd<'_>) -> io::Result<FdFlags> {
237    let flags = unsafe { ret_c_int(c::fcntl(borrowed_fd(fd), c::F_GETFD))? };
238    Ok(FdFlags::from_bits_retain(bitcast!(flags)))
239}
240
241pub(crate) fn fcntl_setfd(fd: BorrowedFd<'_>, flags: FdFlags) -> io::Result<()> {
242    unsafe { ret(c::fcntl(borrowed_fd(fd), c::F_SETFD, flags.bits())) }
243}
244
245#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
246pub(crate) fn fcntl_dupfd_cloexec(fd: BorrowedFd<'_>, min: RawFd) -> io::Result<OwnedFd> {
247    unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD_CLOEXEC, min)) }
248}
249
250#[cfg(target_os = "espidf")]
251pub(crate) fn fcntl_dupfd(fd: BorrowedFd<'_>, min: RawFd) -> io::Result<OwnedFd> {
252    unsafe { ret_owned_fd(c::fcntl(borrowed_fd(fd), c::F_DUPFD, min)) }
253}
254
255#[cfg(not(target_os = "wasi"))]
256pub(crate) fn dup(fd: BorrowedFd<'_>) -> io::Result<OwnedFd> {
257    unsafe { ret_owned_fd(c::dup(borrowed_fd(fd))) }
258}
259
260#[allow(clippy::needless_pass_by_ref_mut)]
261#[cfg(not(target_os = "wasi"))]
262pub(crate) fn dup2(fd: BorrowedFd<'_>, new: &mut OwnedFd) -> io::Result<()> {
263    unsafe { ret_discarded_fd(c::dup2(borrowed_fd(fd), borrowed_fd(new.as_fd()))) }
264}
265
266#[allow(clippy::needless_pass_by_ref_mut)]
267#[cfg(not(any(
268    apple,
269    target_os = "aix",
270    target_os = "android",
271    target_os = "dragonfly",
272    target_os = "espidf",
273    target_os = "haiku",
274    target_os = "horizon",
275    target_os = "nto",
276    target_os = "redox",
277    target_os = "vita",
278    target_os = "wasi",
279)))]
280pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, flags: DupFlags) -> io::Result<()> {
281    unsafe {
282        ret_discarded_fd(c::dup3(
283            borrowed_fd(fd),
284            borrowed_fd(new.as_fd()),
285            bitflags_bits!(flags),
286        ))
287    }
288}
289
290#[cfg(any(
291    apple,
292    target_os = "android",
293    target_os = "dragonfly",
294    target_os = "haiku",
295    target_os = "redox",
296))]
297pub(crate) fn dup3(fd: BorrowedFd<'_>, new: &mut OwnedFd, _flags: DupFlags) -> io::Result<()> {
298    // Android 5.0 has `dup3`, but libc doesn't have bindings. Emulate it
299    // using `dup2`. We don't need to worry about the difference between
300    // `dup2` and `dup3` when the file descriptors are equal because we
301    // have an `&mut OwnedFd` which means `fd` doesn't alias it.
302    dup2(fd, new)
303}