1 use anyhow
::{bail, format_err, Error}
;
2 use std
::convert
::TryFrom
;
5 use serde_json
::{json, Value}
;
7 use crate::backup
::BackupDir
;
9 pub const MANIFEST_BLOB_NAME
: &str = "index.json.blob";
10 pub const CLIENT_LOG_BLOB_NAME
: &str = "client.log.blob";
14 pub encrypted
: Option
<bool
>,
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], encrypted
: Option
<bool
>) -> Result
<(), Error
> {
53 let _archive_type
= archive_type(&filename
)?
; // check type
54 self.files
.push(FileInfo { filename, size, csum, encrypted }
);
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 into_json(self) -> Value
{
89 "backup-type": self.snapshot
.group().backup_type(),
90 "backup-id": self.snapshot
.group().backup_id(),
91 "backup-time": self.snapshot
.backup_time().timestamp(),
92 "files": self.files
.iter()
93 .fold(Vec
::new(), |mut acc
, info
| {
94 let mut value
= json
!({
95 "filename": info
.filename
,
96 "encrypted": info
.encrypted
,
98 "csum": proxmox
::tools
::digest_to_hex(&info
.csum
),
101 if let Some(encrypted
) = info
.encrypted
{
102 value
["encrypted"] = encrypted
.into();
112 impl TryFrom
<super::DataBlob
> for BackupManifest
{
115 fn try_from(blob
: super::DataBlob
) -> Result
<Self, Error
> {
116 let data
= blob
.decode(None
)
117 .map_err(|err
| format_err
!("decode backup manifest blob failed - {}", err
))?
;
118 let json
: Value
= serde_json
::from_slice(&data
[..])
119 .map_err(|err
| format_err
!("unable to parse backup manifest json - {}", err
))?
;
120 BackupManifest
::try_from(json
)
124 impl TryFrom
<Value
> for BackupManifest
{
127 fn try_from(data
: Value
) -> Result
<Self, Error
> {
129 use crate::tools
::{required_string_property, required_integer_property, required_array_property}
;
131 proxmox
::try_block
!({
132 let backup_type
= required_string_property(&data
, "backup-type")?
;
133 let backup_id
= required_string_property(&data
, "backup-id")?
;
134 let backup_time
= required_integer_property(&data
, "backup-time")?
;
136 let snapshot
= BackupDir
::new(backup_type
, backup_id
, backup_time
);
138 let mut manifest
= BackupManifest
::new(snapshot
);
140 for item
in required_array_property(&data
, "files")?
.iter() {
141 let filename
= required_string_property(item
, "filename")?
.to_owned();
142 let csum
= required_string_property(item
, "csum")?
;
143 let csum
= proxmox
::tools
::hex_to_digest(csum
)?
;
144 let size
= required_integer_property(item
, "size")?
as u64;
145 let encrypted
= item
["encrypted"].as_bool();
146 manifest
.add_file(filename
, size
, csum
, encrypted
)?
;
149 if manifest
.files().is_empty() {
150 bail
!("manifest does not list any files.");
154 }).map_err(|err
: Error
| format_err
!("unable to parse backup manifest - {}", err
))