1 | #![allow (nonstandard_style)] |
2 | #![allow (unsafe_op_in_unsafe_fn)] |
3 | // miri has some special hacks here that make things unused. |
4 | #![cfg_attr (miri, allow(unused))] |
5 | |
6 | #[cfg (test)] |
7 | mod tests; |
8 | |
9 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
10 | use libc::c_char; |
11 | #[cfg (any( |
12 | all(target_os = "linux" , not(target_env = "musl" )), |
13 | target_os = "android" , |
14 | target_os = "fuchsia" , |
15 | target_os = "hurd" |
16 | ))] |
17 | use libc::dirfd; |
18 | #[cfg (target_os = "fuchsia" )] |
19 | use libc::fstatat as fstatat64; |
20 | #[cfg (any(all(target_os = "linux" , not(target_env = "musl" )), target_os = "hurd" ))] |
21 | use libc::fstatat64; |
22 | #[cfg (any( |
23 | target_os = "android" , |
24 | target_os = "solaris" , |
25 | target_os = "fuchsia" , |
26 | target_os = "redox" , |
27 | target_os = "illumos" , |
28 | target_os = "aix" , |
29 | target_os = "nto" , |
30 | target_os = "vita" , |
31 | all(target_os = "linux" , target_env = "musl" ), |
32 | ))] |
33 | use libc::readdir as readdir64; |
34 | #[cfg (not(any( |
35 | target_os = "android" , |
36 | target_os = "linux" , |
37 | target_os = "solaris" , |
38 | target_os = "illumos" , |
39 | target_os = "l4re" , |
40 | target_os = "fuchsia" , |
41 | target_os = "redox" , |
42 | target_os = "aix" , |
43 | target_os = "nto" , |
44 | target_os = "vita" , |
45 | target_os = "hurd" , |
46 | )))] |
47 | use libc::readdir_r as readdir64_r; |
48 | #[cfg (any(all(target_os = "linux" , not(target_env = "musl" )), target_os = "hurd" ))] |
49 | use libc::readdir64; |
50 | #[cfg (target_os = "l4re" )] |
51 | use libc::readdir64_r; |
52 | use libc::{c_int, mode_t}; |
53 | #[cfg (target_os = "android" )] |
54 | use libc::{ |
55 | dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64, |
56 | lstat as lstat64, off64_t, open as open64, stat as stat64, |
57 | }; |
58 | #[cfg (not(any( |
59 | all(target_os = "linux" , not(target_env = "musl" )), |
60 | target_os = "l4re" , |
61 | target_os = "android" , |
62 | target_os = "hurd" , |
63 | )))] |
64 | use libc::{ |
65 | dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, |
66 | lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, |
67 | }; |
68 | #[cfg (any( |
69 | all(target_os = "linux" , not(target_env = "musl" )), |
70 | target_os = "l4re" , |
71 | target_os = "hurd" |
72 | ))] |
73 | use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; |
74 | |
75 | use crate::ffi::{CStr, OsStr, OsString}; |
76 | use crate::fmt::{self, Write as _}; |
77 | use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; |
78 | use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; |
79 | use crate::os::unix::prelude::*; |
80 | use crate::path::{Path, PathBuf}; |
81 | use crate::sync::Arc; |
82 | use crate::sys::common::small_c_string::run_path_with_cstr; |
83 | use crate::sys::fd::FileDesc; |
84 | pub use crate::sys::fs::common::exists; |
85 | use crate::sys::time::SystemTime; |
86 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
87 | use crate::sys::weak::syscall; |
88 | #[cfg (target_os = "android" )] |
89 | use crate::sys::weak::weak; |
90 | use crate::sys::{cvt, cvt_r}; |
91 | use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; |
92 | use crate::{mem, ptr}; |
93 | |
94 | pub struct File(FileDesc); |
95 | |
96 | // FIXME: This should be available on Linux with all `target_env`. |
97 | // But currently only glibc exposes `statx` fn and structs. |
98 | // We don't want to import unverified raw C structs here directly. |
99 | // https://github.com/rust-lang/rust/pull/67774 |
100 | macro_rules! cfg_has_statx { |
101 | ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => { |
102 | cfg_if::cfg_if! { |
103 | if #[cfg(all(target_os = "linux" , target_env = "gnu" ))] { |
104 | $($then_tt)* |
105 | } else { |
106 | $($else_tt)* |
107 | } |
108 | } |
109 | }; |
110 | ($($block_inner:tt)*) => { |
111 | #[cfg(all(target_os = "linux" , target_env = "gnu" ))] |
112 | { |
113 | $($block_inner)* |
114 | } |
115 | }; |
116 | } |
117 | |
118 | cfg_has_statx! {{ |
119 | #[derive (Clone)] |
120 | pub struct FileAttr { |
121 | stat: stat64, |
122 | statx_extra_fields: Option<StatxExtraFields>, |
123 | } |
124 | |
125 | #[derive (Clone)] |
126 | struct StatxExtraFields { |
127 | // This is needed to check if btime is supported by the filesystem. |
128 | stx_mask: u32, |
129 | stx_btime: libc::statx_timestamp, |
130 | // With statx, we can overcome 32-bit `time_t` too. |
131 | #[cfg (target_pointer_width = "32" )] |
132 | stx_atime: libc::statx_timestamp, |
133 | #[cfg (target_pointer_width = "32" )] |
134 | stx_ctime: libc::statx_timestamp, |
135 | #[cfg (target_pointer_width = "32" )] |
136 | stx_mtime: libc::statx_timestamp, |
137 | |
138 | } |
139 | |
140 | // We prefer `statx` on Linux if available, which contains file creation time, |
141 | // as well as 64-bit timestamps of all kinds. |
142 | // Default `stat64` contains no creation time and may have 32-bit `time_t`. |
143 | unsafe fn try_statx( |
144 | fd: c_int, |
145 | path: *const c_char, |
146 | flags: i32, |
147 | mask: u32, |
148 | ) -> Option<io::Result<FileAttr>> { |
149 | use crate::sync::atomic::{AtomicU8, Ordering}; |
150 | |
151 | // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx`. |
152 | // We check for it on first failure and remember availability to avoid having to |
153 | // do it again. |
154 | #[repr (u8)] |
155 | enum STATX_STATE{ Unknown = 0, Present, Unavailable } |
156 | static STATX_SAVED_STATE: AtomicU8 = AtomicU8::new(STATX_STATE::Unknown as u8); |
157 | |
158 | syscall! { |
159 | fn statx( |
160 | fd: c_int, |
161 | pathname: *const c_char, |
162 | flags: c_int, |
163 | mask: libc::c_uint, |
164 | statxbuf: *mut libc::statx |
165 | ) -> c_int |
166 | } |
167 | |
168 | let statx_availability = STATX_SAVED_STATE.load(Ordering::Relaxed); |
169 | if statx_availability == STATX_STATE::Unavailable as u8 { |
170 | return None; |
171 | } |
172 | |
173 | let mut buf: libc::statx = mem::zeroed(); |
174 | if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) { |
175 | if STATX_SAVED_STATE.load(Ordering::Relaxed) == STATX_STATE::Present as u8 { |
176 | return Some(Err(err)); |
177 | } |
178 | |
179 | // We're not yet entirely sure whether `statx` is usable on this kernel |
180 | // or not. Syscalls can return errors from things other than the kernel |
181 | // per se, e.g. `EPERM` can be returned if seccomp is used to block the |
182 | // syscall, or `ENOSYS` might be returned from a faulty FUSE driver. |
183 | // |
184 | // Availability is checked by performing a call which expects `EFAULT` |
185 | // if the syscall is usable. |
186 | // |
187 | // See: https://github.com/rust-lang/rust/issues/65662 |
188 | // |
189 | // FIXME what about transient conditions like `ENOMEM`? |
190 | let err2 = cvt(statx(0, ptr::null(), 0, libc::STATX_BASIC_STATS | libc::STATX_BTIME, ptr::null_mut())) |
191 | .err() |
192 | .and_then(|e| e.raw_os_error()); |
193 | if err2 == Some(libc::EFAULT) { |
194 | STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed); |
195 | return Some(Err(err)); |
196 | } else { |
197 | STATX_SAVED_STATE.store(STATX_STATE::Unavailable as u8, Ordering::Relaxed); |
198 | return None; |
199 | } |
200 | } |
201 | if statx_availability == STATX_STATE::Unknown as u8 { |
202 | STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed); |
203 | } |
204 | |
205 | // We cannot fill `stat64` exhaustively because of private padding fields. |
206 | let mut stat: stat64 = mem::zeroed(); |
207 | // `c_ulong` on gnu-mips, `dev_t` otherwise |
208 | stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _; |
209 | stat.st_ino = buf.stx_ino as libc::ino64_t; |
210 | stat.st_nlink = buf.stx_nlink as libc::nlink_t; |
211 | stat.st_mode = buf.stx_mode as libc::mode_t; |
212 | stat.st_uid = buf.stx_uid as libc::uid_t; |
213 | stat.st_gid = buf.stx_gid as libc::gid_t; |
214 | stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _; |
215 | stat.st_size = buf.stx_size as off64_t; |
216 | stat.st_blksize = buf.stx_blksize as libc::blksize_t; |
217 | stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t; |
218 | stat.st_atime = buf.stx_atime.tv_sec as libc::time_t; |
219 | // `i64` on gnu-x86_64-x32, `c_ulong` otherwise. |
220 | stat.st_atime_nsec = buf.stx_atime.tv_nsec as _; |
221 | stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t; |
222 | stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _; |
223 | stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t; |
224 | stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _; |
225 | |
226 | let extra = StatxExtraFields { |
227 | stx_mask: buf.stx_mask, |
228 | stx_btime: buf.stx_btime, |
229 | // Store full times to avoid 32-bit `time_t` truncation. |
230 | #[cfg (target_pointer_width = "32" )] |
231 | stx_atime: buf.stx_atime, |
232 | #[cfg (target_pointer_width = "32" )] |
233 | stx_ctime: buf.stx_ctime, |
234 | #[cfg (target_pointer_width = "32" )] |
235 | stx_mtime: buf.stx_mtime, |
236 | }; |
237 | |
238 | Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) })) |
239 | } |
240 | |
241 | } else { |
242 | #[derive(Clone)] |
243 | pub struct FileAttr { |
244 | stat: stat64, |
245 | } |
246 | }} |
247 | |
248 | // all DirEntry's will have a reference to this struct |
249 | struct InnerReadDir { |
250 | dirp: Dir, |
251 | root: PathBuf, |
252 | } |
253 | |
254 | pub struct ReadDir { |
255 | inner: Arc<InnerReadDir>, |
256 | end_of_stream: bool, |
257 | } |
258 | |
259 | impl ReadDir { |
260 | fn new(inner: InnerReadDir) -> Self { |
261 | Self { inner: Arc::new(data:inner), end_of_stream: false } |
262 | } |
263 | } |
264 | |
265 | struct Dir(*mut libc::DIR); |
266 | |
267 | unsafe impl Send for Dir {} |
268 | unsafe impl Sync for Dir {} |
269 | |
270 | #[cfg (any( |
271 | target_os = "android" , |
272 | target_os = "linux" , |
273 | target_os = "solaris" , |
274 | target_os = "illumos" , |
275 | target_os = "fuchsia" , |
276 | target_os = "redox" , |
277 | target_os = "aix" , |
278 | target_os = "nto" , |
279 | target_os = "vita" , |
280 | target_os = "hurd" , |
281 | ))] |
282 | pub struct DirEntry { |
283 | dir: Arc<InnerReadDir>, |
284 | entry: dirent64_min, |
285 | // We need to store an owned copy of the entry name on platforms that use |
286 | // readdir() (not readdir_r()), because a) struct dirent may use a flexible |
287 | // array to store the name, b) it lives only until the next readdir() call. |
288 | name: crate::ffi::CString, |
289 | } |
290 | |
291 | // Define a minimal subset of fields we need from `dirent64`, especially since |
292 | // we're not using the immediate `d_name` on these targets. Keeping this as an |
293 | // `entry` field in `DirEntry` helps reduce the `cfg` boilerplate elsewhere. |
294 | #[cfg (any( |
295 | target_os = "android" , |
296 | target_os = "linux" , |
297 | target_os = "solaris" , |
298 | target_os = "illumos" , |
299 | target_os = "fuchsia" , |
300 | target_os = "redox" , |
301 | target_os = "aix" , |
302 | target_os = "nto" , |
303 | target_os = "vita" , |
304 | target_os = "hurd" , |
305 | ))] |
306 | struct dirent64_min { |
307 | d_ino: u64, |
308 | #[cfg (not(any( |
309 | target_os = "solaris" , |
310 | target_os = "illumos" , |
311 | target_os = "aix" , |
312 | target_os = "nto" , |
313 | target_os = "vita" , |
314 | )))] |
315 | d_type: u8, |
316 | } |
317 | |
318 | #[cfg (not(any( |
319 | target_os = "android" , |
320 | target_os = "linux" , |
321 | target_os = "solaris" , |
322 | target_os = "illumos" , |
323 | target_os = "fuchsia" , |
324 | target_os = "redox" , |
325 | target_os = "aix" , |
326 | target_os = "nto" , |
327 | target_os = "vita" , |
328 | target_os = "hurd" , |
329 | )))] |
330 | pub struct DirEntry { |
331 | dir: Arc<InnerReadDir>, |
332 | // The full entry includes a fixed-length `d_name`. |
333 | entry: dirent64, |
334 | } |
335 | |
336 | #[derive (Clone)] |
337 | pub struct OpenOptions { |
338 | // generic |
339 | read: bool, |
340 | write: bool, |
341 | append: bool, |
342 | truncate: bool, |
343 | create: bool, |
344 | create_new: bool, |
345 | // system-specific |
346 | custom_flags: i32, |
347 | mode: mode_t, |
348 | } |
349 | |
350 | #[derive (Clone, PartialEq, Eq)] |
351 | pub struct FilePermissions { |
352 | mode: mode_t, |
353 | } |
354 | |
355 | #[derive (Copy, Clone, Debug, Default)] |
356 | pub struct FileTimes { |
357 | accessed: Option<SystemTime>, |
358 | modified: Option<SystemTime>, |
359 | #[cfg (target_vendor = "apple" )] |
360 | created: Option<SystemTime>, |
361 | } |
362 | |
363 | #[derive (Copy, Clone, Eq)] |
364 | pub struct FileType { |
365 | mode: mode_t, |
366 | } |
367 | |
368 | impl PartialEq for FileType { |
369 | fn eq(&self, other: &Self) -> bool { |
370 | self.masked() == other.masked() |
371 | } |
372 | } |
373 | |
374 | impl core::hash::Hash for FileType { |
375 | fn hash<H: core::hash::Hasher>(&self, state: &mut H) { |
376 | self.masked().hash(state); |
377 | } |
378 | } |
379 | |
380 | pub struct DirBuilder { |
381 | mode: mode_t, |
382 | } |
383 | |
384 | #[derive (Copy, Clone)] |
385 | struct Mode(mode_t); |
386 | |
387 | cfg_has_statx! {{ |
388 | impl FileAttr { |
389 | fn from_stat64(stat: stat64) -> Self { |
390 | Self { stat, statx_extra_fields: None } |
391 | } |
392 | |
393 | #[cfg (target_pointer_width = "32" )] |
394 | pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> { |
395 | if let Some(ext) = &self.statx_extra_fields { |
396 | if (ext.stx_mask & libc::STATX_MTIME) != 0 { |
397 | return Some(&ext.stx_mtime); |
398 | } |
399 | } |
400 | None |
401 | } |
402 | |
403 | #[cfg (target_pointer_width = "32" )] |
404 | pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> { |
405 | if let Some(ext) = &self.statx_extra_fields { |
406 | if (ext.stx_mask & libc::STATX_ATIME) != 0 { |
407 | return Some(&ext.stx_atime); |
408 | } |
409 | } |
410 | None |
411 | } |
412 | |
413 | #[cfg (target_pointer_width = "32" )] |
414 | pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> { |
415 | if let Some(ext) = &self.statx_extra_fields { |
416 | if (ext.stx_mask & libc::STATX_CTIME) != 0 { |
417 | return Some(&ext.stx_ctime); |
418 | } |
419 | } |
420 | None |
421 | } |
422 | } |
423 | } else { |
424 | impl FileAttr { |
425 | fn from_stat64(stat: stat64) -> Self { |
426 | Self { stat } |
427 | } |
428 | } |
429 | }} |
430 | |
431 | impl FileAttr { |
432 | pub fn size(&self) -> u64 { |
433 | self.stat.st_size as u64 |
434 | } |
435 | pub fn perm(&self) -> FilePermissions { |
436 | FilePermissions { mode: (self.stat.st_mode as mode_t) } |
437 | } |
438 | |
439 | pub fn file_type(&self) -> FileType { |
440 | FileType { mode: self.stat.st_mode as mode_t } |
441 | } |
442 | } |
443 | |
444 | #[cfg (target_os = "netbsd" )] |
445 | impl FileAttr { |
446 | pub fn modified(&self) -> io::Result<SystemTime> { |
447 | SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64) |
448 | } |
449 | |
450 | pub fn accessed(&self) -> io::Result<SystemTime> { |
451 | SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64) |
452 | } |
453 | |
454 | pub fn created(&self) -> io::Result<SystemTime> { |
455 | SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64) |
456 | } |
457 | } |
458 | |
459 | #[cfg (target_os = "aix" )] |
460 | impl FileAttr { |
461 | pub fn modified(&self) -> io::Result<SystemTime> { |
462 | SystemTime::new(self.stat.st_mtime.tv_sec as i64, self.stat.st_mtime.tv_nsec as i64) |
463 | } |
464 | |
465 | pub fn accessed(&self) -> io::Result<SystemTime> { |
466 | SystemTime::new(self.stat.st_atime.tv_sec as i64, self.stat.st_atime.tv_nsec as i64) |
467 | } |
468 | |
469 | pub fn created(&self) -> io::Result<SystemTime> { |
470 | SystemTime::new(self.stat.st_ctime.tv_sec as i64, self.stat.st_ctime.tv_nsec as i64) |
471 | } |
472 | } |
473 | |
474 | #[cfg (not(any(target_os = "netbsd" , target_os = "nto" , target_os = "aix" )))] |
475 | impl FileAttr { |
476 | #[cfg (not(any( |
477 | target_os = "vxworks" , |
478 | target_os = "espidf" , |
479 | target_os = "horizon" , |
480 | target_os = "vita" , |
481 | target_os = "hurd" , |
482 | target_os = "rtems" , |
483 | target_os = "nuttx" , |
484 | )))] |
485 | pub fn modified(&self) -> io::Result<SystemTime> { |
486 | #[cfg (target_pointer_width = "32" )] |
487 | cfg_has_statx! { |
488 | if let Some(mtime) = self.stx_mtime() { |
489 | return SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64); |
490 | } |
491 | } |
492 | |
493 | SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64) |
494 | } |
495 | |
496 | #[cfg (any( |
497 | target_os = "vxworks" , |
498 | target_os = "espidf" , |
499 | target_os = "vita" , |
500 | target_os = "rtems" , |
501 | ))] |
502 | pub fn modified(&self) -> io::Result<SystemTime> { |
503 | SystemTime::new(self.stat.st_mtime as i64, 0) |
504 | } |
505 | |
506 | #[cfg (any(target_os = "horizon" , target_os = "hurd" , target_os = "nuttx" ))] |
507 | pub fn modified(&self) -> io::Result<SystemTime> { |
508 | SystemTime::new(self.stat.st_mtim.tv_sec as i64, self.stat.st_mtim.tv_nsec as i64) |
509 | } |
510 | |
511 | #[cfg (not(any( |
512 | target_os = "vxworks" , |
513 | target_os = "espidf" , |
514 | target_os = "horizon" , |
515 | target_os = "vita" , |
516 | target_os = "hurd" , |
517 | target_os = "rtems" , |
518 | target_os = "nuttx" , |
519 | )))] |
520 | pub fn accessed(&self) -> io::Result<SystemTime> { |
521 | #[cfg (target_pointer_width = "32" )] |
522 | cfg_has_statx! { |
523 | if let Some(atime) = self.stx_atime() { |
524 | return SystemTime::new(atime.tv_sec, atime.tv_nsec as i64); |
525 | } |
526 | } |
527 | |
528 | SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64) |
529 | } |
530 | |
531 | #[cfg (any( |
532 | target_os = "vxworks" , |
533 | target_os = "espidf" , |
534 | target_os = "vita" , |
535 | target_os = "rtems" |
536 | ))] |
537 | pub fn accessed(&self) -> io::Result<SystemTime> { |
538 | SystemTime::new(self.stat.st_atime as i64, 0) |
539 | } |
540 | |
541 | #[cfg (any(target_os = "horizon" , target_os = "hurd" , target_os = "nuttx" ))] |
542 | pub fn accessed(&self) -> io::Result<SystemTime> { |
543 | SystemTime::new(self.stat.st_atim.tv_sec as i64, self.stat.st_atim.tv_nsec as i64) |
544 | } |
545 | |
546 | #[cfg (any( |
547 | target_os = "freebsd" , |
548 | target_os = "openbsd" , |
549 | target_vendor = "apple" , |
550 | target_os = "cygwin" , |
551 | ))] |
552 | pub fn created(&self) -> io::Result<SystemTime> { |
553 | SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64) |
554 | } |
555 | |
556 | #[cfg (not(any( |
557 | target_os = "freebsd" , |
558 | target_os = "openbsd" , |
559 | target_os = "vita" , |
560 | target_vendor = "apple" , |
561 | target_os = "cygwin" , |
562 | )))] |
563 | pub fn created(&self) -> io::Result<SystemTime> { |
564 | cfg_has_statx! { |
565 | if let Some(ext) = &self.statx_extra_fields { |
566 | return if (ext.stx_mask & libc::STATX_BTIME) != 0 { |
567 | SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64) |
568 | } else { |
569 | Err(io::const_error!( |
570 | io::ErrorKind::Unsupported, |
571 | "creation time is not available for the filesystem" , |
572 | )) |
573 | }; |
574 | } |
575 | } |
576 | |
577 | Err(io::const_error!( |
578 | io::ErrorKind::Unsupported, |
579 | "creation time is not available on this platform currently" , |
580 | )) |
581 | } |
582 | |
583 | #[cfg (target_os = "vita" )] |
584 | pub fn created(&self) -> io::Result<SystemTime> { |
585 | SystemTime::new(self.stat.st_ctime as i64, 0) |
586 | } |
587 | } |
588 | |
589 | #[cfg (target_os = "nto" )] |
590 | impl FileAttr { |
591 | pub fn modified(&self) -> io::Result<SystemTime> { |
592 | SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec) |
593 | } |
594 | |
595 | pub fn accessed(&self) -> io::Result<SystemTime> { |
596 | SystemTime::new(self.stat.st_atim.tv_sec, self.stat.st_atim.tv_nsec) |
597 | } |
598 | |
599 | pub fn created(&self) -> io::Result<SystemTime> { |
600 | SystemTime::new(self.stat.st_ctim.tv_sec, self.stat.st_ctim.tv_nsec) |
601 | } |
602 | } |
603 | |
604 | impl AsInner<stat64> for FileAttr { |
605 | #[inline ] |
606 | fn as_inner(&self) -> &stat64 { |
607 | &self.stat |
608 | } |
609 | } |
610 | |
611 | impl FilePermissions { |
612 | pub fn readonly(&self) -> bool { |
613 | // check if any class (owner, group, others) has write permission |
614 | self.mode & 0o222 == 0 |
615 | } |
616 | |
617 | pub fn set_readonly(&mut self, readonly: bool) { |
618 | if readonly { |
619 | // remove write permission for all classes; equivalent to `chmod a-w <file>` |
620 | self.mode &= !0o222; |
621 | } else { |
622 | // add write permission for all classes; equivalent to `chmod a+w <file>` |
623 | self.mode |= 0o222; |
624 | } |
625 | } |
626 | pub fn mode(&self) -> u32 { |
627 | self.mode as u32 |
628 | } |
629 | } |
630 | |
631 | impl FileTimes { |
632 | pub fn set_accessed(&mut self, t: SystemTime) { |
633 | self.accessed = Some(t); |
634 | } |
635 | |
636 | pub fn set_modified(&mut self, t: SystemTime) { |
637 | self.modified = Some(t); |
638 | } |
639 | |
640 | #[cfg (target_vendor = "apple" )] |
641 | pub fn set_created(&mut self, t: SystemTime) { |
642 | self.created = Some(t); |
643 | } |
644 | } |
645 | |
646 | impl FileType { |
647 | pub fn is_dir(&self) -> bool { |
648 | self.is(mode:libc::S_IFDIR) |
649 | } |
650 | pub fn is_file(&self) -> bool { |
651 | self.is(mode:libc::S_IFREG) |
652 | } |
653 | pub fn is_symlink(&self) -> bool { |
654 | self.is(mode:libc::S_IFLNK) |
655 | } |
656 | |
657 | pub fn is(&self, mode: mode_t) -> bool { |
658 | self.masked() == mode |
659 | } |
660 | |
661 | fn masked(&self) -> mode_t { |
662 | self.mode & libc::S_IFMT |
663 | } |
664 | } |
665 | |
666 | impl fmt::Debug for FileType { |
667 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
668 | let FileType { mode: &u32 } = self; |
669 | f.debug_struct("FileType" ).field(name:"mode" , &Mode(*mode)).finish() |
670 | } |
671 | } |
672 | |
673 | impl FromInner<u32> for FilePermissions { |
674 | fn from_inner(mode: u32) -> FilePermissions { |
675 | FilePermissions { mode: mode as mode_t } |
676 | } |
677 | } |
678 | |
679 | impl fmt::Debug for FilePermissions { |
680 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
681 | let FilePermissions { mode: &u32 } = self; |
682 | f.debug_struct("FilePermissions" ).field(name:"mode" , &Mode(*mode)).finish() |
683 | } |
684 | } |
685 | |
686 | impl fmt::Debug for ReadDir { |
687 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
688 | // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. |
689 | // Thus the result will be e g 'ReadDir("/home")' |
690 | fmt::Debug::fmt(&*self.inner.root, f) |
691 | } |
692 | } |
693 | |
694 | impl Iterator for ReadDir { |
695 | type Item = io::Result<DirEntry>; |
696 | |
697 | #[cfg (any( |
698 | target_os = "android" , |
699 | target_os = "linux" , |
700 | target_os = "solaris" , |
701 | target_os = "fuchsia" , |
702 | target_os = "redox" , |
703 | target_os = "illumos" , |
704 | target_os = "aix" , |
705 | target_os = "nto" , |
706 | target_os = "vita" , |
707 | target_os = "hurd" , |
708 | ))] |
709 | fn next(&mut self) -> Option<io::Result<DirEntry>> { |
710 | use crate::sys::os::{errno, set_errno}; |
711 | |
712 | if self.end_of_stream { |
713 | return None; |
714 | } |
715 | |
716 | unsafe { |
717 | loop { |
718 | // As of POSIX.1-2017, readdir() is not required to be thread safe; only |
719 | // readdir_r() is. However, readdir_r() cannot correctly handle platforms |
720 | // with unlimited or variable NAME_MAX. Many modern platforms guarantee |
721 | // thread safety for readdir() as long an individual DIR* is not accessed |
722 | // concurrently, which is sufficient for Rust. |
723 | set_errno(0); |
724 | let entry_ptr: *const dirent64 = readdir64(self.inner.dirp.0); |
725 | if entry_ptr.is_null() { |
726 | // We either encountered an error, or reached the end. Either way, |
727 | // the next call to next() should return None. |
728 | self.end_of_stream = true; |
729 | |
730 | // To distinguish between errors and end-of-directory, we had to clear |
731 | // errno beforehand to check for an error now. |
732 | return match errno() { |
733 | 0 => None, |
734 | e => Some(Err(Error::from_raw_os_error(e))), |
735 | }; |
736 | } |
737 | |
738 | // The dirent64 struct is a weird imaginary thing that isn't ever supposed |
739 | // to be worked with by value. Its trailing d_name field is declared |
740 | // variously as [c_char; 256] or [c_char; 1] on different systems but |
741 | // either way that size is meaningless; only the offset of d_name is |
742 | // meaningful. The dirent64 pointers that libc returns from readdir64 are |
743 | // allowed to point to allocations smaller _or_ LARGER than implied by the |
744 | // definition of the struct. |
745 | // |
746 | // As such, we need to be even more careful with dirent64 than if its |
747 | // contents were "simply" partially initialized data. |
748 | // |
749 | // Like for uninitialized contents, converting entry_ptr to `&dirent64` |
750 | // would not be legal. However, we can use `&raw const (*entry_ptr).d_name` |
751 | // to refer the fields individually, because that operation is equivalent |
752 | // to `byte_offset` and thus does not require the full extent of `*entry_ptr` |
753 | // to be in bounds of the same allocation, only the offset of the field |
754 | // being referenced. |
755 | |
756 | // d_name is guaranteed to be null-terminated. |
757 | let name = CStr::from_ptr((&raw const (*entry_ptr).d_name).cast()); |
758 | let name_bytes = name.to_bytes(); |
759 | if name_bytes == b"." || name_bytes == b".." { |
760 | continue; |
761 | } |
762 | |
763 | // When loading from a field, we can skip the `&raw const`; `(*entry_ptr).d_ino` as |
764 | // a value expression will do the right thing: `byte_offset` to the field and then |
765 | // only access those bytes. |
766 | #[cfg (not(target_os = "vita" ))] |
767 | let entry = dirent64_min { |
768 | d_ino: (*entry_ptr).d_ino as u64, |
769 | #[cfg (not(any( |
770 | target_os = "solaris" , |
771 | target_os = "illumos" , |
772 | target_os = "aix" , |
773 | target_os = "nto" , |
774 | )))] |
775 | d_type: (*entry_ptr).d_type as u8, |
776 | }; |
777 | |
778 | #[cfg (target_os = "vita" )] |
779 | let entry = dirent64_min { d_ino: 0u64 }; |
780 | |
781 | return Some(Ok(DirEntry { |
782 | entry, |
783 | name: name.to_owned(), |
784 | dir: Arc::clone(&self.inner), |
785 | })); |
786 | } |
787 | } |
788 | } |
789 | |
790 | #[cfg (not(any( |
791 | target_os = "android" , |
792 | target_os = "linux" , |
793 | target_os = "solaris" , |
794 | target_os = "fuchsia" , |
795 | target_os = "redox" , |
796 | target_os = "illumos" , |
797 | target_os = "aix" , |
798 | target_os = "nto" , |
799 | target_os = "vita" , |
800 | target_os = "hurd" , |
801 | )))] |
802 | fn next(&mut self) -> Option<io::Result<DirEntry>> { |
803 | if self.end_of_stream { |
804 | return None; |
805 | } |
806 | |
807 | unsafe { |
808 | let mut ret = DirEntry { entry: mem::zeroed(), dir: Arc::clone(&self.inner) }; |
809 | let mut entry_ptr = ptr::null_mut(); |
810 | loop { |
811 | let err = readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr); |
812 | if err != 0 { |
813 | if entry_ptr.is_null() { |
814 | // We encountered an error (which will be returned in this iteration), but |
815 | // we also reached the end of the directory stream. The `end_of_stream` |
816 | // flag is enabled to make sure that we return `None` in the next iteration |
817 | // (instead of looping forever) |
818 | self.end_of_stream = true; |
819 | } |
820 | return Some(Err(Error::from_raw_os_error(err))); |
821 | } |
822 | if entry_ptr.is_null() { |
823 | return None; |
824 | } |
825 | if ret.name_bytes() != b"." && ret.name_bytes() != b".." { |
826 | return Some(Ok(ret)); |
827 | } |
828 | } |
829 | } |
830 | } |
831 | } |
832 | |
833 | /// Aborts the process if a file desceriptor is not open, if debug asserts are enabled |
834 | /// |
835 | /// Many IO syscalls can't be fully trusted about EBADF error codes because those |
836 | /// might get bubbled up from a remote FUSE server rather than the file descriptor |
837 | /// in the current process being invalid. |
838 | /// |
839 | /// So we check file flags instead which live on the file descriptor and not the underlying file. |
840 | /// The downside is that it costs an extra syscall, so we only do it for debug. |
841 | #[inline ] |
842 | pub(crate) fn debug_assert_fd_is_open(fd: RawFd) { |
843 | use crate::sys::os::errno; |
844 | |
845 | // this is similar to assert_unsafe_precondition!() but it doesn't require const |
846 | if core::ub_checks::check_library_ub() { |
847 | if unsafe { libc::fcntl(fd, cmd:libc::F_GETFD) } == -1 && errno() == libc::EBADF { |
848 | rtabort!("IO Safety violation: owned file descriptor already closed" ); |
849 | } |
850 | } |
851 | } |
852 | |
853 | impl Drop for Dir { |
854 | fn drop(&mut self) { |
855 | // dirfd isn't supported everywhere |
856 | #[cfg (not(any( |
857 | miri, |
858 | target_os = "redox" , |
859 | target_os = "nto" , |
860 | target_os = "vita" , |
861 | target_os = "hurd" , |
862 | target_os = "espidf" , |
863 | target_os = "horizon" , |
864 | target_os = "vxworks" , |
865 | target_os = "rtems" , |
866 | target_os = "nuttx" , |
867 | )))] |
868 | { |
869 | let fd = unsafe { libc::dirfd(self.0) }; |
870 | debug_assert_fd_is_open(fd); |
871 | } |
872 | let r = unsafe { libc::closedir(self.0) }; |
873 | assert!( |
874 | r == 0 || crate::io::Error::last_os_error().is_interrupted(), |
875 | "unexpected error during closedir: {:?}" , |
876 | crate::io::Error::last_os_error() |
877 | ); |
878 | } |
879 | } |
880 | |
881 | impl DirEntry { |
882 | pub fn path(&self) -> PathBuf { |
883 | self.dir.root.join(self.file_name_os_str()) |
884 | } |
885 | |
886 | pub fn file_name(&self) -> OsString { |
887 | self.file_name_os_str().to_os_string() |
888 | } |
889 | |
890 | #[cfg (all( |
891 | any( |
892 | all(target_os = "linux" , not(target_env = "musl" )), |
893 | target_os = "android" , |
894 | target_os = "fuchsia" , |
895 | target_os = "hurd" |
896 | ), |
897 | not(miri) // no dirfd on Miri |
898 | ))] |
899 | pub fn metadata(&self) -> io::Result<FileAttr> { |
900 | let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; |
901 | let name = self.name_cstr().as_ptr(); |
902 | |
903 | cfg_has_statx! { |
904 | if let Some(ret) = unsafe { try_statx( |
905 | fd, |
906 | name, |
907 | libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, |
908 | libc::STATX_BASIC_STATS | libc::STATX_BTIME, |
909 | ) } { |
910 | return ret; |
911 | } |
912 | } |
913 | |
914 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
915 | cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; |
916 | Ok(FileAttr::from_stat64(stat)) |
917 | } |
918 | |
919 | #[cfg (any( |
920 | not(any( |
921 | all(target_os = "linux" , not(target_env = "musl" )), |
922 | target_os = "android" , |
923 | target_os = "fuchsia" , |
924 | target_os = "hurd" , |
925 | )), |
926 | miri |
927 | ))] |
928 | pub fn metadata(&self) -> io::Result<FileAttr> { |
929 | lstat(&self.path()) |
930 | } |
931 | |
932 | #[cfg (any( |
933 | target_os = "solaris" , |
934 | target_os = "illumos" , |
935 | target_os = "haiku" , |
936 | target_os = "vxworks" , |
937 | target_os = "aix" , |
938 | target_os = "nto" , |
939 | target_os = "vita" , |
940 | ))] |
941 | pub fn file_type(&self) -> io::Result<FileType> { |
942 | self.metadata().map(|m| m.file_type()) |
943 | } |
944 | |
945 | #[cfg (not(any( |
946 | target_os = "solaris" , |
947 | target_os = "illumos" , |
948 | target_os = "haiku" , |
949 | target_os = "vxworks" , |
950 | target_os = "aix" , |
951 | target_os = "nto" , |
952 | target_os = "vita" , |
953 | )))] |
954 | pub fn file_type(&self) -> io::Result<FileType> { |
955 | match self.entry.d_type { |
956 | libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }), |
957 | libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }), |
958 | libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }), |
959 | libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }), |
960 | libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }), |
961 | libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }), |
962 | libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }), |
963 | _ => self.metadata().map(|m| m.file_type()), |
964 | } |
965 | } |
966 | |
967 | #[cfg (any( |
968 | target_os = "linux" , |
969 | target_os = "cygwin" , |
970 | target_os = "emscripten" , |
971 | target_os = "android" , |
972 | target_os = "solaris" , |
973 | target_os = "illumos" , |
974 | target_os = "haiku" , |
975 | target_os = "l4re" , |
976 | target_os = "fuchsia" , |
977 | target_os = "redox" , |
978 | target_os = "vxworks" , |
979 | target_os = "espidf" , |
980 | target_os = "horizon" , |
981 | target_os = "vita" , |
982 | target_os = "aix" , |
983 | target_os = "nto" , |
984 | target_os = "hurd" , |
985 | target_os = "rtems" , |
986 | target_vendor = "apple" , |
987 | ))] |
988 | pub fn ino(&self) -> u64 { |
989 | self.entry.d_ino as u64 |
990 | } |
991 | |
992 | #[cfg (any( |
993 | target_os = "freebsd" , |
994 | target_os = "openbsd" , |
995 | target_os = "netbsd" , |
996 | target_os = "dragonfly" |
997 | ))] |
998 | pub fn ino(&self) -> u64 { |
999 | self.entry.d_fileno as u64 |
1000 | } |
1001 | |
1002 | #[cfg (target_os = "nuttx" )] |
1003 | pub fn ino(&self) -> u64 { |
1004 | // Leave this 0 for now, as NuttX does not provide an inode number |
1005 | // in its directory entries. |
1006 | 0 |
1007 | } |
1008 | |
1009 | #[cfg (any( |
1010 | target_os = "netbsd" , |
1011 | target_os = "openbsd" , |
1012 | target_os = "freebsd" , |
1013 | target_os = "dragonfly" , |
1014 | target_vendor = "apple" , |
1015 | ))] |
1016 | fn name_bytes(&self) -> &[u8] { |
1017 | use crate::slice; |
1018 | unsafe { |
1019 | slice::from_raw_parts( |
1020 | self.entry.d_name.as_ptr() as *const u8, |
1021 | self.entry.d_namlen as usize, |
1022 | ) |
1023 | } |
1024 | } |
1025 | #[cfg (not(any( |
1026 | target_os = "netbsd" , |
1027 | target_os = "openbsd" , |
1028 | target_os = "freebsd" , |
1029 | target_os = "dragonfly" , |
1030 | target_vendor = "apple" , |
1031 | )))] |
1032 | fn name_bytes(&self) -> &[u8] { |
1033 | self.name_cstr().to_bytes() |
1034 | } |
1035 | |
1036 | #[cfg (not(any( |
1037 | target_os = "android" , |
1038 | target_os = "linux" , |
1039 | target_os = "solaris" , |
1040 | target_os = "illumos" , |
1041 | target_os = "fuchsia" , |
1042 | target_os = "redox" , |
1043 | target_os = "aix" , |
1044 | target_os = "nto" , |
1045 | target_os = "vita" , |
1046 | target_os = "hurd" , |
1047 | )))] |
1048 | fn name_cstr(&self) -> &CStr { |
1049 | unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) } |
1050 | } |
1051 | #[cfg (any( |
1052 | target_os = "android" , |
1053 | target_os = "linux" , |
1054 | target_os = "solaris" , |
1055 | target_os = "illumos" , |
1056 | target_os = "fuchsia" , |
1057 | target_os = "redox" , |
1058 | target_os = "aix" , |
1059 | target_os = "nto" , |
1060 | target_os = "vita" , |
1061 | target_os = "hurd" , |
1062 | ))] |
1063 | fn name_cstr(&self) -> &CStr { |
1064 | &self.name |
1065 | } |
1066 | |
1067 | pub fn file_name_os_str(&self) -> &OsStr { |
1068 | OsStr::from_bytes(self.name_bytes()) |
1069 | } |
1070 | } |
1071 | |
1072 | impl OpenOptions { |
1073 | pub fn new() -> OpenOptions { |
1074 | OpenOptions { |
1075 | // generic |
1076 | read: false, |
1077 | write: false, |
1078 | append: false, |
1079 | truncate: false, |
1080 | create: false, |
1081 | create_new: false, |
1082 | // system-specific |
1083 | custom_flags: 0, |
1084 | mode: 0o666, |
1085 | } |
1086 | } |
1087 | |
1088 | pub fn read(&mut self, read: bool) { |
1089 | self.read = read; |
1090 | } |
1091 | pub fn write(&mut self, write: bool) { |
1092 | self.write = write; |
1093 | } |
1094 | pub fn append(&mut self, append: bool) { |
1095 | self.append = append; |
1096 | } |
1097 | pub fn truncate(&mut self, truncate: bool) { |
1098 | self.truncate = truncate; |
1099 | } |
1100 | pub fn create(&mut self, create: bool) { |
1101 | self.create = create; |
1102 | } |
1103 | pub fn create_new(&mut self, create_new: bool) { |
1104 | self.create_new = create_new; |
1105 | } |
1106 | |
1107 | pub fn custom_flags(&mut self, flags: i32) { |
1108 | self.custom_flags = flags; |
1109 | } |
1110 | pub fn mode(&mut self, mode: u32) { |
1111 | self.mode = mode as mode_t; |
1112 | } |
1113 | |
1114 | fn get_access_mode(&self) -> io::Result<c_int> { |
1115 | match (self.read, self.write, self.append) { |
1116 | (true, false, false) => Ok(libc::O_RDONLY), |
1117 | (false, true, false) => Ok(libc::O_WRONLY), |
1118 | (true, true, false) => Ok(libc::O_RDWR), |
1119 | (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND), |
1120 | (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND), |
1121 | (false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)), |
1122 | } |
1123 | } |
1124 | |
1125 | fn get_creation_mode(&self) -> io::Result<c_int> { |
1126 | match (self.write, self.append) { |
1127 | (true, false) => {} |
1128 | (false, false) => { |
1129 | if self.truncate || self.create || self.create_new { |
1130 | return Err(Error::from_raw_os_error(libc::EINVAL)); |
1131 | } |
1132 | } |
1133 | (_, true) => { |
1134 | if self.truncate && !self.create_new { |
1135 | return Err(Error::from_raw_os_error(libc::EINVAL)); |
1136 | } |
1137 | } |
1138 | } |
1139 | |
1140 | Ok(match (self.create, self.truncate, self.create_new) { |
1141 | (false, false, false) => 0, |
1142 | (true, false, false) => libc::O_CREAT, |
1143 | (false, true, false) => libc::O_TRUNC, |
1144 | (true, true, false) => libc::O_CREAT | libc::O_TRUNC, |
1145 | (_, _, true) => libc::O_CREAT | libc::O_EXCL, |
1146 | }) |
1147 | } |
1148 | } |
1149 | |
1150 | impl fmt::Debug for OpenOptions { |
1151 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1152 | let OpenOptions { read: &bool, write: &bool, append: &bool, truncate: &bool, create: &bool, create_new: &bool, custom_flags: &i32, mode: &u32 } = |
1153 | self; |
1154 | f&mut DebugStruct<'_, '_>.debug_struct("OpenOptions" ) |
1155 | .field("read" , read) |
1156 | .field("write" , write) |
1157 | .field("append" , append) |
1158 | .field("truncate" , truncate) |
1159 | .field("create" , create) |
1160 | .field("create_new" , create_new) |
1161 | .field("custom_flags" , custom_flags) |
1162 | .field(name:"mode" , &Mode(*mode)) |
1163 | .finish() |
1164 | } |
1165 | } |
1166 | |
1167 | impl File { |
1168 | pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { |
1169 | run_path_with_cstr(path, &|path| File::open_c(path, opts)) |
1170 | } |
1171 | |
1172 | pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> { |
1173 | let flags = libc::O_CLOEXEC |
1174 | | opts.get_access_mode()? |
1175 | | opts.get_creation_mode()? |
1176 | | (opts.custom_flags as c_int & !libc::O_ACCMODE); |
1177 | // The third argument of `open64` is documented to have type `mode_t`. On |
1178 | // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`. |
1179 | // However, since this is a variadic function, C integer promotion rules mean that on |
1180 | // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms). |
1181 | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?; |
1182 | Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) |
1183 | } |
1184 | |
1185 | pub fn file_attr(&self) -> io::Result<FileAttr> { |
1186 | let fd = self.as_raw_fd(); |
1187 | |
1188 | cfg_has_statx! { |
1189 | if let Some(ret) = unsafe { try_statx( |
1190 | fd, |
1191 | c"" .as_ptr() as *const c_char, |
1192 | libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT, |
1193 | libc::STATX_BASIC_STATS | libc::STATX_BTIME, |
1194 | ) } { |
1195 | return ret; |
1196 | } |
1197 | } |
1198 | |
1199 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
1200 | cvt(unsafe { fstat64(fd, &mut stat) })?; |
1201 | Ok(FileAttr::from_stat64(stat)) |
1202 | } |
1203 | |
1204 | pub fn fsync(&self) -> io::Result<()> { |
1205 | cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?; |
1206 | return Ok(()); |
1207 | |
1208 | #[cfg (target_vendor = "apple" )] |
1209 | unsafe fn os_fsync(fd: c_int) -> c_int { |
1210 | libc::fcntl(fd, libc::F_FULLFSYNC) |
1211 | } |
1212 | #[cfg (not(target_vendor = "apple" ))] |
1213 | unsafe fn os_fsync(fd: c_int) -> c_int { |
1214 | libc::fsync(fd) |
1215 | } |
1216 | } |
1217 | |
1218 | pub fn datasync(&self) -> io::Result<()> { |
1219 | cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?; |
1220 | return Ok(()); |
1221 | |
1222 | #[cfg (target_vendor = "apple" )] |
1223 | unsafe fn os_datasync(fd: c_int) -> c_int { |
1224 | libc::fcntl(fd, libc::F_FULLFSYNC) |
1225 | } |
1226 | #[cfg (any( |
1227 | target_os = "freebsd" , |
1228 | target_os = "fuchsia" , |
1229 | target_os = "linux" , |
1230 | target_os = "cygwin" , |
1231 | target_os = "android" , |
1232 | target_os = "netbsd" , |
1233 | target_os = "openbsd" , |
1234 | target_os = "nto" , |
1235 | target_os = "hurd" , |
1236 | ))] |
1237 | unsafe fn os_datasync(fd: c_int) -> c_int { |
1238 | libc::fdatasync(fd) |
1239 | } |
1240 | #[cfg (not(any( |
1241 | target_os = "android" , |
1242 | target_os = "fuchsia" , |
1243 | target_os = "freebsd" , |
1244 | target_os = "linux" , |
1245 | target_os = "cygwin" , |
1246 | target_os = "netbsd" , |
1247 | target_os = "openbsd" , |
1248 | target_os = "nto" , |
1249 | target_os = "hurd" , |
1250 | target_vendor = "apple" , |
1251 | )))] |
1252 | unsafe fn os_datasync(fd: c_int) -> c_int { |
1253 | libc::fsync(fd) |
1254 | } |
1255 | } |
1256 | |
1257 | #[cfg (any( |
1258 | target_os = "freebsd" , |
1259 | target_os = "fuchsia" , |
1260 | target_os = "linux" , |
1261 | target_os = "netbsd" , |
1262 | target_vendor = "apple" , |
1263 | ))] |
1264 | pub fn lock(&self) -> io::Result<()> { |
1265 | cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX) })?; |
1266 | return Ok(()); |
1267 | } |
1268 | |
1269 | #[cfg (not(any( |
1270 | target_os = "freebsd" , |
1271 | target_os = "fuchsia" , |
1272 | target_os = "linux" , |
1273 | target_os = "netbsd" , |
1274 | target_vendor = "apple" , |
1275 | )))] |
1276 | pub fn lock(&self) -> io::Result<()> { |
1277 | Err(io::const_error!(io::ErrorKind::Unsupported, "lock() not supported" )) |
1278 | } |
1279 | |
1280 | #[cfg (any( |
1281 | target_os = "freebsd" , |
1282 | target_os = "fuchsia" , |
1283 | target_os = "linux" , |
1284 | target_os = "netbsd" , |
1285 | target_vendor = "apple" , |
1286 | ))] |
1287 | pub fn lock_shared(&self) -> io::Result<()> { |
1288 | cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH) })?; |
1289 | return Ok(()); |
1290 | } |
1291 | |
1292 | #[cfg (not(any( |
1293 | target_os = "freebsd" , |
1294 | target_os = "fuchsia" , |
1295 | target_os = "linux" , |
1296 | target_os = "netbsd" , |
1297 | target_vendor = "apple" , |
1298 | )))] |
1299 | pub fn lock_shared(&self) -> io::Result<()> { |
1300 | Err(io::const_error!(io::ErrorKind::Unsupported, "lock_shared() not supported" )) |
1301 | } |
1302 | |
1303 | #[cfg (any( |
1304 | target_os = "freebsd" , |
1305 | target_os = "fuchsia" , |
1306 | target_os = "linux" , |
1307 | target_os = "netbsd" , |
1308 | target_vendor = "apple" , |
1309 | ))] |
1310 | pub fn try_lock(&self) -> io::Result<bool> { |
1311 | let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) }); |
1312 | if let Err(ref err) = result { |
1313 | if err.kind() == io::ErrorKind::WouldBlock { |
1314 | return Ok(false); |
1315 | } |
1316 | } |
1317 | result?; |
1318 | return Ok(true); |
1319 | } |
1320 | |
1321 | #[cfg (not(any( |
1322 | target_os = "freebsd" , |
1323 | target_os = "fuchsia" , |
1324 | target_os = "linux" , |
1325 | target_os = "netbsd" , |
1326 | target_vendor = "apple" , |
1327 | )))] |
1328 | pub fn try_lock(&self) -> io::Result<bool> { |
1329 | Err(io::const_error!(io::ErrorKind::Unsupported, "try_lock() not supported" )) |
1330 | } |
1331 | |
1332 | #[cfg (any( |
1333 | target_os = "freebsd" , |
1334 | target_os = "fuchsia" , |
1335 | target_os = "linux" , |
1336 | target_os = "netbsd" , |
1337 | target_vendor = "apple" , |
1338 | ))] |
1339 | pub fn try_lock_shared(&self) -> io::Result<bool> { |
1340 | let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) }); |
1341 | if let Err(ref err) = result { |
1342 | if err.kind() == io::ErrorKind::WouldBlock { |
1343 | return Ok(false); |
1344 | } |
1345 | } |
1346 | result?; |
1347 | return Ok(true); |
1348 | } |
1349 | |
1350 | #[cfg (not(any( |
1351 | target_os = "freebsd" , |
1352 | target_os = "fuchsia" , |
1353 | target_os = "linux" , |
1354 | target_os = "netbsd" , |
1355 | target_vendor = "apple" , |
1356 | )))] |
1357 | pub fn try_lock_shared(&self) -> io::Result<bool> { |
1358 | Err(io::const_error!(io::ErrorKind::Unsupported, "try_lock_shared() not supported" )) |
1359 | } |
1360 | |
1361 | #[cfg (any( |
1362 | target_os = "freebsd" , |
1363 | target_os = "fuchsia" , |
1364 | target_os = "linux" , |
1365 | target_os = "netbsd" , |
1366 | target_vendor = "apple" , |
1367 | ))] |
1368 | pub fn unlock(&self) -> io::Result<()> { |
1369 | cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_UN) })?; |
1370 | return Ok(()); |
1371 | } |
1372 | |
1373 | #[cfg (not(any( |
1374 | target_os = "freebsd" , |
1375 | target_os = "fuchsia" , |
1376 | target_os = "linux" , |
1377 | target_os = "netbsd" , |
1378 | target_vendor = "apple" , |
1379 | )))] |
1380 | pub fn unlock(&self) -> io::Result<()> { |
1381 | Err(io::const_error!(io::ErrorKind::Unsupported, "unlock() not supported" )) |
1382 | } |
1383 | |
1384 | pub fn truncate(&self, size: u64) -> io::Result<()> { |
1385 | let size: off64_t = |
1386 | size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; |
1387 | cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop) |
1388 | } |
1389 | |
1390 | pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { |
1391 | self.0.read(buf) |
1392 | } |
1393 | |
1394 | pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { |
1395 | self.0.read_vectored(bufs) |
1396 | } |
1397 | |
1398 | #[inline ] |
1399 | pub fn is_read_vectored(&self) -> bool { |
1400 | self.0.is_read_vectored() |
1401 | } |
1402 | |
1403 | pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> { |
1404 | self.0.read_at(buf, offset) |
1405 | } |
1406 | |
1407 | pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { |
1408 | self.0.read_buf(cursor) |
1409 | } |
1410 | |
1411 | pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> { |
1412 | self.0.read_vectored_at(bufs, offset) |
1413 | } |
1414 | |
1415 | pub fn write(&self, buf: &[u8]) -> io::Result<usize> { |
1416 | self.0.write(buf) |
1417 | } |
1418 | |
1419 | pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { |
1420 | self.0.write_vectored(bufs) |
1421 | } |
1422 | |
1423 | #[inline ] |
1424 | pub fn is_write_vectored(&self) -> bool { |
1425 | self.0.is_write_vectored() |
1426 | } |
1427 | |
1428 | pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> { |
1429 | self.0.write_at(buf, offset) |
1430 | } |
1431 | |
1432 | pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> { |
1433 | self.0.write_vectored_at(bufs, offset) |
1434 | } |
1435 | |
1436 | #[inline ] |
1437 | pub fn flush(&self) -> io::Result<()> { |
1438 | Ok(()) |
1439 | } |
1440 | |
1441 | pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { |
1442 | let (whence, pos) = match pos { |
1443 | // Casting to `i64` is fine, too large values will end up as |
1444 | // negative which will cause an error in `lseek64`. |
1445 | SeekFrom::Start(off) => (libc::SEEK_SET, off as i64), |
1446 | SeekFrom::End(off) => (libc::SEEK_END, off), |
1447 | SeekFrom::Current(off) => (libc::SEEK_CUR, off), |
1448 | }; |
1449 | let n = cvt(unsafe { lseek64(self.as_raw_fd(), pos as off64_t, whence) })?; |
1450 | Ok(n as u64) |
1451 | } |
1452 | |
1453 | pub fn tell(&self) -> io::Result<u64> { |
1454 | self.seek(SeekFrom::Current(0)) |
1455 | } |
1456 | |
1457 | pub fn duplicate(&self) -> io::Result<File> { |
1458 | self.0.duplicate().map(File) |
1459 | } |
1460 | |
1461 | pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { |
1462 | cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?; |
1463 | Ok(()) |
1464 | } |
1465 | |
1466 | // FIXME(#115199): Rust currently omits weak function definitions |
1467 | // and its metadata from LLVM IR. |
1468 | #[cfg_attr ( |
1469 | any( |
1470 | target_os = "android" , |
1471 | all( |
1472 | target_os = "linux" , |
1473 | target_env = "gnu" , |
1474 | target_pointer_width = "32" , |
1475 | not(target_arch = "riscv32" ) |
1476 | ) |
1477 | ), |
1478 | no_sanitize(cfi) |
1479 | )] |
1480 | pub fn set_times(&self, times: FileTimes) -> io::Result<()> { |
1481 | #[cfg (not(any( |
1482 | target_os = "redox" , |
1483 | target_os = "espidf" , |
1484 | target_os = "horizon" , |
1485 | target_os = "vxworks" , |
1486 | target_os = "nuttx" , |
1487 | )))] |
1488 | let to_timespec = |time: Option<SystemTime>| match time { |
1489 | Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts), |
1490 | Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!( |
1491 | io::ErrorKind::InvalidInput, |
1492 | "timestamp is too large to set as a file time" , |
1493 | )), |
1494 | Some(_) => Err(io::const_error!( |
1495 | io::ErrorKind::InvalidInput, |
1496 | "timestamp is too small to set as a file time" , |
1497 | )), |
1498 | None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), |
1499 | }; |
1500 | cfg_if::cfg_if! { |
1501 | if #[cfg(any(target_os = "redox" , target_os = "espidf" , target_os = "horizon" , target_os = "vxworks" , target_os = "nuttx" ))] { |
1502 | // Redox doesn't appear to support `UTIME_OMIT`. |
1503 | // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore |
1504 | // the same as for Redox. |
1505 | // `futimens` and `UTIME_OMIT` are a work in progress for vxworks. |
1506 | let _ = times; |
1507 | Err(io::const_error!( |
1508 | io::ErrorKind::Unsupported, |
1509 | "setting file times not supported" , |
1510 | )) |
1511 | } else if #[cfg(target_vendor = "apple" )] { |
1512 | let mut buf = [mem::MaybeUninit::<libc::timespec>::uninit(); 3]; |
1513 | let mut num_times = 0; |
1514 | let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; |
1515 | attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; |
1516 | if times.created.is_some() { |
1517 | buf[num_times].write(to_timespec(times.created)?); |
1518 | num_times += 1; |
1519 | attrlist.commonattr |= libc::ATTR_CMN_CRTIME; |
1520 | } |
1521 | if times.modified.is_some() { |
1522 | buf[num_times].write(to_timespec(times.modified)?); |
1523 | num_times += 1; |
1524 | attrlist.commonattr |= libc::ATTR_CMN_MODTIME; |
1525 | } |
1526 | if times.accessed.is_some() { |
1527 | buf[num_times].write(to_timespec(times.accessed)?); |
1528 | num_times += 1; |
1529 | attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; |
1530 | } |
1531 | cvt(unsafe { libc::fsetattrlist( |
1532 | self.as_raw_fd(), |
1533 | (&raw const attrlist).cast::<libc::c_void>().cast_mut(), |
1534 | buf.as_ptr().cast::<libc::c_void>().cast_mut(), |
1535 | num_times * size_of::<libc::timespec>(), |
1536 | 0 |
1537 | ) })?; |
1538 | Ok(()) |
1539 | } else if #[cfg(target_os = "android" )] { |
1540 | let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; |
1541 | // futimens requires Android API level 19 |
1542 | cvt(unsafe { |
1543 | weak!(fn futimens(c_int, *const libc::timespec) -> c_int); |
1544 | match futimens.get() { |
1545 | Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()), |
1546 | None => return Err(io::const_error!( |
1547 | io::ErrorKind::Unsupported, |
1548 | "setting file times requires Android API level >= 19" , |
1549 | )), |
1550 | } |
1551 | })?; |
1552 | Ok(()) |
1553 | } else { |
1554 | #[cfg (all(target_os = "linux" , target_env = "gnu" , target_pointer_width = "32" , not(target_arch = "riscv32" )))] |
1555 | { |
1556 | use crate::sys::{time::__timespec64, weak::weak}; |
1557 | |
1558 | // Added in glibc 2.34 |
1559 | weak!(fn __futimens64(libc::c_int, *const __timespec64) -> libc::c_int); |
1560 | |
1561 | if let Some(futimens64) = __futimens64.get() { |
1562 | let to_timespec = |time: Option<SystemTime>| time.map(|time| time.t.to_timespec64()) |
1563 | .unwrap_or(__timespec64::new(0, libc::UTIME_OMIT as _)); |
1564 | let times = [to_timespec(times.accessed), to_timespec(times.modified)]; |
1565 | cvt(unsafe { futimens64(self.as_raw_fd(), times.as_ptr()) })?; |
1566 | return Ok(()); |
1567 | } |
1568 | } |
1569 | let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; |
1570 | cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) })?; |
1571 | Ok(()) |
1572 | } |
1573 | } |
1574 | } |
1575 | } |
1576 | |
1577 | impl DirBuilder { |
1578 | pub fn new() -> DirBuilder { |
1579 | DirBuilder { mode: 0o777 } |
1580 | } |
1581 | |
1582 | pub fn mkdir(&self, p: &Path) -> io::Result<()> { |
1583 | run_path_with_cstr(path:p, &|p: &CStr| cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) }).map(|_| ())) |
1584 | } |
1585 | |
1586 | pub fn set_mode(&mut self, mode: u32) { |
1587 | self.mode = mode as mode_t; |
1588 | } |
1589 | } |
1590 | |
1591 | impl fmt::Debug for DirBuilder { |
1592 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1593 | let DirBuilder { mode: &u32 } = self; |
1594 | f.debug_struct("DirBuilder" ).field(name:"mode" , &Mode(*mode)).finish() |
1595 | } |
1596 | } |
1597 | |
1598 | impl AsInner<FileDesc> for File { |
1599 | #[inline ] |
1600 | fn as_inner(&self) -> &FileDesc { |
1601 | &self.0 |
1602 | } |
1603 | } |
1604 | |
1605 | impl AsInnerMut<FileDesc> for File { |
1606 | #[inline ] |
1607 | fn as_inner_mut(&mut self) -> &mut FileDesc { |
1608 | &mut self.0 |
1609 | } |
1610 | } |
1611 | |
1612 | impl IntoInner<FileDesc> for File { |
1613 | fn into_inner(self) -> FileDesc { |
1614 | self.0 |
1615 | } |
1616 | } |
1617 | |
1618 | impl FromInner<FileDesc> for File { |
1619 | fn from_inner(file_desc: FileDesc) -> Self { |
1620 | Self(file_desc) |
1621 | } |
1622 | } |
1623 | |
1624 | impl AsFd for File { |
1625 | #[inline ] |
1626 | fn as_fd(&self) -> BorrowedFd<'_> { |
1627 | self.0.as_fd() |
1628 | } |
1629 | } |
1630 | |
1631 | impl AsRawFd for File { |
1632 | #[inline ] |
1633 | fn as_raw_fd(&self) -> RawFd { |
1634 | self.0.as_raw_fd() |
1635 | } |
1636 | } |
1637 | |
1638 | impl IntoRawFd for File { |
1639 | fn into_raw_fd(self) -> RawFd { |
1640 | self.0.into_raw_fd() |
1641 | } |
1642 | } |
1643 | |
1644 | impl FromRawFd for File { |
1645 | unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { |
1646 | Self(FromRawFd::from_raw_fd(raw_fd)) |
1647 | } |
1648 | } |
1649 | |
1650 | impl fmt::Debug for File { |
1651 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1652 | #[cfg (any(target_os = "linux" , target_os = "illumos" , target_os = "solaris" ))] |
1653 | fn get_path(fd: c_int) -> Option<PathBuf> { |
1654 | let mut p = PathBuf::from("/proc/self/fd" ); |
1655 | p.push(&fd.to_string()); |
1656 | readlink(&p).ok() |
1657 | } |
1658 | |
1659 | #[cfg (any(target_vendor = "apple" , target_os = "netbsd" ))] |
1660 | fn get_path(fd: c_int) -> Option<PathBuf> { |
1661 | // FIXME: The use of PATH_MAX is generally not encouraged, but it |
1662 | // is inevitable in this case because Apple targets and NetBSD define `fcntl` |
1663 | // with `F_GETPATH` in terms of `MAXPATHLEN`, and there are no |
1664 | // alternatives. If a better method is invented, it should be used |
1665 | // instead. |
1666 | let mut buf = vec![0; libc::PATH_MAX as usize]; |
1667 | let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }; |
1668 | if n == -1 { |
1669 | cfg_if::cfg_if! { |
1670 | if #[cfg(target_os = "netbsd" )] { |
1671 | // fallback to procfs as last resort |
1672 | let mut p = PathBuf::from("/proc/self/fd" ); |
1673 | p.push(&fd.to_string()); |
1674 | return readlink(&p).ok(); |
1675 | } else { |
1676 | return None; |
1677 | } |
1678 | } |
1679 | } |
1680 | let l = buf.iter().position(|&c| c == 0).unwrap(); |
1681 | buf.truncate(l as usize); |
1682 | buf.shrink_to_fit(); |
1683 | Some(PathBuf::from(OsString::from_vec(buf))) |
1684 | } |
1685 | |
1686 | #[cfg (target_os = "freebsd" )] |
1687 | fn get_path(fd: c_int) -> Option<PathBuf> { |
1688 | let info = Box::<libc::kinfo_file>::new_zeroed(); |
1689 | let mut info = unsafe { info.assume_init() }; |
1690 | info.kf_structsize = size_of::<libc::kinfo_file>() as libc::c_int; |
1691 | let n = unsafe { libc::fcntl(fd, libc::F_KINFO, &mut *info) }; |
1692 | if n == -1 { |
1693 | return None; |
1694 | } |
1695 | let buf = unsafe { CStr::from_ptr(info.kf_path.as_mut_ptr()).to_bytes().to_vec() }; |
1696 | Some(PathBuf::from(OsString::from_vec(buf))) |
1697 | } |
1698 | |
1699 | #[cfg (target_os = "vxworks" )] |
1700 | fn get_path(fd: c_int) -> Option<PathBuf> { |
1701 | let mut buf = vec![0; libc::PATH_MAX as usize]; |
1702 | let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) }; |
1703 | if n == -1 { |
1704 | return None; |
1705 | } |
1706 | let l = buf.iter().position(|&c| c == 0).unwrap(); |
1707 | buf.truncate(l as usize); |
1708 | Some(PathBuf::from(OsString::from_vec(buf))) |
1709 | } |
1710 | |
1711 | #[cfg (not(any( |
1712 | target_os = "linux" , |
1713 | target_os = "vxworks" , |
1714 | target_os = "freebsd" , |
1715 | target_os = "netbsd" , |
1716 | target_os = "illumos" , |
1717 | target_os = "solaris" , |
1718 | target_vendor = "apple" , |
1719 | )))] |
1720 | fn get_path(_fd: c_int) -> Option<PathBuf> { |
1721 | // FIXME(#24570): implement this for other Unix platforms |
1722 | None |
1723 | } |
1724 | |
1725 | fn get_mode(fd: c_int) -> Option<(bool, bool)> { |
1726 | let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) }; |
1727 | if mode == -1 { |
1728 | return None; |
1729 | } |
1730 | match mode & libc::O_ACCMODE { |
1731 | libc::O_RDONLY => Some((true, false)), |
1732 | libc::O_RDWR => Some((true, true)), |
1733 | libc::O_WRONLY => Some((false, true)), |
1734 | _ => None, |
1735 | } |
1736 | } |
1737 | |
1738 | let fd = self.as_raw_fd(); |
1739 | let mut b = f.debug_struct("File" ); |
1740 | b.field("fd" , &fd); |
1741 | if let Some(path) = get_path(fd) { |
1742 | b.field("path" , &path); |
1743 | } |
1744 | if let Some((read, write)) = get_mode(fd) { |
1745 | b.field("read" , &read).field("write" , &write); |
1746 | } |
1747 | b.finish() |
1748 | } |
1749 | } |
1750 | |
1751 | // Format in octal, followed by the mode format used in `ls -l`. |
1752 | // |
1753 | // References: |
1754 | // https://pubs.opengroup.org/onlinepubs/009696899/utilities/ls.html |
1755 | // https://www.gnu.org/software/libc/manual/html_node/Testing-File-Type.html |
1756 | // https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html |
1757 | // |
1758 | // Example: |
1759 | // 0o100664 (-rw-rw-r--) |
1760 | impl fmt::Debug for Mode { |
1761 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1762 | let Self(mode) = *self; |
1763 | write!(f, "0o {mode:06o}" )?; |
1764 | |
1765 | let entry_type = match mode & libc::S_IFMT { |
1766 | libc::S_IFDIR => 'd' , |
1767 | libc::S_IFBLK => 'b' , |
1768 | libc::S_IFCHR => 'c' , |
1769 | libc::S_IFLNK => 'l' , |
1770 | libc::S_IFIFO => 'p' , |
1771 | libc::S_IFREG => '-' , |
1772 | _ => return Ok(()), |
1773 | }; |
1774 | |
1775 | f.write_str(" (" )?; |
1776 | f.write_char(entry_type)?; |
1777 | |
1778 | // Owner permissions |
1779 | f.write_char(if mode & libc::S_IRUSR != 0 { 'r' } else { '-' })?; |
1780 | f.write_char(if mode & libc::S_IWUSR != 0 { 'w' } else { '-' })?; |
1781 | let owner_executable = mode & libc::S_IXUSR != 0; |
1782 | let setuid = mode as c_int & libc::S_ISUID as c_int != 0; |
1783 | f.write_char(match (owner_executable, setuid) { |
1784 | (true, true) => 's' , // executable and setuid |
1785 | (false, true) => 'S' , // setuid |
1786 | (true, false) => 'x' , // executable |
1787 | (false, false) => '-' , |
1788 | })?; |
1789 | |
1790 | // Group permissions |
1791 | f.write_char(if mode & libc::S_IRGRP != 0 { 'r' } else { '-' })?; |
1792 | f.write_char(if mode & libc::S_IWGRP != 0 { 'w' } else { '-' })?; |
1793 | let group_executable = mode & libc::S_IXGRP != 0; |
1794 | let setgid = mode as c_int & libc::S_ISGID as c_int != 0; |
1795 | f.write_char(match (group_executable, setgid) { |
1796 | (true, true) => 's' , // executable and setgid |
1797 | (false, true) => 'S' , // setgid |
1798 | (true, false) => 'x' , // executable |
1799 | (false, false) => '-' , |
1800 | })?; |
1801 | |
1802 | // Other permissions |
1803 | f.write_char(if mode & libc::S_IROTH != 0 { 'r' } else { '-' })?; |
1804 | f.write_char(if mode & libc::S_IWOTH != 0 { 'w' } else { '-' })?; |
1805 | let other_executable = mode & libc::S_IXOTH != 0; |
1806 | let sticky = mode as c_int & libc::S_ISVTX as c_int != 0; |
1807 | f.write_char(match (entry_type, other_executable, sticky) { |
1808 | ('d' , true, true) => 't' , // searchable and restricted deletion |
1809 | ('d' , false, true) => 'T' , // restricted deletion |
1810 | (_, true, _) => 'x' , // executable |
1811 | (_, false, _) => '-' , |
1812 | })?; |
1813 | |
1814 | f.write_char(')' ) |
1815 | } |
1816 | } |
1817 | |
1818 | pub fn readdir(path: &Path) -> io::Result<ReadDir> { |
1819 | let ptr: *mut DIR = run_path_with_cstr(path, &|p: &CStr| unsafe { Ok(libc::opendir(dirname:p.as_ptr())) })?; |
1820 | if ptr.is_null() { |
1821 | Err(Error::last_os_error()) |
1822 | } else { |
1823 | let root: PathBuf = path.to_path_buf(); |
1824 | let inner: InnerReadDir = InnerReadDir { dirp: Dir(ptr), root }; |
1825 | Ok(ReadDir::new(inner)) |
1826 | } |
1827 | } |
1828 | |
1829 | pub fn unlink(p: &Path) -> io::Result<()> { |
1830 | run_path_with_cstr(path:p, &|p: &CStr| cvt(unsafe { libc::unlink(p.as_ptr()) }).map(|_| ())) |
1831 | } |
1832 | |
1833 | pub fn rename(old: &Path, new: &Path) -> io::Result<()> { |
1834 | run_path_with_cstr(path:old, &|old: &CStr| { |
1835 | run_path_with_cstr(path:new, &|new: &CStr| { |
1836 | cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ()) |
1837 | }) |
1838 | }) |
1839 | } |
1840 | |
1841 | pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { |
1842 | run_path_with_cstr(path:p, &|p: &CStr| cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())) |
1843 | } |
1844 | |
1845 | pub fn rmdir(p: &Path) -> io::Result<()> { |
1846 | run_path_with_cstr(path:p, &|p: &CStr| cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())) |
1847 | } |
1848 | |
1849 | pub fn readlink(p: &Path) -> io::Result<PathBuf> { |
1850 | run_path_with_cstr(p, &|c_path| { |
1851 | let p = c_path.as_ptr(); |
1852 | |
1853 | let mut buf = Vec::with_capacity(256); |
1854 | |
1855 | loop { |
1856 | let buf_read = |
1857 | cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? |
1858 | as usize; |
1859 | |
1860 | unsafe { |
1861 | buf.set_len(buf_read); |
1862 | } |
1863 | |
1864 | if buf_read != buf.capacity() { |
1865 | buf.shrink_to_fit(); |
1866 | |
1867 | return Ok(PathBuf::from(OsString::from_vec(buf))); |
1868 | } |
1869 | |
1870 | // Trigger the internal buffer resizing logic of `Vec` by requiring |
1871 | // more space than the current capacity. The length is guaranteed to be |
1872 | // the same as the capacity due to the if statement above. |
1873 | buf.reserve(1); |
1874 | } |
1875 | }) |
1876 | } |
1877 | |
1878 | pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { |
1879 | run_path_with_cstr(path:original, &|original: &CStr| { |
1880 | run_path_with_cstr(path:link, &|link: &CStr| { |
1881 | cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) }).map(|_| ()) |
1882 | }) |
1883 | }) |
1884 | } |
1885 | |
1886 | pub fn link(original: &Path, link: &Path) -> io::Result<()> { |
1887 | run_path_with_cstr(path:original, &|original: &CStr| { |
1888 | run_path_with_cstr(path:link, &|link: &CStr| { |
1889 | cfg_if::cfg_if! { |
1890 | if #[cfg(any(target_os = "vxworks" , target_os = "redox" , target_os = "android" , target_os = "espidf" , target_os = "horizon" , target_os = "vita" , target_env = "nto70" ))] { |
1891 | // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves |
1892 | // it implementation-defined whether `link` follows symlinks, so rely on the |
1893 | // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior. |
1894 | // Android has `linkat` on newer versions, but we happen to know `link` |
1895 | // always has the correct behavior, so it's here as well. |
1896 | cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; |
1897 | } else { |
1898 | // Where we can, use `linkat` instead of `link`; see the comment above |
1899 | // this one for details on why. |
1900 | cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?; |
1901 | } |
1902 | } |
1903 | Ok(()) |
1904 | }) |
1905 | }) |
1906 | } |
1907 | |
1908 | pub fn stat(p: &Path) -> io::Result<FileAttr> { |
1909 | run_path_with_cstr(path:p, &|p: &CStr| { |
1910 | cfg_has_statx! { |
1911 | if let Some(ret) = unsafe { try_statx( |
1912 | libc::AT_FDCWD, |
1913 | p.as_ptr(), |
1914 | libc::AT_STATX_SYNC_AS_STAT, |
1915 | libc::STATX_BASIC_STATS | libc::STATX_BTIME, |
1916 | ) } { |
1917 | return ret; |
1918 | } |
1919 | } |
1920 | |
1921 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
1922 | cvt(unsafe { stat64(path:p.as_ptr(), &mut stat) })?; |
1923 | Ok(FileAttr::from_stat64(stat)) |
1924 | }) |
1925 | } |
1926 | |
1927 | pub fn lstat(p: &Path) -> io::Result<FileAttr> { |
1928 | run_path_with_cstr(path:p, &|p: &CStr| { |
1929 | cfg_has_statx! { |
1930 | if let Some(ret) = unsafe { try_statx( |
1931 | libc::AT_FDCWD, |
1932 | p.as_ptr(), |
1933 | libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, |
1934 | libc::STATX_BASIC_STATS | libc::STATX_BTIME, |
1935 | ) } { |
1936 | return ret; |
1937 | } |
1938 | } |
1939 | |
1940 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
1941 | cvt(unsafe { lstat64(path:p.as_ptr(), &mut stat) })?; |
1942 | Ok(FileAttr::from_stat64(stat)) |
1943 | }) |
1944 | } |
1945 | |
1946 | pub fn canonicalize(p: &Path) -> io::Result<PathBuf> { |
1947 | let r: *mut i8 = run_path_with_cstr(path:p, &|path: &CStr| unsafe { |
1948 | Ok(libc::realpath(pathname:path.as_ptr(), resolved:ptr::null_mut())) |
1949 | })?; |
1950 | if r.is_null() { |
1951 | return Err(io::Error::last_os_error()); |
1952 | } |
1953 | Ok(PathBuf::from(OsString::from_vec(unsafe { |
1954 | let buf: Vec = CStr::from_ptr(r).to_bytes().to_vec(); |
1955 | libc::free(r as *mut _); |
1956 | buf |
1957 | }))) |
1958 | } |
1959 | |
1960 | fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { |
1961 | use crate::fs::File; |
1962 | use crate::sys::fs::common::NOT_FILE_ERROR; |
1963 | |
1964 | let reader: File = File::open(path:from)?; |
1965 | let metadata: Metadata = reader.metadata()?; |
1966 | if !metadata.is_file() { |
1967 | return Err(NOT_FILE_ERROR); |
1968 | } |
1969 | Ok((reader, metadata)) |
1970 | } |
1971 | |
1972 | #[cfg (target_os = "espidf" )] |
1973 | fn open_to_and_set_permissions( |
1974 | to: &Path, |
1975 | _reader_metadata: &crate::fs::Metadata, |
1976 | ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { |
1977 | use crate::fs::OpenOptions; |
1978 | let writer = OpenOptions::new().open(to)?; |
1979 | let writer_metadata = writer.metadata()?; |
1980 | Ok((writer, writer_metadata)) |
1981 | } |
1982 | |
1983 | #[cfg (not(target_os = "espidf" ))] |
1984 | fn open_to_and_set_permissions( |
1985 | to: &Path, |
1986 | reader_metadata: &crate::fs::Metadata, |
1987 | ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { |
1988 | use crate::fs::OpenOptions; |
1989 | use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt}; |
1990 | |
1991 | let perm: Permissions = reader_metadata.permissions(); |
1992 | let writer: File = OpenOptions::new() |
1993 | // create the file with the correct mode right away |
1994 | .mode(perm.mode()) |
1995 | .write(true) |
1996 | .create(true) |
1997 | .truncate(true) |
1998 | .open(path:to)?; |
1999 | let writer_metadata: Metadata = writer.metadata()?; |
2000 | // fchmod is broken on vita |
2001 | #[cfg (not(target_os = "vita" ))] |
2002 | if writer_metadata.is_file() { |
2003 | // Set the correct file permissions, in case the file already existed. |
2004 | // Don't set the permissions on already existing non-files like |
2005 | // pipes/FIFOs or device nodes. |
2006 | writer.set_permissions(perm)?; |
2007 | } |
2008 | Ok((writer, writer_metadata)) |
2009 | } |
2010 | |
2011 | mod cfm { |
2012 | use crate::fs::{File, Metadata}; |
2013 | use crate::io::{BorrowedCursor, IoSlice, IoSliceMut, Read, Result, Write}; |
2014 | |
2015 | #[allow (dead_code)] |
2016 | pub struct CachedFileMetadata(pub File, pub Metadata); |
2017 | |
2018 | impl Read for CachedFileMetadata { |
2019 | fn read(&mut self, buf: &mut [u8]) -> Result<usize> { |
2020 | self.0.read(buf) |
2021 | } |
2022 | fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> { |
2023 | self.0.read_vectored(bufs) |
2024 | } |
2025 | fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> { |
2026 | self.0.read_buf(cursor) |
2027 | } |
2028 | #[inline ] |
2029 | fn is_read_vectored(&self) -> bool { |
2030 | self.0.is_read_vectored() |
2031 | } |
2032 | fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { |
2033 | self.0.read_to_end(buf) |
2034 | } |
2035 | fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { |
2036 | self.0.read_to_string(buf) |
2037 | } |
2038 | } |
2039 | impl Write for CachedFileMetadata { |
2040 | fn write(&mut self, buf: &[u8]) -> Result<usize> { |
2041 | self.0.write(buf) |
2042 | } |
2043 | fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> { |
2044 | self.0.write_vectored(bufs) |
2045 | } |
2046 | #[inline ] |
2047 | fn is_write_vectored(&self) -> bool { |
2048 | self.0.is_write_vectored() |
2049 | } |
2050 | #[inline ] |
2051 | fn flush(&mut self) -> Result<()> { |
2052 | self.0.flush() |
2053 | } |
2054 | } |
2055 | } |
2056 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
2057 | pub(crate) use cfm::CachedFileMetadata; |
2058 | |
2059 | #[cfg (not(target_vendor = "apple" ))] |
2060 | pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { |
2061 | let (reader: File, reader_metadata: Metadata) = open_from(from)?; |
2062 | let (writer: File, writer_metadata: Metadata) = open_to_and_set_permissions(to, &reader_metadata)?; |
2063 | |
2064 | io::copy( |
2065 | &mut cfm::CachedFileMetadata(reader, reader_metadata), |
2066 | &mut cfm::CachedFileMetadata(writer, writer_metadata), |
2067 | ) |
2068 | } |
2069 | |
2070 | #[cfg (target_vendor = "apple" )] |
2071 | pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { |
2072 | const COPYFILE_ALL: libc::copyfile_flags_t = libc::COPYFILE_METADATA | libc::COPYFILE_DATA; |
2073 | |
2074 | struct FreeOnDrop(libc::copyfile_state_t); |
2075 | impl Drop for FreeOnDrop { |
2076 | fn drop(&mut self) { |
2077 | // The code below ensures that `FreeOnDrop` is never a null pointer |
2078 | unsafe { |
2079 | // `copyfile_state_free` returns -1 if the `to` or `from` files |
2080 | // cannot be closed. However, this is not considered an error. |
2081 | libc::copyfile_state_free(self.0); |
2082 | } |
2083 | } |
2084 | } |
2085 | |
2086 | let (reader, reader_metadata) = open_from(from)?; |
2087 | |
2088 | let clonefile_result = run_path_with_cstr(to, &|to| { |
2089 | cvt(unsafe { libc::fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) }) |
2090 | }); |
2091 | match clonefile_result { |
2092 | Ok(_) => return Ok(reader_metadata.len()), |
2093 | Err(e) => match e.raw_os_error() { |
2094 | // `fclonefileat` will fail on non-APFS volumes, if the |
2095 | // destination already exists, or if the source and destination |
2096 | // are on different devices. In all these cases `fcopyfile` |
2097 | // should succeed. |
2098 | Some(libc::ENOTSUP) | Some(libc::EEXIST) | Some(libc::EXDEV) => (), |
2099 | _ => return Err(e), |
2100 | }, |
2101 | } |
2102 | |
2103 | // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. |
2104 | let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?; |
2105 | |
2106 | // We ensure that `FreeOnDrop` never contains a null pointer so it is |
2107 | // always safe to call `copyfile_state_free` |
2108 | let state = unsafe { |
2109 | let state = libc::copyfile_state_alloc(); |
2110 | if state.is_null() { |
2111 | return Err(crate::io::Error::last_os_error()); |
2112 | } |
2113 | FreeOnDrop(state) |
2114 | }; |
2115 | |
2116 | let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { libc::COPYFILE_DATA }; |
2117 | |
2118 | cvt(unsafe { libc::fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?; |
2119 | |
2120 | let mut bytes_copied: libc::off_t = 0; |
2121 | cvt(unsafe { |
2122 | libc::copyfile_state_get( |
2123 | state.0, |
2124 | libc::COPYFILE_STATE_COPIED as u32, |
2125 | (&raw mut bytes_copied) as *mut libc::c_void, |
2126 | ) |
2127 | })?; |
2128 | Ok(bytes_copied as u64) |
2129 | } |
2130 | |
2131 | pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { |
2132 | run_path_with_cstr(path, &|path: &CStr| { |
2133 | cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) }) |
2134 | .map(|_| ()) |
2135 | }) |
2136 | } |
2137 | |
2138 | pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> { |
2139 | cvt(unsafe { libc::fchown(fd, owner:uid as libc::uid_t, group:gid as libc::gid_t) })?; |
2140 | Ok(()) |
2141 | } |
2142 | |
2143 | #[cfg (not(target_os = "vxworks" ))] |
2144 | pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { |
2145 | run_path_with_cstr(path, &|path: &CStr| { |
2146 | cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) }) |
2147 | .map(|_| ()) |
2148 | }) |
2149 | } |
2150 | |
2151 | #[cfg (target_os = "vxworks" )] |
2152 | pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { |
2153 | let (_, _, _) = (path, uid, gid); |
2154 | Err(io::const_error!(io::ErrorKind::Unsupported, "lchown not supported by vxworks" )) |
2155 | } |
2156 | |
2157 | #[cfg (not(any(target_os = "fuchsia" , target_os = "vxworks" )))] |
2158 | pub fn chroot(dir: &Path) -> io::Result<()> { |
2159 | run_path_with_cstr(path:dir, &|dir: &CStr| cvt(unsafe { libc::chroot(dir.as_ptr()) }).map(|_| ())) |
2160 | } |
2161 | |
2162 | #[cfg (target_os = "vxworks" )] |
2163 | pub fn chroot(dir: &Path) -> io::Result<()> { |
2164 | let _ = dir; |
2165 | Err(io::const_error!(io::ErrorKind::Unsupported, "chroot not supported by vxworks" )) |
2166 | } |
2167 | |
2168 | pub use remove_dir_impl::remove_dir_all; |
2169 | |
2170 | // Fallback for REDOX, ESP-ID, Horizon, Vita, Vxworks and Miri |
2171 | #[cfg (any( |
2172 | target_os = "redox" , |
2173 | target_os = "espidf" , |
2174 | target_os = "horizon" , |
2175 | target_os = "vita" , |
2176 | target_os = "nto" , |
2177 | target_os = "vxworks" , |
2178 | miri |
2179 | ))] |
2180 | mod remove_dir_impl { |
2181 | pub use crate::sys::fs::common::remove_dir_all; |
2182 | } |
2183 | |
2184 | // Modern implementation using openat(), unlinkat() and fdopendir() |
2185 | #[cfg (not(any( |
2186 | target_os = "redox" , |
2187 | target_os = "espidf" , |
2188 | target_os = "horizon" , |
2189 | target_os = "vita" , |
2190 | target_os = "nto" , |
2191 | target_os = "vxworks" , |
2192 | miri |
2193 | )))] |
2194 | mod remove_dir_impl { |
2195 | #[cfg (not(all(target_os = "linux" , target_env = "gnu" )))] |
2196 | use libc::{fdopendir, openat, unlinkat}; |
2197 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
2198 | use libc::{fdopendir, openat64 as openat, unlinkat}; |
2199 | |
2200 | use super::{Dir, DirEntry, InnerReadDir, ReadDir, lstat}; |
2201 | use crate::ffi::CStr; |
2202 | use crate::io; |
2203 | use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; |
2204 | use crate::os::unix::prelude::{OwnedFd, RawFd}; |
2205 | use crate::path::{Path, PathBuf}; |
2206 | use crate::sys::common::small_c_string::run_path_with_cstr; |
2207 | use crate::sys::{cvt, cvt_r}; |
2208 | use crate::sys_common::ignore_notfound; |
2209 | |
2210 | pub fn openat_nofollow_dironly(parent_fd: Option<RawFd>, p: &CStr) -> io::Result<OwnedFd> { |
2211 | let fd = cvt_r(|| unsafe { |
2212 | openat( |
2213 | parent_fd.unwrap_or(libc::AT_FDCWD), |
2214 | p.as_ptr(), |
2215 | libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY, |
2216 | ) |
2217 | })?; |
2218 | Ok(unsafe { OwnedFd::from_raw_fd(fd) }) |
2219 | } |
2220 | |
2221 | fn fdreaddir(dir_fd: OwnedFd) -> io::Result<(ReadDir, RawFd)> { |
2222 | let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) }; |
2223 | if ptr.is_null() { |
2224 | return Err(io::Error::last_os_error()); |
2225 | } |
2226 | let dirp = Dir(ptr); |
2227 | // file descriptor is automatically closed by libc::closedir() now, so give up ownership |
2228 | let new_parent_fd = dir_fd.into_raw_fd(); |
2229 | // a valid root is not needed because we do not call any functions involving the full path |
2230 | // of the `DirEntry`s. |
2231 | let dummy_root = PathBuf::new(); |
2232 | let inner = InnerReadDir { dirp, root: dummy_root }; |
2233 | Ok((ReadDir::new(inner), new_parent_fd)) |
2234 | } |
2235 | |
2236 | #[cfg (any( |
2237 | target_os = "solaris" , |
2238 | target_os = "illumos" , |
2239 | target_os = "haiku" , |
2240 | target_os = "vxworks" , |
2241 | target_os = "aix" , |
2242 | ))] |
2243 | fn is_dir(_ent: &DirEntry) -> Option<bool> { |
2244 | None |
2245 | } |
2246 | |
2247 | #[cfg (not(any( |
2248 | target_os = "solaris" , |
2249 | target_os = "illumos" , |
2250 | target_os = "haiku" , |
2251 | target_os = "vxworks" , |
2252 | target_os = "aix" , |
2253 | )))] |
2254 | fn is_dir(ent: &DirEntry) -> Option<bool> { |
2255 | match ent.entry.d_type { |
2256 | libc::DT_UNKNOWN => None, |
2257 | libc::DT_DIR => Some(true), |
2258 | _ => Some(false), |
2259 | } |
2260 | } |
2261 | |
2262 | fn is_enoent(result: &io::Result<()>) -> bool { |
2263 | if let Err(err) = result |
2264 | && matches!(err.raw_os_error(), Some(libc::ENOENT)) |
2265 | { |
2266 | true |
2267 | } else { |
2268 | false |
2269 | } |
2270 | } |
2271 | |
2272 | fn remove_dir_all_recursive(parent_fd: Option<RawFd>, path: &CStr) -> io::Result<()> { |
2273 | // try opening as directory |
2274 | let fd = match openat_nofollow_dironly(parent_fd, &path) { |
2275 | Err(err) if matches!(err.raw_os_error(), Some(libc::ENOTDIR | libc::ELOOP)) => { |
2276 | // not a directory - don't traverse further |
2277 | // (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR) |
2278 | return match parent_fd { |
2279 | // unlink... |
2280 | Some(parent_fd) => { |
2281 | cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }).map(drop) |
2282 | } |
2283 | // ...unless this was supposed to be the deletion root directory |
2284 | None => Err(err), |
2285 | }; |
2286 | } |
2287 | result => result?, |
2288 | }; |
2289 | |
2290 | // open the directory passing ownership of the fd |
2291 | let (dir, fd) = fdreaddir(fd)?; |
2292 | for child in dir { |
2293 | let child = child?; |
2294 | let child_name = child.name_cstr(); |
2295 | // we need an inner try block, because if one of these |
2296 | // directories has already been deleted, then we need to |
2297 | // continue the loop, not return ok. |
2298 | let result: io::Result<()> = try { |
2299 | match is_dir(&child) { |
2300 | Some(true) => { |
2301 | remove_dir_all_recursive(Some(fd), child_name)?; |
2302 | } |
2303 | Some(false) => { |
2304 | cvt(unsafe { unlinkat(fd, child_name.as_ptr(), 0) })?; |
2305 | } |
2306 | None => { |
2307 | // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed |
2308 | // if the process has the appropriate privileges. This however can causing orphaned |
2309 | // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing |
2310 | // into it first instead of trying to unlink() it. |
2311 | remove_dir_all_recursive(Some(fd), child_name)?; |
2312 | } |
2313 | } |
2314 | }; |
2315 | if result.is_err() && !is_enoent(&result) { |
2316 | return result; |
2317 | } |
2318 | } |
2319 | |
2320 | // unlink the directory after removing its contents |
2321 | ignore_notfound(cvt(unsafe { |
2322 | unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), path.as_ptr(), libc::AT_REMOVEDIR) |
2323 | }))?; |
2324 | Ok(()) |
2325 | } |
2326 | |
2327 | fn remove_dir_all_modern(p: &Path) -> io::Result<()> { |
2328 | // We cannot just call remove_dir_all_recursive() here because that would not delete a passed |
2329 | // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse |
2330 | // into symlinks. |
2331 | let attr = lstat(p)?; |
2332 | if attr.file_type().is_symlink() { |
2333 | crate::fs::remove_file(p) |
2334 | } else { |
2335 | run_path_with_cstr(p, &|p| remove_dir_all_recursive(None, &p)) |
2336 | } |
2337 | } |
2338 | |
2339 | pub fn remove_dir_all(p: &Path) -> io::Result<()> { |
2340 | remove_dir_all_modern(p) |
2341 | } |
2342 | } |
2343 | |