1#[cfg(unix)]
2use std::os::unix::{self, fs::PermissionsExt as _};
3use std::{
4 borrow::Cow,
5 ffi::OsStr,
6 fs::{self, File, Metadata, Permissions, ReadDir},
7 io::{self, BufRead as _, BufReader, Lines, Write as _},
8 path::{self, Path, PathBuf},
9};
10
11#[cfg(unix)]
12use pathdiff::diff_paths;
13
14use super::{fs::FileExt as _, temp_path::TempPath};
15
16pub trait PathExt {
17 fn base(&self) -> io::Result<&Path>;
18 fn with_base(&self, base: impl AsRef<Path>) -> Cow<'_, Path>;
19
20 fn file_suffix(&self) -> Option<&OsStr>;
21
22 fn absolute(&self) -> io::Result<PathBuf>;
23
24 fn metadata(&self) -> io::Result<Metadata>;
25 fn symlink_metadata(&self) -> io::Result<Metadata>;
26 fn canonicalize(&self) -> io::Result<PathBuf>;
27 fn read_link(&self) -> io::Result<PathBuf>;
28 fn read_dir(&self) -> io::Result<ReadDir>;
29
30 fn try_exists(&self) -> io::Result<bool>;
31
32 fn try_is_file(&self) -> io::Result<bool>;
33 fn try_is_dir(&self) -> io::Result<bool>;
34 fn try_is_symlink(&self) -> io::Result<bool>;
35
36 fn metadata_if_exists(&self) -> io::Result<Option<Metadata>>;
37 fn symlink_metadata_if_exists(&self) -> io::Result<Option<Metadata>>;
38 fn canonicalize_if_exists(&self) -> io::Result<Option<PathBuf>>;
39 fn read_link_if_exists(&self) -> io::Result<Option<PathBuf>>;
40 fn read_dir_if_exists(&self) -> io::Result<Option<ReadDir>>;
41
42 fn exists_nofollow(&self) -> bool;
43 fn is_file_nofollow(&self) -> bool;
44 fn is_dir_nofollow(&self) -> bool;
45
46 fn try_exists_nofollow(&self) -> io::Result<bool>;
47 fn try_is_file_nofollow(&self) -> io::Result<bool>;
48 fn try_is_dir_nofollow(&self) -> io::Result<bool>;
49
50 fn try_is_read_link_broken(&self) -> io::Result<bool>;
51 fn try_is_read_link_broken_if_exists(&self) -> io::Result<Option<bool>>;
52
53 fn try_is_read_dir_empty(&self) -> io::Result<bool>;
54 fn try_is_read_dir_empty_if_exists(&self) -> io::Result<Option<bool>>;
55
56 fn create_new(&self) -> io::Result<File>;
57 fn create_new_if_not_exists(&self) -> io::Result<Option<File>>;
58
59 fn create(&self) -> io::Result<File>;
60 fn create_if_not_exists(&self) -> io::Result<Option<File>>;
61
62 fn open(&self) -> io::Result<File>;
63 fn open_if_exists(&self) -> io::Result<Option<File>>;
64
65 fn read(&self) -> io::Result<Vec<u8>>;
66 fn read_if_exists(&self) -> io::Result<Option<Vec<u8>>>;
67
68 fn read_to_string(&self) -> io::Result<String>;
69 fn read_to_string_if_exists(&self) -> io::Result<Option<String>>;
70
71 fn read_lines(&self) -> io::Result<Lines<BufReader<File>>>;
72 fn read_lines_if_exists(&self) -> io::Result<Option<Lines<BufReader<File>>>>;
73
74 fn create_dir_all(self) -> io::Result<Self>
75 where
76 Self: Sized;
77
78 fn create_dir(self) -> io::Result<Self>
79 where
80 Self: Sized;
81 fn create_dir_if_not_exists(self) -> io::Result<Option<Self>>
82 where
83 Self: Sized;
84
85 fn write_new(self, contents: impl AsRef<[u8]>) -> io::Result<Self>
86 where
87 Self: Sized;
88 fn write_new_if_not_exists(self, contents: impl AsRef<[u8]>) -> io::Result<Option<Self>>
89 where
90 Self: Sized;
91
92 fn write(self, contents: impl AsRef<[u8]>) -> io::Result<Self>
93 where
94 Self: Sized;
95 fn write_if_exists(self, contents: impl AsRef<[u8]>) -> io::Result<Option<Self>>
96 where
97 Self: Sized;
98
99 fn append(self, contents: impl AsRef<[u8]>) -> io::Result<Self>
100 where
101 Self: Sized;
102 fn append_if_exists(self, contents: impl AsRef<[u8]>) -> io::Result<Option<Self>>
103 where
104 Self: Sized;
105
106 fn copy(self, to: impl AsRef<Path>) -> io::Result<Self>
107 where
108 Self: Sized;
109 fn copy_if_exists(self, to: impl AsRef<Path>) -> io::Result<Option<Self>>
110 where
111 Self: Sized;
112
113 fn rename(self, to: impl AsRef<Path>) -> io::Result<Self>
114 where
115 Self: Sized;
116 fn rename_if_exists(self, to: impl AsRef<Path>) -> io::Result<Option<Self>>
117 where
118 Self: Sized;
119
120 fn remove_file(self) -> io::Result<Self>
121 where
122 Self: Sized;
123 fn remove_file_if_exists(self) -> io::Result<Option<Self>>
124 where
125 Self: Sized;
126
127 fn remove_dir(self) -> io::Result<Self>
128 where
129 Self: Sized;
130 fn remove_dir_if_exists(self) -> io::Result<Option<Self>>
131 where
132 Self: Sized;
133
134 fn remove_dir_all(self) -> io::Result<Self>
135 where
136 Self: Sized;
137 fn remove_dir_all_if_exists(self) -> io::Result<Option<Self>>
138 where
139 Self: Sized;
140
141 fn hard_link(self, link: impl AsRef<Path>) -> io::Result<Self>
142 where
143 Self: Sized;
144 fn hard_link_if_exists(self, link: impl AsRef<Path>) -> io::Result<Option<Self>>
145 where
146 Self: Sized;
147 fn hard_link_atomic(self, link: impl AsRef<Path>) -> io::Result<Self>
148 where
149 Self: Sized;
150 fn hard_link_atomic_if_exists(self, link: impl AsRef<Path>) -> io::Result<Option<Self>>
151 where
152 Self: Sized;
153
154 #[cfg(unix)]
155 fn symlink(self, link: impl AsRef<Path>) -> io::Result<Self>
156 where
157 Self: Sized;
158 #[cfg(unix)]
159 fn symlink_atomic(self, link: impl AsRef<Path>) -> io::Result<Self>
160 where
161 Self: Sized;
162
163 #[cfg(unix)]
164 fn symlink_absolute(self, link: impl AsRef<Path>) -> io::Result<Self>
165 where
166 Self: Sized;
167 #[cfg(unix)]
168 fn symlink_absolute_atomic(self, link: impl AsRef<Path>) -> io::Result<Self>
169 where
170 Self: Sized;
171
172 #[cfg(unix)]
173 fn symlink_relative(self, link: impl AsRef<Path>) -> io::Result<Self>
174 where
175 Self: Sized;
176 #[cfg(unix)]
177 fn symlink_relative_atomic(self, link: impl AsRef<Path>) -> io::Result<Self>
178 where
179 Self: Sized;
180
181 fn set_permissions(self, perm: Permissions) -> io::Result<Self>
182 where
183 Self: Sized;
184 fn set_permissions_if_exists(self, perm: Permissions) -> io::Result<Option<Self>>
185 where
186 Self: Sized;
187
188 fn set_permissions_readonly(self, readonly: bool) -> io::Result<Self>
189 where
190 Self: Sized;
191 fn set_permissions_readonly_if_exists(self, readonly: bool) -> io::Result<Option<Self>>
192 where
193 Self: Sized;
194
195 #[cfg(unix)]
196 fn set_permissions_mode(self, mode: u32) -> io::Result<Self>
197 where
198 Self: Sized;
199 #[cfg(unix)]
200 fn set_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>>
201 where
202 Self: Sized;
203 #[cfg(unix)]
204 fn add_permissions_mode(self, mode: u32) -> io::Result<Self>
205 where
206 Self: Sized;
207 #[cfg(unix)]
208 fn add_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>>
209 where
210 Self: Sized;
211 #[cfg(unix)]
212 fn remove_permissions_mode(self, mode: u32) -> io::Result<Self>
213 where
214 Self: Sized;
215 #[cfg(unix)]
216 fn remove_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>>
217 where
218 Self: Sized;
219
220 #[cfg(unix)]
221 fn chown(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Self>
222 where
223 Self: Sized;
224 #[cfg(unix)]
225 fn chown_if_exists(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Option<Self>>
226 where
227 Self: Sized;
228
229 #[cfg(unix)]
230 fn chown_nofollow(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Self>
231 where
232 Self: Sized;
233 #[cfg(unix)]
234 fn chown_nofollow_if_exists(
235 self,
236 uid: Option<u32>,
237 gid: Option<u32>,
238 ) -> io::Result<Option<Self>>
239 where
240 Self: Sized;
241
242 #[cfg(all(unix, not(target_os = "fuchsia")))]
243 fn chroot(self) -> io::Result<Self>
244 where
245 Self: Sized;
246 #[cfg(all(unix, not(target_os = "fuchsia")))]
247 fn chroot_if_exists(self) -> io::Result<Option<Self>>
248 where
249 Self: Sized;
250}
251
252impl<T: AsRef<Path>> PathExt for T {
253 #[inline]
254 fn base(&self) -> io::Result<&Path> {
255 self.as_ref()
256 .parent()
257 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Path has no parent"))
258 }
259
260 #[inline]
261 fn with_base(&self, base: impl AsRef<Path>) -> Cow<'_, Path> {
262 if self.as_ref().is_relative() {
263 let path = base.as_ref().join(self);
264
265 Cow::Owned(path)
266 } else {
267 Cow::Borrowed(self.as_ref())
268 }
269 }
270
271 #[inline]
272 fn file_suffix(&self) -> Option<&OsStr> {
273 let file_name = self.as_ref().file_name()?;
274 let file_prefix = self.as_ref().file_prefix()?;
275
276 let bytes = file_name.as_encoded_bytes();
277
278 let start = file_prefix.as_encoded_bytes().len() + 1;
279
280 if start <= bytes.len() {
281 let slice = &bytes[start..];
282
283 let os_str = unsafe { OsStr::from_encoded_bytes_unchecked(slice) };
284
285 Some(os_str)
286 } else {
287 None
288 }
289 }
290
291 #[inline]
292 fn absolute(&self) -> io::Result<PathBuf> {
293 path::absolute(self)
294 }
295
296 #[inline]
297 fn metadata(&self) -> io::Result<Metadata> {
298 self.as_ref().metadata()
299 }
300
301 #[inline]
302 fn symlink_metadata(&self) -> io::Result<Metadata> {
303 self.as_ref().symlink_metadata()
304 }
305
306 #[inline]
307 fn canonicalize(&self) -> io::Result<PathBuf> {
308 self.as_ref().canonicalize()
309 }
310
311 #[inline]
312 fn read_link(&self) -> io::Result<PathBuf> {
313 self.as_ref().read_link()
314 }
315
316 #[inline]
317 fn read_dir(&self) -> io::Result<ReadDir> {
318 self.as_ref().read_dir()
319 }
320
321 #[inline]
322 fn try_exists(&self) -> io::Result<bool> {
323 self.as_ref().try_exists()
324 }
325
326 #[inline]
327 fn try_is_file(&self) -> io::Result<bool> {
328 self.metadata_if_exists()
329 .map(|meta_opt| meta_opt.map(|meta| meta.is_file()).unwrap_or(false))
330 }
331
332 #[inline]
333 fn try_is_dir(&self) -> io::Result<bool> {
334 self.metadata_if_exists()
335 .map(|meta_opt| meta_opt.map(|meta| meta.is_dir()).unwrap_or(false))
336 }
337
338 #[inline]
339 fn try_is_symlink(&self) -> io::Result<bool> {
340 self.symlink_metadata_if_exists()
341 .map(|meta_opt| meta_opt.map(|meta| meta.is_symlink()).unwrap_or(false))
342 }
343
344 #[inline]
345 fn metadata_if_exists(&self) -> io::Result<Option<Metadata>> {
346 match self.metadata() {
347 Ok(meta) => Ok(Some(meta)),
348 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
349 Err(err) => Err(err),
350 }
351 }
352
353 #[inline]
354 fn symlink_metadata_if_exists(&self) -> io::Result<Option<Metadata>> {
355 match self.symlink_metadata() {
356 Ok(meta) => Ok(Some(meta)),
357 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
358 Err(err) => Err(err),
359 }
360 }
361
362 #[inline]
363 fn canonicalize_if_exists(&self) -> io::Result<Option<PathBuf>> {
364 match self.canonicalize() {
365 Ok(path) => Ok(Some(path)),
366 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
367 Err(err) => Err(err),
368 }
369 }
370
371 #[inline]
372 fn read_link_if_exists(&self) -> io::Result<Option<PathBuf>> {
373 match self.read_link() {
374 Ok(path) => Ok(Some(path)),
375 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
376 Err(err) => Err(err),
377 }
378 }
379
380 #[inline]
381 fn read_dir_if_exists(&self) -> io::Result<Option<ReadDir>> {
382 match self.read_dir() {
383 Ok(entries) => Ok(Some(entries)),
384 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
385 Err(err) => Err(err),
386 }
387 }
388
389 #[inline]
390 fn exists_nofollow(&self) -> bool {
391 self.symlink_metadata().is_ok()
392 }
393
394 #[inline]
395 fn is_file_nofollow(&self) -> bool {
396 self.symlink_metadata()
397 .map(|meta| meta.is_file())
398 .unwrap_or(false)
399 }
400
401 #[inline]
402 fn is_dir_nofollow(&self) -> bool {
403 self.symlink_metadata()
404 .map(|meta| meta.is_dir())
405 .unwrap_or(false)
406 }
407
408 #[inline]
409 fn try_exists_nofollow(&self) -> io::Result<bool> {
410 self.symlink_metadata_if_exists()
411 .map(|meta_opt| meta_opt.is_some())
412 }
413
414 #[inline]
415 fn try_is_file_nofollow(&self) -> io::Result<bool> {
416 self.symlink_metadata_if_exists()
417 .map(|meta_opt| meta_opt.map(|meta| meta.is_file()).unwrap_or(false))
418 }
419
420 #[inline]
421 fn try_is_dir_nofollow(&self) -> io::Result<bool> {
422 self.symlink_metadata_if_exists()
423 .map(|meta_opt| meta_opt.map(|meta| meta.is_dir()).unwrap_or(false))
424 }
425
426 #[inline]
427 fn try_is_read_link_broken(&self) -> io::Result<bool> {
428 self.read_link()?;
429
430 self.try_exists().map(|is_not_broken| !is_not_broken)
431 }
432
433 #[inline]
434 fn try_is_read_link_broken_if_exists(&self) -> io::Result<Option<bool>> {
435 match self.try_is_read_link_broken() {
436 Ok(is_broken) => Ok(Some(is_broken)),
437 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
438 Err(err) => Err(err),
439 }
440 }
441
442 #[inline]
443 fn try_is_read_dir_empty(&self) -> io::Result<bool> {
444 let mut entries = self.read_dir()?;
445
446 entries
447 .next()
448 .transpose()
449 .map(|entry_opt| entry_opt.is_none())
450 }
451
452 #[inline]
453 fn try_is_read_dir_empty_if_exists(&self) -> io::Result<Option<bool>> {
454 match self.try_is_read_dir_empty() {
455 Ok(is_empty) => Ok(Some(is_empty)),
456 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
457 Err(err) => Err(err),
458 }
459 }
460
461 #[inline]
462 fn create_new(&self) -> io::Result<File> {
463 File::create_new(self)
464 }
465
466 #[inline]
467 fn create_new_if_not_exists(&self) -> io::Result<Option<File>> {
468 File::create_new_if_not_exists(self)
469 }
470
471 #[inline]
472 fn create(&self) -> io::Result<File> {
473 File::create(self)
474 }
475
476 #[inline]
477 fn create_if_not_exists(&self) -> io::Result<Option<File>> {
478 File::create_if_not_exists(self)
479 }
480
481 #[inline]
482 fn open(&self) -> io::Result<File> {
483 File::open(self)
484 }
485
486 #[inline]
487 fn open_if_exists(&self) -> io::Result<Option<File>> {
488 File::open_if_exists(self)
489 }
490
491 #[inline]
492 fn read(&self) -> io::Result<Vec<u8>> {
493 fs::read(self)
494 }
495
496 #[inline]
497 fn read_if_exists(&self) -> io::Result<Option<Vec<u8>>> {
498 match self.read() {
499 Ok(bytes) => Ok(Some(bytes)),
500 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
501 Err(err) => Err(err),
502 }
503 }
504
505 #[inline]
506 fn read_to_string(&self) -> io::Result<String> {
507 fs::read_to_string(self)
508 }
509
510 #[inline]
511 fn read_to_string_if_exists(&self) -> io::Result<Option<String>> {
512 match self.read_to_string() {
513 Ok(string) => Ok(Some(string)),
514 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
515 Err(err) => Err(err),
516 }
517 }
518
519 #[inline]
520 fn read_lines(&self) -> io::Result<Lines<BufReader<File>>> {
521 let file = self.open()?;
522
523 let buf_reader = BufReader::new(file);
524
525 let lines = buf_reader.lines();
526
527 Ok(lines)
528 }
529
530 #[inline]
531 fn read_lines_if_exists(&self) -> io::Result<Option<Lines<BufReader<File>>>> {
532 match self.read_lines() {
533 Ok(lines) => Ok(Some(lines)),
534 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
535 Err(err) => Err(err),
536 }
537 }
538
539 #[inline]
540 fn create_dir_all(self) -> io::Result<Self> {
541 fs::create_dir_all(self.as_ref())?;
542
543 Ok(self)
544 }
545
546 #[inline]
547 fn create_dir(self) -> io::Result<Self> {
548 fs::create_dir(self.as_ref())?;
549
550 Ok(self)
551 }
552
553 #[inline]
554 fn create_dir_if_not_exists(self) -> io::Result<Option<Self>> {
555 match self.as_ref().create_dir() {
556 Ok(_) => Ok(Some(self)),
557 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(None),
558 Err(err) => Err(err),
559 }
560 }
561
562 #[inline]
563 fn write_new(self, contents: impl AsRef<[u8]>) -> io::Result<Self> {
564 let mut options = File::options();
565
566 options.write(true).create_new(true);
567
568 let mut file = options.open(self.as_ref())?;
569
570 file.write_all(contents.as_ref())?;
571
572 Ok(self)
573 }
574
575 #[inline]
576 fn write_new_if_not_exists(self, contents: impl AsRef<[u8]>) -> io::Result<Option<Self>> {
577 match self.as_ref().write_new(contents) {
578 Ok(_) => Ok(Some(self)),
579 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => Ok(None),
580 Err(err) => Err(err),
581 }
582 }
583
584 #[inline]
585 fn write(self, contents: impl AsRef<[u8]>) -> io::Result<Self> {
586 fs::write(self.as_ref(), contents)?;
587
588 Ok(self)
589 }
590
591 #[inline]
592 fn write_if_exists(self, contents: impl AsRef<[u8]>) -> io::Result<Option<Self>> {
593 let mut options = File::options();
594
595 options.write(true).truncate(true);
596
597 match options.open(self.as_ref()) {
598 Ok(mut file) => {
599 file.write_all(contents.as_ref())?;
600
601 Ok(Some(self))
602 },
603 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
604 Err(err) => Err(err),
605 }
606 }
607
608 #[inline]
609 fn append(self, contents: impl AsRef<[u8]>) -> io::Result<Self> {
610 let mut options = File::options();
611
612 options.append(true).create(true);
613
614 let mut file = options.open(self.as_ref())?;
615
616 file.write_all(contents.as_ref())?;
617
618 Ok(self)
619 }
620
621 #[inline]
622 fn append_if_exists(self, contents: impl AsRef<[u8]>) -> io::Result<Option<Self>> {
623 let mut options = File::options();
624
625 options.append(true);
626
627 match options.open(self.as_ref()) {
628 Ok(mut file) => {
629 file.write_all(contents.as_ref())?;
630
631 Ok(Some(self))
632 },
633 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
634 Err(err) => Err(err),
635 }
636 }
637
638 #[inline]
639 fn copy(self, to: impl AsRef<Path>) -> io::Result<Self> {
640 fs::copy(self.as_ref(), to)?;
641
642 Ok(self)
643 }
644
645 #[inline]
646 fn copy_if_exists(self, to: impl AsRef<Path>) -> io::Result<Option<Self>> {
647 match self.as_ref().copy(to) {
648 Ok(_) => Ok(Some(self)),
649 Err(err) if err.kind() == io::ErrorKind::NotFound => {
650 if !self.try_exists()? {
651 Ok(None)
652 } else {
653 Err(err)
654 }
655 },
656 Err(err) => Err(err),
657 }
658 }
659
660 #[inline]
661 fn rename(self, to: impl AsRef<Path>) -> io::Result<Self> {
662 fs::rename(self.as_ref(), to)?;
663
664 Ok(self)
665 }
666
667 #[inline]
668 fn rename_if_exists(self, to: impl AsRef<Path>) -> io::Result<Option<Self>> {
669 match self.as_ref().rename(to) {
670 Ok(_) => Ok(Some(self)),
671 Err(err) if err.kind() == io::ErrorKind::NotFound => {
672 if !self.try_exists_nofollow()? {
673 Ok(None)
674 } else {
675 Err(err)
676 }
677 },
678 Err(err) => Err(err),
679 }
680 }
681
682 #[inline]
683 fn remove_file(self) -> io::Result<Self> {
684 fs::remove_file(self.as_ref())?;
685
686 Ok(self)
687 }
688
689 #[inline]
690 fn remove_file_if_exists(self) -> io::Result<Option<Self>> {
691 match self.as_ref().remove_file() {
692 Ok(_) => Ok(Some(self)),
693 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
694 Err(err) => Err(err),
695 }
696 }
697
698 #[inline]
699 fn remove_dir(self) -> io::Result<Self> {
700 fs::remove_dir(self.as_ref())?;
701
702 Ok(self)
703 }
704
705 #[inline]
706 fn remove_dir_if_exists(self) -> io::Result<Option<Self>> {
707 match self.as_ref().remove_dir() {
708 Ok(_) => Ok(Some(self)),
709 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
710 Err(err) => Err(err),
711 }
712 }
713
714 #[inline]
715 fn remove_dir_all(self) -> io::Result<Self> {
716 fs::remove_dir_all(self.as_ref())?;
717
718 Ok(self)
719 }
720
721 #[inline]
722 fn remove_dir_all_if_exists(self) -> io::Result<Option<Self>> {
723 match self.as_ref().remove_dir_all() {
724 Ok(_) => Ok(Some(self)),
725 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
726 Err(err) => Err(err),
727 }
728 }
729
730 #[inline]
731 fn hard_link(self, link: impl AsRef<Path>) -> io::Result<Self> {
732 fs::hard_link(self.as_ref(), link)?;
733
734 Ok(self)
735 }
736
737 #[inline]
738 fn hard_link_if_exists(self, link: impl AsRef<Path>) -> io::Result<Option<Self>> {
739 match self.as_ref().hard_link(link) {
740 Ok(_) => Ok(Some(self)),
741 Err(err) if err.kind() == io::ErrorKind::NotFound => {
742 if !self.try_exists_nofollow()? {
743 Ok(None)
744 } else {
745 Err(err)
746 }
747 },
748 Err(err) => Err(err),
749 }
750 }
751
752 #[inline]
753 fn hard_link_atomic(self, link: impl AsRef<Path>) -> io::Result<Self> {
754 let temp = TempPath::try_from_path(link.as_ref())?;
755
756 self.as_ref().hard_link(temp.path())?;
757
758 temp.persist(link)?;
759
760 Ok(self)
761 }
762
763 #[inline]
764 fn hard_link_atomic_if_exists(self, link: impl AsRef<Path>) -> io::Result<Option<Self>> {
765 match self.as_ref().hard_link_atomic(link) {
766 Ok(_) => Ok(Some(self)),
767 Err(err) if err.kind() == io::ErrorKind::NotFound => {
768 if !self.try_exists_nofollow()? {
769 Ok(None)
770 } else {
771 Err(err)
772 }
773 },
774 Err(err) => Err(err),
775 }
776 }
777
778 #[cfg(unix)]
779 #[inline]
780 fn symlink(self, link: impl AsRef<Path>) -> io::Result<Self> {
781 unix::fs::symlink(self.as_ref(), link)?;
782
783 Ok(self)
784 }
785
786 #[cfg(unix)]
787 #[inline]
788 fn symlink_atomic(self, link: impl AsRef<Path>) -> io::Result<Self> {
789 let temp = TempPath::try_from_path(link.as_ref())?;
790
791 self.as_ref().symlink(temp.path())?;
792
793 temp.persist(link)?;
794
795 Ok(self)
796 }
797
798 #[cfg(unix)]
799 #[inline]
800 fn symlink_absolute(self, link: impl AsRef<Path>) -> io::Result<Self> {
801 #[expect(unstable_name_collisions)]
802 let absolute = self.as_ref().absolute()?;
803
804 absolute.symlink(link)?;
805
806 Ok(self)
807 }
808
809 #[cfg(unix)]
810 #[inline]
811 fn symlink_absolute_atomic(self, link: impl AsRef<Path>) -> io::Result<Self> {
812 #[expect(unstable_name_collisions)]
813 let absolute = self.as_ref().absolute()?;
814
815 absolute.symlink_atomic(link)?;
816
817 Ok(self)
818 }
819
820 #[cfg(unix)]
821 #[inline]
822 fn symlink_relative(self, link: impl AsRef<Path>) -> io::Result<Self> {
823 let base = link.base()?;
824
825 let relative = diff_paths(self.as_ref(), base)
826 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Failed to diff paths"))?;
827
828 relative.symlink(link)?;
829
830 Ok(self)
831 }
832
833 #[cfg(unix)]
834 #[inline]
835 fn symlink_relative_atomic(self, link: impl AsRef<Path>) -> io::Result<Self> {
836 let base = link.base()?;
837
838 let relative = diff_paths(self.as_ref(), base)
839 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Failed to diff paths"))?;
840
841 relative.symlink_atomic(link)?;
842
843 Ok(self)
844 }
845
846 #[inline]
847 fn set_permissions(self, perm: Permissions) -> io::Result<Self> {
848 fs::set_permissions(self.as_ref(), perm)?;
849
850 Ok(self)
851 }
852
853 #[inline]
854 fn set_permissions_if_exists(self, perm: Permissions) -> io::Result<Option<Self>> {
855 match self.as_ref().set_permissions(perm) {
856 Ok(_) => Ok(Some(self)),
857 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
858 Err(err) => Err(err),
859 }
860 }
861
862 #[inline]
863 fn set_permissions_readonly(self, readonly: bool) -> io::Result<Self> {
864 let meta = self.metadata()?;
865
866 let mut perm = meta.permissions();
867
868 perm.set_readonly(readonly);
869
870 self.set_permissions(perm)
871 }
872
873 #[inline]
874 fn set_permissions_readonly_if_exists(self, readonly: bool) -> io::Result<Option<Self>> {
875 match self.as_ref().set_permissions_readonly(readonly) {
876 Ok(_) => Ok(Some(self)),
877 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
878 Err(err) => Err(err),
879 }
880 }
881
882 #[cfg(unix)]
883 #[inline]
884 fn set_permissions_mode(self, mode: u32) -> io::Result<Self> {
885 let perm = Permissions::from_mode(mode);
886
887 self.set_permissions(perm)
888 }
889
890 #[cfg(unix)]
891 #[inline]
892 fn set_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>> {
893 match self.as_ref().set_permissions_mode(mode) {
894 Ok(_) => Ok(Some(self)),
895 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
896 Err(err) => Err(err),
897 }
898 }
899
900 #[cfg(unix)]
901 #[inline]
902 fn add_permissions_mode(self, mode: u32) -> io::Result<Self> {
903 let meta = self.metadata()?;
904
905 let perm = meta.permissions();
906
907 self.set_permissions_mode(perm.mode() | mode)
908 }
909
910 #[cfg(unix)]
911 #[inline]
912 fn add_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>> {
913 match self.as_ref().add_permissions_mode(mode) {
914 Ok(_) => Ok(Some(self)),
915 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
916 Err(err) => Err(err),
917 }
918 }
919
920 #[cfg(unix)]
921 #[inline]
922 fn remove_permissions_mode(self, mode: u32) -> io::Result<Self> {
923 let meta = self.metadata()?;
924
925 let perm = meta.permissions();
926
927 self.set_permissions_mode(perm.mode() & !mode)
928 }
929
930 #[cfg(unix)]
931 #[inline]
932 fn remove_permissions_mode_if_exists(self, mode: u32) -> io::Result<Option<Self>> {
933 match self.as_ref().remove_permissions_mode(mode) {
934 Ok(_) => Ok(Some(self)),
935 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
936 Err(err) => Err(err),
937 }
938 }
939
940 #[cfg(unix)]
941 #[inline]
942 fn chown(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Self> {
943 unix::fs::chown(self.as_ref(), uid, gid)?;
944
945 Ok(self)
946 }
947
948 #[cfg(unix)]
949 #[inline]
950 fn chown_if_exists(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Option<Self>> {
951 match self.as_ref().chown(uid, gid) {
952 Ok(_) => Ok(Some(self)),
953 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
954 Err(err) => Err(err),
955 }
956 }
957
958 #[cfg(unix)]
959 #[inline]
960 fn chown_nofollow(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Self> {
961 unix::fs::lchown(self.as_ref(), uid, gid)?;
962
963 Ok(self)
964 }
965
966 #[cfg(unix)]
967 #[inline]
968 fn chown_nofollow_if_exists(
969 self,
970 uid: Option<u32>,
971 gid: Option<u32>,
972 ) -> io::Result<Option<Self>> {
973 match self.as_ref().chown_nofollow(uid, gid) {
974 Ok(_) => Ok(Some(self)),
975 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
976 Err(err) => Err(err),
977 }
978 }
979
980 #[cfg(all(unix, not(target_os = "fuchsia")))]
981 #[inline]
982 fn chroot(self) -> io::Result<Self> {
983 unix::fs::chroot(self.as_ref())?;
984
985 Ok(self)
986 }
987
988 #[cfg(all(unix, not(target_os = "fuchsia")))]
989 #[inline]
990 fn chroot_if_exists(self) -> io::Result<Option<Self>> {
991 match self.as_ref().chroot() {
992 Ok(_) => Ok(Some(self)),
993 Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
994 Err(err) => Err(err),
995 }
996 }
997}
998
999macro_rules! define_option_path_ext {
1000 (
1001 $(
1002 $(#[$meta:meta])*
1003 fn $method:ident(self $(, $arg:ident: $ty:ty)* $(,)*) -> $ret:ty;
1004 )*
1005 ) => {
1006 pub trait OptionPathExt<T> {
1007 $(
1008 $(#[$meta])*
1009 fn $method(self $(, $arg: $ty)*) -> $ret;
1010 )*
1011 }
1012
1013 impl<T: PathExt> OptionPathExt<T> for Option<T> {
1014 $(
1015 #[inline]
1016 $(#[$meta])*
1017 fn $method(self $(, $arg: $ty)*) -> $ret {
1018 match self {
1019 Some(path) => path.$method($($arg),*).map(Some),
1020 None => Ok(None),
1021 }
1022 }
1023 )*
1024 }
1025 };
1026}
1027
1028define_option_path_ext! {
1029 fn metadata(self) -> io::Result<Option<Metadata>>;
1030 fn symlink_metadata(self) -> io::Result<Option<Metadata>>;
1031 fn canonicalize(self) -> io::Result<Option<PathBuf>>;
1032 fn read_link(self) -> io::Result<Option<PathBuf>>;
1033 fn read_dir(self) -> io::Result<Option<ReadDir>>;
1034
1035 fn try_exists(self) -> io::Result<Option<bool>>;
1036
1037 fn try_is_file(self) -> io::Result<Option<bool>>;
1038 fn try_is_dir(self) -> io::Result<Option<bool>>;
1039 fn try_is_symlink(self) -> io::Result<Option<bool>>;
1040
1041 fn try_exists_nofollow(self) -> io::Result<Option<bool>>;
1042 fn try_is_file_nofollow(self) -> io::Result<Option<bool>>;
1043 fn try_is_dir_nofollow(self) -> io::Result<Option<bool>>;
1044
1045 fn try_is_read_link_broken(self) -> io::Result<Option<bool>>;
1046
1047 fn try_is_read_dir_empty(self) -> io::Result<Option<bool>>;
1048
1049 fn create_new(self) -> io::Result<Option<File>>;
1050
1051 fn create(self) -> io::Result<Option<File>>;
1052
1053 fn open(self) -> io::Result<Option<File>>;
1054
1055 fn read(self) -> io::Result<Option<Vec<u8>>>;
1056
1057 fn read_to_string(self) -> io::Result<Option<String>>;
1058
1059 fn read_lines(self) -> io::Result<Option<Lines<BufReader<File>>>>;
1060
1061 fn create_dir_all(self) -> io::Result<Option<T>>;
1062
1063 fn create_dir(self) -> io::Result<Option<T>>;
1064
1065 fn write_new(self, contents: impl AsRef<[u8]>) -> io::Result<Option<T>>;
1066
1067 fn write(self, contents: impl AsRef<[u8]>) -> io::Result<Option<T>>;
1068
1069 fn append(self, contents: impl AsRef<[u8]>) -> io::Result<Option<T>>;
1070
1071 fn copy(self, to: impl AsRef<Path>) -> io::Result<Option<T>>;
1072
1073 fn rename(self, to: impl AsRef<Path>) -> io::Result<Option<T>>;
1074
1075 fn remove_file(self) -> io::Result<Option<T>>;
1076
1077 fn remove_dir(self) -> io::Result<Option<T>>;
1078
1079 fn remove_dir_all(self) -> io::Result<Option<T>>;
1080
1081 fn hard_link(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1082 fn hard_link_atomic(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1083
1084 #[cfg(unix)]
1085 fn symlink(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1086 #[cfg(unix)]
1087 fn symlink_atomic(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1088
1089 #[cfg(unix)]
1090 fn symlink_absolute(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1091 #[cfg(unix)]
1092 fn symlink_absolute_atomic(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1093
1094 #[cfg(unix)]
1095 fn symlink_relative(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1096 #[cfg(unix)]
1097 fn symlink_relative_atomic(self, link: impl AsRef<Path>) -> io::Result<Option<T>>;
1098
1099 fn set_permissions(self, perm: Permissions) -> io::Result<Option<T>>;
1100
1101 fn set_permissions_readonly(self, readonly: bool) -> io::Result<Option<T>>;
1102
1103 #[cfg(unix)]
1104 fn set_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
1105 #[cfg(unix)]
1106 fn add_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
1107 #[cfg(unix)]
1108 fn remove_permissions_mode(self, mode: u32) -> io::Result<Option<T>>;
1109
1110 #[cfg(unix)]
1111 fn chown(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Option<T>>;
1112
1113 #[cfg(unix)]
1114 fn chown_nofollow(self, uid: Option<u32>, gid: Option<u32>) -> io::Result<Option<T>>;
1115
1116 #[cfg(all(unix, not(target_os = "fuchsia")))]
1117 fn chroot(self) -> io::Result<Option<T>>;
1118}