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}