Skip to main content

path_extra/std/
path.rs

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}