winbrew_core\fs/
error.rs

1use std::error::Error as StdError;
2use std::io;
3use std::path::{Path, PathBuf};
4
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum FsError {
9    #[error("failed to inspect {path}")]
10    Inspect {
11        path: PathBuf,
12        #[source]
13        source: io::Error,
14    },
15
16    #[error("failed to remove {path}")]
17    Remove {
18        path: PathBuf,
19        #[source]
20        source: io::Error,
21    },
22
23    /// Raised when immediate deletion fails and the path cannot be renamed for deferred cleanup.
24    #[error("failed to remove {path} and defer deletion to {deferred_path}")]
25    RemoveAndDefer {
26        path: PathBuf,
27        deferred_path: PathBuf,
28        #[source]
29        source: io::Error,
30    },
31
32    #[error("failed to create directory {path}")]
33    CreateDirectory {
34        path: PathBuf,
35        #[source]
36        source: io::Error,
37    },
38
39    #[error("failed to create extracted file {path}")]
40    CreateExtractedFile {
41        path: PathBuf,
42        #[source]
43        source: io::Error,
44    },
45
46    #[error("failed to create temp file at {path}")]
47    CreateTempFile {
48        path: PathBuf,
49        #[source]
50        source: io::Error,
51    },
52
53    #[error("failed to write temp file at {path}")]
54    WriteTempFile {
55        path: PathBuf,
56        #[source]
57        source: io::Error,
58    },
59
60    #[error("failed to sync temp file at {path}")]
61    SyncTempFile {
62        path: PathBuf,
63        #[source]
64        source: io::Error,
65    },
66
67    #[error("failed to finalize file: {temp_path} -> {final_path}")]
68    FinalizeFile {
69        temp_path: PathBuf,
70        final_path: PathBuf,
71        #[source]
72        source: io::Error,
73    },
74
75    #[error("failed to open zip archive {zip_path}")]
76    OpenZipArchive {
77        zip_path: PathBuf,
78        #[source]
79        source: Box<dyn StdError + Send + Sync + 'static>,
80    },
81
82    #[error("failed to open archive {archive_path}")]
83    OpenArchive {
84        archive_path: PathBuf,
85        #[source]
86        source: Box<dyn StdError + Send + Sync + 'static>,
87    },
88
89    #[error("failed to read zip entry for {path}")]
90    ReadZipEntry {
91        path: PathBuf,
92        #[source]
93        source: Box<dyn StdError + Send + Sync + 'static>,
94    },
95
96    #[error("failed to read archive entry for {path}")]
97    ReadArchiveEntry {
98        path: PathBuf,
99        #[source]
100        source: Box<dyn StdError + Send + Sync + 'static>,
101    },
102
103    #[error("failed to read {path}")]
104    ReadEntry {
105        path: PathBuf,
106        #[source]
107        source: io::Error,
108    },
109
110    #[error("failed to write {path}")]
111    WriteEntry {
112        path: PathBuf,
113        #[source]
114        source: io::Error,
115    },
116
117    #[error("failed to read directory {path}")]
118    ReadDirectory {
119        path: PathBuf,
120        #[source]
121        source: io::Error,
122    },
123
124    #[error("failed to read directory entry in {path}")]
125    ReadDirectoryEntry {
126        path: PathBuf,
127        #[source]
128        source: io::Error,
129    },
130
131    #[error("failed to copy file {source_path} -> {target_path}")]
132    CopyFile {
133        source_path: PathBuf,
134        target_path: PathBuf,
135        #[source]
136        source: io::Error,
137    },
138
139    #[error("zip entry contains an invalid path")]
140    InvalidZipEntryPath,
141
142    #[error("archive entry contains an invalid path")]
143    InvalidArchiveEntryPath,
144
145    #[error("refusing to extract symlink entry {path}")]
146    SymlinkEntry { path: PathBuf },
147
148    #[error(
149        "suspicious compression ratio for {path}: {uncompressed_size} bytes from {compressed_size} compressed bytes exceeds {max_ratio}x"
150    )]
151    SuspiciousCompressionRatio {
152        path: PathBuf,
153        uncompressed_size: u64,
154        compressed_size: u64,
155        max_ratio: u64,
156    },
157
158    #[error(
159        "zip extraction quota exceeded: current total {current_total_size} bytes + entry {entry_size} bytes exceeds limit {max_total_size}"
160    )]
161    QuotaExceeded {
162        max_total_size: u64,
163        current_total_size: u64,
164        entry_size: u64,
165    },
166
167    #[error("zip entry path is too deep {path}: depth {depth} exceeds limit {max_depth}")]
168    PathTooDeep {
169        path: PathBuf,
170        depth: usize,
171        max_depth: usize,
172    },
173
174    #[error("no archive backend is registered for {archive_kind} archives")]
175    ArchiveBackendUnavailable { archive_kind: &'static str },
176
177    #[error("failed to extract {archive_kind} archive {archive_path} using backend {backend_path}")]
178    ArchiveBackendFailed {
179        archive_kind: &'static str,
180        archive_path: PathBuf,
181        backend_path: PathBuf,
182        #[source]
183        source: Box<dyn StdError + Send + Sync + 'static>,
184    },
185
186    #[error(
187        "zip extraction entry count exceeded: current count {current_file_count} exceeds limit {max_file_count}"
188    )]
189    FileCountExceeded {
190        max_file_count: usize,
191        current_file_count: usize,
192    },
193
194    #[error("refusing to create directory through reparse point {path}")]
195    ReparsePoint { path: PathBuf },
196
197    #[error("failed to create directory {path}: path exists and is not a directory")]
198    PathNotDirectory { path: PathBuf },
199
200    #[error("refusing to overwrite hardlinked file {path}")]
201    HardlinkedTarget { path: PathBuf },
202
203    #[error("failed to copy staged installation across volumes: {source_dir} -> {target_dir}")]
204    CopyAcrossVolumes {
205        source_dir: PathBuf,
206        target_dir: PathBuf,
207        #[source]
208        source: Box<dyn StdError + Send + Sync + 'static>,
209    },
210
211    #[error("failed to move staged installation into place: {source_dir} -> {target_dir}")]
212    MoveIntoPlace {
213        source_dir: PathBuf,
214        target_dir: PathBuf,
215        #[source]
216        source: io::Error,
217    },
218
219    #[error("failed to move existing installation aside: {target_dir} -> {backup_dir}")]
220    MoveAside {
221        target_dir: PathBuf,
222        backup_dir: PathBuf,
223        #[source]
224        source: io::Error,
225    },
226
227    /// Raised when staged replacement fails and restoring the backup also fails.
228    #[error(
229        "{action}: {source_dir} -> {target_dir} (original error: {source}; rollback also failed: {rollback_error})"
230    )]
231    RollbackFailed {
232        action: &'static str,
233        source_dir: PathBuf,
234        target_dir: PathBuf,
235        #[source]
236        source: Box<dyn StdError + Send + Sync + 'static>,
237        rollback_error: Box<dyn StdError + Send + Sync + 'static>,
238    },
239
240    #[error("refusing to copy symlink {source_path}")]
241    CopySymlink { source_path: PathBuf },
242
243    #[error("unsupported entry type {source_path}")]
244    UnsupportedEntry { source_path: PathBuf },
245}
246
247pub type Result<T> = std::result::Result<T, FsError>;
248
249impl FsError {
250    pub(crate) fn inspect(path: &Path, source: io::Error) -> Self {
251        Self::Inspect {
252            path: path.to_path_buf(),
253            source,
254        }
255    }
256
257    pub(crate) fn remove(path: &Path, source: io::Error) -> Self {
258        Self::Remove {
259            path: path.to_path_buf(),
260            source,
261        }
262    }
263
264    pub(crate) fn remove_and_defer(path: &Path, deferred_path: &Path, source: io::Error) -> Self {
265        Self::RemoveAndDefer {
266            path: path.to_path_buf(),
267            deferred_path: deferred_path.to_path_buf(),
268            source,
269        }
270    }
271
272    pub(crate) fn create_directory(path: &Path, source: io::Error) -> Self {
273        Self::CreateDirectory {
274            path: path.to_path_buf(),
275            source,
276        }
277    }
278
279    pub(crate) fn create_extracted_file(path: &Path, source: io::Error) -> Self {
280        Self::CreateExtractedFile {
281            path: path.to_path_buf(),
282            source,
283        }
284    }
285
286    pub(crate) fn archive_backend_unavailable(archive_kind: &'static str) -> Self {
287        Self::ArchiveBackendUnavailable { archive_kind }
288    }
289
290    pub(crate) fn archive_backend_failed(
291        archive_kind: &'static str,
292        archive_path: &Path,
293        backend_path: &Path,
294        source: impl StdError + Send + Sync + 'static,
295    ) -> Self {
296        Self::ArchiveBackendFailed {
297            archive_kind,
298            archive_path: archive_path.to_path_buf(),
299            backend_path: backend_path.to_path_buf(),
300            source: Box::new(source),
301        }
302    }
303
304    pub(crate) fn create_temp_file(path: &Path, source: io::Error) -> Self {
305        Self::CreateTempFile {
306            path: path.to_path_buf(),
307            source,
308        }
309    }
310
311    pub(crate) fn write_temp_file(path: &Path, source: io::Error) -> Self {
312        Self::WriteTempFile {
313            path: path.to_path_buf(),
314            source,
315        }
316    }
317
318    pub(crate) fn sync_temp_file(path: &Path, source: io::Error) -> Self {
319        Self::SyncTempFile {
320            path: path.to_path_buf(),
321            source,
322        }
323    }
324
325    pub(crate) fn finalize_file(temp_path: &Path, final_path: &Path, source: io::Error) -> Self {
326        Self::FinalizeFile {
327            temp_path: temp_path.to_path_buf(),
328            final_path: final_path.to_path_buf(),
329            source,
330        }
331    }
332
333    pub(crate) fn open_zip_archive(
334        zip_path: &Path,
335        source: impl StdError + Send + Sync + 'static,
336    ) -> Self {
337        Self::OpenZipArchive {
338            zip_path: zip_path.to_path_buf(),
339            source: Box::new(source),
340        }
341    }
342
343    pub(crate) fn open_archive(
344        archive_path: &Path,
345        source: impl StdError + Send + Sync + 'static,
346    ) -> Self {
347        Self::OpenArchive {
348            archive_path: archive_path.to_path_buf(),
349            source: Box::new(source),
350        }
351    }
352
353    pub(crate) fn read_zip_entry(
354        path: &Path,
355        source: impl StdError + Send + Sync + 'static,
356    ) -> Self {
357        Self::ReadZipEntry {
358            path: path.to_path_buf(),
359            source: Box::new(source),
360        }
361    }
362
363    pub(crate) fn read_archive_entry(
364        path: &Path,
365        source: impl StdError + Send + Sync + 'static,
366    ) -> Self {
367        Self::ReadArchiveEntry {
368            path: path.to_path_buf(),
369            source: Box::new(source),
370        }
371    }
372
373    pub(crate) fn read_entry(path: &Path, source: io::Error) -> Self {
374        Self::ReadEntry {
375            path: path.to_path_buf(),
376            source,
377        }
378    }
379
380    pub(crate) fn write_entry(path: &Path, source: io::Error) -> Self {
381        Self::WriteEntry {
382            path: path.to_path_buf(),
383            source,
384        }
385    }
386
387    pub(crate) fn read_directory(path: &Path, source: io::Error) -> Self {
388        Self::ReadDirectory {
389            path: path.to_path_buf(),
390            source,
391        }
392    }
393
394    pub(crate) fn read_directory_entry(path: &Path, source: io::Error) -> Self {
395        Self::ReadDirectoryEntry {
396            path: path.to_path_buf(),
397            source,
398        }
399    }
400
401    pub(crate) fn copy_file(source_path: &Path, target_path: &Path, source: io::Error) -> Self {
402        Self::CopyFile {
403            source_path: source_path.to_path_buf(),
404            target_path: target_path.to_path_buf(),
405            source,
406        }
407    }
408
409    pub(crate) fn invalid_zip_entry_path() -> Self {
410        Self::InvalidZipEntryPath
411    }
412
413    pub(crate) fn invalid_archive_entry_path() -> Self {
414        Self::InvalidArchiveEntryPath
415    }
416
417    pub(crate) fn symlink_entry(path: &Path) -> Self {
418        Self::SymlinkEntry {
419            path: path.to_path_buf(),
420        }
421    }
422
423    pub(crate) fn suspicious_compression_ratio(
424        path: &Path,
425        uncompressed_size: u64,
426        compressed_size: u64,
427        max_ratio: u64,
428    ) -> Self {
429        Self::SuspiciousCompressionRatio {
430            path: path.to_path_buf(),
431            uncompressed_size,
432            compressed_size,
433            max_ratio,
434        }
435    }
436
437    pub(crate) fn quota_exceeded(
438        max_total_size: u64,
439        current_total_size: u64,
440        entry_size: u64,
441    ) -> Self {
442        Self::QuotaExceeded {
443            max_total_size,
444            current_total_size,
445            entry_size,
446        }
447    }
448
449    pub(crate) fn path_too_deep(path: &Path, depth: usize, max_depth: usize) -> Self {
450        Self::PathTooDeep {
451            path: path.to_path_buf(),
452            depth,
453            max_depth,
454        }
455    }
456
457    pub(crate) fn file_count_exceeded(max_file_count: usize, current_file_count: usize) -> Self {
458        Self::FileCountExceeded {
459            max_file_count,
460            current_file_count,
461        }
462    }
463
464    pub(crate) fn reparse_point(path: &Path) -> Self {
465        Self::ReparsePoint {
466            path: path.to_path_buf(),
467        }
468    }
469
470    pub(crate) fn path_not_directory(path: &Path) -> Self {
471        Self::PathNotDirectory {
472            path: path.to_path_buf(),
473        }
474    }
475
476    pub(crate) fn hardlinked_target(path: &Path) -> Self {
477        Self::HardlinkedTarget {
478            path: path.to_path_buf(),
479        }
480    }
481
482    pub(crate) fn copy_across_volumes(
483        source_dir: &Path,
484        target_dir: &Path,
485        source: impl StdError + Send + Sync + 'static,
486    ) -> Self {
487        Self::CopyAcrossVolumes {
488            source_dir: source_dir.to_path_buf(),
489            target_dir: target_dir.to_path_buf(),
490            source: Box::new(source),
491        }
492    }
493
494    pub(crate) fn move_into_place(source_dir: &Path, target_dir: &Path, source: io::Error) -> Self {
495        Self::MoveIntoPlace {
496            source_dir: source_dir.to_path_buf(),
497            target_dir: target_dir.to_path_buf(),
498            source,
499        }
500    }
501
502    pub(crate) fn move_aside(target_dir: &Path, backup_dir: &Path, source: io::Error) -> Self {
503        Self::MoveAside {
504            target_dir: target_dir.to_path_buf(),
505            backup_dir: backup_dir.to_path_buf(),
506            source,
507        }
508    }
509
510    pub(crate) fn rollback_failed(
511        action: &'static str,
512        source_dir: &Path,
513        target_dir: &Path,
514        source: impl StdError + Send + Sync + 'static,
515        rollback_error: impl StdError + Send + Sync + 'static,
516    ) -> Self {
517        Self::RollbackFailed {
518            action,
519            source_dir: source_dir.to_path_buf(),
520            target_dir: target_dir.to_path_buf(),
521            source: Box::new(source),
522            rollback_error: Box::new(rollback_error),
523        }
524    }
525
526    pub(crate) fn copy_symlink(source_path: &Path) -> Self {
527        Self::CopySymlink {
528            source_path: source_path.to_path_buf(),
529        }
530    }
531
532    pub(crate) fn unsupported_entry(source_path: &Path) -> Self {
533        Self::UnsupportedEntry {
534            source_path: source_path.to_path_buf(),
535        }
536    }
537}