1 use anyhow
::{bail, format_err, Error}
;
2 use std
::convert
::TryFrom
;
5 use serde_json
::{json, Value}
;
7 use crate::backup
::{BackupDir, CryptMode, CryptConfig}
;
9 pub const MANIFEST_BLOB_NAME
: &str = "index.json.blob";
10 pub const CLIENT_LOG_BLOB_NAME
: &str = "client.log.blob";
14 pub crypt_mode
: CryptMode
,
19 pub struct BackupManifest
{
25 pub enum ArchiveType
{
31 pub fn archive_type
<P
: AsRef
<Path
>>(
33 ) -> Result
<ArchiveType
, Error
> {
35 let archive_name
= archive_name
.as_ref();
36 let archive_type
= match archive_name
.extension().and_then(|ext
| ext
.to_str()) {
37 Some("didx") => ArchiveType
::DynamicIndex
,
38 Some("fidx") => ArchiveType
::FixedIndex
,
39 Some("blob") => ArchiveType
::Blob
,
40 _
=> bail
!("unknown archive type: {:?}", archive_name
),
48 pub fn new(snapshot
: BackupDir
) -> Self {
49 Self { files: Vec::new(), snapshot }
52 pub fn add_file(&mut self, filename
: String
, size
: u64, csum
: [u8; 32], crypt_mode
: CryptMode
) -> Result
<(), Error
> {
53 let _archive_type
= archive_type(&filename
)?
; // check type
54 self.files
.push(FileInfo { filename, size, csum, crypt_mode }
);
58 pub fn files(&self) -> &[FileInfo
] {
62 fn lookup_file_info(&self, name
: &str) -> Result
<&FileInfo
, Error
> {
64 let info
= self.files
.iter().find(|item
| item
.filename
== name
);
67 None
=> bail
!("manifest does not contain file '{}'", name
),
68 Some(info
) => Ok(info
),
72 pub fn verify_file(&self, name
: &str, csum
: &[u8; 32], size
: u64) -> Result
<(), Error
> {
74 let info
= self.lookup_file_info(name
)?
;
76 if size
!= info
.size
{
77 bail
!("wrong size for file '{}' ({} != {})", name
, info
.size
, size
);
80 if csum
!= &info
.csum
{
81 bail
!("wrong checksum for file '{}'", name
);
87 pub fn signature(&self, crypt_config
: &CryptConfig
) -> [u8; 32] {
89 let mut data
= String
::new();
91 data
.push_str(self.snapshot
.group().backup_type());
93 data
.push_str(self.snapshot
.group().backup_id());
95 data
.push_str(&format
!("{}", self.snapshot
.backup_time().timestamp()));
99 for info
in self.files
.iter() {
100 data
.push_str(&info
.filename
);
102 data
.push_str(match info
.crypt_mode
{
103 CryptMode
::None
=> "None",
104 CryptMode
::SignOnly
=> "SignOnly",
105 CryptMode
::Encrypt
=> "Encrypt",
108 data
.push_str(&format
!("{}", info
.size
));
110 data
.push_str(&proxmox
::tools
::digest_to_hex(&info
.csum
));
116 crypt_config
.compute_auth_tag(data
.as_bytes())
119 pub fn into_json(self, crypt_config
: Option
<&CryptConfig
>) -> Value
{
121 let mut manifest
= json
!({
122 "backup-type": self.snapshot
.group().backup_type(),
123 "backup-id": self.snapshot
.group().backup_id(),
124 "backup-time": self.snapshot
.backup_time().timestamp(),
125 "files": self.files
.iter()
126 .fold(Vec
::new(), |mut acc
, info
| {
128 "filename": info
.filename
,
129 "crypt-mode": info
.crypt_mode
,
131 "csum": proxmox
::tools
::digest_to_hex(&info
.csum
),
137 if let Some(crypt_config
) = crypt_config
{
138 let sig
= self.signature(crypt_config
);
139 manifest
["signature"] = proxmox
::tools
::digest_to_hex(&sig
).into();
146 impl TryFrom
<super::DataBlob
> for BackupManifest
{
149 fn try_from(blob
: super::DataBlob
) -> Result
<Self, Error
> {
150 let data
= blob
.decode(None
)
151 .map_err(|err
| format_err
!("decode backup manifest blob failed - {}", err
))?
;
152 let json
: Value
= serde_json
::from_slice(&data
[..])
153 .map_err(|err
| format_err
!("unable to parse backup manifest json - {}", err
))?
;
154 BackupManifest
::try_from(json
)
158 impl TryFrom
<Value
> for BackupManifest
{
161 fn try_from(data
: Value
) -> Result
<Self, Error
> {
163 use crate::tools
::{required_string_property, required_integer_property, required_array_property}
;
165 proxmox
::try_block
!({
166 let backup_type
= required_string_property(&data
, "backup-type")?
;
167 let backup_id
= required_string_property(&data
, "backup-id")?
;
168 let backup_time
= required_integer_property(&data
, "backup-time")?
;
170 let snapshot
= BackupDir
::new(backup_type
, backup_id
, backup_time
);
172 let mut manifest
= BackupManifest
::new(snapshot
);
174 for item
in required_array_property(&data
, "files")?
.iter() {
175 let filename
= required_string_property(item
, "filename")?
.to_owned();
176 let csum
= required_string_property(item
, "csum")?
;
177 let csum
= proxmox
::tools
::hex_to_digest(csum
)?
;
178 let size
= required_integer_property(item
, "size")?
as u64;
180 let mut crypt_mode
= CryptMode
::None
;
182 if let Some(true) = item
["encrypted"].as_bool() { // compatible to < 0.8.0
183 crypt_mode
= CryptMode
::Encrypt
;
186 if let Some(mode
) = item
.get("crypt-mode") {
187 crypt_mode
= serde_json
::from_value(mode
.clone())?
;
190 manifest
.add_file(filename
, size
, csum
, crypt_mode
)?
;
193 if manifest
.files().is_empty() {
194 bail
!("manifest does not list any files.");
198 }).map_err(|err
: Error
| format_err
!("unable to parse backup manifest - {}", err
))