datastore: rustfmt whole package

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2022-04-14 13:27:53 +02:00
parent fb3c007f8a
commit 42c2b5bec9
21 changed files with 843 additions and 588 deletions

View File

@ -5,13 +5,8 @@ use std::str::FromStr;
use anyhow::{bail, format_err, Error};
use pbs_api_types::{
BACKUP_ID_REGEX,
BACKUP_TYPE_REGEX,
BACKUP_DATE_REGEX,
GROUP_PATH_REGEX,
SNAPSHOT_PATH_REGEX,
BACKUP_FILE_REGEX,
GroupFilter,
GroupFilter, BACKUP_DATE_REGEX, BACKUP_FILE_REGEX, BACKUP_ID_REGEX, BACKUP_TYPE_REGEX,
GROUP_PATH_REGEX, SNAPSHOT_PATH_REGEX,
};
use super::manifest::MANIFEST_BLOB_NAME;
@ -96,7 +91,11 @@ impl BackupGroup {
let protected = backup_dir.is_protected(base_path.to_owned());
list.push(BackupInfo { backup_dir, files, protected });
list.push(BackupInfo {
backup_dir,
files,
protected,
});
Ok(())
},
@ -331,7 +330,11 @@ impl BackupInfo {
let files = list_backup_files(libc::AT_FDCWD, &path)?;
let protected = backup_dir.is_protected(base_path.to_owned());
Ok(BackupInfo { backup_dir, files, protected })
Ok(BackupInfo {
backup_dir,
files,
protected,
})
}
/// Finds the latest backup inside a backup group
@ -399,9 +402,7 @@ impl BackupInfo {
pub fn is_finished(&self) -> bool {
// backup is considered unfinished if there is no manifest
self.files
.iter()
.any(|name| name == MANIFEST_BLOB_NAME)
self.files.iter().any(|name| name == MANIFEST_BLOB_NAME)
}
}

View File

@ -10,8 +10,8 @@ use anyhow::Error;
use futures::ready;
use tokio::io::{AsyncRead, AsyncSeek, ReadBuf};
use proxmox_lang::io_format_err;
use proxmox_lang::error::io_err_other;
use proxmox_lang::io_format_err;
use pbs_tools::async_lru_cache::{AsyncCacher, AsyncLruCache};

View File

