1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Conserve backup system.
// Copyright 2015-2023 Martin Pool.

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

//! Conserve error types.

use std::borrow::Cow;
use std::io;
use std::path::PathBuf;

use thiserror::Error;

use crate::blockdir::Address;
use crate::*;

/// Conserve specific error.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum Error {
    #[error("Block file {hash:?} corrupt: does not have the expected hash")]
    BlockCorrupt { hash: BlockHash },

    #[error("{address:?} extends beyond decompressed block length {actual_len:?}")]
    AddressTooLong { address: Address, actual_len: usize },

    #[error("Not a Conserve archive (no CONSERVE header found)")]
    NotAnArchive,

    #[error(
        "Archive version {:?} is not supported by Conserve {}",
        version,
        crate::version()
    )]
    UnsupportedArchiveVersion { version: String },

    #[error("Unsupported band version {version:?} in {band_id}")]
    UnsupportedBandVersion { band_id: BandId, version: String },

    #[error("Archive is empty")]
    ArchiveEmpty,

    #[error("Archive has no complete bands")]
    NoCompleteBands,

    #[error("Unsupported band format flags {unsupported_flags:?} in {band_id}")]
    UnsupportedBandFormatFlags {
        band_id: BandId,
        unsupported_flags: Vec<Cow<'static, str>>,
    },

    #[error("Destination directory is not empty")]
    DestinationNotEmpty,

    #[error("Directory for new archive is not empty")]
    NewArchiveDirectoryNotEmpty,

    #[error("Invalid backup version number {:?}", version)]
    InvalidVersion { version: String },

    #[error("Band {band_id} head file missing")]
    BandHeadMissing { band_id: BandId },

    #[error(
        "Can't delete blocks because the last band ({}) is incomplete and may be in use",
        band_id
    )]
    DeleteWithIncompleteBackup { band_id: BandId },

    #[error("Can't continue with deletion because the archive was changed by another process")]
    DeleteWithConcurrentActivity,

    #[error("Archive is locked for garbage collection")]
    GarbageCollectionLockHeld,

    #[error("A backup was created while the garbage collection lock was held; CHECK ARCHIVE NOW")]
    GarbageCollectionLockHeldDuringBackup,

    #[error(transparent)]
    ParseGlob {
        #[from]
        source: globset::Error,
    },

    #[error("Failed to deserialize json from {:?}", path)]
    DeserializeJson {
        path: String,
        #[source]
        source: serde_json::Error,
    },

    #[error("Failed to serialize json")]
    SerializeJson {
        #[from]
        source: serde_json::Error,
    },

    #[error("Invalid metadata: {details}")]
    InvalidMetadata { details: String },

    #[error("Band not found: {band_id}")]
    BandNotFound { band_id: BandId },

    #[error("Failed to list bands")]
    ListBands { source: io::Error },

    #[error("Failed to read source file {:?}", path)]
    ReadSourceFile { path: PathBuf, source: io::Error },

    #[error("Unsupported source file kind: {path:?}")]
    UnsupportedSourceKind { path: PathBuf },

    #[error("Unsupported symlink encoding: {path:?}")]
    UnsupportedTargetEncoding { path: PathBuf },

    #[error("Failed to read source tree {:?}", path)]
    ListSourceTree { path: PathBuf, source: io::Error },

    #[error("Failed to restore {:?}", path)]
    Restore { path: PathBuf, source: io::Error },

    #[error("Failed to restore modification time on {:?}", path)]
    RestoreModificationTime { path: PathBuf, source: io::Error },

    #[error("Unsupported URL scheme {:?}", scheme)]
    UrlScheme { scheme: String },

    #[error("Unexpected file {path:?} in archive directory")]
    UnexpectedFile { path: String },

    /// Generic IO error.
    #[error(transparent)]
    IOError {
        #[from]
        source: io::Error,
    },

    #[error("Failed to set owner of {path:?}")]
    SetOwner { source: io::Error, path: PathBuf },

    #[error(transparent)]
    SnapCompressionError {
        // TODO: Maybe say in which file, etc.
        #[from]
        source: snap::Error,
    },

    #[error(transparent)]
    Transport {
        #[from]
        source: transport::Error,
    },
}

impl From<jsonio::Error> for Error {
    fn from(value: jsonio::Error) -> Self {
        match value {
            jsonio::Error::Io { source } => Error::IOError { source },
            jsonio::Error::Json { source, path } => Error::DeserializeJson {
                source,
                path: path.to_string_lossy().into_owned(),
            }, // conflates serialize/deserialize
            jsonio::Error::Transport { source } => Error::Transport { source },
        }
    }
}