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