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