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