1#[cfg(feature = "fs_utf8")]
2use camino::Utf8Path;
3#[cfg(not(windows))]
4use cap_primitives::fs::symlink;
5use cap_primitives::fs::{
6 access, open_dir_nofollow, set_symlink_permissions, set_times, set_times_nofollow,
7 FollowSymlinks, Permissions,
8};
9#[cfg(windows)]
10use cap_primitives::fs::{symlink_dir, symlink_file};
11use io_lifetimes::AsFilelike;
12use std::io;
13use std::path::Path;
14
15pub use cap_primitives::fs::{AccessType, SystemTimeSpec};
16
17pub trait DirExt {
19 fn set_atime<P: AsRef<Path>>(&self, path: P, atime: SystemTimeSpec) -> io::Result<()>;
25
26 fn set_mtime<P: AsRef<Path>>(&self, path: P, mtime: SystemTimeSpec) -> io::Result<()>;
32
33 fn set_times<P: AsRef<Path>>(
39 &self,
40 path: P,
41 atime: Option<SystemTimeSpec>,
42 mtime: Option<SystemTimeSpec>,
43 ) -> io::Result<()>;
44
45 fn set_symlink_times<P: AsRef<Path>>(
52 &self,
53 path: P,
54 atime: Option<SystemTimeSpec>,
55 mtime: Option<SystemTimeSpec>,
56 ) -> io::Result<()>;
57
58 fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()>;
66
67 fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()>;
75
76 fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()>;
84
85 fn open_dir_nofollow<P: AsRef<Path>>(&self, path: P) -> io::Result<Self>
88 where
89 Self: Sized;
90
91 fn remove_file_or_symlink<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
97
98 fn access<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;
100
101 fn access_symlink<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;
104
105 fn set_symlink_permissions<P: AsRef<Path>>(&self, path: P, perm: Permissions)
108 -> io::Result<()>;
109}
110
111#[cfg(all(feature = "std", feature = "fs_utf8"))]
113pub trait DirExtUtf8 {
114 fn set_atime<P: AsRef<Utf8Path>>(&self, path: P, atime: SystemTimeSpec) -> io::Result<()>;
120
121 fn set_mtime<P: AsRef<Utf8Path>>(&self, path: P, mtime: SystemTimeSpec) -> io::Result<()>;
127
128 fn set_times<P: AsRef<Utf8Path>>(
134 &self,
135 path: P,
136 atime: Option<SystemTimeSpec>,
137 mtime: Option<SystemTimeSpec>,
138 ) -> io::Result<()>;
139
140 fn set_symlink_times<P: AsRef<Utf8Path>>(
147 &self,
148 path: P,
149 atime: Option<SystemTimeSpec>,
150 mtime: Option<SystemTimeSpec>,
151 ) -> io::Result<()>;
152
153 fn symlink<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(&self, src: P, dst: Q) -> io::Result<()>;
161
162 fn symlink_file<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(
170 &self,
171 src: P,
172 dst: Q,
173 ) -> io::Result<()>;
174
175 fn symlink_dir<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(&self, src: P, dst: Q)
183 -> io::Result<()>;
184
185 fn open_dir_nofollow<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<Self>
188 where
189 Self: Sized;
190
191 fn remove_file_or_symlink<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<()>;
197
198 fn access<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;
200
201 fn access_symlink<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()>;
204
205 fn set_symlink_permissions<P: AsRef<Utf8Path>>(
208 &self,
209 path: P,
210 perm: Permissions,
211 ) -> io::Result<()>;
212}
213
214#[cfg(feature = "std")]
215impl DirExt for cap_std::fs::Dir {
216 #[inline]
217 fn set_atime<P: AsRef<Path>>(&self, path: P, atime: SystemTimeSpec) -> io::Result<()> {
218 set_times(
219 &self.as_filelike_view::<std::fs::File>(),
220 path.as_ref(),
221 Some(atime),
222 None,
223 )
224 }
225
226 #[inline]
227 fn set_mtime<P: AsRef<Path>>(&self, path: P, mtime: SystemTimeSpec) -> io::Result<()> {
228 set_times(
229 &self.as_filelike_view::<std::fs::File>(),
230 path.as_ref(),
231 None,
232 Some(mtime),
233 )
234 }
235
236 #[inline]
237 fn set_times<P: AsRef<Path>>(
238 &self,
239 path: P,
240 atime: Option<SystemTimeSpec>,
241 mtime: Option<SystemTimeSpec>,
242 ) -> io::Result<()> {
243 set_times(
244 &self.as_filelike_view::<std::fs::File>(),
245 path.as_ref(),
246 atime,
247 mtime,
248 )
249 }
250
251 #[inline]
252 fn set_symlink_times<P: AsRef<Path>>(
253 &self,
254 path: P,
255 atime: Option<SystemTimeSpec>,
256 mtime: Option<SystemTimeSpec>,
257 ) -> io::Result<()> {
258 set_times_nofollow(
259 &self.as_filelike_view::<std::fs::File>(),
260 path.as_ref(),
261 atime,
262 mtime,
263 )
264 }
265
266 #[cfg(not(windows))]
267 #[inline]
268 fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()> {
269 symlink(
270 src.as_ref(),
271 &self.as_filelike_view::<std::fs::File>(),
272 dst.as_ref(),
273 )
274 }
275
276 #[cfg(not(windows))]
277 #[inline]
278 fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()> {
279 self.symlink(src, dst)
280 }
281
282 #[cfg(not(windows))]
283 #[inline]
284 fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()> {
285 self.symlink(src, dst)
286 }
287
288 #[cfg(windows)]
289 #[inline]
290 fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()> {
291 if self.metadata(src.as_ref())?.is_dir() {
292 self.symlink_dir(src, dst)
293 } else {
294 self.symlink_file(src, dst)
295 }
296 }
297
298 #[cfg(windows)]
299 #[inline]
300 fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()> {
301 symlink_file(
302 src.as_ref(),
303 &self.as_filelike_view::<std::fs::File>(),
304 dst.as_ref(),
305 )
306 }
307
308 #[cfg(windows)]
309 #[inline]
310 fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> io::Result<()> {
311 symlink_dir(
312 src.as_ref(),
313 &self.as_filelike_view::<std::fs::File>(),
314 dst.as_ref(),
315 )
316 }
317
318 #[inline]
319 fn open_dir_nofollow<P: AsRef<Path>>(&self, path: P) -> io::Result<Self> {
320 match open_dir_nofollow(&self.as_filelike_view::<std::fs::File>(), path.as_ref()) {
321 Ok(file) => Ok(Self::from_std_file(file)),
322 Err(e) => Err(e),
323 }
324 }
325
326 #[cfg(not(windows))]
327 #[inline]
328 fn remove_file_or_symlink<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
329 self.remove_file(path.as_ref())
330 }
331
332 #[cfg(windows)]
333 #[inline]
334 fn remove_file_or_symlink<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
335 use crate::OpenOptionsFollowExt;
336 use cap_primitives::fs::_WindowsByHandle;
337 use cap_std::fs::{OpenOptions, OpenOptionsExt};
338 use windows_sys::Win32::Storage::FileSystem::{
339 DELETE, FILE_ATTRIBUTE_DIRECTORY, FILE_FLAG_BACKUP_SEMANTICS,
340 FILE_FLAG_OPEN_REPARSE_POINT,
341 };
342 let path = path.as_ref();
343
344 let mut opts = OpenOptions::new();
345 opts.access_mode(DELETE);
346 opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS);
347 opts.follow(FollowSymlinks::No);
348 let file = self.open_with(path, &opts)?;
349
350 let meta = file.metadata()?;
351 if meta.file_type().is_symlink()
352 && meta.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY
353 {
354 self.remove_dir(path)?;
355 } else {
356 self.remove_file(path)?;
357 }
358
359 drop(file);
364
365 Ok(())
366 }
367
368 fn access<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
370 access(
371 &self.as_filelike_view::<std::fs::File>(),
372 path.as_ref(),
373 type_,
374 FollowSymlinks::Yes,
375 )
376 }
377
378 fn access_symlink<P: AsRef<Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
380 access(
381 &self.as_filelike_view::<std::fs::File>(),
382 path.as_ref(),
383 type_,
384 FollowSymlinks::No,
385 )
386 }
387
388 fn set_symlink_permissions<P: AsRef<Path>>(
391 &self,
392 path: P,
393 perm: Permissions,
394 ) -> io::Result<()> {
395 set_symlink_permissions(
396 &self.as_filelike_view::<std::fs::File>(),
397 path.as_ref(),
398 perm,
399 )
400 }
401}
402
403#[cfg(all(feature = "std", feature = "fs_utf8"))]
404impl DirExtUtf8 for cap_std::fs_utf8::Dir {
405 #[inline]
406 fn set_atime<P: AsRef<Utf8Path>>(&self, path: P, atime: SystemTimeSpec) -> io::Result<()> {
407 let path = from_utf8(path.as_ref())?;
408 set_times(
409 &self.as_filelike_view::<std::fs::File>(),
410 &path,
411 Some(atime),
412 None,
413 )
414 }
415
416 #[inline]
417 fn set_mtime<P: AsRef<Utf8Path>>(&self, path: P, mtime: SystemTimeSpec) -> io::Result<()> {
418 let path = from_utf8(path.as_ref())?;
419 set_times(
420 &self.as_filelike_view::<std::fs::File>(),
421 &path,
422 None,
423 Some(mtime),
424 )
425 }
426
427 #[inline]
428 fn set_times<P: AsRef<Utf8Path>>(
429 &self,
430 path: P,
431 atime: Option<SystemTimeSpec>,
432 mtime: Option<SystemTimeSpec>,
433 ) -> io::Result<()> {
434 let path = from_utf8(path.as_ref())?;
435 set_times(
436 &self.as_filelike_view::<std::fs::File>(),
437 &path,
438 atime,
439 mtime,
440 )
441 }
442
443 #[inline]
444 fn set_symlink_times<P: AsRef<Utf8Path>>(
445 &self,
446 path: P,
447 atime: Option<SystemTimeSpec>,
448 mtime: Option<SystemTimeSpec>,
449 ) -> io::Result<()> {
450 let path = from_utf8(path.as_ref())?;
451 set_times_nofollow(
452 &self.as_filelike_view::<std::fs::File>(),
453 &path,
454 atime,
455 mtime,
456 )
457 }
458
459 #[cfg(not(windows))]
460 #[inline]
461 fn symlink<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(&self, src: P, dst: Q) -> io::Result<()> {
462 Self::symlink(self, src, dst)
463 }
464
465 #[cfg(not(windows))]
466 #[inline]
467 fn symlink_file<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(
468 &self,
469 src: P,
470 dst: Q,
471 ) -> io::Result<()> {
472 Self::symlink(self, src, dst)
473 }
474
475 #[cfg(not(windows))]
476 #[inline]
477 fn symlink_dir<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(
478 &self,
479 src: P,
480 dst: Q,
481 ) -> io::Result<()> {
482 Self::symlink(self, src, dst)
483 }
484
485 #[cfg(windows)]
486 #[inline]
487 fn symlink<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(&self, src: P, dst: Q) -> io::Result<()> {
488 if self.metadata(src.as_ref())?.is_dir() {
489 Self::symlink_dir(self, src, dst)
490 } else {
491 Self::symlink_file(self, src, dst)
492 }
493 }
494
495 #[cfg(windows)]
496 #[inline]
497 fn symlink_file<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(
498 &self,
499 src: P,
500 dst: Q,
501 ) -> io::Result<()> {
502 Self::symlink_file(self, src, dst)
503 }
504
505 #[cfg(windows)]
506 #[inline]
507 fn symlink_dir<P: AsRef<Utf8Path>, Q: AsRef<Utf8Path>>(
508 &self,
509 src: P,
510 dst: Q,
511 ) -> io::Result<()> {
512 Self::symlink_dir(self, src, dst)
513 }
514
515 #[inline]
516 fn open_dir_nofollow<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<Self> {
517 match open_dir_nofollow(
518 &self.as_filelike_view::<std::fs::File>(),
519 path.as_ref().as_ref(),
520 ) {
521 Ok(file) => Ok(Self::from_std_file(file)),
522 Err(e) => Err(e),
523 }
524 }
525
526 #[cfg(not(windows))]
527 #[inline]
528 fn remove_file_or_symlink<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<()> {
529 self.remove_file(path.as_ref())
530 }
531
532 #[cfg(windows)]
533 #[inline]
534 fn remove_file_or_symlink<P: AsRef<Utf8Path>>(&self, path: P) -> io::Result<()> {
535 use crate::OpenOptionsFollowExt;
536 use cap_primitives::fs::_WindowsByHandle;
537 use cap_std::fs::{OpenOptions, OpenOptionsExt};
538 use windows_sys::Win32::Storage::FileSystem::{
539 DELETE, FILE_ATTRIBUTE_DIRECTORY, FILE_FLAG_BACKUP_SEMANTICS,
540 FILE_FLAG_OPEN_REPARSE_POINT,
541 };
542 let path = path.as_ref();
543
544 let mut opts = OpenOptions::new();
545 opts.access_mode(DELETE);
546 opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS);
547 opts.follow(FollowSymlinks::No);
548 let file = self.open_with(path, &opts)?;
549
550 let meta = file.metadata()?;
551 if meta.file_type().is_symlink()
552 && meta.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY
553 {
554 self.remove_dir(path)?;
555 } else {
556 self.remove_file(path)?;
557 }
558
559 drop(file);
564
565 Ok(())
566 }
567
568 fn access<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
570 access(
571 &self.as_filelike_view::<std::fs::File>(),
572 path.as_ref().as_ref(),
573 type_,
574 FollowSymlinks::Yes,
575 )
576 }
577
578 fn access_symlink<P: AsRef<Utf8Path>>(&self, path: P, type_: AccessType) -> io::Result<()> {
580 access(
581 &self.as_filelike_view::<std::fs::File>(),
582 path.as_ref().as_ref(),
583 type_,
584 FollowSymlinks::No,
585 )
586 }
587
588 fn set_symlink_permissions<P: AsRef<Utf8Path>>(
591 &self,
592 path: P,
593 perm: Permissions,
594 ) -> io::Result<()> {
595 set_symlink_permissions(
596 &self.as_filelike_view::<std::fs::File>(),
597 path.as_ref().as_ref(),
598 perm,
599 )
600 }
601}
602
603#[cfg(all(feature = "std", feature = "fs_utf8"))]
604#[cfg(not(feature = "arf_strings"))]
605fn from_utf8<'a>(path: &'a Utf8Path) -> io::Result<&'a std::path::Path> {
606 Ok(path.as_std_path())
607}
608
609#[cfg(all(feature = "std", feature = "fs_utf8"))]
610#[cfg(feature = "arf_strings")]
611fn from_utf8<'a>(path: &'a Utf8Path) -> io::Result<std::path::PathBuf> {
612 #[cfg(not(windows))]
613 let path = {
614 #[cfg(unix)]
615 use std::{ffi::OsString, os::unix::ffi::OsStringExt};
616 #[cfg(target_os = "wasi")]
617 use std::{ffi::OsString, os::wasi::ffi::OsStringExt};
618
619 let string = arf_strings::str_to_host(path.as_str())?;
620 OsString::from_vec(string.into_bytes())
621 };
622
623 #[cfg(windows)]
624 let path = arf_strings::str_to_host(path.as_str())?;
625
626 Ok(path.into())
627}