@ -1,7 +1,7 @@
use std::convert::TryFrom;
use std::ffi::{CStr, CString, OsStr};
use std::fmt;
use std::io::{Read, Write, Seek, SeekFrom};
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::ffi::OsStrExt;
use anyhow::{bail, format_err, Error};
@ -106,40 +106,46 @@ pub enum DirEntryAttribute {
}
impl DirEntry {
fn new(etype: CatalogEntryType, name: Vec<u8>, start: u64, size: u64, mtime: i64) -> Self {
match etype {
CatalogEntryType::Directory => {
DirEntry { name, attr: DirEntryAttribute::Directory { start } }
}
CatalogEntryType::File => {
DirEntry { name, attr: DirEntryAttribute::File { size, mtime } }
}
CatalogEntryType::Symlink => {
DirEntry { name, attr: DirEntryAttribute::Symlink }
}
CatalogEntryType::Hardlink => {
DirEntry { name, attr: DirEntryAttribute::Hardlink }
}
CatalogEntryType::BlockDevice => {
DirEntry { name, attr: DirEntryAttribute::BlockDevice }
}
CatalogEntryType::CharDevice => {
DirEntry { name, attr: DirEntryAttribute::CharDevice }
}
CatalogEntryType::Fifo => {
DirEntry { name, attr: DirEntryAttribute::Fifo }
}
CatalogEntryType::Socket => {
DirEntry { name, attr: DirEntryAttribute::Socket }
}
CatalogEntryType::Directory => DirEntry {
name,
attr: DirEntryAttribute::Directory { start },
},
CatalogEntryType::File => DirEntry {
name,
attr: DirEntryAttribute::File { size, mtime },
},
CatalogEntryType::Symlink => DirEntry {
name,
attr: DirEntryAttribute::Symlink,
},
CatalogEntryType::Hardlink => DirEntry {
name,
attr: DirEntryAttribute::Hardlink,
},
CatalogEntryType::BlockDevice => DirEntry {
name,
attr: DirEntryAttribute::BlockDevice,
},
CatalogEntryType::CharDevice => DirEntry {
name,
attr: DirEntryAttribute::CharDevice,
},
CatalogEntryType::Fifo => DirEntry {
name,
attr: DirEntryAttribute::Fifo,
},
CatalogEntryType::Socket => DirEntry {
name,
attr: DirEntryAttribute::Socket,
},
}
}
/// Get file mode bits for this entry to be used with the `MatchList` api.
pub fn get_file_mode(&self) -> Option<u32> {
Some(
match self.attr {
Some(match self.attr {
DirEntryAttribute::Directory { .. } => pxar::mode::IFDIR,
DirEntryAttribute::File { .. } => pxar::mode::IFREG,
DirEntryAttribute::Symlink => pxar::mode::IFLNK,
@ -148,9 +154,7 @@ impl DirEntry {
DirEntryAttribute::CharDevice => pxar::mode::IFCHR,
DirEntryAttribute::Fifo => pxar::mode::IFIFO,
DirEntryAttribute::Socket => pxar::mode::IFSOCK,
}
as u32
)
} as u32)
}
/// Check if DirEntry is a directory
@ -170,60 +174,82 @@ struct DirInfo {
}
impl DirInfo {
fn new(name: CString) -> Self {
DirInfo { name, entries: Vec::new() }
DirInfo {
name,
entries: Vec::new(),
}
}
fn new_rootdir() -> Self {
DirInfo::new(CString::new(b"/".to_vec()).unwrap())
}
fn encode_entry<W: Write>(
writer: &mut W,
entry: &DirEntry,
pos: u64,
) -> Result<(), Error> {
fn encode_entry<W: Write>(writer: &mut W, entry: &DirEntry, pos: u64) -> Result<(), Error> {
match entry {
DirEntry { name, attr: DirEntryAttribute::Directory { start } } => {
DirEntry {
name,
attr: DirEntryAttribute::Directory { start },
} => {
writer.write_all(&[CatalogEntryType::Directory as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
catalog_encode_u64(writer, pos - start)?;
}
DirEntry { name, attr: DirEntryAttribute::File { size, mtime } } => {
DirEntry {
name,
attr: DirEntryAttribute::File { size, mtime },
} => {
writer.write_all(&[CatalogEntryType::File as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
catalog_encode_u64(writer, *size)?;
catalog_encode_i64(writer, *mtime)?;
}
DirEntry { name, attr: DirEntryAttribute::Symlink } => {
DirEntry {
name,
attr: DirEntryAttribute::Symlink,
} => {
writer.write_all(&[CatalogEntryType::Symlink as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
}
DirEntry { name, attr: DirEntryAttribute::Hardlink } => {
DirEntry {
name,
attr: DirEntryAttribute::Hardlink,
} => {
writer.write_all(&[CatalogEntryType::Hardlink as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
}
DirEntry { name, attr: DirEntryAttribute::BlockDevice } => {
DirEntry {
name,
attr: DirEntryAttribute::BlockDevice,
} => {
writer.write_all(&[CatalogEntryType::BlockDevice as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
}
DirEntry { name, attr: DirEntryAttribute::CharDevice } => {
DirEntry {
name,
attr: DirEntryAttribute::CharDevice,
} => {
writer.write_all(&[CatalogEntryType::CharDevice as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
}
DirEntry { name, attr: DirEntryAttribute::Fifo } => {
DirEntry {
name,
attr: DirEntryAttribute::Fifo,
} => {
writer.write_all(&[CatalogEntryType::Fifo as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
}
DirEntry { name, attr: DirEntryAttribute::Socket } => {
DirEntry {
name,
attr: DirEntryAttribute::Socket,
} => {
writer.write_all(&[CatalogEntryType::Socket as u8])?;
catalog_encode_u64(writer, name.len() as u64)?;
writer.write_all(name)?;
@ -250,7 +276,6 @@ impl DirInfo {
data: &[u8],
mut callback: C,
) -> Result<(), Error> {
let mut cursor = data;
let entries = catalog_decode_u64(&mut cursor)?;
@ -258,14 +283,17 @@ impl DirInfo {
let mut name_buf = vec![0u8; 4096];
for _ in 0..entries {
let mut buf = [0u8];
cursor.read_exact(&mut buf)?;
let etype = CatalogEntryType::try_from(buf[0])?;
let name_len = catalog_decode_u64(&mut cursor)? as usize;
if name_len >= name_buf.len() {
bail!("directory entry name too long ({} >= {})", name_len, name_buf.len());
bail!(
"directory entry name too long ({} >= {})",
name_len,
name_buf.len()
);
}
let name = &mut name_buf[0..name_len];
cursor.read_exact(name)?;
@ -280,9 +308,7 @@ impl DirInfo {
let mtime = catalog_decode_i64(&mut cursor)?;
callback(etype, name, 0, size, mtime)?
}
_ => {
callback(etype, name, 0, 0, 0)?
}
_ => callback(etype, name, 0, 0, 0)?,
};
if !cont {
return Ok(());
@ -310,10 +336,13 @@ pub struct CatalogWriter<W> {
}
impl<W: Write> CatalogWriter<W> {
/// Create a new CatalogWriter instance
pub fn new(writer: W) -> Result<Self, Error> {
let mut me = Self { writer, dirstack: vec![ DirInfo::new_rootdir() ], pos: 0 };
let mut me = Self {
writer,
dirstack: vec![DirInfo::new_rootdir()],
pos: 0,
};
me.write_all(&PROXMOX_CATALOG_FILE_MAGIC_1_0)?;
Ok(me)
}
@ -347,7 +376,6 @@ impl <W: Write> CatalogWriter<W> {
}
impl<W: Write> BackupCatalogWriter for CatalogWriter<W> {
fn start_directory(&mut self, name: &CStr) -> Result<(), Error> {
let new = DirInfo::new(name.to_owned());
self.dirstack.push(new);
@ -367,59 +395,107 @@ impl <W: Write> BackupCatalogWriter for CatalogWriter<W> {
}
};
let current = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let current = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
current.entries.push(DirEntry { name, attr: DirEntryAttribute::Directory { start } });
current.entries.push(DirEntry {
name,
attr: DirEntryAttribute::Directory { start },
});
Ok(())
}
fn add_file(&mut self, name: &CStr, size: u64, mtime: i64) -> Result<(), Error> {
let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let dir = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
dir.entries.push(DirEntry { name, attr: DirEntryAttribute::File { size, mtime } });
dir.entries.push(DirEntry {
name,
attr: DirEntryAttribute::File { size, mtime },
});
Ok(())
}
fn add_symlink(&mut self, name: &CStr) -> Result<(), Error> {
let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let dir = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Symlink });
dir.entries.push(DirEntry {
name,
attr: DirEntryAttribute::Symlink,
});
Ok(())
}
fn add_hardlink(&mut self, name: &CStr) -> Result<(), Error> {
let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let dir = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Hardlink });
dir.entries.push(DirEntry {
name,
attr: DirEntryAttribute::Hardlink,
});
Ok(())
}
fn add_block_device(&mut self, name: &CStr) -> Result<(), Error> {
let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let dir = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
dir.entries.push(DirEntry { name, attr: DirEntryAttribute::BlockDevice });
dir.entries.push(DirEntry {
name,
attr: DirEntryAttribute::BlockDevice,
});
Ok(())
}
fn add_char_device(&mut self, name: &CStr) -> Result<(), Error> {
let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let dir = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
dir.entries.push(DirEntry { name, attr: DirEntryAttribute::CharDevice });
dir.entries.push(DirEntry {
name,
attr: DirEntryAttribute::CharDevice,
});
Ok(())
}
fn add_fifo(&mut self, name: &CStr) -> Result<(), Error> {
let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let dir = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Fifo });
dir.entries.push(DirEntry {
name,
attr: DirEntryAttribute::Fifo,
});
Ok(())
}
fn add_socket(&mut self, name: &CStr) -> Result<(), Error> {
let dir = self.dirstack.last_mut().ok_or_else(|| format_err!("outside root"))?;
let dir = self
.dirstack
.last_mut()
.ok_or_else(|| format_err!("outside root"))?;
let name = name.to_bytes().to_vec();
dir.entries.push(DirEntry { name, attr: DirEntryAttribute::Socket });
dir.entries.push(DirEntry {
name,
attr: DirEntryAttribute::Socket,
});
Ok(())
}
}
@ -430,7 +506,6 @@ pub struct CatalogReader<R> {
}
impl<R: Read + Seek> CatalogReader<R> {
/// Create a new CatalogReader instance
pub fn new(reader: R) -> Self {
Self { reader }
@ -438,12 +513,12 @@ impl <R: Read + Seek> CatalogReader<R> {
/// Print whole catalog to stdout
pub fn dump(&mut self) -> Result<(), Error> {
let root = self.root()?;
match root {
DirEntry { attr: DirEntryAttribute::Directory { start }, .. }=> {
self.dump_dir(std::path::Path::new("./"), start)
}
DirEntry {
attr: DirEntryAttribute::Directory { start },
..
} => self.dump_dir(std::path::Path::new("./"), start),
_ => unreachable!(),
}
}
@ -459,15 +534,14 @@ impl <R: Read + Seek> CatalogReader<R> {
}
self.reader.seek(SeekFrom::End(-8))?;
let start = unsafe { self.reader.read_le_value::<u64>()? };
Ok(DirEntry { name: b"".to_vec(), attr: DirEntryAttribute::Directory { start } })
Ok(DirEntry {
name: b"".to_vec(),
attr: DirEntryAttribute::Directory { start },
})
}
/// Read all directory entries
pub fn read_dir(
&mut self,
parent: &DirEntry,
) -> Result<Vec<DirEntry>, Error> {
pub fn read_dir(&mut self, parent: &DirEntry) -> Result<Vec<DirEntry>, Error> {
let start = match parent.attr {
DirEntryAttribute::Directory { start } => start,
_ => bail!("parent is not a directory - internal error"),
@ -487,10 +561,7 @@ impl <R: Read + Seek> CatalogReader<R> {
}
/// Lookup a DirEntry from an absolute path
pub fn lookup_recursive(
&mut self,
path: &[u8],
) -> Result<DirEntry, Error> {
pub fn lookup_recursive(&mut self, path: &[u8]) -> Result<DirEntry, Error> {
let mut current = self.root()?;
if path == b"/" {
return Ok(current);
@ -500,13 +571,17 @@ impl <R: Read + Seek> CatalogReader<R> {
&path[1..]
} else {
path
}.split(|c| *c == b'/');
}
.split(|c| *c == b'/');
for comp in components {
if let Some(entry) = self.lookup(&current, comp)? {
current = entry;
} else {
bail!("path {:?} not found in catalog", String::from_utf8_lossy(path));
bail!(
"path {:?} not found in catalog",
String::from_utf8_lossy(path)
);
}
}
Ok(current)
@ -518,7 +593,6 @@ impl <R: Read + Seek> CatalogReader<R> {
parent: &DirEntry,
filename: &[u8],
) -> Result<Option<DirEntry>, Error> {
let start = match parent.attr {
DirEntryAttribute::Directory { start } => start,
_ => bail!("parent is not a directory - internal error"),
@ -544,18 +618,18 @@ impl <R: Read + Seek> CatalogReader<R> {
fn read_raw_dirinfo_block(&mut self, start: u64) -> Result<Vec<u8>, Error> {
self.reader.seek(SeekFrom::Start(start))?;
let size = catalog_decode_u64(&mut self.reader)?;
if size < 1 { bail!("got small directory size {}", size) };
if size < 1 {
bail!("got small directory size {}", size)
};
let data = self.reader.read_exact_allocated(size as usize)?;
Ok(data)
}
/// Print the content of a directory to stdout
pub fn dump_dir(&mut self, prefix: &std::path::Path, start: u64) -> Result<(), Error> {
let data = self.read_raw_dirinfo_block(start)?;
DirInfo::parse(&data, |etype, name, offset, size, mtime| {
let mut path = std::path::PathBuf::from(prefix);
let name: &OsStr = OsStrExt::from_bytes(name);
path.push(name);
@ -575,13 +649,7 @@ impl <R: Read + Seek> CatalogReader<R> {
mtime_string = s;
}
println!(
"{} {:?} {} {}",
etype,
path,
size,
mtime_string,
);
println!("{} {:?} {} {}", etype, path, size, mtime_string,);
}
_ => {
println!("{} {:?}", etype, path);
@ -688,11 +756,11 @@ pub fn catalog_encode_i64<W: Write>(writer: &mut W, v: i64) -> Result<(), Error>
/// value encoded is <= 2^63 (values > 2^63 cannot be represented in an i64)
#[allow(clippy::neg_multiply)]
pub fn catalog_decode_i64<R: Read>(reader: &mut R) -> Result<i64, Error> {
let mut v: u64 = 0;
let mut buf = [0u8];
for i in 0..11 { // only allow 11 bytes (70 bits + sign marker)
for i in 0..11 {
// only allow 11 bytes (70 bits + sign marker)
if buf.is_empty() {
bail!("decode_i64 failed - unexpected EOB");
}
@ -741,11 +809,11 @@ pub fn catalog_encode_u64<W: Write>(writer: &mut W, v: u64) -> Result<(), Error>
/// We currently read maximal 10 bytes, which give a maximum of 70 bits,
/// but we currently only encode up to 64 bits
pub fn catalog_decode_u64<R: Read>(reader: &mut R) -> Result<u64, Error> {
let mut v: u64 = 0;
let mut buf = [0u8];
for i in 0..10 { // only allow 10 bytes (70 bits)
for i in 0..10 {
// only allow 10 bytes (70 bits)
if buf.is_empty() {
bail!("decode_u64 failed - unexpected EOB");
}
@ -764,9 +832,7 @@ pub fn catalog_decode_u64<R: Read>(reader: &mut R) -> Result<u64, Error> {
#[test]
fn test_catalog_u64_encoder() {
fn test_encode_decode(value: u64) {
let mut data = Vec::new();
catalog_encode_u64(&mut data, value).unwrap();
@ -790,9 +856,7 @@ fn test_catalog_u64_encoder() {
#[test]
fn test_catalog_i64_encoder() {
fn test_encode_decode(value: i64) {
let mut data = Vec::new();
catalog_encode_i64(&mut data, value).unwrap();
@ -816,9 +880,7 @@ fn test_catalog_i64_encoder() {
#[test]
fn test_catalog_i64_compatibility() {
fn test_encode_decode(value: u64) {
let mut data = Vec::new();
catalog_encode_u64(&mut data, value).unwrap();

View File

@ -1,6 +1,6 @@
use anyhow::{Error};
use std::sync::Arc;
use anyhow::Error;
use std::io::Read;
use std::sync::Arc;
use proxmox_borrow::Tied;
@ -13,7 +13,6 @@ pub struct ChecksumReader<R> {
}
impl<R: Read> ChecksumReader<R> {
pub fn new(reader: R, config: Option<Arc<CryptConfig>>) -> Self {
let hasher = crc32fast::Hasher::new();
let signer = match config {
@ -26,7 +25,11 @@ impl <R: Read> ChecksumReader<R> {
None => None,
};
Self { reader, hasher, signer }
Self {
reader,
hasher,
signer,
}
}
pub fn finish(mut self) -> Result<(R, u32, Option<[u8; 32]>), Error> {
@ -43,17 +46,16 @@ impl <R: Read> ChecksumReader<R> {
}
impl<R: Read> Read for ChecksumReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
let count = self.reader.read(buf)?;
if count > 0 {
self.hasher.update(&buf[..count]);
if let Some(ref mut signer) = self.signer {
signer.update(&buf[..count])
.map_err(|err| {
signer.update(&buf[..count]).map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("hmac update failed - {}", err))
format!("hmac update failed - {}", err),
)
})?;
}
}

View File

@ -1,7 +1,7 @@
use std::sync::Arc;
use std::io::Write;
use std::sync::Arc;
use anyhow::{Error};
use anyhow::Error;
use proxmox_borrow::Tied;
@ -14,7 +14,6 @@ pub struct ChecksumWriter<W> {
}
impl<W: Write> ChecksumWriter<W> {
pub fn new(writer: W, config: Option<Arc<CryptConfig>>) -> Self {
let hasher = crc32fast::Hasher::new();
let signer = match config {
@ -26,7 +25,11 @@ impl <W: Write> ChecksumWriter<W> {
}
None => None,
};
Self { writer, hasher, signer }
Self {
writer,
hasher,
signer,
}
}
pub fn finish(mut self) -> Result<(W, u32, Option<[u8; 32]>), Error> {
@ -43,15 +46,14 @@ impl <W: Write> ChecksumWriter<W> {
}
impl<W: Write> Write for ChecksumWriter<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.hasher.update(buf);
if let Some(ref mut signer) = self.signer {
signer.update(buf)
.map_err(|err| {
signer.update(buf).map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("hmac update failed - {}", err))
format!("hmac update failed - {}", err),
)
})?;
}
self.writer.write(buf)

View File

@ -10,7 +10,6 @@ pub struct ChunkStat {
}
impl ChunkStat {
pub fn new(size: u64) -> Self {
ChunkStat {
size,
@ -32,8 +31,7 @@ impl std::fmt::Debug for ChunkStat {
let rate = (self.disk_size * 100) / (self.size as u64);
let elapsed = self.start_time.elapsed().unwrap();
let elapsed = (elapsed.as_secs() as f64) +
(elapsed.subsec_millis() as f64)/1000.0;
let elapsed = (elapsed.as_secs() as f64) + (elapsed.subsec_millis() as f64) / 1000.0;
let write_speed = ((self.size as f64) / (1024.0 * 1024.0)) / elapsed;

View File

@ -5,12 +5,13 @@ use std::sync::{Arc, Mutex};
use anyhow::{bail, format_err, Error};
use proxmox_sys::fs::{CreateOptions, create_path, create_dir};
use proxmox_sys::process_locker::{ProcessLocker, ProcessLockSharedGuard, ProcessLockExclusiveGuard};
use proxmox_sys::WorkerTaskContext;
use proxmox_sys::task_log;
use pbs_api_types::GarbageCollectionStatus;
use proxmox_sys::fs::{create_dir, create_path, CreateOptions};
use proxmox_sys::process_locker::{
ProcessLockExclusiveGuard, ProcessLockSharedGuard, ProcessLocker,
};
use proxmox_sys::task_log;
use proxmox_sys::WorkerTaskContext;
use crate::DataBlob;
@ -26,8 +27,15 @@ pub struct ChunkStore {
// TODO: what about sysctl setting vm.vfs_cache_pressure (0 - 100) ?
pub fn verify_chunk_size(size: usize) -> Result<(), Error> {
static SIZES: [usize; 7] = [64*1024, 128*1024, 256*1024, 512*1024, 1024*1024, 2048*1024, 4096*1024];
static SIZES: [usize; 7] = [
64 * 1024,
128 * 1024,
256 * 1024,
512 * 1024,
1024 * 1024,
2048 * 1024,
4096 * 1024,
];
if !SIZES.contains(&size) {
bail!("Got unsupported chunk size '{}'", size);
@ -36,7 +44,6 @@ pub fn verify_chunk_size(size: usize) -> Result<(), Error> {
}
fn digest_to_prefix(digest: &[u8]) -> PathBuf {
let mut buf = Vec::<u8>::with_capacity(2 + 1 + 2 + 1);
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
@ -53,9 +60,7 @@ fn digest_to_prefix(digest: &[u8]) -> PathBuf {
}
impl ChunkStore {
fn chunk_dir<P: AsRef<Path>>(path: P) -> PathBuf {
let mut chunk_dir: PathBuf = PathBuf::from(path.as_ref());
chunk_dir.push(".chunks");
@ -66,11 +71,16 @@ impl ChunkStore {
&self.base
}
pub fn create<P>(name: &str, path: P, uid: nix::unistd::Uid, gid: nix::unistd::Gid, worker: Option<&dyn WorkerTaskContext>) -> Result<Self, Error>
pub fn create<P>(
name: &str,
path: P,
uid: nix::unistd::Uid,
gid: nix::unistd::Gid,
worker: Option<&dyn WorkerTaskContext>,
) -> Result<Self, Error>
where
P: Into<PathBuf>,
{
let base: PathBuf = path.into();
if !base.is_absolute() {
@ -79,19 +89,31 @@ impl ChunkStore {
let chunk_dir = Self::chunk_dir(&base);
let options = CreateOptions::new()
.owner(uid)
.group(gid);
let options = CreateOptions::new().owner(uid).group(gid);
let default_options = CreateOptions::new();
match create_path(&base, Some(default_options), Some(options.clone())) {
Err(err) => bail!("unable to create chunk store '{}' at {:?} - {}", name, base, err),
Ok(res) => if ! res { nix::unistd::chown(&base, Some(uid), Some(gid))? },
Err(err) => bail!(
"unable to create chunk store '{}' at {:?} - {}",
name,
base,
err
),
Ok(res) => {
if !res {
nix::unistd::chown(&base, Some(uid), Some(gid))?
}
}
}
if let Err(err) = create_dir(&chunk_dir, options.clone()) {
bail!("unable to create chunk store '{}' subdir {:?} - {}", name, chunk_dir, err);
bail!(
"unable to create chunk store '{}' subdir {:?} - {}",
name,
chunk_dir,
err
);
}
// create lock file with correct owner/group
@ -105,7 +127,12 @@ impl ChunkStore {
let mut l1path = chunk_dir.clone();
l1path.push(format!("{:04x}", i));
if let Err(err) = create_dir(&l1path, options.clone()) {
bail!("unable to create chunk store '{}' subdir {:?} - {}", name, l1path, err);
bail!(
"unable to create chunk store '{}' subdir {:?} - {}",
name,
l1path,
err
);
}
let percentage = (i * 100) / (64 * 1024);
if percentage != last_percentage {
@ -128,7 +155,6 @@ impl ChunkStore {
}
pub fn open<P: Into<PathBuf>>(name: &str, base: P) -> Result<Self, Error> {
let base: PathBuf = base.into();
if !base.is_absolute() {
@ -138,7 +164,12 @@ impl ChunkStore {
let chunk_dir = Self::chunk_dir(&base);
if let Err(err) = std::fs::metadata(&chunk_dir) {
bail!("unable to open chunk store '{}' at {:?} - {}", name, chunk_dir, err);
bail!(
"unable to open chunk store '{}' at {:?} - {}",
name,
chunk_dir,
err
);
}
let lockfile_path = Self::lockfile_path(&base);
@ -150,7 +181,7 @@ impl ChunkStore {
base,
chunk_dir,
locker,
mutex: Mutex::new(())
mutex: Mutex::new(()),
})
}
@ -159,7 +190,11 @@ impl ChunkStore {
Ok(())
}
pub fn cond_touch_chunk(&self, digest: &[u8; 32], fail_if_not_exist: bool) -> Result<bool, Error> {
pub fn cond_touch_chunk(
&self,
digest: &[u8; 32],
fail_if_not_exist: bool,
) -> Result<bool, Error> {
let (chunk_path, _digest_str) = self.chunk_path(digest);
self.cond_touch_path(&chunk_path, fail_if_not_exist)
}
@ -169,8 +204,14 @@ impl ChunkStore {
const UTIME_OMIT: i64 = (1 << 30) - 2;
let times: [libc::timespec; 2] = [
libc::timespec { tv_sec: 0, tv_nsec: UTIME_NOW },
libc::timespec { tv_sec: 0, tv_nsec: UTIME_OMIT }
libc::timespec {
tv_sec: 0,
tv_nsec: UTIME_NOW,
},
libc::timespec {
tv_sec: 0,
tv_nsec: UTIME_OMIT,
},
];
use nix::NixPath;
@ -194,15 +235,16 @@ impl ChunkStore {
pub fn get_chunk_iterator(
&self,
) -> Result<
impl Iterator<Item = (Result<proxmox_sys::fs::ReadDirEntry, Error>, usize, bool)> + std::iter::FusedIterator,
Error
impl Iterator<Item = (Result<proxmox_sys::fs::ReadDirEntry, Error>, usize, bool)>
+ std::iter::FusedIterator,
Error,
> {
use nix::dir::Dir;
use nix::fcntl::OFlag;
use nix::sys::stat::Mode;
let base_handle = Dir::open(&self.chunk_dir, OFlag::O_RDONLY, Mode::empty())
.map_err(|err| {
let base_handle =
Dir::open(&self.chunk_dir, OFlag::O_RDONLY, Mode::empty()).map_err(|err| {
format_err!(
"unable to open store '{}' chunk dir {:?} - {}",
self.name,
@ -270,11 +312,16 @@ impl ChunkStore {
// other errors are fatal, so end our iteration
done = true;
// and pass the error through:
return Some((Err(format_err!("unable to read subdir '{}' - {}", subdir, err)), percentage, false));
return Some((
Err(format_err!("unable to read subdir '{}' - {}", subdir, err)),
percentage,
false,
));
}
}
}
}).fuse())
})
.fuse())
}
pub fn oldest_writer(&self) -> Option<i64> {
@ -305,12 +352,7 @@ impl ChunkStore {
for (entry, percentage, bad) in self.get_chunk_iterator()? {
if last_percentage != percentage {
last_percentage = percentage;
task_log!(
worker,
"processed {}% ({} chunks)",
percentage,
chunk_count,
);
task_log!(worker, "processed {}% ({} chunks)", percentage, chunk_count,);
}
worker.check_abort()?;
@ -318,12 +360,19 @@ impl ChunkStore {
let (dirfd, entry) = match entry {
Ok(entry) => (entry.parent_fd(), entry),
Err(err) => bail!("chunk iterator on chunk store '{}' failed - {}", self.name, err),
Err(err) => bail!(
"chunk iterator on chunk store '{}' failed - {}",
self.name,
err
),
};
let file_type = match entry.file_type() {
Some(file_type) => file_type,
None => bail!("unsupported file system type on chunk store '{}'", self.name),
None => bail!(
"unsupported file system type on chunk store '{}'",
self.name
),
};
if file_type != nix::dir::Type::File {
continue;
@ -376,12 +425,7 @@ impl ChunkStore {
Ok(())
}
pub fn insert_chunk(
&self,
chunk: &DataBlob,
digest: &[u8; 32],
) -> Result<(bool, u64), Error> {
pub fn insert_chunk(&self, chunk: &DataBlob, digest: &[u8; 32]) -> Result<(bool, u64), Error> {
//println!("DIGEST {}", hex::encode(digest));
let (chunk_path, digest_str) = self.chunk_path(digest);
@ -393,7 +437,11 @@ impl ChunkStore {
self.touch_chunk(digest)?;
return Ok((true, metadata.len()));
} else {
bail!("Got unexpected file type on store '{}' for chunk {}", self.name, digest_str);
bail!(
"Got unexpected file type on store '{}' for chunk {}",
self.name,
digest_str
);
}
}
@ -446,7 +494,6 @@ impl ChunkStore {
}
pub fn relative_path(&self, path: &Path) -> PathBuf {
let mut full_path = self.base.clone();
full_path.push(path);
full_path
@ -469,10 +516,8 @@ impl ChunkStore {
}
}
#[test]
fn test_chunk_store1() {
let mut path = std::fs::canonicalize(".").unwrap(); // we need absolute path
path.push(".testdir");
@ -481,10 +526,14 @@ fn test_chunk_store1() {
let chunk_store = ChunkStore::open("test", &path);
assert!(chunk_store.is_err());
let user = nix::unistd::User::from_uid(nix::unistd::Uid::current()).unwrap().unwrap();
let user = nix::unistd::User::from_uid(nix::unistd::Uid::current())
.unwrap()
.unwrap();
let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid, None).unwrap();
let (chunk, digest) = crate::data_blob::DataChunkBuilder::new(&[0u8, 1u8]).build().unwrap();
let (chunk, digest) = crate::data_blob::DataChunkBuilder::new(&[0u8, 1u8])
.build()
.unwrap();
let (exists, _) = chunk_store.insert_chunk(&chunk, &digest).unwrap();
assert!(!exists);
@ -492,7 +541,6 @@ fn test_chunk_store1() {
let (exists, _) = chunk_store.insert_chunk(&chunk, &digest).unwrap();
assert!(exists);
let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid, None);
assert!(chunk_store.is_err());

View File

@ -1,5 +1,5 @@
use std::io::{BufRead, Read};
use std::sync::Arc;
use std::io::{Read, BufRead};
use anyhow::{bail, Error};
@ -14,8 +14,12 @@ pub struct CryptReader<R> {
}
impl<R: BufRead> CryptReader<R> {
pub fn new(reader: R, iv: [u8; 16], tag: [u8; 16], config: Arc<CryptConfig>) -> Result<Self, Error> {
pub fn new(
reader: R,
iv: [u8; 16],
tag: [u8; 16],
config: Arc<CryptConfig>,
) -> Result<Self, Error> {
let block_size = config.cipher().block_size(); // Note: block size is normally 1 byte for stream ciphers
if block_size.count_ones() != 1 || block_size > 512 {
bail!("unexpected Cipher block size {}", block_size);
@ -23,7 +27,13 @@ impl <R: BufRead> CryptReader<R> {
let mut crypter = config.data_crypter(&iv, openssl::symm::Mode::Decrypt)?;
crypter.set_tag(&tag)?;
Ok(Self { reader, crypter, block_size, finalized: false, small_read_buf: Vec::new() })
Ok(Self {
reader,
crypter,
block_size,
finalized: false,
small_read_buf: Vec::new(),
})
}
pub fn finish(self) -> Result<R, Error> {
@ -35,10 +45,13 @@ impl <R: BufRead> CryptReader<R> {
}
impl<R: BufRead> Read for CryptReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
if !self.small_read_buf.is_empty() {
let max = if self.small_read_buf.len() > buf.len() { buf.len() } else { self.small_read_buf.len() };
let max = if self.small_read_buf.len() > buf.len() {
buf.len()
} else {
self.small_read_buf.len()
};
let rest = self.small_read_buf.split_off(max);
buf[..max].copy_from_slice(&self.small_read_buf);
self.small_read_buf = rest;
@ -51,7 +64,8 @@ impl <R: BufRead> Read for CryptReader<R> {
if buf.len() <= 2 * self.block_size {
let mut outbuf = [0u8; 1024];
let count = if data.is_empty() { // EOF
let count = if data.is_empty() {
// EOF
let written = self.crypter.finalize(&mut outbuf)?;
self.finalized = true;
written
@ -73,7 +87,8 @@ impl <R: BufRead> Read for CryptReader<R> {
buf[..count].copy_from_slice(&outbuf[..count]);
Ok(count)
}
} else if data.is_empty() { // EOF
} else if data.is_empty() {
// EOF
let rest = self.crypter.finalize(buf)?;
self.finalized = true;
Ok(rest)

View File

@ -1,5 +1,5 @@
use std::sync::Arc;
use std::io::Write;
use std::sync::Arc;
use anyhow::Error;
@ -14,7 +14,6 @@ pub struct CryptWriter<W> {
}
impl<W: Write> CryptWriter<W> {
pub fn new(writer: W, config: Arc<CryptConfig>) -> Result<Self, Error> {
let mut iv = [0u8; 16];
proxmox_sys::linux::fill_with_random_data(&mut iv)?;
@ -22,7 +21,13 @@ impl <W: Write> CryptWriter<W> {
let crypter = config.data_crypter(&iv, openssl::symm::Mode::Encrypt)?;
Ok(Self { writer, iv, crypter, block_size, encr_buf: Box::new([0u8; 64*1024]) })
Ok(Self {
writer,
iv,
crypter,
block_size,
encr_buf: Box::new([0u8; 64 * 1024]),
})
}
pub fn finish(mut self) -> Result<(W, [u8; 16], [u8; 16]), Error> {
@ -41,17 +46,19 @@ impl <W: Write> CryptWriter<W> {
}
impl<W: Write> Write for CryptWriter<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
let mut write_size = buf.len();
if write_size > (self.encr_buf.len() - self.block_size) {
write_size = self.encr_buf.len() - self.block_size;
}
let count = self.crypter.update(&buf[..write_size], self.encr_buf.as_mut())
let count = self
.crypter
.update(&buf[..write_size], self.encr_buf.as_mut())
.map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("crypter update failed - {}", err))
format!("crypter update failed - {}", err),
)
})?;
self.writer.write_all(&self.encr_buf[..count])?;

View File

@ -6,8 +6,8 @@ use openssl::symm::{decrypt_aead, Mode};
use proxmox_io::{ReadExt, WriteExt};
use pbs_tools::crypt_config::CryptConfig;
use pbs_api_types::CryptMode;
use pbs_tools::crypt_config::CryptConfig;
use super::file_formats::*;
@ -35,7 +35,6 @@ pub struct DataBlob {
}
impl DataBlob {
/// accessor to raw_data field
pub fn raw_data(&self) -> &[u8] {
&self.raw_data
@ -91,13 +90,11 @@ impl DataBlob {
config: Option<&CryptConfig>,
compress: bool,
) -> Result<Self, Error> {
if data.len() > MAX_BLOB_SIZE {
bail!("data blob too large ({} bytes).", data.len());
}
let mut blob = if let Some(config) = config {
let compr_data;
let (_compress, data, magic) = if compress {
compr_data = zstd::block::compress(data, 1)?;
@ -115,7 +112,10 @@ impl DataBlob {
let mut raw_data = Vec::with_capacity(data.len() + header_len);
let dummy_head = EncryptedDataBlobHeader {
head: DataBlobHeader { magic: [0u8; 8], crc: [0; 4] },
head: DataBlobHeader {
magic: [0u8; 8],
crc: [0; 4],
},
iv: [0u8; 16],
tag: [0u8; 16],
};
@ -126,7 +126,9 @@ impl DataBlob {
let (iv, tag) = Self::encrypt_to(config, data, &mut raw_data)?;
let head = EncryptedDataBlobHeader {
head: DataBlobHeader { magic, crc: [0; 4] }, iv, tag,
head: DataBlobHeader { magic, crc: [0; 4] },
iv,
tag,
};
unsafe {
@ -135,7 +137,6 @@ impl DataBlob {
DataBlob { raw_data }
} else {
let max_data_len = data.len() + std::mem::size_of::<DataBlobHeader>();
if compress {
let mut comp_data = Vec::with_capacity(max_data_len);
@ -151,7 +152,9 @@ impl DataBlob {
zstd::stream::copy_encode(data, &mut comp_data, 1)?;
if comp_data.len() < max_data_len {
let mut blob = DataBlob { raw_data: comp_data };
let mut blob = DataBlob {
raw_data: comp_data,
};
blob.set_crc(blob.compute_crc());
return Ok(blob);
}
@ -180,18 +183,23 @@ impl DataBlob {
pub fn crypt_mode(&self) -> Result<CryptMode, Error> {
let magic = self.magic();
Ok(if magic == &UNCOMPRESSED_BLOB_MAGIC_1_0 || magic == &COMPRESSED_BLOB_MAGIC_1_0 {
Ok(
if magic == &UNCOMPRESSED_BLOB_MAGIC_1_0 || magic == &COMPRESSED_BLOB_MAGIC_1_0 {
CryptMode::None
} else if magic == &ENCR_COMPR_BLOB_MAGIC_1_0 || magic == &ENCRYPTED_BLOB_MAGIC_1_0 {
CryptMode::Encrypt
} else {
bail!("Invalid blob magic number.");
})
},
)
}
/// Decode blob data
pub fn decode(&self, config: Option<&CryptConfig>, digest: Option<&[u8; 32]>) -> Result<Vec<u8>, Error> {
pub fn decode(
&self,
config: Option<&CryptConfig>,
digest: Option<&[u8; 32]>,
) -> Result<Vec<u8>, Error> {
let magic = self.magic();
if magic == &UNCOMPRESSED_BLOB_MAGIC_1_0 {
@ -219,9 +227,19 @@ impl DataBlob {
if let Some(config) = config {
let data = if magic == &ENCR_COMPR_BLOB_MAGIC_1_0 {
Self::decode_compressed_chunk(config, &self.raw_data[header_len..], &head.iv, &head.tag)?
Self::decode_compressed_chunk(
config,
&self.raw_data[header_len..],
&head.iv,
&head.tag,
)?
} else {
Self::decode_uncompressed_chunk(config, &self.raw_data[header_len..], &head.iv, &head.tag)?
Self::decode_uncompressed_chunk(
config,
&self.raw_data[header_len..],
&head.iv,
&head.tag,
)?
};
if let Some(digest) = digest {
Self::verify_digest(&data, Some(config), digest)?;
@ -237,7 +255,6 @@ impl DataBlob {
/// Load blob from ``reader``, verify CRC
pub fn load_from_reader(reader: &mut dyn std::io::Read) -> Result<Self, Error> {
let mut data = Vec::with_capacity(1024 * 1024);
reader.read_to_end(&mut data)?;
@ -250,7 +267,6 @@ impl DataBlob {
/// Create Instance from raw data
pub fn from_raw(data: Vec<u8>) -> Result<Self, Error> {
if data.len() < std::mem::size_of::<DataBlobHeader>() {
bail!("blob too small ({} bytes).", data.len());
}
@ -258,7 +274,6 @@ impl DataBlob {
let magic = &data[0..8];
if magic == ENCR_COMPR_BLOB_MAGIC_1_0 || magic == ENCRYPTED_BLOB_MAGIC_1_0 {
if data.len() < std::mem::size_of::<EncryptedDataBlobHeader>() {
bail!("encrypted blob too small ({} bytes).", data.len());
}
@ -267,7 +282,6 @@ impl DataBlob {
Ok(blob)
} else if magic == COMPRESSED_BLOB_MAGIC_1_0 || magic == UNCOMPRESSED_BLOB_MAGIC_1_0 {
let blob = DataBlob { raw_data: data };
Ok(blob)
@ -293,7 +307,6 @@ impl DataBlob {
expected_chunk_size: usize,
expected_digest: &[u8; 32],
) -> Result<(), Error> {
let magic = self.magic();
if magic == &ENCR_COMPR_BLOB_MAGIC_1_0 || magic == &ENCRYPTED_BLOB_MAGIC_1_0 {
@ -304,7 +317,11 @@ impl DataBlob {
let data = self.decode(None, Some(expected_digest))?;
if expected_chunk_size != data.len() {
bail!("detected chunk with wrong length ({} != {})", expected_chunk_size, data.len());
bail!(
"detected chunk with wrong length ({} != {})",
expected_chunk_size,
data.len()
);
}
Ok(())
@ -315,7 +332,6 @@ impl DataBlob {
config: Option<&CryptConfig>,
expected_digest: &[u8; 32],
) -> Result<(), Error> {
let digest = match config {
Some(config) => config.compute_digest(data),
None => openssl::sha::sha256(data),
@ -345,7 +361,6 @@ impl DataBlob {
data: &[u8],
mut output: W,
) -> Result<([u8; 16], [u8; 16]), Error> {
let mut iv = [0u8; 16];
proxmox_sys::linux::fill_with_random_data(&mut iv)?;
@ -361,7 +376,9 @@ impl DataBlob {
let mut start = 0;
loop {
let mut end = start + max_encoder_input;
if end > data.len() { end = data.len(); }
if end > data.len() {
end = data.len();
}
if end > start {
let count = c.update(&data[start..end], &mut encr_buf)?;
output.write_all(&encr_buf[..count])?;
@ -372,7 +389,9 @@ impl DataBlob {
}
let rest = c.finalize(&mut encr_buf)?;
if rest > 0 { output.write_all(&encr_buf[..rest])?; }
if rest > 0 {
output.write_all(&encr_buf[..rest])?;
}
output.flush()?;
@ -388,7 +407,6 @@ impl DataBlob {
iv: &[u8; 16],
tag: &[u8; 16],
) -> Result<Vec<u8>, Error> {
let dec = Vec::with_capacity(1024 * 1024);
let mut decompressor = zstd::stream::write::Decoder::new(dec)?;
@ -403,7 +421,9 @@ impl DataBlob {
let mut start = 0;
loop {
let mut end = start + max_decoder_input;
if end > data.len() { end = data.len(); }
if end > data.len() {
end = data.len();
}
if end > start {
let count = c.update(&data[start..end], &mut decr_buf)?;
decompressor.write_all(&decr_buf[0..count])?;
@ -415,7 +435,9 @@ impl DataBlob {
c.set_tag(tag)?;
let rest = c.finalize(&mut decr_buf)?;
if rest > 0 { decompressor.write_all(&decr_buf[..rest])?; }
if rest > 0 {
decompressor.write_all(&decr_buf[..rest])?;
}
decompressor.flush()?;
@ -429,7 +451,6 @@ impl DataBlob {
iv: &[u8; 16],
tag: &[u8; 16],
) -> Result<Vec<u8>, Error> {
let decr_data = decrypt_aead(
*config.cipher(),
config.enc_key(),
@ -441,7 +462,6 @@ impl DataBlob {
Ok(decr_data)
}
}
/// Builder for chunk DataBlobs
@ -458,7 +478,6 @@ pub struct DataChunkBuilder<'a, 'b> {
}
impl<'a, 'b> DataChunkBuilder<'a, 'b> {
/// Create a new builder instance.
pub fn new(orig_data: &'a [u8]) -> Self {
Self {
@ -537,5 +556,4 @@ impl <'a, 'b> DataChunkBuilder<'a, 'b> {
chunk_builder.build()
}
}

View File

@ -1,9 +1,9 @@
use std::collections::{HashSet, HashMap};
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::convert::TryFrom;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use anyhow::{bail, format_err, Error};
@ -11,43 +11,40 @@ use lazy_static::lazy_static;
use proxmox_schema::ApiType;
use proxmox_sys::fs::{replace_file, file_read_optional_string, CreateOptions};
use proxmox_sys::fs::{file_read_optional_string, replace_file, CreateOptions};
use proxmox_sys::fs::{lock_dir_noblock, DirLockGuard};
use proxmox_sys::process_locker::ProcessLockSharedGuard;
use proxmox_sys::WorkerTaskContext;
use proxmox_sys::{task_log, task_warn};
use proxmox_sys::fs::{lock_dir_noblock, DirLockGuard};
use pbs_api_types::{
UPID, DataStoreConfig, Authid, GarbageCollectionStatus, HumanByte,
ChunkOrder, DatastoreTuning, Operation,
Authid, ChunkOrder, DataStoreConfig, DatastoreTuning, GarbageCollectionStatus, HumanByte,
Operation, UPID,
};
use pbs_config::{open_backup_lockfile, BackupLockGuard, ConfigVersionCache};
use crate::DataBlob;
use crate::backup_info::{BackupGroup, BackupDir};
use crate::backup_info::{BackupDir, BackupGroup};
use crate::chunk_store::ChunkStore;
use crate::dynamic_index::{DynamicIndexReader, DynamicIndexWriter};
use crate::fixed_index::{FixedIndexReader, FixedIndexWriter};
use crate::index::IndexFile;
use crate::manifest::{
MANIFEST_BLOB_NAME, MANIFEST_LOCK_NAME, CLIENT_LOG_BLOB_NAME,
ArchiveType, BackupManifest,
archive_type,
archive_type, ArchiveType, BackupManifest, CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME,
MANIFEST_LOCK_NAME,
};
use crate::task_tracking::update_active_operations;
use crate::DataBlob;
lazy_static! {
static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStoreImpl>>> = Mutex::new(HashMap::new());
static ref DATASTORE_MAP: Mutex<HashMap<String, Arc<DataStoreImpl>>> =
Mutex::new(HashMap::new());
}
/// checks if auth_id is owner, or, if owner is a token, if
/// auth_id is the user of the token
pub fn check_backup_owner(
owner: &Authid,
auth_id: &Authid,
) -> Result<(), Error> {
let correct_owner = owner == auth_id
|| (owner.is_token() && &Authid::from(owner.user().clone()) == auth_id);
pub fn check_backup_owner(owner: &Authid, auth_id: &Authid) -> Result<(), Error> {
let correct_owner =
owner == auth_id || (owner.is_token() && &Authid::from(owner.user().clone()) == auth_id);
if !correct_owner {
bail!("backup owner check failed ({} != {})", auth_id, owner);
}
@ -152,9 +149,7 @@ impl DataStore {
let mut map = DATASTORE_MAP.lock().unwrap();
// removes all elements that are not in the config
map.retain(|key, _| {
config.sections.contains_key(key)
});
map.retain(|key, _| config.sections.contains_key(key));
Ok(())
}
@ -183,7 +178,8 @@ impl DataStore {
};
let tuning: DatastoreTuning = serde_json::from_value(
DatastoreTuning::API_SCHEMA.parse_property_string(config.tuning.as_deref().unwrap_or(""))?
DatastoreTuning::API_SCHEMA
.parse_property_string(config.tuning.as_deref().unwrap_or(""))?,
)?;
let chunk_order = tuning.chunk_order.unwrap_or(ChunkOrder::Inode);
@ -202,20 +198,31 @@ impl DataStore {
&self,
) -> Result<
impl Iterator<Item = (Result<proxmox_sys::fs::ReadDirEntry, Error>, usize, bool)>,
Error
Error,
> {
self.inner.chunk_store.get_chunk_iterator()
}
pub fn create_fixed_writer<P: AsRef<Path>>(&self, filename: P, size: usize, chunk_size: usize) -> Result<FixedIndexWriter, Error> {
let index = FixedIndexWriter::create(self.inner.chunk_store.clone(), filename.as_ref(), size, chunk_size)?;
pub fn create_fixed_writer<P: AsRef<Path>>(
&self,
filename: P,
size: usize,
chunk_size: usize,
) -> Result<FixedIndexWriter, Error> {
let index = FixedIndexWriter::create(
self.inner.chunk_store.clone(),
filename.as_ref(),
size,
chunk_size,
)?;
Ok(index)
}
pub fn open_fixed_reader<P: AsRef<Path>>(&self, filename: P) -> Result<FixedIndexReader, Error> {
pub fn open_fixed_reader<P: AsRef<Path>>(
&self,
filename: P,
) -> Result<FixedIndexReader, Error> {
let full_path = self.inner.chunk_store.relative_path(filename.as_ref());
let index = FixedIndexReader::open(&full_path)?;
@ -224,17 +231,18 @@ impl DataStore {
}
pub fn create_dynamic_writer<P: AsRef<Path>>(
&self, filename: P,
&self,
filename: P,
) -> Result<DynamicIndexWriter, Error> {
let index = DynamicIndexWriter::create(
self.inner.chunk_store.clone(), filename.as_ref())?;
let index = DynamicIndexWriter::create(self.inner.chunk_store.clone(), filename.as_ref())?;
Ok(index)
}
pub fn open_dynamic_reader<P: AsRef<Path>>(&self, filename: P) -> Result<DynamicIndexReader, Error> {
pub fn open_dynamic_reader<P: AsRef<Path>>(
&self,
filename: P,
) -> Result<DynamicIndexReader, Error> {
let full_path = self.inner.chunk_store.relative_path(filename.as_ref());
let index = DynamicIndexReader::open(&full_path)?;
@ -247,8 +255,7 @@ impl DataStore {
P: AsRef<Path>,
{
let filename = filename.as_ref();
let out: Box<dyn IndexFile + Send> =
match archive_type(filename)? {
let out: Box<dyn IndexFile + Send> = match archive_type(filename)? {
ArchiveType::DynamicIndex => Box::new(self.open_dynamic_reader(filename)?),
ArchiveType::FixedIndex => Box::new(self.open_fixed_reader(filename)?),
_ => bail!("cannot open index file of unknown type: {:?}", filename),
@ -262,15 +269,13 @@ impl DataStore {
index: &dyn IndexFile,
checked: &mut HashSet<[u8; 32]>,
) -> Result<(), Error> {
for pos in 0..index.index_count() {
let info = index.chunk_info(pos).unwrap();
if checked.contains(&info.digest) {
continue;
}
self.stat_chunk(&info.digest).
map_err(|err| {
self.stat_chunk(&info.digest).map_err(|err| {
format_err!(
"fast_index_verification error, stat_chunk {} failed - {}",
hex::encode(&info.digest),
@ -295,25 +300,35 @@ impl DataStore {
/// Cleanup a backup directory
///
/// Removes all files not mentioned in the manifest.
pub fn cleanup_backup_dir(&self, backup_dir: &BackupDir, manifest: &BackupManifest
pub fn cleanup_backup_dir(
&self,
backup_dir: &BackupDir,
manifest: &BackupManifest,
) -> Result<(), Error> {
let mut full_path = self.base_path();
full_path.push(backup_dir.relative_path());
let mut wanted_files = HashSet::new();
wanted_files.insert(MANIFEST_BLOB_NAME.to_string());
wanted_files.insert(CLIENT_LOG_BLOB_NAME.to_string());
manifest.files().iter().for_each(|item| { wanted_files.insert(item.filename.clone()); });
manifest.files().iter().for_each(|item| {
wanted_files.insert(item.filename.clone());
});
for item in proxmox_sys::fs::read_subdir(libc::AT_FDCWD, &full_path)?.flatten() {
if let Some(file_type) = item.file_type() {
if file_type != nix::dir::Type::File { continue; }
if file_type != nix::dir::Type::File {
continue;
}
}
let file_name = item.file_name().to_bytes();
if file_name == b"." || file_name == b".." { continue; };
if file_name == b"." || file_name == b".." {
continue;
};
if let Ok(name) = std::str::from_utf8(file_name) {
if wanted_files.contains(name) { continue; }
if wanted_files.contains(name) {
continue;
}
}
println!("remove unused file {:?}", item.file_name());
let dirfd = item.parent_fd();
@ -340,10 +355,13 @@ impl DataStore {
/// Remove a complete backup group including all snapshots, returns true
/// if all snapshots were removed, and false if some were protected
pub fn remove_backup_group(&self, backup_group: &BackupGroup) -> Result<bool, Error> {
let full_path = self.group_path(backup_group);
let _guard = proxmox_sys::fs::lock_dir_noblock(&full_path, "backup group", "possible running backup")?;
let _guard = proxmox_sys::fs::lock_dir_noblock(
&full_path,
"backup group",
"possible running backup",
)?;
log::info!("removing backup group {:?}", full_path);
@ -360,8 +378,7 @@ impl DataStore {
if removed_all {
// no snapshots left, we can now safely remove the empty folder
std::fs::remove_dir_all(&full_path)
.map_err(|err| {
std::fs::remove_dir_all(&full_path).map_err(|err| {
format_err!(
"removing backup group directory {:?} failed - {}",
full_path,
@ -375,7 +392,6 @@ impl DataStore {
/// Remove a backup directory including all content
pub fn remove_backup_dir(&self, backup_dir: &BackupDir, force: bool) -> Result<(), Error> {
let full_path = self.snapshot_path(backup_dir);
let (_guard, _manifest_guard);
@ -389,13 +405,8 @@ impl DataStore {
}
log::info!("removing backup snapshot {:?}", full_path);
std::fs::remove_dir_all(&full_path)
.map_err(|err| {
format_err!(
"removing backup snapshot {:?} failed - {}",
full_path,
err,
)
std::fs::remove_dir_all(&full_path).map_err(|err| {
format_err!("removing backup snapshot {:?} failed - {}", full_path, err,)
})?;
// the manifest does not exists anymore, we do not need to keep the lock
@ -460,7 +471,8 @@ impl DataStore {
open_options.create_new(true);
}
let mut file = open_options.open(&path)
let mut file = open_options
.open(&path)
.map_err(|err| format_err!("unable to create owner file {:?} - {}", path, err))?;
writeln!(file, "{}", auth_id)
@ -490,13 +502,21 @@ impl DataStore {
// create the last component now
match std::fs::create_dir(&full_path) {
Ok(_) => {
let guard = lock_dir_noblock(&full_path, "backup group", "another backup is already running")?;
let guard = lock_dir_noblock(
&full_path,
"backup group",
"another backup is already running",
)?;
self.set_owner(backup_group, auth_id, false)?;
let owner = self.get_owner(backup_group)?; // just to be sure
Ok((owner, guard))
}
Err(ref err) if err.kind() == io::ErrorKind::AlreadyExists => {
let guard = lock_dir_noblock(&full_path, "backup group", "another backup is already running")?;
let guard = lock_dir_noblock(
&full_path,
"backup group",
"another backup is already running",
)?;
let owner = self.get_owner(backup_group)?; // just to be sure
Ok((owner, guard))
}
@ -507,20 +527,28 @@ impl DataStore {
/// Creates a new backup snapshot inside a BackupGroup
///
/// The BackupGroup directory needs to exist.
pub fn create_locked_backup_dir(&self, backup_dir: &BackupDir)
-> Result<(PathBuf, bool, DirLockGuard), Error>
{
pub fn create_locked_backup_dir(
&self,
backup_dir: &BackupDir,
) -> Result<(PathBuf, bool, DirLockGuard), Error> {
let relative_path = backup_dir.relative_path();
let mut full_path = self.base_path();
full_path.push(&relative_path);
let lock = ||
lock_dir_noblock(&full_path, "snapshot", "internal error - tried creating snapshot that's already in use");
let lock = || {
lock_dir_noblock(
&full_path,
"snapshot",
"internal error - tried creating snapshot that's already in use",
)
};
match std::fs::create_dir(&full_path) {
Ok(_) => Ok((relative_path, true, lock()?)),
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok((relative_path, false, lock()?)),
Err(e) => Err(e.into())
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => {
Ok((relative_path, false, lock()?))
}
Err(e) => Err(e.into()),
}
}
@ -535,7 +563,8 @@ impl DataStore {
// make sure we skip .chunks (and other hidden files to keep it simple)
fn is_hidden(entry: &walkdir::DirEntry) -> bool {
entry.file_name()
entry
.file_name()
.to_str()
.map(|s| s.starts_with('.'))
.unwrap_or(false)
@ -550,7 +579,11 @@ impl DataStore {
bail!("cannot continue garbage-collection safely, permission denied on: {:?}", path)
}
} else {
bail!("unexpected error on datastore traversal: {} - {:?}", inner, path)
bail!(
"unexpected error on datastore traversal: {} - {:?}",
inner,
path
)
}
} else {
bail!("unexpected error on datastore traversal: {}", inner)
@ -563,11 +596,13 @@ impl DataStore {
Ok(entry) => entry.into_path(),
Err(err) => {
handle_entry_err(err)?;
continue
},
continue;
}
};
if let Ok(archive_type) = archive_type(&path) {
if archive_type == ArchiveType::FixedIndex || archive_type == ArchiveType::DynamicIndex {
if archive_type == ArchiveType::FixedIndex
|| archive_type == ArchiveType::DynamicIndex
{
list.push(path);
}
}
@ -584,7 +619,6 @@ impl DataStore {
status: &mut GarbageCollectionStatus,
worker: &dyn WorkerTaskContext,
) -> Result<(), Error> {
status.index_file_count += 1;
status.index_data_bytes += index.index_bytes();
@ -620,7 +654,6 @@ impl DataStore {
status: &mut GarbageCollectionStatus,
worker: &dyn WorkerTaskContext,
) -> Result<(), Error> {
let image_list = self.list_images()?;
let image_count = image_list.len();
@ -629,7 +662,6 @@ impl DataStore {
let mut strange_paths_count: u64 = 0;
for (i, img) in image_list.into_iter().enumerate() {
worker.check_abort()?;
worker.fail_on_shutdown()?;
@ -683,7 +715,6 @@ impl DataStore {
);
}
Ok(())
}
@ -695,17 +726,23 @@ impl DataStore {
!matches!(self.inner.gc_mutex.try_lock(), Ok(_))
}
pub fn garbage_collection(&self, worker: &dyn WorkerTaskContext, upid: &UPID) -> Result<(), Error> {
pub fn garbage_collection(
&self,
worker: &dyn WorkerTaskContext,
upid: &UPID,
) -> Result<(), Error> {
if let Ok(ref mut _mutex) = self.inner.gc_mutex.try_lock() {
// avoids that we run GC if an old daemon process has still a
// running backup writer, which is not save as we have no "oldest
// writer" information and thus no safe atime cutoff
let _exclusive_lock = self.inner.chunk_store.try_exclusive_lock()?;
let phase1_start_time = proxmox_time::epoch_i64();
let oldest_writer = self.inner.chunk_store.oldest_writer().unwrap_or(phase1_start_time);
let oldest_writer = self
.inner
.chunk_store
.oldest_writer()
.unwrap_or(phase1_start_time);
let mut gc_status = GarbageCollectionStatus::default();
gc_status.upid = Some(upid.to_string());
@ -751,7 +788,8 @@ impl DataStore {
);
if gc_status.index_data_bytes > 0 {
let comp_per = (gc_status.disk_bytes as f64 * 100.)/gc_status.index_data_bytes as f64;
let comp_per =
(gc_status.disk_bytes as f64 * 100.) / gc_status.index_data_bytes as f64;
task_log!(
worker,
"On-Disk usage: {} ({:.2}%)",
@ -793,7 +831,6 @@ impl DataStore {
}
*self.inner.last_gc_status.lock().unwrap() = gc_status;
} else {
bail!("Start GC failed - (already running/locked)");
}
@ -809,15 +846,17 @@ impl DataStore {
self.inner.chunk_store.chunk_path(digest)
}
pub fn cond_touch_chunk(&self, digest: &[u8; 32], fail_if_not_exist: bool) -> Result<bool, Error> {
self.inner.chunk_store.cond_touch_chunk(digest, fail_if_not_exist)
pub fn cond_touch_chunk(
&self,
digest: &[u8; 32],
fail_if_not_exist: bool,
) -> Result<bool, Error> {
self.inner
.chunk_store
.cond_touch_chunk(digest, fail_if_not_exist)
}
pub fn insert_chunk(
&self,
chunk: &DataBlob,
digest: &[u8; 32],
) -> Result<(bool, u64), Error> {
pub fn insert_chunk(&self, chunk: &DataBlob, digest: &[u8; 32]) -> Result<(bool, u64), Error> {
self.inner.chunk_store.insert_chunk(chunk, digest)
}
@ -829,38 +868,37 @@ impl DataStore {
proxmox_lang::try_block!({
let mut file = std::fs::File::open(&path)?;
DataBlob::load_from_reader(&mut file)
}).map_err(|err| format_err!("unable to load blob '{:?}' - {}", path, err))
})
.map_err(|err| format_err!("unable to load blob '{:?}' - {}", path, err))
}
pub fn stat_chunk(&self, digest: &[u8; 32]) -> Result<std::fs::Metadata, Error> {
let (chunk_path, _digest_str) = self.inner.chunk_store.chunk_path(digest);
std::fs::metadata(chunk_path).map_err(Error::from)
}
pub fn load_chunk(&self, digest: &[u8; 32]) -> Result<DataBlob, Error> {
let (chunk_path, digest_str) = self.inner.chunk_store.chunk_path(digest);
proxmox_lang::try_block!({
let mut file = std::fs::File::open(&chunk_path)?;
DataBlob::load_from_reader(&mut file)
}).map_err(|err| format_err!(
})
.map_err(|err| {
format_err!(
"store '{}', unable to load chunk '{}' - {}",
self.name(),
digest_str,
err,
))
)
})
}
/// Returns the filename to lock a manifest
///
/// Also creates the basedir. The lockfile is located in
/// '/run/proxmox-backup/locks/{datastore}/{type}/{id}/{timestamp}.index.json.lck'
fn manifest_lock_path(
&self,
backup_dir: &BackupDir,
) -> Result<String, Error> {
fn manifest_lock_path(&self, backup_dir: &BackupDir) -> Result<String, Error> {
let mut path = format!(
"/run/proxmox-backup/locks/{}/{}/{}",
self.name(),
@ -869,32 +907,27 @@ impl DataStore {
);
std::fs::create_dir_all(&path)?;
use std::fmt::Write;
write!(path, "/{}{}", backup_dir.backup_time_string(), &MANIFEST_LOCK_NAME)?;
write!(
path,
"/{}{}",
backup_dir.backup_time_string(),
&MANIFEST_LOCK_NAME
)?;
Ok(path)
}
fn lock_manifest(
&self,
backup_dir: &BackupDir,
) -> Result<BackupLockGuard, Error> {
fn lock_manifest(&self, backup_dir: &BackupDir) -> Result<BackupLockGuard, Error> {
let path = self.manifest_lock_path(backup_dir)?;
// update_manifest should never take a long time, so if someone else has
// the lock we can simply block a bit and should get it soon
open_backup_lockfile(&path, Some(Duration::from_secs(5)), true)
.map_err(|err| {
format_err!(
"unable to acquire manifest lock {:?} - {}", &path, err
)
})
.map_err(|err| format_err!("unable to acquire manifest lock {:?} - {}", &path, err))
}
/// Load the manifest without a lock. Must not be written back.
pub fn load_manifest(
&self,
backup_dir: &BackupDir,
) -> Result<(BackupManifest, u64), Error> {
pub fn load_manifest(&self, backup_dir: &BackupDir) -> Result<(BackupManifest, u64), Error> {
let blob = self.load_blob(backup_dir, MANIFEST_BLOB_NAME)?;
let raw_size = blob.raw_size();
let manifest = BackupManifest::try_from(blob)?;
@ -908,7 +941,6 @@ impl DataStore {
backup_dir: &BackupDir,
update_fn: impl FnOnce(&mut BackupManifest),
) -> Result<(), Error> {
let _guard = self.lock_manifest(backup_dir)?;
let (mut manifest, _) = self.load_manifest(backup_dir)?;
@ -930,11 +962,7 @@ impl DataStore {
}
/// Updates the protection status of the specified snapshot.
pub fn update_protection(
&self,
backup_dir: &BackupDir,
protection: bool
) -> Result<(), Error> {
pub fn update_protection(&self, backup_dir: &BackupDir, protection: bool) -> Result<(), Error> {
let full_path = self.snapshot_path(backup_dir);
let _guard = lock_dir_noblock(&full_path, "snapshot", "possibly running or in use")?;

View File

@ -9,21 +9,21 @@ use std::task::Context;
use anyhow::{bail, format_err, Error};
use proxmox_sys::mmap::Mmap;
use proxmox_io::ReadExt;
use proxmox_uuid::Uuid;
use proxmox_sys::mmap::Mmap;
use proxmox_sys::process_locker::ProcessLockSharedGuard;
use proxmox_uuid::Uuid;
use pxar::accessor::{MaybeReady, ReadAt, ReadAtOperation};
use pbs_tools::lru_cache::LruCache;
use crate::Chunker;
use crate::chunk_stat::ChunkStat;
use crate::chunk_store::ChunkStore;
use crate::data_blob::{DataBlob, DataChunkBuilder};
use crate::file_formats;
use crate::index::{IndexFile, ChunkReadInfo};
use crate::index::{ChunkReadInfo, IndexFile};
use crate::read_chunk::ReadChunk;
use crate::Chunker;
/// Header format definition for dynamic index files (`.dixd`)
#[repr(C)]
@ -228,7 +228,11 @@ impl IndexFile for DynamicIndexReader {
if pos >= self.index.len() {
return None;
}
let start = if pos == 0 { 0 } else { self.index[pos - 1].end() };
let start = if pos == 0 {
0
} else {
self.index[pos - 1].end()
};
let end = self.index[pos].end();
@ -252,7 +256,7 @@ impl IndexFile for DynamicIndexReader {
let found_idx = self.binary_search(0, 0, end_idx, end, offset);
let found_idx = match found_idx {
Ok(i) => i,
Err(_) => return None
Err(_) => return None,
};
let found_start = if found_idx == 0 {
@ -581,13 +585,16 @@ impl<S: ReadChunk> BufferedDynamicReader<S> {
fn buffer_chunk(&mut self, idx: usize) -> Result<(), Error> {
//let (start, end, data) = self.lru_cache.access(
let cached_chunk = self.lru_cache.access(
let cached_chunk = self
.lru_cache
.access(
idx,
&mut ChunkCacher {
store: &mut self.store,
index: &self.index,
},
)?.ok_or_else(|| format_err!("chunk not found by cacher"))?;
)?
.ok_or_else(|| format_err!("chunk not found by cacher"))?;
// fixme: avoid copy
self.read_buffer.clear();

View File

@ -1,14 +1,14 @@
use std::fs::File;
use std::io::Write;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::io::{Seek, SeekFrom};
use anyhow::{bail, format_err, Error};
use proxmox_sys::process_locker::ProcessLockSharedGuard;
use proxmox_io::ReadExt;
use proxmox_sys::process_locker::ProcessLockSharedGuard;
use proxmox_uuid::Uuid;
use crate::chunk_stat::ChunkStat;

View File

@ -45,13 +45,17 @@ pub trait IndexFile {
let mut most_used = Vec::new();
for (digest, count) in map {
if count <= 1 { continue; }
if count <= 1 {
continue;
}
match most_used.binary_search_by_key(&count, |&(_digest, count)| count) {
Ok(p) => most_used.insert(p, (digest, count)),
Err(p) => most_used.insert(p, (digest, count)),
}
if most_used.len() > max { let _ = most_used.pop(); }
if most_used.len() > max {
let _ = most_used.pop();
}
}
let mut map = HashMap::new();

View File

@ -146,7 +146,10 @@
pub const CATALOG_NAME: &str = "catalog.pcat1.didx";
/// Directory path where active operations counters are saved.
pub const ACTIVE_OPERATIONS_DIR: &str = concat!(pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M!(), "/active-operations");
pub const ACTIVE_OPERATIONS_DIR: &str = concat!(
pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M!(),
"/active-operations"
);
#[macro_export]
macro_rules! PROXMOX_BACKUP_PROTOCOL_ID_V1 {
@ -163,8 +166,8 @@ macro_rules! PROXMOX_BACKUP_READER_PROTOCOL_ID_V1 {
}
pub mod backup_info;
pub mod catalog;
pub mod cached_chunk_reader;
pub mod catalog;
pub mod checksum_reader;
pub mod checksum_writer;
pub mod chunk_stat;

View File

@ -4,11 +4,11 @@ use std::sync::Arc;
use anyhow::{bail, Error};
use pbs_tools::crypt_config::CryptConfig;
use pbs_api_types::CryptMode;
use pbs_tools::crypt_config::CryptConfig;
use crate::data_blob::DataBlob;
use crate::read_chunk::{ReadChunk, AsyncReadChunk};
use crate::read_chunk::{AsyncReadChunk, ReadChunk};
use crate::DataStore;
#[derive(Clone)]
@ -19,7 +19,11 @@ pub struct LocalChunkReader {
}
impl LocalChunkReader {
pub fn new(store: Arc<DataStore>, crypt_config: Option<Arc<CryptConfig>>, crypt_mode: CryptMode) -> Self {
pub fn new(
store: Arc<DataStore>,
crypt_config: Option<Arc<CryptConfig>>,
crypt_mode: CryptMode,
) -> Self {
Self {
store,
crypt_config,
@ -29,17 +33,15 @@ impl LocalChunkReader {
fn ensure_crypt_mode(&self, chunk_mode: CryptMode) -> Result<(), Error> {
match self.crypt_mode {
CryptMode::Encrypt => {
match chunk_mode {
CryptMode::Encrypt => match chunk_mode {
CryptMode::Encrypt => Ok(()),
CryptMode::SignOnly | CryptMode::None => bail!("Index and chunk CryptMode don't match."),
CryptMode::SignOnly | CryptMode::None => {
bail!("Index and chunk CryptMode don't match.")
}
},
CryptMode::SignOnly | CryptMode::None => {
match chunk_mode {
CryptMode::SignOnly | CryptMode::None => match chunk_mode {
CryptMode::Encrypt => bail!("Index and chunk CryptMode don't match."),
CryptMode::SignOnly | CryptMode::None => Ok(()),
}
},
}
}
@ -85,7 +87,8 @@ impl AsyncReadChunk for LocalChunkReader {
Box::pin(async move {
let chunk = AsyncReadChunk::read_raw_chunk(self, digest).await?;
let raw_data = chunk.decode(self.crypt_config.as_ref().map(Arc::as_ref), Some(digest))?;
let raw_data =
chunk.decode(self.crypt_config.as_ref().map(Arc::as_ref), Some(digest))?;
// fixme: verify digest?

View File

@ -3,11 +3,11 @@ use std::path::Path;
use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use pbs_tools::crypt_config::CryptConfig;
use pbs_api_types::{CryptMode, Fingerprint};
use pbs_tools::crypt_config::CryptConfig;
use crate::BackupDir;
@ -16,9 +16,12 @@ pub const MANIFEST_LOCK_NAME: &str = ".index.json.lck";
pub const CLIENT_LOG_BLOB_NAME: &str = "client.log.blob";
pub const ENCRYPTED_KEY_BLOB_NAME: &str = "rsa-encrypted.key.blob";
fn crypt_mode_none() -> CryptMode { CryptMode::None }
fn empty_value() -> Value { json!({}) }
fn crypt_mode_none() -> CryptMode {
CryptMode::None
}
fn empty_value() -> Value {
json!({})
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
@ -32,7 +35,6 @@ pub struct FileInfo {
}
impl FileInfo {
/// Return expected CryptMode of referenced chunks
///
/// Encrypted Indices should only reference encrypted chunks, while signed or plain indices
@ -78,14 +80,11 @@ impl ArchiveType {
}
//#[deprecated(note = "use ArchivType::from_path instead")] later...
pub fn archive_type<P: AsRef<Path>>(
archive_name: P,
) -> Result<ArchiveType, Error> {
pub fn archive_type<P: AsRef<Path>>(archive_name: P) -> Result<ArchiveType, Error> {
ArchiveType::from_path(archive_name)
}
impl BackupManifest {
pub fn new(snapshot: BackupDir) -> Self {
Self {
backup_type: snapshot.group().backup_type().into(),
@ -97,9 +96,20 @@ impl BackupManifest {
}
}
pub fn add_file(&mut self, filename: String, size: u64, csum: [u8; 32], crypt_mode: CryptMode) -> Result<(), Error> {
pub fn add_file(
&mut self,
filename: String,
size: u64,
csum: [u8; 32],
crypt_mode: CryptMode,
) -> Result<(), Error> {
let _archive_type = ArchiveType::from_path(&filename)?; // check type
self.files.push(FileInfo { filename, size, csum, crypt_mode });
self.files.push(FileInfo {
filename,
size,
csum,
crypt_mode,
});
Ok(())
}
@ -108,7 +118,6 @@ impl BackupManifest {
}
pub fn lookup_file_info(&self, name: &str) -> Result<&FileInfo, Error> {
let info = self.files.iter().find(|item| item.filename == name);
match info {
@ -118,7 +127,6 @@ impl BackupManifest {
}
pub fn verify_file(&self, name: &str, csum: &[u8; 32], size: u64) -> Result<(), Error> {
let info = self.lookup_file_info(name)?;
if size != info.size {
@ -146,7 +154,6 @@ impl BackupManifest {
}
fn json_signature(data: &Value, crypt_config: &CryptConfig) -> Result<[u8; 32], Error> {
let mut signed_data = data.clone();
signed_data.as_object_mut().unwrap().remove("unprotected"); // exclude
@ -161,7 +168,6 @@ impl BackupManifest {
/// Converts the Manifest into json string, and add a signature if there is a crypt_config.
pub fn to_string(&self, crypt_config: Option<&CryptConfig>) -> Result<String, Error> {
let mut manifest = serde_json::to_value(&self)?;
if let Some(crypt_config) = crypt_config {
@ -178,7 +184,7 @@ impl BackupManifest {
pub fn fingerprint(&self) -> Result<Option<Fingerprint>, Error> {
match &self.unprotected["key-fingerprint"] {
Value::Null => Ok(None),
value => Ok(Some(Deserialize::deserialize(value)?))
value => Ok(Some(Deserialize::deserialize(value)?)),
}
}
@ -210,7 +216,10 @@ impl BackupManifest {
}
/// Try to read the manifest. This verifies the signature if there is a crypt_config.
pub fn from_data(data: &[u8], crypt_config: Option<&CryptConfig>) -> Result<BackupManifest, Error> {
pub fn from_data(
data: &[u8],
crypt_config: Option<&CryptConfig>,
) -> Result<BackupManifest, Error> {
let json: Value = serde_json::from_slice(data)?;
let signature = json["signature"].as_str().map(String::from);
@ -243,13 +252,13 @@ impl BackupManifest {
}
}
impl TryFrom<super::DataBlob> for BackupManifest {
type Error = Error;
fn try_from(blob: super::DataBlob) -> Result<Self, Error> {
// no expected digest available
let data = blob.decode(None, None)
let data = blob
.decode(None, None)
.map_err(|err| format_err!("decode backup manifest blob failed - {}", err))?;
let json: Value = serde_json::from_slice(&data[..])
.map_err(|err| format_err!("unable to parse backup manifest json - {}", err))?;
@ -258,10 +267,8 @@ impl TryFrom<super::DataBlob> for BackupManifest {
}
}
#[test]
fn test_manifest_signature() -> Result<(), Error> {
use pbs_config::key_config::KeyDerivationConfig;
let pw = b"test";
@ -291,7 +298,10 @@ fn test_manifest_signature() -> Result<(), Error> {
let manifest: Value = serde_json::from_str(&text)?;
let signature = manifest["signature"].as_str().unwrap().to_string();
assert_eq!(signature, "d7b446fb7db081662081d4b40fedd858a1d6307a5aff4ecff7d5bf4fd35679e9");
assert_eq!(
signature,
"d7b446fb7db081662081d4b40fedd858a1d6307a5aff4ecff7d5bf4fd35679e9"
);
let manifest: BackupManifest = serde_json::from_value(manifest)?;
let expected_signature = hex::encode(&manifest.signature(&crypt_config)?);

View File

@ -1,14 +1,19 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use anyhow::{Error};
use anyhow::Error;
use pbs_api_types::PruneOptions;
use super::BackupInfo;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum PruneMark { Protected, Keep, KeepPartial, Remove }
pub enum PruneMark {
Protected,
Keep,
KeepPartial,
Remove,
}
impl PruneMark {
pub fn keep(self) -> bool {
@ -37,7 +42,6 @@ fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> (
keep: usize,
select_id: F,
) -> Result<(), Error> {
let mut include_hash = HashSet::new();
let mut already_included = HashSet::new();
@ -51,17 +55,23 @@ fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> (
for info in list {
let backup_id = info.backup_dir.relative_path();
if mark.get(&backup_id).is_some() { continue; }
if mark.get(&backup_id).is_some() {
continue;
}
if info.protected {
mark.insert(backup_id, PruneMark::Protected);
continue;
}
let sel_id: String = select_id(info)?;
if already_included.contains(&sel_id) { continue; }
if already_included.contains(&sel_id) {
continue;
}
if !include_hash.contains(&sel_id) {
if include_hash.len() >= keep { break; }
if include_hash.len() >= keep {
break;
}
include_hash.insert(sel_id);
mark.insert(backup_id, PruneMark::Keep);
} else {
@ -72,11 +82,7 @@ fn mark_selections<F: Fn(&BackupInfo) -> Result<String, Error>> (
Ok(())
}
fn remove_incomplete_snapshots(
mark: &mut HashMap<PathBuf, PruneMark>,
list: &[BackupInfo],
) {
fn remove_incomplete_snapshots(mark: &mut HashMap<PathBuf, PruneMark>, list: &[BackupInfo]) {
let mut keep_unfinished = true;
for info in list.iter() {
// backup is considered unfinished if there is no manifest
@ -86,7 +92,8 @@ fn remove_incomplete_snapshots(
keep_unfinished = false;
} else {
let backup_id = info.backup_dir.relative_path();
if keep_unfinished { // keep first unfinished
if keep_unfinished {
// keep first unfinished
mark.insert(backup_id, PruneMark::KeepPartial);
} else {
mark.insert(backup_id, PruneMark::Remove);
@ -98,12 +105,36 @@ fn remove_incomplete_snapshots(
pub fn keeps_something(options: &PruneOptions) -> bool {
let mut keep_something = false;
if let Some(count) = options.keep_last { if count > 0 { keep_something = true; } }
if let Some(count) = options.keep_hourly { if count > 0 { keep_something = true; } }
if let Some(count) = options.keep_daily { if count > 0 { keep_something = true; } }
if let Some(count) = options.keep_weekly { if count > 0 { keep_something = true; } }
if let Some(count) = options.keep_monthly { if count > 0 { keep_something = true; } }
if let Some(count) = options.keep_yearly { if count > 0 { keep_something = true; } }
if let Some(count) = options.keep_last {
if count > 0 {
keep_something = true;
}
}
if let Some(count) = options.keep_hourly {
if count > 0 {
keep_something = true;
}
}
if let Some(count) = options.keep_daily {
if count > 0 {
keep_something = true;
}
}
if let Some(count) = options.keep_weekly {
if count > 0 {
keep_something = true;
}
}
if let Some(count) = options.keep_monthly {
if count > 0 {
keep_something = true;
}
}
if let Some(count) = options.keep_yearly {
if count > 0 {
keep_something = true;
}
}
keep_something
}
@ -148,7 +179,6 @@ pub fn compute_prune_info(
mut list: Vec<BackupInfo>,
options: &PruneOptions,
) -> Result<Vec<(BackupInfo, PruneMark)>, Error> {
let mut mark = HashMap::new();
BackupInfo::sort_list(&mut list, false);
@ -195,7 +225,8 @@ pub fn compute_prune_info(
})?;
}
let prune_info: Vec<(BackupInfo, PruneMark)> = list.into_iter()
let prune_info: Vec<(BackupInfo, PruneMark)> = list
.into_iter()
.map(|info| {
let backup_id = info.backup_dir.relative_path();
let mark = if info.protected {

View File

@ -1,7 +1,7 @@
use std::fs::File;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::Path;
use std::sync::Arc;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::fs::File;
use anyhow::{bail, Error};
use nix::dir::Dir;
@ -9,9 +9,9 @@ use nix::dir::Dir;
use proxmox_sys::fs::lock_dir_noblock_shared;
use crate::backup_info::BackupDir;
use crate::index::IndexFile;
use crate::fixed_index::FixedIndexReader;
use crate::dynamic_index::DynamicIndexReader;
use crate::fixed_index::FixedIndexReader;
use crate::index::IndexFile;
use crate::manifest::{archive_type, ArchiveType, CLIENT_LOG_BLOB_NAME, MANIFEST_BLOB_NAME};
use crate::DataStore;
use pbs_api_types::Operation;
@ -27,24 +27,24 @@ pub struct SnapshotReader {
}
impl SnapshotReader {
/// Lock snapshot, reads the manifest and returns a new instance
pub fn new(datastore: Arc<DataStore>, snapshot: BackupDir) -> Result<Self, Error> {
let snapshot_path = datastore.snapshot_path(&snapshot);
let locked_dir = lock_dir_noblock_shared(
&snapshot_path,
"snapshot",
"locked by another operation")?;
let locked_dir =
lock_dir_noblock_shared(&snapshot_path, "snapshot", "locked by another operation")?;
let datastore_name = datastore.name().to_string();
let manifest = match datastore.load_manifest(&snapshot) {
Ok((manifest, _)) => manifest,
Err(err) => {
bail!("manifest load error on datastore '{}' snapshot '{}' - {}",
datastore_name, snapshot, err);
bail!(
"manifest load error on datastore '{}' snapshot '{}' - {}",
datastore_name,
snapshot,
err
);
}
};
@ -53,12 +53,19 @@ impl SnapshotReader {
let mut file_list = Vec::new();
file_list.push(MANIFEST_BLOB_NAME.to_string());
for item in manifest.files() { file_list.push(item.filename.clone()); }
for item in manifest.files() {
file_list.push(item.filename.clone());
}
if client_log_path.exists() {
file_list.push(CLIENT_LOG_BLOB_NAME.to_string());
}
Ok(Self { snapshot, datastore_name, file_list, locked_dir })
Ok(Self {
snapshot,
datastore_name,
file_list,
locked_dir,
})
}
/// Return the snapshot directory
@ -89,7 +96,10 @@ impl SnapshotReader {
}
/// Returns an iterator for all chunks not skipped by `skip_fn`.
pub fn chunk_iterator<F: Fn(&[u8;32]) -> bool>(&self, skip_fn: F) -> Result<SnapshotChunkIterator<F>, Error> {
pub fn chunk_iterator<F: Fn(&[u8; 32]) -> bool>(
&self,
skip_fn: F,
) -> Result<SnapshotChunkIterator<F>, Error> {
SnapshotChunkIterator::new(self, skip_fn)
}
}
@ -118,15 +128,17 @@ impl <'a, F: Fn(&[u8;32]) -> bool> Iterator for SnapshotChunkIterator<'a, F> {
let index: Box<dyn IndexFile + Send> = match archive_type(&filename)? {
ArchiveType::FixedIndex => Box::new(FixedIndexReader::new(file)?),
ArchiveType::DynamicIndex => Box::new(DynamicIndexReader::new(file)?),
_ => bail!("SnapshotChunkIterator: got unknown file type - internal error"),
_ => bail!(
"SnapshotChunkIterator: got unknown file type - internal error"
),
};
let datastore =
DataStore::lookup_datastore(
let datastore = DataStore::lookup_datastore(
self.snapshot_reader.datastore_name(),
Some(Operation::Read)
Some(Operation::Read),
)?;
let order = datastore.get_chunks_in_order(&index, &self.skip_fn, |_| Ok(()))?;
let order =
datastore.get_chunks_in_order(&index, &self.skip_fn, |_| Ok(()))?;
self.current_index = Some((Arc::new(index), 0, order));
} else {
@ -143,25 +155,29 @@ impl <'a, F: Fn(&[u8;32]) -> bool> Iterator for SnapshotChunkIterator<'a, F> {
// pop next index
}
}
}).transpose()
})
.transpose()
}
}
impl<'a, F: Fn(&[u8; 32]) -> bool> SnapshotChunkIterator<'a, F> {
pub fn new(snapshot_reader: &'a SnapshotReader, skip_fn: F) -> Result<Self, Error> {
let mut todo_list = Vec::new();
for filename in snapshot_reader.file_list() {
match archive_type(filename)? {
ArchiveType::FixedIndex | ArchiveType::DynamicIndex => {
todo_list.push(filename.to_owned());
},
ArchiveType::Blob => { /* no chunks, do nothing */ },
}
ArchiveType::Blob => { /* no chunks, do nothing */ }
}
}
Ok(Self { snapshot_reader, todo_list, current_index: None, skip_fn })
Ok(Self {
snapshot_reader,
todo_list,
current_index: None,
skip_fn,
})
}
}