fs_more/error/directory.rs
1use std::path::PathBuf;
2
3use thiserror::Error;
4
5use super::FileError;
6use crate::directory::DestinationDirectoryRule;
7
8
9/// Source directory path validation error.
10#[derive(Error, Debug)]
11pub enum SourceDirectoryPathValidationError {
12 /// The source directory (path to the directory you want to copy)
13 /// does not exist.
14 #[error(
15 "source directory path does not exist: {}",
16 .directory_path.display()
17 )]
18 NotFound {
19 /// Source directory path.
20 directory_path: PathBuf,
21 },
22
23 /// The source path (path to the directory you want to copy)
24 /// exists, but does not point to a directory.
25 #[error(
26 "source path exists, but is not a directory: {}",
27 .path.display()
28 )]
29 NotADirectory {
30 /// The base source path that was supposed to be a directory.
31 path: PathBuf,
32 },
33
34 /// The source directory could not be read, or its path could not be canonicalized.
35 ///
36 /// Among other things, this can happen due to missing read permissions.
37 ///
38 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
39 #[error("unable to access source directory: {}", .directory_path.display())]
40 UnableToAccess {
41 /// The exact path we are unable to access.
42 directory_path: PathBuf,
43
44 /// IO error describing why the source directory could not be accessed.
45 #[source]
46 error: std::io::Error,
47 },
48}
49
50
51
52/// Destination directory path validation error.
53#[derive(Error, Debug)]
54pub enum DestinationDirectoryPathValidationError {
55 /// The base source path (path to the directory you want to copy)
56 /// exists, but does not point to a directory.
57 #[error(
58 "destination path exists, but is not a directory: {}",
59 .directory_path.display()
60 )]
61 NotADirectory {
62 /// Destination directory path.
63 directory_path: PathBuf,
64 },
65
66 /// The destination directory could not be read, or its path could not be canonicalized.
67 ///
68 /// Among other things, this can happen due to missing read permissions.
69 ///
70 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
71 #[error("unable to access destination directory: {}", .directory_path.display())]
72 UnableToAccess {
73 /// The exact path we were unable to access.
74 directory_path: PathBuf,
75
76 /// IO error describing why the source directory could not be accessed.
77 #[source]
78 error: std::io::Error,
79 },
80
81 /// A destination directory or a file inside it already exists,
82 /// which is against the provided [`DestinationDirectoryRule`].
83 #[error(
84 "destination path already exists, which is against \
85 the configured destination directory rule ({:?}): {}",
86 .destination_directory_rule,
87 .path.display()
88 )]
89 AlreadyExists {
90 /// Path to the file or directory that should not exist based on the provided rules.
91 path: PathBuf,
92
93 /// Destination directory rule that made the existing destination
94 /// directory invalid (see [`DestinationDirectoryRule::DisallowExisting`]).
95 destination_directory_rule: DestinationDirectoryRule,
96 },
97
98 /// A destination directory or a file inside it exists and is not empty,
99 /// which is against the provided [`DestinationDirectoryRule`].
100 #[error(
101 "destination directory exists and is not empty, which is against \
102 the configured destination directory rule ({:?}): {}",
103 .destination_directory_rule,
104 .directory_path.display(),
105 )]
106 NotEmpty {
107 /// Path to the destination directory that should be empty based on the provided rules.
108 directory_path: PathBuf,
109
110 /// Destination directory rule that made the existing destination
111 /// directory invalid (see [`DestinationDirectoryRule::AllowEmpty`]).
112 destination_directory_rule: DestinationDirectoryRule,
113 },
114
115 /// The destination directory path equals or points inside the source directory,
116 /// which is very problematic for copies or moves.
117 #[error(
118 "destination directory path equals or points inside the source directory, \
119 which is invalid: {} (but source path is {})",
120 .destination_directory_path.display(),
121 .source_directory_path.display()
122 )]
123 DescendantOfSourceDirectory {
124 /// Invalid destination directory path.
125 destination_directory_path: PathBuf,
126
127 /// Corresponding source directory path.
128 source_directory_path: PathBuf,
129 },
130}
131
132
133
134/// Directory copy or move planning error.
135#[derive(Error, Debug)]
136pub enum DirectoryExecutionPlanError {
137 /// A source or destination directory, one of its sub-directories or a file
138 /// in it (or its metadata) cannot be read.
139 ///
140 /// For example, this can happen due to missing read permissions.
141 ///
142 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
143 #[error("unable to access path: {}", .path.display())]
144 UnableToAccess {
145 /// The path we were unable to access.
146 path: PathBuf,
147
148 /// IO error describing why the path could not be accessed.
149 #[source]
150 error: std::io::Error,
151 },
152
153 /// An item inside the source directory "escaped" outside of
154 /// the base source directory.
155 ///
156 /// # Implementation detail
157 /// This is an extremely unlikely error, because its requirement
158 /// is that [`std::fs::read_dir`]'s iterator returns a directory entry
159 /// outside the provided directory path.
160 ///
161 /// Even though this seems extremely unlikely, a `panic!` would be
162 /// an extreme measure due to the many types of filesystems that exist.
163 /// Instead, treat this as a truly fatal error.
164 #[error(
165 "a directory entry inside the source directory escaped out of it: {}",
166 .path.display()
167 )]
168 EntryEscapesSourceDirectory {
169 /// The path that "escaped" the source directory.
170 path: PathBuf,
171 },
172
173 /// A destination directory or a file inside it already exists,
174 /// which is against the configured [`DestinationDirectoryRule`].
175 ///
176 /// This can also happen when we intended to copy a file to the destination,
177 /// but a directory with the same name appeared mid-copy
178 /// (an unavoidable [time-of-check time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) bug).
179 ///
180 /// The `path` field contains the path that already existed, causing this error.
181 #[error("destination directory or file already exists: {}", .path.display())]
182 DestinationItemAlreadyExists {
183 /// Path of the target directory or file that already exists.
184 path: PathBuf,
185 },
186
187 /// A broken symbolic link has been encountered inside the source directory.
188 ///
189 /// This error can occur only when `broken_symlink_behaviour` is set to
190 /// [`BrokenSymlinkBehaviour::Abort`].
191 ///
192 ///
193 /// [`BrokenSymlinkBehaviour::Abort`]: crate::directory::BrokenSymlinkBehaviour::Abort
194 #[error(
195 "symbolic link inside source directory is broken, \
196 and the behaviour is set to abort"
197 )]
198 SymbolicLinkIsBroken {
199 /// Path of the broken symbolic link.
200 path: PathBuf,
201 },
202}
203
204
205
206/// An item inside the source directory "escaped" outside of
207/// the base source directory.
208///
209/// # Implementation detail
210/// This is an extremely unlikely error, because its requirement
211/// is that [`fs::read_dir`]'s iterator returns a directory entry
212/// outside the provided directory path.
213///
214/// Even though this seems extremely unlikely, a `panic!` would be
215/// an extreme measure due to the many types of filesystems that exist.
216/// Instead, treat this as a truly fatal error.
217#[derive(Error, Debug)]
218#[error(
219 "a directory entry inside the source directory escaped out of it: {}",
220 .path.display()
221)]
222pub(crate) struct SourceSubPathNotUnderBaseSourceDirectory {
223 /// The path that "escaped" the source directory.
224 pub(crate) path: PathBuf,
225}
226
227
228
229/// Directory copy preparation error.
230#[derive(Error, Debug)]
231pub enum CopyDirectoryPreparationError {
232 /// A source directory validation error.
233 #[error(transparent)]
234 SourceDirectoryValidationError(#[from] SourceDirectoryPathValidationError),
235
236 /// A destination directory validation error.
237 #[error(transparent)]
238 DestinationDirectoryValidationError(#[from] DestinationDirectoryPathValidationError),
239
240 /// A directory copy planning error.
241 #[error(transparent)]
242 CopyPlanningError(#[from] DirectoryExecutionPlanError),
243}
244
245
246
247/// Directory copy execution error.
248#[derive(Error, Debug)]
249pub enum CopyDirectoryExecutionError {
250 /// Failed to create a directory inside the destination folder.
251 ///
252 /// For example, this can happen due to missing write permissions.
253 ///
254 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
255 #[error("unable to create directory: {}", .directory_path.display())]
256 UnableToCreateDirectory {
257 /// Directory we were unable to create.
258 directory_path: PathBuf,
259
260 /// IO error describing why the directory could not be created.
261 #[source]
262 error: std::io::Error,
263 },
264
265 /// A file or directory inside the destination directory could not be accessed.
266 #[error("unable to access destination path: {}", .path.display())]
267 UnableToAccessDestination {
268 /// The path we were unable to access.
269 path: PathBuf,
270
271 /// IO error describing why the directory could not be created.
272 #[source]
273 error: std::io::Error,
274 },
275
276 /// An error occurred while trying to copy a file to the destination.
277 #[error(
278 "an error occurred while copying a file to the destination: {}",
279 .file_path.display(),
280 )]
281 FileCopyError {
282 /// The file path that could not be copied.
283 file_path: PathBuf,
284
285 /// The underlying file copying error.
286 #[source]
287 error: FileError,
288 },
289
290 /// An error occurred while trying to create a symlink at the destination.
291 #[error(
292 "failed while creating a symlink at {}",
293 .symlink_path.display()
294 )]
295 SymlinkCreationError {
296 /// The path to the symbolic link that could not be created.
297 symlink_path: PathBuf,
298
299 /// The underlying symlink creation error.
300 #[source]
301 error: std::io::Error,
302 },
303
304 /// A destination directory, a file, or a sub-directory inside it
305 /// has changed since the preparation phase of the directory copy.
306 ///
307 /// We can't guarantee that all destination directory changes
308 /// will trigger this, but some more obvious problematic ones, like
309 /// a file appearing in one of the destinations we wanted to copy to, will.
310 ///
311 /// This is essentially an unavoidable
312 /// [time-of-check time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)
313 /// bug, but we try to catch it if possible.
314 ///
315 /// The `path` field contains the path that already existed, causing this error.
316 #[error("destination directory or file has been created externally mid-execution: {}", .path.display())]
317 DestinationEntryUnexpected {
318 /// The path of the target directory or file that already exists.
319 path: PathBuf,
320 },
321}
322
323
324
325/// Directory copying error (see [`copy_directory`] / [`copy_directory_with_progress`]).
326///
327///
328/// [`copy_directory`]: crate::directory::copy_directory
329/// [`copy_directory_with_progress`]: crate::directory::copy_directory_with_progress
330#[derive(Error, Debug)]
331pub enum CopyDirectoryError {
332 /// Directory copy preparation error.
333 #[error(transparent)]
334 PreparationError(#[from] CopyDirectoryPreparationError),
335
336 /// Directory copy execution error.
337 #[error(transparent)]
338 ExecutionError(#[from] CopyDirectoryExecutionError),
339}
340
341
342
343/// Directory move preparation error.
344#[derive(Error, Debug)]
345pub enum MoveDirectoryPreparationError {
346 /// Source directory validation error.
347 #[error(transparent)]
348 SourceDirectoryValidationError(#[from] SourceDirectoryPathValidationError),
349
350 /// Destination directory validation error.
351 #[error(transparent)]
352 DestinationDirectoryValidationError(#[from] DestinationDirectoryPathValidationError),
353
354 /// Source directory entry scanning error.
355 #[error(transparent)]
356 DirectoryScanError(#[from] DirectoryScanError),
357
358 /// Directory copy planning error. These errors can happen
359 /// when a move-by-rename fails and a copy-and-delete is attempted instead.
360 #[error(transparent)]
361 CopyPlanningError(#[from] DirectoryExecutionPlanError),
362}
363
364
365
366/// Directory move execution error.
367#[derive(Error, Debug)]
368pub enum MoveDirectoryExecutionError {
369 /// A file or directory inside the source directory could not be accessed.
370 #[error("unable to access source path: {}", .path.display())]
371 UnableToAccessSource {
372 /// The path we were unable to access.
373 path: PathBuf,
374
375 /// IO error describing why the directory could not be created.
376 #[source]
377 error: std::io::Error,
378 },
379
380 /// An item inside the source directory "escaped" outside of
381 /// the base source directory.
382 ///
383 /// # Implementation detail
384 /// This is an extremely unlikely error, because its requirement
385 /// is that [`std::fs::read_dir`]'s iterator returns a directory entry
386 /// outside the provided directory path.
387 ///
388 /// Even though this seems extremely unlikely, a `panic!` would be
389 /// an extreme measure due to the many types of filesystems that exist.
390 /// Instead, treat this as a truly fatal error.
391 #[error(
392 "a directory entry inside the source directory escaped out of it: {}",
393 .path.display()
394 )]
395 EntryEscapesSourceDirectory {
396 /// The path that "escaped" the source directory.
397 path: PathBuf,
398 },
399
400 /// A destination directory, a file, or a sub-directory inside it
401 /// has changed since the preparation phase of the directory move call.
402 ///
403 /// We can't guarantee that all destination directory changes
404 /// will trigger this, but some more obvious problematic ones, like
405 /// a file appearing in one of the destinations we wanted to copy to, will.
406 ///
407 /// This is essentially an unavoidable
408 /// [time-of-check time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)
409 /// bug, but we try to catch it if possible.
410 ///
411 /// The `path` field contains the path that already existed, causing this error.
412 #[error("destination directory or file has been created externally mid-execution: {}", .path.display())]
413 DestinationEntryUnexpected {
414 /// The path of the target directory or file that already exists.
415 path: PathBuf,
416 },
417
418 /// Directory copy execution error.
419 ///
420 /// These errors can happen when a move-by-rename fails
421 /// and a copy-and-delete is performed instead.
422 #[error(transparent)]
423 CopyDirectoryError(#[from] CopyDirectoryExecutionError),
424
425 /// Occurs when renaming is the only enabled directory move strategy,
426 /// but it fails.
427 ///
428 /// This commonly indicates that the source and destination directory are
429 /// on different mount points, which would require copy-and-delete, and sometimes
430 /// even following (instead of preserving) symbolic links.
431 #[error(
432 "only rename strategy is enabled (with no copy-and-delete \
433 fallback strategy), but we were unable to rename the directory"
434 )]
435 RenameFailedAndNoFallbackStrategy,
436
437 /// An uncategorized unrecoverable IO error.
438 /// See `error` field for more information.
439 #[error("uncategorized std::io::Error")]
440 OtherIoError {
441 /// IO error describing the cause of the outer error.
442 #[source]
443 error: std::io::Error,
444 },
445}
446
447
448
449/// Directory moving error (see [`move_directory`] / [`move_directory_with_progress`]).
450///
451///
452/// [`move_directory`]: crate::directory::move_directory
453/// [`move_directory_with_progress`]: crate::directory::move_directory_with_progress
454#[derive(Error, Debug)]
455pub enum MoveDirectoryError {
456 /// Directory move preparation error.
457 #[error(transparent)]
458 PreparationError(#[from] MoveDirectoryPreparationError),
459
460 /// Directory move execution error.
461 #[error(transparent)]
462 ExecutionError(#[from] MoveDirectoryExecutionError),
463}
464
465
466
467/// An error that can occur when copying or moving a directory.
468#[derive(Error, Debug)]
469pub enum DirectoryError {
470 /// The base source directory (i.e. the directory you want to copy from) does not exist.
471 #[error(
472 "source directory path does not exist: {}",
473 .directory_path.display()
474 )]
475 SourceDirectoryNotFound {
476 /// Source directory path.
477 directory_path: PathBuf,
478 },
479
480 /// The base source directory path (i.e. the directory you want to copy from) exists,
481 /// but does not point to a directory.
482 #[error(
483 "source directory path exists, but is not a directory: {}",
484 .directory_path.display()
485 )]
486 SourceDirectoryNotADirectory {
487 /// Source directory path.
488 directory_path: PathBuf,
489 },
490
491 /// A base source directory, its sub-directory or a file inside it cannot be read.
492 ///
493 /// For example, this can happen due to missing permissions,
494 /// files or directories being removed externally mid-copy or mid-move, etc.
495 ///
496 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
497 #[error("unable to access source directory or file: {}", .path.display())]
498 UnableToAccessSource {
499 /// The path we are unable to access.
500 path: PathBuf,
501
502 /// IO error describing why the source directory could not be accessed.
503 #[source]
504 error: std::io::Error,
505 },
506
507 /// A base source directory, its sub-directory or a file inside it
508 /// no longer exists (since being first scanned when preparing for a copy, move etc.).
509 ///
510 /// This is basically a [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)
511 /// race condition.
512 #[error(
513 "directory or file inside the source directory has been \
514 unexpectedly removed while processing: {}",
515 .path.display()
516 )]
517 SourceEntryNoLongerExists {
518 /// The path to a directory or file that is invalid.
519 path: PathBuf,
520 },
521
522 /// The provided destination directory path points to an invalid location.
523 ///
524 /// This can occur due to (not an exhaustive list):
525 /// - source and destination directory are the same,
526 /// - destination directory is a subdirectory of the source directory, or,
527 /// - destination path already exists, but is not a directory.
528 #[error("destination directory path points to an invalid location: {}", .path.display())]
529 InvalidDestinationDirectoryPath {
530 /// Invalid destination path.
531 path: PathBuf,
532 },
533
534 /// The file system state of the destination directory does not match
535 /// the provided [`DestinationDirectoryRule`].
536 ///
537 /// For example, this happens when the the destination directory rule is set to
538 /// [`DestinationDirectoryRule::AllowEmpty`], but the destination directory isn't actually empty.
539 #[error(
540 "destination directory is not empty, but configured rules ({:?}) require so: {}",
541 destination_directory_rule,
542 .destination_path.display()
543 )]
544 DestinationDirectoryNotEmpty {
545 /// Destination directory path.
546 destination_path: PathBuf,
547
548 /// Requirements for the destination directory
549 /// (e.g. it should be empty or it should not exist at all).
550 destination_directory_rule: DestinationDirectoryRule,
551 },
552
553 /// A destination directory or a file inside it cannot be created
554 /// or written to (e.g. due to missing permissions).
555 ///
556 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
557 #[error("unable to access destination directory or file: {}", .path.display())]
558 UnableToAccessDestination {
559 /// Path that cannot be accessed.
560 path: PathBuf,
561
562 /// IO error describing why the target directory could not be accessed.
563 #[source]
564 error: std::io::Error,
565 },
566
567 /// A destination directory or a file inside it already exists.
568 ///
569 /// This can also happen when we intended to copy a file to the destination,
570 /// but a directory with the same name appeared mid-copy
571 /// (an unavoidable [time-of-check time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) bug).
572 ///
573 /// The `path` field contains the path that already existed, causing this error.
574 #[error("destination directory or file already exists: {}", .path.display())]
575 DestinationItemAlreadyExists {
576 /// Path of the target directory or file that already exists.
577 path: PathBuf,
578 },
579
580 /// An item inside the source directory somehow escaped outside
581 /// the base source directory.
582 #[error(
583 "a sub-path inside the source directory escaped out of it: {}",
584 .path.display()
585 )]
586 SourceSubPathEscapesSourceDirectory {
587 /// The related path that "escaped" the source directory.
588 path: PathBuf,
589 },
590
591 /// An uncategorized unrecoverable IO error. See `error` for more information.
592 #[error("uncategorized std::io::Error")]
593 OtherIoError {
594 /// IO error describing the cause of the outer error.
595 #[source]
596 error: std::io::Error,
597 },
598}
599
600/// An error that can occur when querying the size of a directory
601/// (see [`directory_size_in_bytes`]).
602///
603///
604/// [`directory_size_in_bytes`]: crate::directory::directory_size_in_bytes
605#[derive(Error, Debug)]
606pub enum DirectorySizeScanError {
607 /// An error occurred while scanning the directory.
608 #[error("failed while scanning directory: {}", .directory_path.display())]
609 ScanError {
610 /// The scanning error.
611 #[source]
612 error: DirectoryScanError,
613
614 /// Base directory path for the scan.
615 directory_path: PathBuf,
616 },
617}
618
619
620
621/// An error that can occur when checking whether a directory is empty
622/// (see [`is_directory_empty`]).
623///
624///
625/// [`is_directory_empty`]: crate::directory::is_directory_empty
626#[derive(Error, Debug)]
627pub enum DirectoryEmptinessScanError {
628 /// The provided directory path to scan doesn't exist.
629 #[error("path doesn't exist: {}", .path.display())]
630 NotFound {
631 /// The directory path that couldn't be scanned.
632 path: PathBuf,
633 },
634
635 /// The provided directory path exists, but is not a directory.
636 #[error(
637 "path exists, but is not a directory nor a symlink to one: {}",
638 .path.display()
639 )]
640 NotADirectory {
641 /// The directory path that couldn't be scanned.
642 path: PathBuf,
643 },
644
645 /// The provided directory path is a directory,
646 /// but could not be read due to an IO error.
647 ///
648 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
649 #[error("unable to read directory: {}", .directory_path.display())]
650 UnableToReadDirectory {
651 /// The directory path that could not be read.
652 directory_path: PathBuf,
653
654 /// IO error describing why the given root directory could not be read.
655 #[source]
656 error: std::io::Error,
657 },
658
659 /// A directory contains an entry (i.e. directory or file)
660 /// that could not be read due to an IO error.
661 ///
662 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
663 #[error("unable to read directory entry for {}", .directory_path.display())]
664 UnableToReadDirectoryEntry {
665 /// The directory path whose entries could not be read.
666 directory_path: PathBuf,
667
668 /// IO error describing why the given file or directory could not be read.
669 #[source]
670 error: std::io::Error,
671 },
672}
673
674
675
676
677/// An error that can occur when scanning a directory.
678#[derive(Error, Debug)]
679pub enum DirectoryScanError {
680 /// The provided directory path to scan doesn't exist.
681 #[error("path doesn't exist: {}", .path.display())]
682 NotFound {
683 /// The directory path that couldn't be scanned.
684 path: PathBuf,
685 },
686
687 /// The provided directory path exists, but is not a directory.
688 #[error(
689 "path exists, but is not a directory nor a symlink to one: {}",
690 .path.display()
691 )]
692 NotADirectory {
693 /// The directory path that couldn't be scanned.
694 path: PathBuf,
695 },
696
697 /// The provided directory path is a directory,
698 /// but could not be read due to an IO error.
699 ///
700 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
701 #[error("unable to read directory: {}", .directory_path.display())]
702 UnableToReadDirectory {
703 /// The drectory path that could not be read.
704 directory_path: PathBuf,
705
706 /// IO error describing why the given root directory could not be read.
707 #[source]
708 error: std::io::Error,
709 },
710
711 /// A directory contains an entry (i.e. directory or file)
712 /// that could not be read due to an IO error.
713 ///
714 /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
715 #[error("unable to read directory entry for {}", .directory_path.display())]
716 UnableToReadDirectoryEntry {
717 /// The directory path whose entries could not be read.
718 directory_path: PathBuf,
719
720 /// IO error describing why the given file or directory could not be read.
721 #[source]
722 error: std::io::Error,
723 },
724
725 /// A symlink inside the scan tree is cyclical.
726 #[error("encountered a directory symlink cycle at {}", .directory_path.display())]
727 SymlinkCycleEncountered {
728 /// The directory path at which the cycle loops around (i.e. where the cycle was detected).
729 directory_path: PathBuf,
730 },
731}