1 | #![unstable (reason = "not public" , issue = "none" , feature = "fd" )] |
2 | |
3 | #[cfg (test)] |
4 | mod tests; |
5 | |
6 | use crate::cmp; |
7 | use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read}; |
8 | use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; |
9 | use crate::sys::cvt; |
10 | use crate::sys_common::{AsInner, FromInner, IntoInner}; |
11 | |
12 | #[cfg (any( |
13 | target_os = "android" , |
14 | target_os = "linux" , |
15 | target_os = "emscripten" , |
16 | target_os = "l4re" , |
17 | target_os = "hurd" , |
18 | ))] |
19 | use libc::off64_t; |
20 | #[cfg (not(any( |
21 | target_os = "linux" , |
22 | target_os = "emscripten" , |
23 | target_os = "l4re" , |
24 | target_os = "android" , |
25 | target_os = "hurd" , |
26 | )))] |
27 | use libc::off_t as off64_t; |
28 | |
29 | #[derive (Debug)] |
30 | pub struct FileDesc(OwnedFd); |
31 | |
32 | // The maximum read limit on most POSIX-like systems is `SSIZE_MAX`, |
33 | // with the man page quoting that if the count of bytes to read is |
34 | // greater than `SSIZE_MAX` the result is "unspecified". |
35 | // |
36 | // On macOS, however, apparently the 64-bit libc is either buggy or |
37 | // intentionally showing odd behavior by rejecting any read with a size |
38 | // larger than or equal to INT_MAX. To handle both of these the read |
39 | // size is capped on both platforms. |
40 | #[cfg (target_os = "macos" )] |
41 | const READ_LIMIT: usize = libc::c_int::MAX as usize - 1; |
42 | #[cfg (not(target_os = "macos" ))] |
43 | const READ_LIMIT: usize = libc::ssize_t::MAX as usize; |
44 | |
45 | #[cfg (any( |
46 | target_os = "dragonfly" , |
47 | target_os = "freebsd" , |
48 | target_os = "ios" , |
49 | target_os = "tvos" , |
50 | target_os = "macos" , |
51 | target_os = "netbsd" , |
52 | target_os = "openbsd" , |
53 | target_os = "watchos" , |
54 | ))] |
55 | const fn max_iov() -> usize { |
56 | libc::IOV_MAX as usize |
57 | } |
58 | |
59 | #[cfg (any( |
60 | target_os = "android" , |
61 | target_os = "emscripten" , |
62 | target_os = "linux" , |
63 | target_os = "nto" , |
64 | ))] |
65 | const fn max_iov() -> usize { |
66 | libc::UIO_MAXIOV as usize |
67 | } |
68 | |
69 | #[cfg (not(any( |
70 | target_os = "android" , |
71 | target_os = "dragonfly" , |
72 | target_os = "emscripten" , |
73 | target_os = "freebsd" , |
74 | target_os = "ios" , |
75 | target_os = "tvos" , |
76 | target_os = "linux" , |
77 | target_os = "macos" , |
78 | target_os = "netbsd" , |
79 | target_os = "nto" , |
80 | target_os = "openbsd" , |
81 | target_os = "horizon" , |
82 | target_os = "vita" , |
83 | target_os = "watchos" , |
84 | )))] |
85 | const fn max_iov() -> usize { |
86 | 16 // The minimum value required by POSIX. |
87 | } |
88 | |
89 | impl FileDesc { |
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(target_os = "espidf" , target_os = "horizon" , target_os = "vita" )))] |
102 | pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { |
103 | let ret = cvt(unsafe { |
104 | libc::readv( |
105 | self.as_raw_fd(), |
106 | bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, |
107 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
108 | ) |
109 | })?; |
110 | Ok(ret as usize) |
111 | } |
112 | |
113 | #[cfg (any(target_os = "espidf" , target_os = "horizon" , target_os = "vita" ))] |
114 | pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { |
115 | io::default_read_vectored(|b| self.read(b), bufs) |
116 | } |
117 | |
118 | #[inline ] |
119 | pub fn is_read_vectored(&self) -> bool { |
120 | cfg!(not(any(target_os = "espidf" , target_os = "horizon" , target_os = "vita" ))) |
121 | } |
122 | |
123 | pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> { |
124 | let mut me = self; |
125 | (&mut me).read_to_end(buf) |
126 | } |
127 | |
128 | pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> { |
129 | #[cfg (not(any( |
130 | all(target_os = "linux" , not(target_env = "musl" )), |
131 | target_os = "android" , |
132 | target_os = "hurd" |
133 | )))] |
134 | use libc::pread as pread64; |
135 | #[cfg (any( |
136 | all(target_os = "linux" , not(target_env = "musl" )), |
137 | target_os = "android" , |
138 | target_os = "hurd" |
139 | ))] |
140 | use libc::pread64; |
141 | |
142 | unsafe { |
143 | cvt(pread64( |
144 | self.as_raw_fd(), |
145 | buf.as_mut_ptr() as *mut libc::c_void, |
146 | cmp::min(buf.len(), READ_LIMIT), |
147 | offset as off64_t, |
148 | )) |
149 | .map(|n| n as usize) |
150 | } |
151 | } |
152 | |
153 | pub fn read_buf(&self, mut cursor: BorrowedCursor<'_>) -> io::Result<()> { |
154 | let ret = cvt(unsafe { |
155 | libc::read( |
156 | self.as_raw_fd(), |
157 | cursor.as_mut().as_mut_ptr() as *mut libc::c_void, |
158 | cmp::min(cursor.capacity(), READ_LIMIT), |
159 | ) |
160 | })?; |
161 | |
162 | // Safety: `ret` bytes were written to the initialized portion of the buffer |
163 | unsafe { |
164 | cursor.advance(ret as usize); |
165 | } |
166 | Ok(()) |
167 | } |
168 | |
169 | #[cfg (any( |
170 | target_os = "emscripten" , |
171 | target_os = "freebsd" , |
172 | target_os = "fuchsia" , |
173 | target_os = "hurd" , |
174 | target_os = "illumos" , |
175 | target_os = "linux" , |
176 | target_os = "netbsd" , |
177 | ))] |
178 | pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { |
179 | let ret = cvt(unsafe { |
180 | libc::preadv( |
181 | self.as_raw_fd(), |
182 | bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, |
183 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
184 | offset as _, |
185 | ) |
186 | })?; |
187 | Ok(ret as usize) |
188 | } |
189 | |
190 | #[cfg (not(any( |
191 | target_os = "android" , |
192 | target_os = "emscripten" , |
193 | target_os = "freebsd" , |
194 | target_os = "fuchsia" , |
195 | target_os = "hurd" , |
196 | target_os = "illumos" , |
197 | target_os = "ios" , |
198 | target_os = "tvos" , |
199 | target_os = "linux" , |
200 | target_os = "macos" , |
201 | target_os = "netbsd" , |
202 | )))] |
203 | pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { |
204 | io::default_read_vectored(|b| self.read_at(b, offset), bufs) |
205 | } |
206 | |
207 | // We support some old Android versions that do not have `preadv` in libc, |
208 | // so we use weak linkage and fallback to a direct syscall if not available. |
209 | // |
210 | // On 32-bit targets, we don't want to deal with weird ABI issues around |
211 | // passing 64-bits parameters to syscalls, so we fallback to the default |
212 | // implementation if `preadv` is not available. |
213 | #[cfg (all(target_os = "android" , target_pointer_width = "64" ))] |
214 | pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { |
215 | super::weak::syscall! { |
216 | fn preadv( |
217 | fd: libc::c_int, |
218 | iovec: *const libc::iovec, |
219 | n_iovec: libc::c_int, |
220 | offset: off64_t |
221 | ) -> isize |
222 | } |
223 | |
224 | let ret = cvt(unsafe { |
225 | preadv( |
226 | self.as_raw_fd(), |
227 | bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, |
228 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
229 | offset as _, |
230 | ) |
231 | })?; |
232 | Ok(ret as usize) |
233 | } |
234 | |
235 | // We support old MacOS and iOS versions that do not have `preadv`. There is |
236 | // no `syscall` possible in these platform. |
237 | #[cfg (any( |
238 | all(target_os = "android" , target_pointer_width = "32" ), |
239 | target_os = "ios" , |
240 | target_os = "tvos" , |
241 | target_os = "macos" , |
242 | ))] |
243 | pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { |
244 | super::weak::weak!(fn preadv64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize); |
245 | |
246 | match preadv64.get() { |
247 | Some(preadv) => { |
248 | let ret = cvt(unsafe { |
249 | preadv( |
250 | self.as_raw_fd(), |
251 | bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec, |
252 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
253 | offset as _, |
254 | ) |
255 | })?; |
256 | Ok(ret as usize) |
257 | } |
258 | None => io::default_read_vectored(|b| self.read_at(b, offset), bufs), |
259 | } |
260 | } |
261 | |
262 | pub fn write(&self, buf: &[u8]) -> io::Result<usize> { |
263 | let ret = cvt(unsafe { |
264 | libc::write( |
265 | self.as_raw_fd(), |
266 | buf.as_ptr() as *const libc::c_void, |
267 | cmp::min(buf.len(), READ_LIMIT), |
268 | ) |
269 | })?; |
270 | Ok(ret as usize) |
271 | } |
272 | |
273 | #[cfg (not(any(target_os = "espidf" , target_os = "horizon" , target_os = "vita" )))] |
274 | pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { |
275 | let ret = cvt(unsafe { |
276 | libc::writev( |
277 | self.as_raw_fd(), |
278 | bufs.as_ptr() as *const libc::iovec, |
279 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
280 | ) |
281 | })?; |
282 | Ok(ret as usize) |
283 | } |
284 | |
285 | #[cfg (any(target_os = "espidf" , target_os = "horizon" , target_os = "vita" ))] |
286 | pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { |
287 | io::default_write_vectored(|b| self.write(b), bufs) |
288 | } |
289 | |
290 | #[inline ] |
291 | pub fn is_write_vectored(&self) -> bool { |
292 | cfg!(not(any(target_os = "espidf" , target_os = "horizon" , target_os = "vita" ))) |
293 | } |
294 | |
295 | pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> { |
296 | #[cfg (not(any( |
297 | all(target_os = "linux" , not(target_env = "musl" )), |
298 | target_os = "android" , |
299 | target_os = "hurd" |
300 | )))] |
301 | use libc::pwrite as pwrite64; |
302 | #[cfg (any( |
303 | all(target_os = "linux" , not(target_env = "musl" )), |
304 | target_os = "android" , |
305 | target_os = "hurd" |
306 | ))] |
307 | use libc::pwrite64; |
308 | |
309 | unsafe { |
310 | cvt(pwrite64( |
311 | self.as_raw_fd(), |
312 | buf.as_ptr() as *const libc::c_void, |
313 | cmp::min(buf.len(), READ_LIMIT), |
314 | offset as off64_t, |
315 | )) |
316 | .map(|n| n as usize) |
317 | } |
318 | } |
319 | |
320 | #[cfg (any( |
321 | target_os = "emscripten" , |
322 | target_os = "freebsd" , |
323 | target_os = "fuchsia" , |
324 | target_os = "hurd" , |
325 | target_os = "illumos" , |
326 | target_os = "linux" , |
327 | target_os = "netbsd" , |
328 | ))] |
329 | pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { |
330 | let ret = cvt(unsafe { |
331 | libc::pwritev( |
332 | self.as_raw_fd(), |
333 | bufs.as_ptr() as *const libc::iovec, |
334 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
335 | offset as _, |
336 | ) |
337 | })?; |
338 | Ok(ret as usize) |
339 | } |
340 | |
341 | #[cfg (not(any( |
342 | target_os = "android" , |
343 | target_os = "emscripten" , |
344 | target_os = "freebsd" , |
345 | target_os = "fuchsia" , |
346 | target_os = "hurd" , |
347 | target_os = "illumos" , |
348 | target_os = "ios" , |
349 | target_os = "tvos" , |
350 | target_os = "linux" , |
351 | target_os = "macos" , |
352 | target_os = "netbsd" , |
353 | )))] |
354 | pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { |
355 | io::default_write_vectored(|b| self.write_at(b, offset), bufs) |
356 | } |
357 | |
358 | // We support some old Android versions that do not have `pwritev` in libc, |
359 | // so we use weak linkage and fallback to a direct syscall if not available. |
360 | // |
361 | // On 32-bit targets, we don't want to deal with weird ABI issues around |
362 | // passing 64-bits parameters to syscalls, so we fallback to the default |
363 | // implementation if `pwritev` is not available. |
364 | #[cfg (all(target_os = "android" , target_pointer_width = "64" ))] |
365 | pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { |
366 | super::weak::syscall! { |
367 | fn pwritev( |
368 | fd: libc::c_int, |
369 | iovec: *const libc::iovec, |
370 | n_iovec: libc::c_int, |
371 | offset: off64_t |
372 | ) -> isize |
373 | } |
374 | |
375 | let ret = cvt(unsafe { |
376 | pwritev( |
377 | self.as_raw_fd(), |
378 | bufs.as_ptr() as *const libc::iovec, |
379 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
380 | offset as _, |
381 | ) |
382 | })?; |
383 | Ok(ret as usize) |
384 | } |
385 | |
386 | // We support old MacOS and iOS versions that do not have `pwritev`. There is |
387 | // no `syscall` possible in these platform. |
388 | #[cfg (any( |
389 | all(target_os = "android" , target_pointer_width = "32" ), |
390 | target_os = "ios" , |
391 | target_os = "tvos" , |
392 | target_os = "macos" , |
393 | ))] |
394 | pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { |
395 | super::weak::weak!(fn pwritev64(libc::c_int, *const libc::iovec, libc::c_int, off64_t) -> isize); |
396 | |
397 | match pwritev64.get() { |
398 | Some(pwritev) => { |
399 | let ret = cvt(unsafe { |
400 | pwritev( |
401 | self.as_raw_fd(), |
402 | bufs.as_ptr() as *const libc::iovec, |
403 | cmp::min(bufs.len(), max_iov()) as libc::c_int, |
404 | offset as _, |
405 | ) |
406 | })?; |
407 | Ok(ret as usize) |
408 | } |
409 | None => io::default_write_vectored(|b| self.write_at(b, offset), bufs), |
410 | } |
411 | } |
412 | |
413 | #[cfg (not(any( |
414 | target_env = "newlib" , |
415 | target_os = "solaris" , |
416 | target_os = "illumos" , |
417 | target_os = "emscripten" , |
418 | target_os = "fuchsia" , |
419 | target_os = "l4re" , |
420 | target_os = "linux" , |
421 | target_os = "haiku" , |
422 | target_os = "redox" , |
423 | target_os = "vxworks" , |
424 | target_os = "nto" , |
425 | )))] |
426 | pub fn set_cloexec(&self) -> io::Result<()> { |
427 | unsafe { |
428 | cvt(libc::ioctl(self.as_raw_fd(), libc::FIOCLEX))?; |
429 | Ok(()) |
430 | } |
431 | } |
432 | #[cfg (any( |
433 | all( |
434 | target_env = "newlib" , |
435 | not(any(target_os = "espidf" , target_os = "horizon" , target_os = "vita" )) |
436 | ), |
437 | target_os = "solaris" , |
438 | target_os = "illumos" , |
439 | target_os = "emscripten" , |
440 | target_os = "fuchsia" , |
441 | target_os = "l4re" , |
442 | target_os = "linux" , |
443 | target_os = "haiku" , |
444 | target_os = "redox" , |
445 | target_os = "vxworks" , |
446 | target_os = "nto" , |
447 | ))] |
448 | pub fn set_cloexec(&self) -> io::Result<()> { |
449 | unsafe { |
450 | let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?; |
451 | let new = previous | libc::FD_CLOEXEC; |
452 | if new != previous { |
453 | cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?; |
454 | } |
455 | Ok(()) |
456 | } |
457 | } |
458 | #[cfg (any(target_os = "espidf" , target_os = "horizon" , target_os = "vita" ))] |
459 | pub fn set_cloexec(&self) -> io::Result<()> { |
460 | // FD_CLOEXEC is not supported in ESP-IDF, Horizon OS and Vita but there's no need to, |
461 | // because none of them supports spawning processes. |
462 | Ok(()) |
463 | } |
464 | |
465 | #[cfg (target_os = "linux" )] |
466 | pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { |
467 | unsafe { |
468 | let v = nonblocking as libc::c_int; |
469 | cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?; |
470 | Ok(()) |
471 | } |
472 | } |
473 | |
474 | #[cfg (not(target_os = "linux" ))] |
475 | pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { |
476 | unsafe { |
477 | let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFL))?; |
478 | let new = if nonblocking { |
479 | previous | libc::O_NONBLOCK |
480 | } else { |
481 | previous & !libc::O_NONBLOCK |
482 | }; |
483 | if new != previous { |
484 | cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFL, new))?; |
485 | } |
486 | Ok(()) |
487 | } |
488 | } |
489 | |
490 | #[inline ] |
491 | pub fn duplicate(&self) -> io::Result<FileDesc> { |
492 | Ok(Self(self.0.try_clone()?)) |
493 | } |
494 | } |
495 | |
496 | impl<'a> Read for &'a FileDesc { |
497 | fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
498 | (**self).read(buf) |
499 | } |
500 | |
501 | fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { |
502 | (**self).read_buf(cursor) |
503 | } |
504 | |
505 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { |
506 | (**self).read_vectored(bufs) |
507 | } |
508 | |
509 | #[inline ] |
510 | fn is_read_vectored(&self) -> bool { |
511 | (**self).is_read_vectored() |
512 | } |
513 | } |
514 | |
515 | impl AsInner<OwnedFd> for FileDesc { |
516 | #[inline ] |
517 | fn as_inner(&self) -> &OwnedFd { |
518 | &self.0 |
519 | } |
520 | } |
521 | |
522 | impl IntoInner<OwnedFd> for FileDesc { |
523 | fn into_inner(self) -> OwnedFd { |
524 | self.0 |
525 | } |
526 | } |
527 | |
528 | impl FromInner<OwnedFd> for FileDesc { |
529 | fn from_inner(owned_fd: OwnedFd) -> Self { |
530 | Self(owned_fd) |
531 | } |
532 | } |
533 | |
534 | impl AsFd for FileDesc { |
535 | fn as_fd(&self) -> BorrowedFd<'_> { |
536 | self.0.as_fd() |
537 | } |
538 | } |
539 | |
540 | impl AsRawFd for FileDesc { |
541 | #[inline ] |
542 | fn as_raw_fd(&self) -> RawFd { |
543 | self.0.as_raw_fd() |
544 | } |
545 | } |
546 | |
547 | impl IntoRawFd for FileDesc { |
548 | fn into_raw_fd(self) -> RawFd { |
549 | self.0.into_raw_fd() |
550 | } |
551 | } |
552 | |
553 | impl FromRawFd for FileDesc { |
554 | unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { |
555 | Self(FromRawFd::from_raw_fd(raw_fd)) |
556 | } |
557 | } |
558 | |