]>
Commit | Line | Data |
---|---|---|
532ac7d7 XL |
1 | use crate::os::unix::prelude::*; |
2 | ||
dfeec247 | 3 | use crate::ffi::{CStr, CString, OsStr, OsString}; |
532ac7d7 | 4 | use crate::fmt; |
a2a8927a | 5 | use crate::io::{self, Error, IoSlice, IoSliceMut, ReadBuf, SeekFrom}; |
532ac7d7 | 6 | use crate::mem; |
94222f64 | 7 | use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; |
532ac7d7 XL |
8 | use crate::path::{Path, PathBuf}; |
9 | use crate::ptr; | |
10 | use crate::sync::Arc; | |
11 | use crate::sys::fd::FileDesc; | |
12 | use crate::sys::time::SystemTime; | |
13 | use crate::sys::{cvt, cvt_r}; | |
94222f64 | 14 | use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; |
532ac7d7 | 15 | |
136023e0 XL |
16 | #[cfg(any( |
17 | all(target_os = "linux", target_env = "gnu"), | |
18 | target_os = "macos", | |
19 | target_os = "ios", | |
20 | ))] | |
21 | use crate::sys::weak::syscall; | |
22 | #[cfg(target_os = "macos")] | |
23 | use crate::sys::weak::weak; | |
24 | ||
532ac7d7 | 25 | use libc::{c_int, mode_t}; |
7453a54e | 26 | |
136023e0 XL |
27 | #[cfg(any( |
28 | target_os = "macos", | |
29 | target_os = "ios", | |
30 | all(target_os = "linux", target_env = "gnu") | |
31 | ))] | |
32 | use libc::c_char; | |
94b46f34 XL |
33 | #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))] |
34 | use libc::dirfd; | |
dfeec247 XL |
35 | #[cfg(any(target_os = "linux", target_os = "emscripten"))] |
36 | use libc::fstatat64; | |
5099ac24 FG |
37 | #[cfg(any( |
38 | target_os = "android", | |
39 | target_os = "solaris", | |
40 | target_os = "fuchsia", | |
41 | target_os = "redox", | |
42 | target_os = "illumos" | |
43 | ))] | |
44 | use libc::readdir as readdir64; | |
45 | #[cfg(target_os = "linux")] | |
46 | use libc::readdir64; | |
47 | #[cfg(any(target_os = "emscripten", target_os = "l4re"))] | |
48 | use libc::readdir64_r; | |
dfeec247 | 49 | #[cfg(not(any( |
5099ac24 | 50 | target_os = "android", |
dfeec247 XL |
51 | target_os = "linux", |
52 | target_os = "emscripten", | |
53 | target_os = "solaris", | |
ba9703b0 | 54 | target_os = "illumos", |
dfeec247 XL |
55 | target_os = "l4re", |
56 | target_os = "fuchsia", | |
57 | target_os = "redox" | |
58 | )))] | |
59 | use libc::readdir_r as readdir64_r; | |
7453a54e | 60 | #[cfg(target_os = "android")] |
dfeec247 | 61 | use libc::{ |
a2a8927a XL |
62 | dirent as dirent64, fstat as fstat64, fstatat as fstatat64, ftruncate64, lseek64, |
63 | lstat as lstat64, off64_t, open as open64, stat as stat64, | |
dfeec247 XL |
64 | }; |
65 | #[cfg(not(any( | |
66 | target_os = "linux", | |
67 | target_os = "emscripten", | |
68 | target_os = "l4re", | |
69 | target_os = "android" | |
70 | )))] | |
71 | use libc::{ | |
72 | dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, | |
73 | lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, | |
74 | }; | |
75 | #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "l4re"))] | |
5099ac24 | 76 | use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; |
85aaf69f | 77 | |
3c0e092e | 78 | pub use crate::sys_common::fs::try_exists; |
532ac7d7 | 79 | |
85aaf69f SL |
80 | pub struct File(FileDesc); |
81 | ||
dfeec247 XL |
82 | // FIXME: This should be available on Linux with all `target_env`. |
83 | // But currently only glibc exposes `statx` fn and structs. | |
84 | // We don't want to import unverified raw C structs here directly. | |
85 | // https://github.com/rust-lang/rust/pull/67774 | |
e74abb32 XL |
86 | macro_rules! cfg_has_statx { |
87 | ({ $($then_tt:tt)* } else { $($else_tt:tt)* }) => { | |
88 | cfg_if::cfg_if! { | |
dfeec247 | 89 | if #[cfg(all(target_os = "linux", target_env = "gnu"))] { |
e74abb32 XL |
90 | $($then_tt)* |
91 | } else { | |
92 | $($else_tt)* | |
93 | } | |
94 | } | |
95 | }; | |
96 | ($($block_inner:tt)*) => { | |
dfeec247 | 97 | #[cfg(all(target_os = "linux", target_env = "gnu"))] |
e74abb32 XL |
98 | { |
99 | $($block_inner)* | |
100 | } | |
101 | }; | |
85aaf69f SL |
102 | } |
103 | ||
e74abb32 XL |
104 | cfg_has_statx! {{ |
105 | #[derive(Clone)] | |
106 | pub struct FileAttr { | |
107 | stat: stat64, | |
108 | statx_extra_fields: Option<StatxExtraFields>, | |
109 | } | |
110 | ||
111 | #[derive(Clone)] | |
112 | struct StatxExtraFields { | |
113 | // This is needed to check if btime is supported by the filesystem. | |
114 | stx_mask: u32, | |
115 | stx_btime: libc::statx_timestamp, | |
04454e1e FG |
116 | // With statx, we can overcome 32-bit `time_t` too. |
117 | #[cfg(target_pointer_width = "32")] | |
118 | stx_atime: libc::statx_timestamp, | |
119 | #[cfg(target_pointer_width = "32")] | |
120 | stx_ctime: libc::statx_timestamp, | |
121 | #[cfg(target_pointer_width = "32")] | |
122 | stx_mtime: libc::statx_timestamp, | |
123 | ||
e74abb32 XL |
124 | } |
125 | ||
04454e1e FG |
126 | // We prefer `statx` on Linux if available, which contains file creation time, |
127 | // as well as 64-bit timestamps of all kinds. | |
128 | // Default `stat64` contains no creation time and may have 32-bit `time_t`. | |
e74abb32 XL |
129 | unsafe fn try_statx( |
130 | fd: c_int, | |
136023e0 | 131 | path: *const c_char, |
e74abb32 XL |
132 | flags: i32, |
133 | mask: u32, | |
134 | ) -> Option<io::Result<FileAttr>> { | |
135 | use crate::sync::atomic::{AtomicU8, Ordering}; | |
136 | ||
137 | // Linux kernel prior to 4.11 or glibc prior to glibc 2.28 don't support `statx` | |
138 | // We store the availability in global to avoid unnecessary syscalls. | |
139 | // 0: Unknown | |
140 | // 1: Not available | |
141 | // 2: Available | |
142 | static STATX_STATE: AtomicU8 = AtomicU8::new(0); | |
143 | syscall! { | |
144 | fn statx( | |
145 | fd: c_int, | |
136023e0 | 146 | pathname: *const c_char, |
e74abb32 XL |
147 | flags: c_int, |
148 | mask: libc::c_uint, | |
149 | statxbuf: *mut libc::statx | |
150 | ) -> c_int | |
151 | } | |
152 | ||
153 | match STATX_STATE.load(Ordering::Relaxed) { | |
154 | 0 => { | |
17df50a5 | 155 | // It is a trick to call `statx` with null pointers to check if the syscall |
e74abb32 XL |
156 | // is available. According to the manual, it is expected to fail with EFAULT. |
157 | // We do this mainly for performance, since it is nearly hundreds times | |
60c5eb7d | 158 | // faster than a normal successful call. |
e74abb32 XL |
159 | let err = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut())) |
160 | .err() | |
161 | .and_then(|e| e.raw_os_error()); | |
162 | // We don't check `err == Some(libc::ENOSYS)` because the syscall may be limited | |
163 | // and returns `EPERM`. Listing all possible errors seems not a good idea. | |
164 | // See: https://github.com/rust-lang/rust/issues/65662 | |
165 | if err != Some(libc::EFAULT) { | |
166 | STATX_STATE.store(1, Ordering::Relaxed); | |
167 | return None; | |
168 | } | |
169 | STATX_STATE.store(2, Ordering::Relaxed); | |
170 | } | |
171 | 1 => return None, | |
172 | _ => {} | |
173 | } | |
174 | ||
175 | let mut buf: libc::statx = mem::zeroed(); | |
176 | if let Err(err) = cvt(statx(fd, path, flags, mask, &mut buf)) { | |
177 | return Some(Err(err)); | |
178 | } | |
179 | ||
180 | // We cannot fill `stat64` exhaustively because of private padding fields. | |
181 | let mut stat: stat64 = mem::zeroed(); | |
182 | // `c_ulong` on gnu-mips, `dev_t` otherwise | |
183 | stat.st_dev = libc::makedev(buf.stx_dev_major, buf.stx_dev_minor) as _; | |
184 | stat.st_ino = buf.stx_ino as libc::ino64_t; | |
185 | stat.st_nlink = buf.stx_nlink as libc::nlink_t; | |
186 | stat.st_mode = buf.stx_mode as libc::mode_t; | |
187 | stat.st_uid = buf.stx_uid as libc::uid_t; | |
188 | stat.st_gid = buf.stx_gid as libc::gid_t; | |
189 | stat.st_rdev = libc::makedev(buf.stx_rdev_major, buf.stx_rdev_minor) as _; | |
190 | stat.st_size = buf.stx_size as off64_t; | |
191 | stat.st_blksize = buf.stx_blksize as libc::blksize_t; | |
192 | stat.st_blocks = buf.stx_blocks as libc::blkcnt64_t; | |
193 | stat.st_atime = buf.stx_atime.tv_sec as libc::time_t; | |
194 | // `i64` on gnu-x86_64-x32, `c_ulong` otherwise. | |
195 | stat.st_atime_nsec = buf.stx_atime.tv_nsec as _; | |
196 | stat.st_mtime = buf.stx_mtime.tv_sec as libc::time_t; | |
197 | stat.st_mtime_nsec = buf.stx_mtime.tv_nsec as _; | |
198 | stat.st_ctime = buf.stx_ctime.tv_sec as libc::time_t; | |
199 | stat.st_ctime_nsec = buf.stx_ctime.tv_nsec as _; | |
200 | ||
201 | let extra = StatxExtraFields { | |
202 | stx_mask: buf.stx_mask, | |
203 | stx_btime: buf.stx_btime, | |
04454e1e FG |
204 | // Store full times to avoid 32-bit `time_t` truncation. |
205 | #[cfg(target_pointer_width = "32")] | |
206 | stx_atime: buf.stx_atime, | |
207 | #[cfg(target_pointer_width = "32")] | |
208 | stx_ctime: buf.stx_ctime, | |
209 | #[cfg(target_pointer_width = "32")] | |
210 | stx_mtime: buf.stx_mtime, | |
e74abb32 XL |
211 | }; |
212 | ||
213 | Some(Ok(FileAttr { stat, statx_extra_fields: Some(extra) })) | |
214 | } | |
215 | ||
216 | } else { | |
217 | #[derive(Clone)] | |
218 | pub struct FileAttr { | |
219 | stat: stat64, | |
220 | } | |
221 | }} | |
222 | ||
94b46f34 XL |
223 | // all DirEntry's will have a reference to this struct |
224 | struct InnerReadDir { | |
c34b1796 | 225 | dirp: Dir, |
94b46f34 | 226 | root: PathBuf, |
85aaf69f SL |
227 | } |
228 | ||
8faf50e0 XL |
229 | pub struct ReadDir { |
230 | inner: Arc<InnerReadDir>, | |
29967ef6 | 231 | #[cfg(not(any( |
5099ac24 FG |
232 | target_os = "android", |
233 | target_os = "linux", | |
29967ef6 XL |
234 | target_os = "solaris", |
235 | target_os = "illumos", | |
236 | target_os = "fuchsia", | |
237 | target_os = "redox", | |
238 | )))] | |
8faf50e0 XL |
239 | end_of_stream: bool, |
240 | } | |
94b46f34 | 241 | |
c34b1796 AL |
242 | struct Dir(*mut libc::DIR); |
243 | ||
244 | unsafe impl Send for Dir {} | |
245 | unsafe impl Sync for Dir {} | |
246 | ||
5e7ed085 FG |
247 | #[cfg(any( |
248 | target_os = "android", | |
249 | target_os = "linux", | |
250 | target_os = "solaris", | |
251 | target_os = "illumos", | |
252 | target_os = "fuchsia", | |
253 | target_os = "redox" | |
254 | ))] | |
85aaf69f | 255 | pub struct DirEntry { |
29967ef6 | 256 | dir: Arc<InnerReadDir>, |
5e7ed085 | 257 | entry: dirent64_min, |
5099ac24 FG |
258 | // We need to store an owned copy of the entry name on platforms that use |
259 | // readdir() (not readdir_r()), because a) struct dirent may use a flexible | |
260 | // array to store the name, b) it lives only until the next readdir() call. | |
3c0e092e | 261 | name: CString, |
85aaf69f SL |
262 | } |
263 | ||
5e7ed085 FG |
264 | // Define a minimal subset of fields we need from `dirent64`, especially since |
265 | // we're not using the immediate `d_name` on these targets. Keeping this as an | |
266 | // `entry` field in `DirEntry` helps reduce the `cfg` boilerplate elsewhere. | |
267 | #[cfg(any( | |
268 | target_os = "android", | |
269 | target_os = "linux", | |
270 | target_os = "solaris", | |
271 | target_os = "illumos", | |
272 | target_os = "fuchsia", | |
273 | target_os = "redox" | |
274 | ))] | |
275 | struct dirent64_min { | |
276 | d_ino: u64, | |
277 | #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] | |
278 | d_type: u8, | |
279 | } | |
280 | ||
281 | #[cfg(not(any( | |
282 | target_os = "android", | |
283 | target_os = "linux", | |
284 | target_os = "solaris", | |
285 | target_os = "illumos", | |
286 | target_os = "fuchsia", | |
287 | target_os = "redox" | |
288 | )))] | |
289 | pub struct DirEntry { | |
290 | dir: Arc<InnerReadDir>, | |
291 | // The full entry includes a fixed-length `d_name`. | |
292 | entry: dirent64, | |
293 | } | |
294 | ||
32a655c1 | 295 | #[derive(Clone, Debug)] |
85aaf69f | 296 | pub struct OpenOptions { |
7453a54e | 297 | // generic |
85aaf69f SL |
298 | read: bool, |
299 | write: bool, | |
7453a54e SL |
300 | append: bool, |
301 | truncate: bool, | |
302 | create: bool, | |
303 | create_new: bool, | |
304 | // system-specific | |
305 | custom_flags: i32, | |
85aaf69f SL |
306 | mode: mode_t, |
307 | } | |
308 | ||
309 | #[derive(Clone, PartialEq, Eq, Debug)] | |
dfeec247 XL |
310 | pub struct FilePermissions { |
311 | mode: mode_t, | |
312 | } | |
85aaf69f | 313 | |
5bcae85e | 314 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] |
dfeec247 XL |
315 | pub struct FileType { |
316 | mode: mode_t, | |
317 | } | |
d9579d0f | 318 | |
32a655c1 | 319 | #[derive(Debug)] |
dfeec247 XL |
320 | pub struct DirBuilder { |
321 | mode: mode_t, | |
322 | } | |
d9579d0f | 323 | |
e74abb32 XL |
324 | cfg_has_statx! {{ |
325 | impl FileAttr { | |
326 | fn from_stat64(stat: stat64) -> Self { | |
327 | Self { stat, statx_extra_fields: None } | |
328 | } | |
04454e1e FG |
329 | |
330 | #[cfg(target_pointer_width = "32")] | |
331 | pub fn stx_mtime(&self) -> Option<&libc::statx_timestamp> { | |
332 | if let Some(ext) = &self.statx_extra_fields { | |
333 | if (ext.stx_mask & libc::STATX_MTIME) != 0 { | |
334 | return Some(&ext.stx_mtime); | |
335 | } | |
336 | } | |
337 | None | |
338 | } | |
339 | ||
340 | #[cfg(target_pointer_width = "32")] | |
341 | pub fn stx_atime(&self) -> Option<&libc::statx_timestamp> { | |
342 | if let Some(ext) = &self.statx_extra_fields { | |
343 | if (ext.stx_mask & libc::STATX_ATIME) != 0 { | |
344 | return Some(&ext.stx_atime); | |
345 | } | |
346 | } | |
347 | None | |
348 | } | |
349 | ||
350 | #[cfg(target_pointer_width = "32")] | |
351 | pub fn stx_ctime(&self) -> Option<&libc::statx_timestamp> { | |
352 | if let Some(ext) = &self.statx_extra_fields { | |
353 | if (ext.stx_mask & libc::STATX_CTIME) != 0 { | |
354 | return Some(&ext.stx_ctime); | |
355 | } | |
356 | } | |
357 | None | |
358 | } | |
e74abb32 XL |
359 | } |
360 | } else { | |
361 | impl FileAttr { | |
362 | fn from_stat64(stat: stat64) -> Self { | |
363 | Self { stat } | |
364 | } | |
365 | } | |
366 | }} | |
367 | ||
85aaf69f | 368 | impl FileAttr { |
dfeec247 XL |
369 | pub fn size(&self) -> u64 { |
370 | self.stat.st_size as u64 | |
371 | } | |
85aaf69f | 372 | pub fn perm(&self) -> FilePermissions { |
ea8adc8c | 373 | FilePermissions { mode: (self.stat.st_mode as mode_t) } |
85aaf69f SL |
374 | } |
375 | ||
d9579d0f AL |
376 | pub fn file_type(&self) -> FileType { |
377 | FileType { mode: self.stat.st_mode as mode_t } | |
85aaf69f | 378 | } |
d9579d0f | 379 | } |
85aaf69f | 380 | |
7453a54e SL |
381 | #[cfg(target_os = "netbsd")] |
382 | impl FileAttr { | |
383 | pub fn modified(&self) -> io::Result<SystemTime> { | |
04454e1e | 384 | Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtimensec as i64)) |
7453a54e SL |
385 | } |
386 | ||
387 | pub fn accessed(&self) -> io::Result<SystemTime> { | |
04454e1e | 388 | Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atimensec as i64)) |
7453a54e SL |
389 | } |
390 | ||
391 | pub fn created(&self) -> io::Result<SystemTime> { | |
04454e1e | 392 | Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtimensec as i64)) |
7453a54e | 393 | } |
d9579d0f AL |
394 | } |
395 | ||
a7813a04 | 396 | #[cfg(not(target_os = "netbsd"))] |
7453a54e | 397 | impl FileAttr { |
923072b8 | 398 | #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))] |
7453a54e | 399 | pub fn modified(&self) -> io::Result<SystemTime> { |
04454e1e FG |
400 | #[cfg(target_pointer_width = "32")] |
401 | cfg_has_statx! { | |
402 | if let Some(mtime) = self.stx_mtime() { | |
403 | return Ok(SystemTime::new(mtime.tv_sec, mtime.tv_nsec as i64)); | |
404 | } | |
405 | } | |
406 | ||
407 | Ok(SystemTime::new(self.stat.st_mtime as i64, self.stat.st_mtime_nsec as i64)) | |
7453a54e SL |
408 | } |
409 | ||
94222f64 | 410 | #[cfg(any(target_os = "vxworks", target_os = "espidf"))] |
29967ef6 | 411 | pub fn modified(&self) -> io::Result<SystemTime> { |
04454e1e | 412 | Ok(SystemTime::new(self.stat.st_mtime as i64, 0)) |
29967ef6 XL |
413 | } |
414 | ||
923072b8 FG |
415 | #[cfg(target_os = "horizon")] |
416 | pub fn modified(&self) -> io::Result<SystemTime> { | |
417 | Ok(SystemTime::from(self.stat.st_mtim)) | |
418 | } | |
419 | ||
420 | #[cfg(not(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon")))] | |
7453a54e | 421 | pub fn accessed(&self) -> io::Result<SystemTime> { |
04454e1e FG |
422 | #[cfg(target_pointer_width = "32")] |
423 | cfg_has_statx! { | |
424 | if let Some(atime) = self.stx_atime() { | |
425 | return Ok(SystemTime::new(atime.tv_sec, atime.tv_nsec as i64)); | |
426 | } | |
427 | } | |
428 | ||
429 | Ok(SystemTime::new(self.stat.st_atime as i64, self.stat.st_atime_nsec as i64)) | |
7453a54e SL |
430 | } |
431 | ||
94222f64 | 432 | #[cfg(any(target_os = "vxworks", target_os = "espidf"))] |
29967ef6 | 433 | pub fn accessed(&self) -> io::Result<SystemTime> { |
04454e1e | 434 | Ok(SystemTime::new(self.stat.st_atime as i64, 0)) |
29967ef6 XL |
435 | } |
436 | ||
923072b8 FG |
437 | #[cfg(target_os = "horizon")] |
438 | pub fn accessed(&self) -> io::Result<SystemTime> { | |
439 | Ok(SystemTime::from(self.stat.st_atim)) | |
440 | } | |
441 | ||
dfeec247 XL |
442 | #[cfg(any( |
443 | target_os = "freebsd", | |
444 | target_os = "openbsd", | |
445 | target_os = "macos", | |
446 | target_os = "ios" | |
447 | ))] | |
7453a54e | 448 | pub fn created(&self) -> io::Result<SystemTime> { |
04454e1e | 449 | Ok(SystemTime::new(self.stat.st_birthtime as i64, self.stat.st_birthtime_nsec as i64)) |
7453a54e SL |
450 | } |
451 | ||
dfeec247 XL |
452 | #[cfg(not(any( |
453 | target_os = "freebsd", | |
454 | target_os = "openbsd", | |
455 | target_os = "macos", | |
456 | target_os = "ios" | |
457 | )))] | |
7453a54e | 458 | pub fn created(&self) -> io::Result<SystemTime> { |
e74abb32 XL |
459 | cfg_has_statx! { |
460 | if let Some(ext) = &self.statx_extra_fields { | |
461 | return if (ext.stx_mask & libc::STATX_BTIME) != 0 { | |
04454e1e | 462 | Ok(SystemTime::new(ext.stx_btime.tv_sec, ext.stx_btime.tv_nsec as i64)) |
e74abb32 | 463 | } else { |
5099ac24 | 464 | Err(io::const_io_error!( |
136023e0 | 465 | io::ErrorKind::Uncategorized, |
5099ac24 | 466 | "creation time is not available for the filesystem", |
e74abb32 XL |
467 | )) |
468 | }; | |
469 | } | |
470 | } | |
471 | ||
5099ac24 | 472 | Err(io::const_io_error!( |
cdc7bbd5 | 473 | io::ErrorKind::Unsupported, |
5099ac24 | 474 | "creation time is not available on this platform \ |
dfeec247 XL |
475 | currently", |
476 | )) | |
7453a54e SL |
477 | } |
478 | } | |
479 | ||
480 | impl AsInner<stat64> for FileAttr { | |
dfeec247 XL |
481 | fn as_inner(&self) -> &stat64 { |
482 | &self.stat | |
483 | } | |
85aaf69f SL |
484 | } |
485 | ||
486 | impl FilePermissions { | |
3b2f2976 XL |
487 | pub fn readonly(&self) -> bool { |
488 | // check if any class (owner, group, others) has write permission | |
489 | self.mode & 0o222 == 0 | |
490 | } | |
491 | ||
85aaf69f SL |
492 | pub fn set_readonly(&mut self, readonly: bool) { |
493 | if readonly { | |
3b2f2976 | 494 | // remove write permission for all classes; equivalent to `chmod a-w <file>` |
85aaf69f SL |
495 | self.mode &= !0o222; |
496 | } else { | |
3b2f2976 | 497 | // add write permission for all classes; equivalent to `chmod a+w <file>` |
85aaf69f SL |
498 | self.mode |= 0o222; |
499 | } | |
500 | } | |
dfeec247 XL |
501 | pub fn mode(&self) -> u32 { |
502 | self.mode as u32 | |
503 | } | |
d9579d0f AL |
504 | } |
505 | ||
506 | impl FileType { | |
dfeec247 XL |
507 | pub fn is_dir(&self) -> bool { |
508 | self.is(libc::S_IFDIR) | |
509 | } | |
510 | pub fn is_file(&self) -> bool { | |
511 | self.is(libc::S_IFREG) | |
512 | } | |
513 | pub fn is_symlink(&self) -> bool { | |
514 | self.is(libc::S_IFLNK) | |
515 | } | |
d9579d0f | 516 | |
dfeec247 XL |
517 | pub fn is(&self, mode: mode_t) -> bool { |
518 | self.mode & libc::S_IFMT == mode | |
519 | } | |
85aaf69f SL |
520 | } |
521 | ||
7453a54e SL |
522 | impl FromInner<u32> for FilePermissions { |
523 | fn from_inner(mode: u32) -> FilePermissions { | |
85aaf69f SL |
524 | FilePermissions { mode: mode as mode_t } |
525 | } | |
526 | } | |
527 | ||
c30ab7b3 | 528 | impl fmt::Debug for ReadDir { |
532ac7d7 | 529 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
c30ab7b3 SL |
530 | // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. |
531 | // Thus the result will be e g 'ReadDir("/home")' | |
8faf50e0 | 532 | fmt::Debug::fmt(&*self.inner.root, f) |
c30ab7b3 SL |
533 | } |
534 | } | |
535 | ||
85aaf69f SL |
536 | impl Iterator for ReadDir { |
537 | type Item = io::Result<DirEntry>; | |
538 | ||
ba9703b0 | 539 | #[cfg(any( |
5099ac24 FG |
540 | target_os = "android", |
541 | target_os = "linux", | |
ba9703b0 XL |
542 | target_os = "solaris", |
543 | target_os = "fuchsia", | |
544 | target_os = "redox", | |
545 | target_os = "illumos" | |
546 | ))] | |
7453a54e SL |
547 | fn next(&mut self) -> Option<io::Result<DirEntry>> { |
548 | unsafe { | |
549 | loop { | |
5099ac24 FG |
550 | // As of POSIX.1-2017, readdir() is not required to be thread safe; only |
551 | // readdir_r() is. However, readdir_r() cannot correctly handle platforms | |
552 | // with unlimited or variable NAME_MAX. Many modern platforms guarantee | |
553 | // thread safety for readdir() as long an individual DIR* is not accessed | |
554 | // concurrently, which is sufficient for Rust. | |
5bcae85e | 555 | super::os::set_errno(0); |
5099ac24 | 556 | let entry_ptr = readdir64(self.inner.dirp.0); |
7453a54e | 557 | if entry_ptr.is_null() { |
17df50a5 | 558 | // null can mean either the end is reached or an error occurred. |
5bcae85e SL |
559 | // So we had to clear errno beforehand to check for an error now. |
560 | return match super::os::errno() { | |
561 | 0 => None, | |
562 | e => Some(Err(Error::from_raw_os_error(e))), | |
dfeec247 | 563 | }; |
7453a54e SL |
564 | } |
565 | ||
5099ac24 FG |
566 | // Only d_reclen bytes of *entry_ptr are valid, so we can't just copy the |
567 | // whole thing (#93384). Instead, copy everything except the name. | |
5e7ed085 FG |
568 | let mut copy: dirent64 = mem::zeroed(); |
569 | // Can't dereference entry_ptr, so use the local entry to get | |
570 | // offsetof(struct dirent, d_name) | |
571 | let copy_bytes = &mut copy as *mut _ as *mut u8; | |
572 | let copy_name = &mut copy.d_name as *mut _ as *mut u8; | |
573 | let name_offset = copy_name.offset_from(copy_bytes) as usize; | |
5099ac24 | 574 | let entry_bytes = entry_ptr as *const u8; |
5e7ed085 FG |
575 | let entry_name = entry_bytes.add(name_offset); |
576 | ptr::copy_nonoverlapping(entry_bytes, copy_bytes, name_offset); | |
577 | ||
578 | let entry = dirent64_min { | |
579 | d_ino: copy.d_ino as u64, | |
580 | #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] | |
581 | d_type: copy.d_type as u8, | |
582 | }; | |
5099ac24 | 583 | |
7453a54e | 584 | let ret = DirEntry { |
5099ac24 | 585 | entry, |
3c0e092e | 586 | // d_name is guaranteed to be null-terminated. |
5099ac24 | 587 | name: CStr::from_ptr(entry_name as *const _).to_owned(), |
29967ef6 | 588 | dir: Arc::clone(&self.inner), |
7453a54e SL |
589 | }; |
590 | if ret.name_bytes() != b"." && ret.name_bytes() != b".." { | |
dfeec247 | 591 | return Some(Ok(ret)); |
7453a54e SL |
592 | } |
593 | } | |
594 | } | |
595 | } | |
596 | ||
ba9703b0 | 597 | #[cfg(not(any( |
5099ac24 FG |
598 | target_os = "android", |
599 | target_os = "linux", | |
ba9703b0 XL |
600 | target_os = "solaris", |
601 | target_os = "fuchsia", | |
602 | target_os = "redox", | |
603 | target_os = "illumos" | |
604 | )))] | |
85aaf69f | 605 | fn next(&mut self) -> Option<io::Result<DirEntry>> { |
8faf50e0 XL |
606 | if self.end_of_stream { |
607 | return None; | |
608 | } | |
609 | ||
9cc50fc6 | 610 | unsafe { |
29967ef6 | 611 | let mut ret = DirEntry { entry: mem::zeroed(), dir: Arc::clone(&self.inner) }; |
9cc50fc6 SL |
612 | let mut entry_ptr = ptr::null_mut(); |
613 | loop { | |
94222f64 XL |
614 | let err = readdir64_r(self.inner.dirp.0, &mut ret.entry, &mut entry_ptr); |
615 | if err != 0 { | |
8faf50e0 XL |
616 | if entry_ptr.is_null() { |
617 | // We encountered an error (which will be returned in this iteration), but | |
618 | // we also reached the end of the directory stream. The `end_of_stream` | |
619 | // flag is enabled to make sure that we return `None` in the next iteration | |
620 | // (instead of looping forever) | |
621 | self.end_of_stream = true; | |
622 | } | |
94222f64 | 623 | return Some(Err(Error::from_raw_os_error(err))); |
9cc50fc6 SL |
624 | } |
625 | if entry_ptr.is_null() { | |
dfeec247 | 626 | return None; |
9cc50fc6 SL |
627 | } |
628 | if ret.name_bytes() != b"." && ret.name_bytes() != b".." { | |
dfeec247 | 629 | return Some(Ok(ret)); |
9cc50fc6 | 630 | } |
85aaf69f SL |
631 | } |
632 | } | |
633 | } | |
634 | } | |
635 | ||
c34b1796 | 636 | impl Drop for Dir { |
85aaf69f | 637 | fn drop(&mut self) { |
c34b1796 | 638 | let r = unsafe { libc::closedir(self.0) }; |
85aaf69f SL |
639 | debug_assert_eq!(r, 0); |
640 | } | |
641 | } | |
642 | ||
643 | impl DirEntry { | |
644 | pub fn path(&self) -> PathBuf { | |
5099ac24 | 645 | self.dir.root.join(self.file_name_os_str()) |
85aaf69f SL |
646 | } |
647 | ||
d9579d0f | 648 | pub fn file_name(&self) -> OsString { |
5099ac24 | 649 | self.file_name_os_str().to_os_string() |
d9579d0f AL |
650 | } |
651 | ||
94b46f34 XL |
652 | #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "android"))] |
653 | pub fn metadata(&self) -> io::Result<FileAttr> { | |
29967ef6 | 654 | let fd = cvt(unsafe { dirfd(self.dir.dirp.0) })?; |
5099ac24 | 655 | let name = self.name_cstr().as_ptr(); |
e74abb32 XL |
656 | |
657 | cfg_has_statx! { | |
658 | if let Some(ret) = unsafe { try_statx( | |
659 | fd, | |
660 | name, | |
661 | libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, | |
662 | libc::STATX_ALL, | |
663 | ) } { | |
664 | return ret; | |
665 | } | |
666 | } | |
667 | ||
94b46f34 | 668 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
dfeec247 | 669 | cvt(unsafe { fstatat64(fd, name, &mut stat, libc::AT_SYMLINK_NOFOLLOW) })?; |
e74abb32 | 670 | Ok(FileAttr::from_stat64(stat)) |
94b46f34 XL |
671 | } |
672 | ||
673 | #[cfg(not(any(target_os = "linux", target_os = "emscripten", target_os = "android")))] | |
d9579d0f AL |
674 | pub fn metadata(&self) -> io::Result<FileAttr> { |
675 | lstat(&self.path()) | |
676 | } | |
677 | ||
29967ef6 XL |
678 | #[cfg(any( |
679 | target_os = "solaris", | |
680 | target_os = "illumos", | |
681 | target_os = "haiku", | |
682 | target_os = "vxworks" | |
683 | ))] | |
9e0c209e | 684 | pub fn file_type(&self) -> io::Result<FileType> { |
5099ac24 | 685 | self.metadata().map(|m| m.file_type()) |
9e0c209e SL |
686 | } |
687 | ||
29967ef6 XL |
688 | #[cfg(not(any( |
689 | target_os = "solaris", | |
690 | target_os = "illumos", | |
691 | target_os = "haiku", | |
692 | target_os = "vxworks" | |
693 | )))] | |
d9579d0f | 694 | pub fn file_type(&self) -> io::Result<FileType> { |
9cc50fc6 SL |
695 | match self.entry.d_type { |
696 | libc::DT_CHR => Ok(FileType { mode: libc::S_IFCHR }), | |
697 | libc::DT_FIFO => Ok(FileType { mode: libc::S_IFIFO }), | |
698 | libc::DT_LNK => Ok(FileType { mode: libc::S_IFLNK }), | |
699 | libc::DT_REG => Ok(FileType { mode: libc::S_IFREG }), | |
700 | libc::DT_SOCK => Ok(FileType { mode: libc::S_IFSOCK }), | |
701 | libc::DT_DIR => Ok(FileType { mode: libc::S_IFDIR }), | |
702 | libc::DT_BLK => Ok(FileType { mode: libc::S_IFBLK }), | |
5099ac24 | 703 | _ => self.metadata().map(|m| m.file_type()), |
d9579d0f AL |
704 | } |
705 | } | |
706 | ||
dfeec247 XL |
707 | #[cfg(any( |
708 | target_os = "macos", | |
709 | target_os = "ios", | |
710 | target_os = "linux", | |
711 | target_os = "emscripten", | |
712 | target_os = "android", | |
713 | target_os = "solaris", | |
ba9703b0 | 714 | target_os = "illumos", |
dfeec247 XL |
715 | target_os = "haiku", |
716 | target_os = "l4re", | |
717 | target_os = "fuchsia", | |
29967ef6 | 718 | target_os = "redox", |
94222f64 | 719 | target_os = "vxworks", |
923072b8 FG |
720 | target_os = "espidf", |
721 | target_os = "horizon" | |
dfeec247 | 722 | ))] |
7453a54e SL |
723 | pub fn ino(&self) -> u64 { |
724 | self.entry.d_ino as u64 | |
9cc50fc6 SL |
725 | } |
726 | ||
dfeec247 XL |
727 | #[cfg(any( |
728 | target_os = "freebsd", | |
729 | target_os = "openbsd", | |
730 | target_os = "netbsd", | |
731 | target_os = "dragonfly" | |
732 | ))] | |
7453a54e SL |
733 | pub fn ino(&self) -> u64 { |
734 | self.entry.d_fileno as u64 | |
d9579d0f AL |
735 | } |
736 | ||
dfeec247 XL |
737 | #[cfg(any( |
738 | target_os = "macos", | |
739 | target_os = "ios", | |
740 | target_os = "netbsd", | |
741 | target_os = "openbsd", | |
742 | target_os = "freebsd", | |
743 | target_os = "dragonfly" | |
744 | ))] | |
9cc50fc6 | 745 | fn name_bytes(&self) -> &[u8] { |
532ac7d7 | 746 | use crate::slice; |
85aaf69f | 747 | unsafe { |
dfeec247 XL |
748 | slice::from_raw_parts( |
749 | self.entry.d_name.as_ptr() as *const u8, | |
750 | self.entry.d_namlen as usize, | |
751 | ) | |
85aaf69f SL |
752 | } |
753 | } | |
5099ac24 FG |
754 | #[cfg(not(any( |
755 | target_os = "macos", | |
756 | target_os = "ios", | |
757 | target_os = "netbsd", | |
758 | target_os = "openbsd", | |
759 | target_os = "freebsd", | |
760 | target_os = "dragonfly" | |
761 | )))] | |
7453a54e | 762 | fn name_bytes(&self) -> &[u8] { |
5099ac24 | 763 | self.name_cstr().to_bytes() |
3c0e092e XL |
764 | } |
765 | ||
766 | #[cfg(not(any( | |
5099ac24 FG |
767 | target_os = "android", |
768 | target_os = "linux", | |
3c0e092e XL |
769 | target_os = "solaris", |
770 | target_os = "illumos", | |
771 | target_os = "fuchsia", | |
772 | target_os = "redox" | |
773 | )))] | |
774 | fn name_cstr(&self) -> &CStr { | |
775 | unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) } | |
776 | } | |
5099ac24 FG |
777 | #[cfg(any( |
778 | target_os = "android", | |
779 | target_os = "linux", | |
780 | target_os = "solaris", | |
781 | target_os = "illumos", | |
782 | target_os = "fuchsia", | |
783 | target_os = "redox" | |
784 | ))] | |
3c0e092e XL |
785 | fn name_cstr(&self) -> &CStr { |
786 | &self.name | |
7453a54e | 787 | } |
136023e0 XL |
788 | |
789 | pub fn file_name_os_str(&self) -> &OsStr { | |
790 | OsStr::from_bytes(self.name_bytes()) | |
791 | } | |
85aaf69f SL |
792 | } |
793 | ||
794 | impl OpenOptions { | |
795 | pub fn new() -> OpenOptions { | |
796 | OpenOptions { | |
7453a54e | 797 | // generic |
85aaf69f SL |
798 | read: false, |
799 | write: false, | |
7453a54e SL |
800 | append: false, |
801 | truncate: false, | |
802 | create: false, | |
803 | create_new: false, | |
804 | // system-specific | |
805 | custom_flags: 0, | |
85aaf69f SL |
806 | mode: 0o666, |
807 | } | |
808 | } | |
809 | ||
dfeec247 XL |
810 | pub fn read(&mut self, read: bool) { |
811 | self.read = read; | |
812 | } | |
813 | pub fn write(&mut self, write: bool) { | |
814 | self.write = write; | |
815 | } | |
816 | pub fn append(&mut self, append: bool) { | |
817 | self.append = append; | |
818 | } | |
819 | pub fn truncate(&mut self, truncate: bool) { | |
820 | self.truncate = truncate; | |
821 | } | |
822 | pub fn create(&mut self, create: bool) { | |
823 | self.create = create; | |
824 | } | |
825 | pub fn create_new(&mut self, create_new: bool) { | |
826 | self.create_new = create_new; | |
827 | } | |
7453a54e | 828 | |
dfeec247 XL |
829 | pub fn custom_flags(&mut self, flags: i32) { |
830 | self.custom_flags = flags; | |
831 | } | |
832 | pub fn mode(&mut self, mode: u32) { | |
833 | self.mode = mode as mode_t; | |
834 | } | |
7453a54e SL |
835 | |
836 | fn get_access_mode(&self) -> io::Result<c_int> { | |
837 | match (self.read, self.write, self.append) { | |
dfeec247 XL |
838 | (true, false, false) => Ok(libc::O_RDONLY), |
839 | (false, true, false) => Ok(libc::O_WRONLY), | |
840 | (true, true, false) => Ok(libc::O_RDWR), | |
841 | (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND), | |
842 | (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND), | |
7453a54e SL |
843 | (false, false, false) => Err(Error::from_raw_os_error(libc::EINVAL)), |
844 | } | |
85aaf69f SL |
845 | } |
846 | ||
7453a54e SL |
847 | fn get_creation_mode(&self) -> io::Result<c_int> { |
848 | match (self.write, self.append) { | |
849 | (true, false) => {} | |
dfeec247 | 850 | (false, false) => { |
7453a54e SL |
851 | if self.truncate || self.create || self.create_new { |
852 | return Err(Error::from_raw_os_error(libc::EINVAL)); | |
dfeec247 XL |
853 | } |
854 | } | |
855 | (_, true) => { | |
7453a54e SL |
856 | if self.truncate && !self.create_new { |
857 | return Err(Error::from_raw_os_error(libc::EINVAL)); | |
dfeec247 XL |
858 | } |
859 | } | |
85aaf69f | 860 | } |
7453a54e SL |
861 | |
862 | Ok(match (self.create, self.truncate, self.create_new) { | |
dfeec247 XL |
863 | (false, false, false) => 0, |
864 | (true, false, false) => libc::O_CREAT, | |
865 | (false, true, false) => libc::O_TRUNC, | |
866 | (true, true, false) => libc::O_CREAT | libc::O_TRUNC, | |
867 | (_, _, true) => libc::O_CREAT | libc::O_EXCL, | |
868 | }) | |
85aaf69f SL |
869 | } |
870 | } | |
871 | ||
872 | impl File { | |
873 | pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> { | |
54a0048b | 874 | let path = cstr(path)?; |
9346a6ac AL |
875 | File::open_c(&path, opts) |
876 | } | |
877 | ||
878 | pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result<File> { | |
dfeec247 XL |
879 | let flags = libc::O_CLOEXEC |
880 | | opts.get_access_mode()? | |
881 | | opts.get_creation_mode()? | |
882 | | (opts.custom_flags as c_int & !libc::O_ACCMODE); | |
f9f354fc XL |
883 | // The third argument of `open64` is documented to have type `mode_t`. On |
884 | // some platforms (like macOS, where `open64` is actually `open`), `mode_t` is `u16`. | |
885 | // However, since this is a variadic function, C integer promotion rules mean that on | |
886 | // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms). | |
dfeec247 | 887 | let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?; |
94222f64 | 888 | Ok(File(unsafe { FileDesc::from_raw_fd(fd) })) |
85aaf69f SL |
889 | } |
890 | ||
891 | pub fn file_attr(&self) -> io::Result<FileAttr> { | |
94222f64 | 892 | let fd = self.as_raw_fd(); |
e74abb32 XL |
893 | |
894 | cfg_has_statx! { | |
895 | if let Some(ret) = unsafe { try_statx( | |
896 | fd, | |
136023e0 | 897 | b"\0" as *const _ as *const c_char, |
e74abb32 XL |
898 | libc::AT_EMPTY_PATH | libc::AT_STATX_SYNC_AS_STAT, |
899 | libc::STATX_ALL, | |
900 | ) } { | |
901 | return ret; | |
902 | } | |
903 | } | |
904 | ||
7453a54e | 905 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
dfeec247 | 906 | cvt(unsafe { fstat64(fd, &mut stat) })?; |
e74abb32 | 907 | Ok(FileAttr::from_stat64(stat)) |
85aaf69f SL |
908 | } |
909 | ||
910 | pub fn fsync(&self) -> io::Result<()> { | |
94222f64 | 911 | cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?; |
48663c56 XL |
912 | return Ok(()); |
913 | ||
914 | #[cfg(any(target_os = "macos", target_os = "ios"))] | |
915 | unsafe fn os_fsync(fd: c_int) -> c_int { | |
916 | libc::fcntl(fd, libc::F_FULLFSYNC) | |
917 | } | |
918 | #[cfg(not(any(target_os = "macos", target_os = "ios")))] | |
dfeec247 XL |
919 | unsafe fn os_fsync(fd: c_int) -> c_int { |
920 | libc::fsync(fd) | |
921 | } | |
85aaf69f SL |
922 | } |
923 | ||
924 | pub fn datasync(&self) -> io::Result<()> { | |
94222f64 | 925 | cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?; |
85aaf69f SL |
926 | return Ok(()); |
927 | ||
928 | #[cfg(any(target_os = "macos", target_os = "ios"))] | |
929 | unsafe fn os_datasync(fd: c_int) -> c_int { | |
930 | libc::fcntl(fd, libc::F_FULLFSYNC) | |
931 | } | |
29967ef6 XL |
932 | #[cfg(any( |
933 | target_os = "freebsd", | |
934 | target_os = "linux", | |
935 | target_os = "android", | |
936 | target_os = "netbsd", | |
937 | target_os = "openbsd" | |
938 | ))] | |
dfeec247 XL |
939 | unsafe fn os_datasync(fd: c_int) -> c_int { |
940 | libc::fdatasync(fd) | |
941 | } | |
29967ef6 XL |
942 | #[cfg(not(any( |
943 | target_os = "android", | |
944 | target_os = "freebsd", | |
945 | target_os = "ios", | |
946 | target_os = "linux", | |
947 | target_os = "macos", | |
948 | target_os = "netbsd", | |
949 | target_os = "openbsd" | |
950 | )))] | |
dfeec247 XL |
951 | unsafe fn os_datasync(fd: c_int) -> c_int { |
952 | libc::fsync(fd) | |
953 | } | |
85aaf69f SL |
954 | } |
955 | ||
956 | pub fn truncate(&self, size: u64) -> io::Result<()> { | |
a2a8927a XL |
957 | let size: off64_t = |
958 | size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; | |
959 | cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop) | |
85aaf69f SL |
960 | } |
961 | ||
962 | pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> { | |
963 | self.0.read(buf) | |
964 | } | |
965 | ||
48663c56 XL |
966 | pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> { |
967 | self.0.read_vectored(bufs) | |
968 | } | |
969 | ||
f9f354fc XL |
970 | #[inline] |
971 | pub fn is_read_vectored(&self) -> bool { | |
972 | self.0.is_read_vectored() | |
973 | } | |
974 | ||
c30ab7b3 SL |
975 | pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> { |
976 | self.0.read_at(buf, offset) | |
977 | } | |
978 | ||
a2a8927a XL |
979 | pub fn read_buf(&self, buf: &mut ReadBuf<'_>) -> io::Result<()> { |
980 | self.0.read_buf(buf) | |
981 | } | |
982 | ||
85aaf69f SL |
983 | pub fn write(&self, buf: &[u8]) -> io::Result<usize> { |
984 | self.0.write(buf) | |
985 | } | |
986 | ||
48663c56 XL |
987 | pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> { |
988 | self.0.write_vectored(bufs) | |
989 | } | |
990 | ||
f9f354fc XL |
991 | #[inline] |
992 | pub fn is_write_vectored(&self) -> bool { | |
993 | self.0.is_write_vectored() | |
994 | } | |
995 | ||
c30ab7b3 SL |
996 | pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> { |
997 | self.0.write_at(buf, offset) | |
998 | } | |
999 | ||
dfeec247 XL |
1000 | pub fn flush(&self) -> io::Result<()> { |
1001 | Ok(()) | |
1002 | } | |
85aaf69f SL |
1003 | |
1004 | pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> { | |
1005 | let (whence, pos) = match pos { | |
5bcae85e SL |
1006 | // Casting to `i64` is fine, too large values will end up as |
1007 | // negative which will cause an error in `lseek64`. | |
1008 | SeekFrom::Start(off) => (libc::SEEK_SET, off as i64), | |
1009 | SeekFrom::End(off) => (libc::SEEK_END, off), | |
1010 | SeekFrom::Current(off) => (libc::SEEK_CUR, off), | |
85aaf69f | 1011 | }; |
04454e1e | 1012 | let n = cvt(unsafe { lseek64(self.as_raw_fd(), pos as off64_t, whence) })?; |
85aaf69f SL |
1013 | Ok(n as u64) |
1014 | } | |
1015 | ||
7453a54e SL |
1016 | pub fn duplicate(&self) -> io::Result<File> { |
1017 | self.0.duplicate().map(File) | |
1018 | } | |
1019 | ||
476ff2be | 1020 | pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { |
94222f64 | 1021 | cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?; |
476ff2be SL |
1022 | Ok(()) |
1023 | } | |
85aaf69f SL |
1024 | } |
1025 | ||
d9579d0f AL |
1026 | impl DirBuilder { |
1027 | pub fn new() -> DirBuilder { | |
1028 | DirBuilder { mode: 0o777 } | |
1029 | } | |
1030 | ||
1031 | pub fn mkdir(&self, p: &Path) -> io::Result<()> { | |
54a0048b SL |
1032 | let p = cstr(p)?; |
1033 | cvt(unsafe { libc::mkdir(p.as_ptr(), self.mode) })?; | |
d9579d0f AL |
1034 | Ok(()) |
1035 | } | |
1036 | ||
7453a54e SL |
1037 | pub fn set_mode(&mut self, mode: u32) { |
1038 | self.mode = mode as mode_t; | |
d9579d0f AL |
1039 | } |
1040 | } | |
1041 | ||
85aaf69f | 1042 | fn cstr(path: &Path) -> io::Result<CString> { |
54a0048b | 1043 | Ok(CString::new(path.as_os_str().as_bytes())?) |
c34b1796 AL |
1044 | } |
1045 | ||
94222f64 XL |
1046 | impl AsInner<FileDesc> for File { |
1047 | fn as_inner(&self) -> &FileDesc { | |
1048 | &self.0 | |
1049 | } | |
1050 | } | |
1051 | ||
1052 | impl AsInnerMut<FileDesc> for File { | |
1053 | fn as_inner_mut(&mut self) -> &mut FileDesc { | |
1054 | &mut self.0 | |
1055 | } | |
1056 | } | |
1057 | ||
1058 | impl IntoInner<FileDesc> for File { | |
1059 | fn into_inner(self) -> FileDesc { | |
1060 | self.0 | |
1061 | } | |
1062 | } | |
1063 | ||
1064 | impl FromInner<FileDesc> for File { | |
1065 | fn from_inner(file_desc: FileDesc) -> Self { | |
1066 | Self(file_desc) | |
1067 | } | |
1068 | } | |
1069 | ||
1070 | impl AsFd for File { | |
1071 | fn as_fd(&self) -> BorrowedFd<'_> { | |
1072 | self.0.as_fd() | |
1073 | } | |
1074 | } | |
1075 | ||
1076 | impl AsRawFd for File { | |
1077 | fn as_raw_fd(&self) -> RawFd { | |
1078 | self.0.as_raw_fd() | |
1079 | } | |
1080 | } | |
1081 | ||
1082 | impl IntoRawFd for File { | |
1083 | fn into_raw_fd(self) -> RawFd { | |
1084 | self.0.into_raw_fd() | |
1085 | } | |
1086 | } | |
1087 | ||
1088 | impl FromRawFd for File { | |
1089 | unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { | |
1090 | Self(FromRawFd::from_raw_fd(raw_fd)) | |
c34b1796 | 1091 | } |
85aaf69f SL |
1092 | } |
1093 | ||
d9579d0f | 1094 | impl fmt::Debug for File { |
532ac7d7 | 1095 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
94222f64 | 1096 | #[cfg(any(target_os = "linux", target_os = "netbsd"))] |
d9579d0f | 1097 | fn get_path(fd: c_int) -> Option<PathBuf> { |
d9579d0f AL |
1098 | let mut p = PathBuf::from("/proc/self/fd"); |
1099 | p.push(&fd.to_string()); | |
1100 | readlink(&p).ok() | |
1101 | } | |
1102 | ||
c1a9b12d SL |
1103 | #[cfg(target_os = "macos")] |
1104 | fn get_path(fd: c_int) -> Option<PathBuf> { | |
e9174d1e | 1105 | // FIXME: The use of PATH_MAX is generally not encouraged, but it |
cc61c64b | 1106 | // is inevitable in this case because macOS defines `fcntl` with |
e9174d1e SL |
1107 | // `F_GETPATH` in terms of `MAXPATHLEN`, and there are no |
1108 | // alternatives. If a better method is invented, it should be used | |
1109 | // instead. | |
dfeec247 | 1110 | let mut buf = vec![0; libc::PATH_MAX as usize]; |
c1a9b12d SL |
1111 | let n = unsafe { libc::fcntl(fd, libc::F_GETPATH, buf.as_ptr()) }; |
1112 | if n == -1 { | |
1113 | return None; | |
1114 | } | |
1115 | let l = buf.iter().position(|&c| c == 0).unwrap(); | |
1116 | buf.truncate(l as usize); | |
e9174d1e | 1117 | buf.shrink_to_fit(); |
c1a9b12d SL |
1118 | Some(PathBuf::from(OsString::from_vec(buf))) |
1119 | } | |
1120 | ||
29967ef6 XL |
1121 | #[cfg(target_os = "vxworks")] |
1122 | fn get_path(fd: c_int) -> Option<PathBuf> { | |
1123 | let mut buf = vec![0; libc::PATH_MAX as usize]; | |
1124 | let n = unsafe { libc::ioctl(fd, libc::FIOGETNAME, buf.as_ptr()) }; | |
1125 | if n == -1 { | |
1126 | return None; | |
1127 | } | |
1128 | let l = buf.iter().position(|&c| c == 0).unwrap(); | |
1129 | buf.truncate(l as usize); | |
1130 | Some(PathBuf::from(OsString::from_vec(buf))) | |
1131 | } | |
1132 | ||
94222f64 XL |
1133 | #[cfg(not(any( |
1134 | target_os = "linux", | |
1135 | target_os = "macos", | |
1136 | target_os = "vxworks", | |
1137 | target_os = "netbsd" | |
1138 | )))] | |
d9579d0f AL |
1139 | fn get_path(_fd: c_int) -> Option<PathBuf> { |
1140 | // FIXME(#24570): implement this for other Unix platforms | |
1141 | None | |
1142 | } | |
1143 | ||
29967ef6 | 1144 | #[cfg(any(target_os = "linux", target_os = "macos", target_os = "vxworks"))] |
d9579d0f AL |
1145 | fn get_mode(fd: c_int) -> Option<(bool, bool)> { |
1146 | let mode = unsafe { libc::fcntl(fd, libc::F_GETFL) }; | |
1147 | if mode == -1 { | |
1148 | return None; | |
1149 | } | |
1150 | match mode & libc::O_ACCMODE { | |
1151 | libc::O_RDONLY => Some((true, false)), | |
1152 | libc::O_RDWR => Some((true, true)), | |
1153 | libc::O_WRONLY => Some((false, true)), | |
dfeec247 | 1154 | _ => None, |
d9579d0f AL |
1155 | } |
1156 | } | |
1157 | ||
29967ef6 | 1158 | #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "vxworks")))] |
d9579d0f AL |
1159 | fn get_mode(_fd: c_int) -> Option<(bool, bool)> { |
1160 | // FIXME(#24570): implement this for other Unix platforms | |
1161 | None | |
1162 | } | |
1163 | ||
94222f64 | 1164 | let fd = self.as_raw_fd(); |
62682a34 SL |
1165 | let mut b = f.debug_struct("File"); |
1166 | b.field("fd", &fd); | |
d9579d0f | 1167 | if let Some(path) = get_path(fd) { |
62682a34 | 1168 | b.field("path", &path); |
d9579d0f AL |
1169 | } |
1170 | if let Some((read, write)) = get_mode(fd) { | |
62682a34 | 1171 | b.field("read", &read).field("write", &write); |
d9579d0f AL |
1172 | } |
1173 | b.finish() | |
1174 | } | |
85aaf69f SL |
1175 | } |
1176 | ||
1177 | pub fn readdir(p: &Path) -> io::Result<ReadDir> { | |
94b46f34 | 1178 | let root = p.to_path_buf(); |
54a0048b | 1179 | let p = cstr(p)?; |
85aaf69f SL |
1180 | unsafe { |
1181 | let ptr = libc::opendir(p.as_ptr()); | |
1182 | if ptr.is_null() { | |
1183 | Err(Error::last_os_error()) | |
1184 | } else { | |
94b46f34 | 1185 | let inner = InnerReadDir { dirp: Dir(ptr), root }; |
29967ef6 XL |
1186 | Ok(ReadDir { |
1187 | inner: Arc::new(inner), | |
1188 | #[cfg(not(any( | |
5099ac24 FG |
1189 | target_os = "android", |
1190 | target_os = "linux", | |
29967ef6 XL |
1191 | target_os = "solaris", |
1192 | target_os = "illumos", | |
1193 | target_os = "fuchsia", | |
1194 | target_os = "redox", | |
1195 | )))] | |
1196 | end_of_stream: false, | |
1197 | }) | |
85aaf69f SL |
1198 | } |
1199 | } | |
1200 | } | |
1201 | ||
1202 | pub fn unlink(p: &Path) -> io::Result<()> { | |
54a0048b SL |
1203 | let p = cstr(p)?; |
1204 | cvt(unsafe { libc::unlink(p.as_ptr()) })?; | |
85aaf69f SL |
1205 | Ok(()) |
1206 | } | |
1207 | ||
1208 | pub fn rename(old: &Path, new: &Path) -> io::Result<()> { | |
54a0048b SL |
1209 | let old = cstr(old)?; |
1210 | let new = cstr(new)?; | |
1211 | cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) })?; | |
85aaf69f SL |
1212 | Ok(()) |
1213 | } | |
1214 | ||
1215 | pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { | |
54a0048b SL |
1216 | let p = cstr(p)?; |
1217 | cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) })?; | |
85aaf69f SL |
1218 | Ok(()) |
1219 | } | |
1220 | ||
1221 | pub fn rmdir(p: &Path) -> io::Result<()> { | |
54a0048b SL |
1222 | let p = cstr(p)?; |
1223 | cvt(unsafe { libc::rmdir(p.as_ptr()) })?; | |
85aaf69f SL |
1224 | Ok(()) |
1225 | } | |
1226 | ||
85aaf69f | 1227 | pub fn readlink(p: &Path) -> io::Result<PathBuf> { |
54a0048b | 1228 | let c_path = cstr(p)?; |
85aaf69f | 1229 | let p = c_path.as_ptr(); |
e9174d1e SL |
1230 | |
1231 | let mut buf = Vec::with_capacity(256); | |
1232 | ||
1233 | loop { | |
dfeec247 XL |
1234 | let buf_read = |
1235 | cvt(unsafe { libc::readlink(p, buf.as_mut_ptr() as *mut _, buf.capacity()) })? as usize; | |
e9174d1e | 1236 | |
dfeec247 XL |
1237 | unsafe { |
1238 | buf.set_len(buf_read); | |
1239 | } | |
e9174d1e SL |
1240 | |
1241 | if buf_read != buf.capacity() { | |
1242 | buf.shrink_to_fit(); | |
1243 | ||
1244 | return Ok(PathBuf::from(OsString::from_vec(buf))); | |
1245 | } | |
1246 | ||
1247 | // Trigger the internal buffer resizing logic of `Vec` by requiring | |
1248 | // more space than the current capacity. The length is guaranteed to be | |
1249 | // the same as the capacity due to the if statement above. | |
1250 | buf.reserve(1); | |
85aaf69f | 1251 | } |
85aaf69f SL |
1252 | } |
1253 | ||
fc512014 XL |
1254 | pub fn symlink(original: &Path, link: &Path) -> io::Result<()> { |
1255 | let original = cstr(original)?; | |
1256 | let link = cstr(link)?; | |
1257 | cvt(unsafe { libc::symlink(original.as_ptr(), link.as_ptr()) })?; | |
85aaf69f SL |
1258 | Ok(()) |
1259 | } | |
1260 | ||
fc512014 XL |
1261 | pub fn link(original: &Path, link: &Path) -> io::Result<()> { |
1262 | let original = cstr(original)?; | |
1263 | let link = cstr(link)?; | |
29967ef6 | 1264 | cfg_if::cfg_if! { |
923072b8 | 1265 | if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon"))] { |
94222f64 | 1266 | // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves |
136023e0 XL |
1267 | // it implementation-defined whether `link` follows symlinks, so rely on the |
1268 | // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior. | |
1269 | // Android has `linkat` on newer versions, but we happen to know `link` | |
1270 | // always has the correct behavior, so it's here as well. | |
fc512014 | 1271 | cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; |
136023e0 XL |
1272 | } else if #[cfg(target_os = "macos")] { |
1273 | // On MacOS, older versions (<=10.9) lack support for linkat while newer | |
1274 | // versions have it. We want to use linkat if it is available, so we use weak! | |
a2a8927a | 1275 | // to check. `linkat` is preferable to `link` because it gives us a flag to |
136023e0 XL |
1276 | // specify how symlinks should be handled. We pass 0 as the flags argument, |
1277 | // meaning it shouldn't follow symlinks. | |
1278 | weak!(fn linkat(c_int, *const c_char, c_int, *const c_char, c_int) -> c_int); | |
1279 | ||
1280 | if let Some(f) = linkat.get() { | |
1281 | cvt(unsafe { f(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?; | |
1282 | } else { | |
1283 | cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; | |
1284 | }; | |
29967ef6 | 1285 | } else { |
136023e0 XL |
1286 | // Where we can, use `linkat` instead of `link`; see the comment above |
1287 | // this one for details on why. | |
fc512014 | 1288 | cvt(unsafe { libc::linkat(libc::AT_FDCWD, original.as_ptr(), libc::AT_FDCWD, link.as_ptr(), 0) })?; |
29967ef6 XL |
1289 | } |
1290 | } | |
85aaf69f SL |
1291 | Ok(()) |
1292 | } | |
1293 | ||
1294 | pub fn stat(p: &Path) -> io::Result<FileAttr> { | |
54a0048b | 1295 | let p = cstr(p)?; |
e74abb32 XL |
1296 | |
1297 | cfg_has_statx! { | |
1298 | if let Some(ret) = unsafe { try_statx( | |
1299 | libc::AT_FDCWD, | |
1300 | p.as_ptr(), | |
1301 | libc::AT_STATX_SYNC_AS_STAT, | |
1302 | libc::STATX_ALL, | |
1303 | ) } { | |
1304 | return ret; | |
1305 | } | |
1306 | } | |
1307 | ||
7453a54e | 1308 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
dfeec247 | 1309 | cvt(unsafe { stat64(p.as_ptr(), &mut stat) })?; |
e74abb32 | 1310 | Ok(FileAttr::from_stat64(stat)) |
85aaf69f SL |
1311 | } |
1312 | ||
1313 | pub fn lstat(p: &Path) -> io::Result<FileAttr> { | |
54a0048b | 1314 | let p = cstr(p)?; |
e74abb32 XL |
1315 | |
1316 | cfg_has_statx! { | |
1317 | if let Some(ret) = unsafe { try_statx( | |
1318 | libc::AT_FDCWD, | |
1319 | p.as_ptr(), | |
1320 | libc::AT_SYMLINK_NOFOLLOW | libc::AT_STATX_SYNC_AS_STAT, | |
1321 | libc::STATX_ALL, | |
1322 | ) } { | |
1323 | return ret; | |
1324 | } | |
1325 | } | |
1326 | ||
7453a54e | 1327 | let mut stat: stat64 = unsafe { mem::zeroed() }; |
dfeec247 | 1328 | cvt(unsafe { lstat64(p.as_ptr(), &mut stat) })?; |
e74abb32 | 1329 | Ok(FileAttr::from_stat64(stat)) |
85aaf69f SL |
1330 | } |
1331 | ||
d9579d0f | 1332 | pub fn canonicalize(p: &Path) -> io::Result<PathBuf> { |
54a0048b | 1333 | let path = CString::new(p.as_os_str().as_bytes())?; |
e9174d1e | 1334 | let buf; |
d9579d0f | 1335 | unsafe { |
92a42be0 | 1336 | let r = libc::realpath(path.as_ptr(), ptr::null_mut()); |
d9579d0f | 1337 | if r.is_null() { |
dfeec247 | 1338 | return Err(io::Error::last_os_error()); |
d9579d0f | 1339 | } |
e9174d1e SL |
1340 | buf = CStr::from_ptr(r).to_bytes().to_vec(); |
1341 | libc::free(r as *mut _); | |
d9579d0f | 1342 | } |
d9579d0f AL |
1343 | Ok(PathBuf::from(OsString::from_vec(buf))) |
1344 | } | |
c1a9b12d | 1345 | |
48663c56 XL |
1346 | fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { |
1347 | use crate::fs::File; | |
cdc7bbd5 | 1348 | use crate::sys_common::fs::NOT_FILE_ERROR; |
48663c56 XL |
1349 | |
1350 | let reader = File::open(from)?; | |
1351 | let metadata = reader.metadata()?; | |
1352 | if !metadata.is_file() { | |
cdc7bbd5 | 1353 | return Err(NOT_FILE_ERROR); |
48663c56 XL |
1354 | } |
1355 | Ok((reader, metadata)) | |
1356 | } | |
1357 | ||
94222f64 XL |
1358 | #[cfg(target_os = "espidf")] |
1359 | fn open_to_and_set_permissions( | |
1360 | to: &Path, | |
1361 | reader_metadata: crate::fs::Metadata, | |
1362 | ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { | |
1363 | use crate::fs::OpenOptions; | |
1364 | let writer = OpenOptions::new().open(to)?; | |
1365 | let writer_metadata = writer.metadata()?; | |
1366 | Ok((writer, writer_metadata)) | |
1367 | } | |
1368 | ||
1369 | #[cfg(not(target_os = "espidf"))] | |
48663c56 | 1370 | fn open_to_and_set_permissions( |
532ac7d7 | 1371 | to: &Path, |
48663c56 XL |
1372 | reader_metadata: crate::fs::Metadata, |
1373 | ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { | |
1374 | use crate::fs::OpenOptions; | |
532ac7d7 XL |
1375 | use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt}; |
1376 | ||
48663c56 | 1377 | let perm = reader_metadata.permissions(); |
532ac7d7 XL |
1378 | let writer = OpenOptions::new() |
1379 | // create the file with the correct mode right away | |
1380 | .mode(perm.mode()) | |
1381 | .write(true) | |
1382 | .create(true) | |
1383 | .truncate(true) | |
1384 | .open(to)?; | |
1385 | let writer_metadata = writer.metadata()?; | |
1386 | if writer_metadata.is_file() { | |
1387 | // Set the correct file permissions, in case the file already existed. | |
1388 | // Don't set the permissions on already existing non-files like | |
1389 | // pipes/FIFOs or device nodes. | |
1390 | writer.set_permissions(perm)?; | |
c1a9b12d | 1391 | } |
48663c56 | 1392 | Ok((writer, writer_metadata)) |
532ac7d7 | 1393 | } |
c1a9b12d | 1394 | |
dfeec247 XL |
1395 | #[cfg(not(any( |
1396 | target_os = "linux", | |
1397 | target_os = "android", | |
1398 | target_os = "macos", | |
1399 | target_os = "ios" | |
1400 | )))] | |
532ac7d7 | 1401 | pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { |
48663c56 XL |
1402 | let (mut reader, reader_metadata) = open_from(from)?; |
1403 | let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?; | |
c1a9b12d | 1404 | |
532ac7d7 | 1405 | io::copy(&mut reader, &mut writer) |
c1a9b12d | 1406 | } |
94b46f34 XL |
1407 | |
1408 | #[cfg(any(target_os = "linux", target_os = "android"))] | |
1409 | pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { | |
48663c56 | 1410 | let (mut reader, reader_metadata) = open_from(from)?; |
1b1a35ee | 1411 | let max_len = u64::MAX; |
48663c56 | 1412 | let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?; |
94b46f34 | 1413 | |
fc512014 XL |
1414 | use super::kernel_copy::{copy_regular_files, CopyResult}; |
1415 | ||
1416 | match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) { | |
1417 | CopyResult::Ended(bytes) => Ok(bytes), | |
1418 | CopyResult::Error(e, _) => Err(e), | |
1419 | CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) { | |
1420 | Ok(bytes) => Ok(bytes + written), | |
1421 | Err(e) => Err(e), | |
1422 | }, | |
94b46f34 | 1423 | } |
94b46f34 | 1424 | } |
532ac7d7 XL |
1425 | |
1426 | #[cfg(any(target_os = "macos", target_os = "ios"))] | |
1427 | pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { | |
48663c56 XL |
1428 | use crate::sync::atomic::{AtomicBool, Ordering}; |
1429 | ||
532ac7d7 XL |
1430 | const COPYFILE_ACL: u32 = 1 << 0; |
1431 | const COPYFILE_STAT: u32 = 1 << 1; | |
1432 | const COPYFILE_XATTR: u32 = 1 << 2; | |
1433 | const COPYFILE_DATA: u32 = 1 << 3; | |
1434 | ||
1435 | const COPYFILE_SECURITY: u32 = COPYFILE_STAT | COPYFILE_ACL; | |
1436 | const COPYFILE_METADATA: u32 = COPYFILE_SECURITY | COPYFILE_XATTR; | |
1437 | const COPYFILE_ALL: u32 = COPYFILE_METADATA | COPYFILE_DATA; | |
1438 | ||
1439 | const COPYFILE_STATE_COPIED: u32 = 8; | |
1440 | ||
1441 | #[allow(non_camel_case_types)] | |
1442 | type copyfile_state_t = *mut libc::c_void; | |
1443 | #[allow(non_camel_case_types)] | |
1444 | type copyfile_flags_t = u32; | |
1445 | ||
1446 | extern "C" { | |
1447 | fn fcopyfile( | |
1448 | from: libc::c_int, | |
1449 | to: libc::c_int, | |
1450 | state: copyfile_state_t, | |
1451 | flags: copyfile_flags_t, | |
1452 | ) -> libc::c_int; | |
1453 | fn copyfile_state_alloc() -> copyfile_state_t; | |
1454 | fn copyfile_state_free(state: copyfile_state_t) -> libc::c_int; | |
1455 | fn copyfile_state_get( | |
1456 | state: copyfile_state_t, | |
1457 | flag: u32, | |
1458 | dst: *mut libc::c_void, | |
1459 | ) -> libc::c_int; | |
1460 | } | |
1461 | ||
1462 | struct FreeOnDrop(copyfile_state_t); | |
1463 | impl Drop for FreeOnDrop { | |
1464 | fn drop(&mut self) { | |
1465 | // The code below ensures that `FreeOnDrop` is never a null pointer | |
1466 | unsafe { | |
1467 | // `copyfile_state_free` returns -1 if the `to` or `from` files | |
60c5eb7d | 1468 | // cannot be closed. However, this is not considered this an |
532ac7d7 XL |
1469 | // error. |
1470 | copyfile_state_free(self.0); | |
1471 | } | |
1472 | } | |
1473 | } | |
1474 | ||
48663c56 XL |
1475 | // MacOS prior to 10.12 don't support `fclonefileat` |
1476 | // We store the availability in a global to avoid unnecessary syscalls | |
1477 | static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true); | |
1478 | syscall! { | |
1479 | fn fclonefileat( | |
1480 | srcfd: libc::c_int, | |
1481 | dst_dirfd: libc::c_int, | |
136023e0 | 1482 | dst: *const c_char, |
48663c56 XL |
1483 | flags: libc::c_int |
1484 | ) -> libc::c_int | |
1485 | } | |
1486 | ||
1487 | let (reader, reader_metadata) = open_from(from)?; | |
1488 | ||
1489 | // Opportunistically attempt to create a copy-on-write clone of `from` | |
1490 | // using `fclonefileat`. | |
1491 | if HAS_FCLONEFILEAT.load(Ordering::Relaxed) { | |
1492 | let to = cstr(to)?; | |
dfeec247 XL |
1493 | let clonefile_result = |
1494 | cvt(unsafe { fclonefileat(reader.as_raw_fd(), libc::AT_FDCWD, to.as_ptr(), 0) }); | |
48663c56 XL |
1495 | match clonefile_result { |
1496 | Ok(_) => return Ok(reader_metadata.len()), | |
1497 | Err(err) => match err.raw_os_error() { | |
1498 | // `fclonefileat` will fail on non-APFS volumes, if the | |
1499 | // destination already exists, or if the source and destination | |
1500 | // are on different devices. In all these cases `fcopyfile` | |
1501 | // should succeed. | |
1502 | Some(libc::ENOTSUP) | Some(libc::EEXIST) | Some(libc::EXDEV) => (), | |
1503 | Some(libc::ENOSYS) => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed), | |
1504 | _ => return Err(err), | |
dfeec247 | 1505 | }, |
48663c56 XL |
1506 | } |
1507 | } | |
1508 | ||
1509 | // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. | |
1510 | let (writer, writer_metadata) = open_to_and_set_permissions(to, reader_metadata)?; | |
532ac7d7 XL |
1511 | |
1512 | // We ensure that `FreeOnDrop` never contains a null pointer so it is | |
1513 | // always safe to call `copyfile_state_free` | |
1514 | let state = unsafe { | |
1515 | let state = copyfile_state_alloc(); | |
1516 | if state.is_null() { | |
1517 | return Err(crate::io::Error::last_os_error()); | |
1518 | } | |
1519 | FreeOnDrop(state) | |
1520 | }; | |
1521 | ||
dfeec247 | 1522 | let flags = if writer_metadata.is_file() { COPYFILE_ALL } else { COPYFILE_DATA }; |
532ac7d7 | 1523 | |
dfeec247 | 1524 | cvt(unsafe { fcopyfile(reader.as_raw_fd(), writer.as_raw_fd(), state.0, flags) })?; |
532ac7d7 XL |
1525 | |
1526 | let mut bytes_copied: libc::off_t = 0; | |
1527 | cvt(unsafe { | |
1528 | copyfile_state_get( | |
1529 | state.0, | |
1530 | COPYFILE_STATE_COPIED, | |
1531 | &mut bytes_copied as *mut libc::off_t as *mut libc::c_void, | |
1532 | ) | |
1533 | })?; | |
1534 | Ok(bytes_copied as u64) | |
1535 | } | |
cdc7bbd5 | 1536 | |
c295e0f8 XL |
1537 | pub fn chown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { |
1538 | let path = cstr(path)?; | |
1539 | cvt(unsafe { libc::chown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })?; | |
1540 | Ok(()) | |
1541 | } | |
1542 | ||
1543 | pub fn fchown(fd: c_int, uid: u32, gid: u32) -> io::Result<()> { | |
1544 | cvt(unsafe { libc::fchown(fd, uid as libc::uid_t, gid as libc::gid_t) })?; | |
1545 | Ok(()) | |
1546 | } | |
1547 | ||
1548 | pub fn lchown(path: &Path, uid: u32, gid: u32) -> io::Result<()> { | |
1549 | let path = cstr(path)?; | |
1550 | cvt(unsafe { libc::lchown(path.as_ptr(), uid as libc::uid_t, gid as libc::gid_t) })?; | |
1551 | Ok(()) | |
1552 | } | |
1553 | ||
17df50a5 | 1554 | #[cfg(not(any(target_os = "fuchsia", target_os = "vxworks")))] |
cdc7bbd5 XL |
1555 | pub fn chroot(dir: &Path) -> io::Result<()> { |
1556 | let dir = cstr(dir)?; | |
1557 | cvt(unsafe { libc::chroot(dir.as_ptr()) })?; | |
1558 | Ok(()) | |
1559 | } | |
3c0e092e XL |
1560 | |
1561 | pub use remove_dir_impl::remove_dir_all; | |
1562 | ||
923072b8 FG |
1563 | // Fallback for REDOX, ESP-ID, Horizon, and Miri |
1564 | #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri))] | |
3c0e092e XL |
1565 | mod remove_dir_impl { |
1566 | pub use crate::sys_common::fs::remove_dir_all; | |
1567 | } | |
1568 | ||
5e7ed085 | 1569 | // Modern implementation using openat(), unlinkat() and fdopendir() |
923072b8 | 1570 | #[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon", miri)))] |
3c0e092e | 1571 | mod remove_dir_impl { |
5e7ed085 | 1572 | use super::{cstr, lstat, Dir, DirEntry, InnerReadDir, ReadDir}; |
3c0e092e XL |
1573 | use crate::ffi::CStr; |
1574 | use crate::io; | |
1575 | use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; | |
1576 | use crate::os::unix::prelude::{OwnedFd, RawFd}; | |
1577 | use crate::path::{Path, PathBuf}; | |
1578 | use crate::sync::Arc; | |
3c0e092e | 1579 | use crate::sys::{cvt, cvt_r}; |
3c0e092e | 1580 | |
5e7ed085 FG |
1581 | #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64")),))] |
1582 | use libc::{fdopendir, openat, unlinkat}; | |
1583 | #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] | |
1584 | use macos_weak::{fdopendir, openat, unlinkat}; | |
3c0e092e | 1585 | |
5e7ed085 FG |
1586 | #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] |
1587 | mod macos_weak { | |
1588 | use crate::sys::weak::weak; | |
1589 | use libc::{c_char, c_int, DIR}; | |
3c0e092e | 1590 | |
5e7ed085 FG |
1591 | fn get_openat_fn() -> Option<unsafe extern "C" fn(c_int, *const c_char, c_int) -> c_int> { |
1592 | weak!(fn openat(c_int, *const c_char, c_int) -> c_int); | |
1593 | openat.get() | |
1594 | } | |
3c0e092e | 1595 | |
5e7ed085 FG |
1596 | pub fn has_openat() -> bool { |
1597 | get_openat_fn().is_some() | |
3c0e092e XL |
1598 | } |
1599 | ||
5e7ed085 FG |
1600 | pub unsafe fn openat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int { |
1601 | get_openat_fn().map(|openat| openat(dirfd, pathname, flags)).unwrap_or_else(|| { | |
1602 | crate::sys::unix::os::set_errno(libc::ENOSYS); | |
1603 | -1 | |
1604 | }) | |
1605 | } | |
3c0e092e | 1606 | |
5e7ed085 FG |
1607 | pub unsafe fn fdopendir(fd: c_int) -> *mut DIR { |
1608 | #[cfg(all(target_os = "macos", target_arch = "x86"))] | |
1609 | weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64$UNIX2003"); | |
1610 | #[cfg(all(target_os = "macos", target_arch = "x86_64"))] | |
1611 | weak!(fn fdopendir(c_int) -> *mut DIR, "fdopendir$INODE64"); | |
1612 | fdopendir.get().map(|fdopendir| fdopendir(fd)).unwrap_or_else(|| { | |
1613 | crate::sys::unix::os::set_errno(libc::ENOSYS); | |
1614 | crate::ptr::null_mut() | |
1615 | }) | |
3c0e092e | 1616 | } |
3c0e092e | 1617 | |
5e7ed085 FG |
1618 | pub unsafe fn unlinkat(dirfd: c_int, pathname: *const c_char, flags: c_int) -> c_int { |
1619 | weak!(fn unlinkat(c_int, *const c_char, c_int) -> c_int); | |
1620 | unlinkat.get().map(|unlinkat| unlinkat(dirfd, pathname, flags)).unwrap_or_else(|| { | |
1621 | crate::sys::unix::os::set_errno(libc::ENOSYS); | |
1622 | -1 | |
1623 | }) | |
3c0e092e XL |
1624 | } |
1625 | } | |
3c0e092e XL |
1626 | |
1627 | pub fn openat_nofollow_dironly(parent_fd: Option<RawFd>, p: &CStr) -> io::Result<OwnedFd> { | |
1628 | let fd = cvt_r(|| unsafe { | |
1629 | openat( | |
1630 | parent_fd.unwrap_or(libc::AT_FDCWD), | |
1631 | p.as_ptr(), | |
1632 | libc::O_CLOEXEC | libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_DIRECTORY, | |
1633 | ) | |
1634 | })?; | |
1635 | Ok(unsafe { OwnedFd::from_raw_fd(fd) }) | |
1636 | } | |
1637 | ||
1638 | fn fdreaddir(dir_fd: OwnedFd) -> io::Result<(ReadDir, RawFd)> { | |
1639 | let ptr = unsafe { fdopendir(dir_fd.as_raw_fd()) }; | |
1640 | if ptr.is_null() { | |
1641 | return Err(io::Error::last_os_error()); | |
1642 | } | |
1643 | let dirp = Dir(ptr); | |
1644 | // file descriptor is automatically closed by libc::closedir() now, so give up ownership | |
1645 | let new_parent_fd = dir_fd.into_raw_fd(); | |
1646 | // a valid root is not needed because we do not call any functions involving the full path | |
1647 | // of the DirEntrys. | |
1648 | let dummy_root = PathBuf::new(); | |
1649 | Ok(( | |
1650 | ReadDir { | |
1651 | inner: Arc::new(InnerReadDir { dirp, root: dummy_root }), | |
1652 | #[cfg(not(any( | |
5099ac24 FG |
1653 | target_os = "android", |
1654 | target_os = "linux", | |
3c0e092e XL |
1655 | target_os = "solaris", |
1656 | target_os = "illumos", | |
1657 | target_os = "fuchsia", | |
1658 | target_os = "redox", | |
1659 | )))] | |
1660 | end_of_stream: false, | |
1661 | }, | |
1662 | new_parent_fd, | |
1663 | )) | |
1664 | } | |
1665 | ||
1666 | #[cfg(any( | |
1667 | target_os = "solaris", | |
1668 | target_os = "illumos", | |
1669 | target_os = "haiku", | |
1670 | target_os = "vxworks", | |
3c0e092e XL |
1671 | ))] |
1672 | fn is_dir(_ent: &DirEntry) -> Option<bool> { | |
1673 | None | |
1674 | } | |
1675 | ||
1676 | #[cfg(not(any( | |
1677 | target_os = "solaris", | |
1678 | target_os = "illumos", | |
1679 | target_os = "haiku", | |
1680 | target_os = "vxworks", | |
3c0e092e XL |
1681 | )))] |
1682 | fn is_dir(ent: &DirEntry) -> Option<bool> { | |
1683 | match ent.entry.d_type { | |
1684 | libc::DT_UNKNOWN => None, | |
1685 | libc::DT_DIR => Some(true), | |
1686 | _ => Some(false), | |
1687 | } | |
1688 | } | |
1689 | ||
5e7ed085 FG |
1690 | fn remove_dir_all_recursive(parent_fd: Option<RawFd>, path: &CStr) -> io::Result<()> { |
1691 | // try opening as directory | |
1692 | let fd = match openat_nofollow_dironly(parent_fd, &path) { | |
04454e1e | 1693 | Err(err) if matches!(err.raw_os_error(), Some(libc::ENOTDIR | libc::ELOOP)) => { |
5e7ed085 | 1694 | // not a directory - don't traverse further |
04454e1e | 1695 | // (for symlinks, older Linux kernels may return ELOOP instead of ENOTDIR) |
5e7ed085 FG |
1696 | return match parent_fd { |
1697 | // unlink... | |
1698 | Some(parent_fd) => { | |
1699 | cvt(unsafe { unlinkat(parent_fd, path.as_ptr(), 0) }).map(drop) | |
1700 | } | |
1701 | // ...unless this was supposed to be the deletion root directory | |
1702 | None => Err(err), | |
1703 | }; | |
1704 | } | |
1705 | result => result?, | |
1706 | }; | |
3c0e092e XL |
1707 | |
1708 | // open the directory passing ownership of the fd | |
1709 | let (dir, fd) = fdreaddir(fd)?; | |
1710 | for child in dir { | |
1711 | let child = child?; | |
5e7ed085 | 1712 | let child_name = child.name_cstr(); |
3c0e092e XL |
1713 | match is_dir(&child) { |
1714 | Some(true) => { | |
5e7ed085 | 1715 | remove_dir_all_recursive(Some(fd), child_name)?; |
3c0e092e XL |
1716 | } |
1717 | Some(false) => { | |
5e7ed085 FG |
1718 | cvt(unsafe { unlinkat(fd, child_name.as_ptr(), 0) })?; |
1719 | } | |
1720 | None => { | |
1721 | // POSIX specifies that calling unlink()/unlinkat(..., 0) on a directory can succeed | |
1722 | // if the process has the appropriate privileges. This however can causing orphaned | |
1723 | // directories requiring an fsck e.g. on Solaris and Illumos. So we try recursing | |
1724 | // into it first instead of trying to unlink() it. | |
1725 | remove_dir_all_recursive(Some(fd), child_name)?; | |
3c0e092e | 1726 | } |
3c0e092e XL |
1727 | } |
1728 | } | |
1729 | ||
1730 | // unlink the directory after removing its contents | |
1731 | cvt(unsafe { | |
5e7ed085 | 1732 | unlinkat(parent_fd.unwrap_or(libc::AT_FDCWD), path.as_ptr(), libc::AT_REMOVEDIR) |
3c0e092e XL |
1733 | })?; |
1734 | Ok(()) | |
1735 | } | |
1736 | ||
5e7ed085 | 1737 | fn remove_dir_all_modern(p: &Path) -> io::Result<()> { |
3c0e092e XL |
1738 | // We cannot just call remove_dir_all_recursive() here because that would not delete a passed |
1739 | // symlink. No need to worry about races, because remove_dir_all_recursive() does not recurse | |
1740 | // into symlinks. | |
1741 | let attr = lstat(p)?; | |
1742 | if attr.file_type().is_symlink() { | |
1743 | crate::fs::remove_file(p) | |
1744 | } else { | |
5e7ed085 FG |
1745 | remove_dir_all_recursive(None, &cstr(p)?) |
1746 | } | |
1747 | } | |
1748 | ||
1749 | #[cfg(not(all(target_os = "macos", not(target_arch = "aarch64"))))] | |
1750 | pub fn remove_dir_all(p: &Path) -> io::Result<()> { | |
1751 | remove_dir_all_modern(p) | |
1752 | } | |
1753 | ||
1754 | #[cfg(all(target_os = "macos", not(target_arch = "aarch64")))] | |
1755 | pub fn remove_dir_all(p: &Path) -> io::Result<()> { | |
1756 | if macos_weak::has_openat() { | |
1757 | // openat() is available with macOS 10.10+, just like unlinkat() and fdopendir() | |
1758 | remove_dir_all_modern(p) | |
1759 | } else { | |
1760 | // fall back to classic implementation | |
1761 | crate::sys_common::fs::remove_dir_all(p) | |
3c0e092e XL |
1762 | } |
1763 | } | |
1764 | } |