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 #[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 #[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}