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