use std::error::Error;
use std::hash::Hash;
use std::io::{self, Read};
use byteorder::{BigEndian, ReadBytesExt};
use num_traits::FromPrimitive;
use crate::hosts::BackupInformation;
const BPC_ATTRIB_TYPE_XATTR: u32 = 0x1756_5353;
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum FileType {
File = 0,
Hardlink = 1,
Symlink = 2,
Chardev = 3,
Blockdev = 4,
Dir = 5,
Fifo = 6,
Unknown = 7,
Socket = 8,
Deleted = 9,
}
pub trait VarintRead: Read {
fn read_varint<T: FromPrimitive>(&mut self) -> io::Result<T> {
let mut result = 0;
let mut shift = 0;
loop {
let mut buf: [u8; 1] = [0u8; 1];
self.read_exact(&mut buf)?;
let byte = buf[0];
let val = u64::from(byte & 0x7F);
if shift >= 64 || val << shift >> shift != val {
eprintln!("Varint too large: probably corrupted data");
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Varint too large: probably corrupted data",
));
}
result |= val << shift;
if byte & 0x80 == 0 {
return T::from_u64(result).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"Varint too large for the target type",
)
});
}
shift += 7;
}
}
}
impl<R: Read + ?Sized> VarintRead for R {}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct XattrEntry {
pub key: String,
pub value: String,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct BpcDigest {
pub len: u64,
pub digest: Vec<u8>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct FileAttributes {
pub name: Vec<u8>,
pub type_: FileType,
pub compress: u64,
pub mode: u16,
pub uid: u32,
pub gid: u32,
pub nlinks: u32,
pub mtime: u64,
pub size: u64,
pub inode: u64,
pub bpc_digest: BpcDigest,
pub xattr_num_entries: u64,
pub xattrs: Vec<XattrEntry>,
}
impl FileAttributes {
#[must_use]
pub fn from_host(host: &[u8]) -> Self {
Self {
name: host.to_vec(),
type_: FileType::Dir,
compress: 0,
mode: 0,
uid: 0,
gid: 0,
nlinks: 0,
mtime: 0,
size: 0,
inode: 0,
bpc_digest: BpcDigest {
len: 0,
digest: Vec::new(),
},
xattr_num_entries: 0,
xattrs: Vec::new(),
}
}
#[must_use]
pub fn from_backup(backup: &BackupInformation) -> Self {
Self {
name: backup.num.to_string().into_bytes(),
type_: FileType::Dir,
compress: 0,
mode: 0,
uid: 0,
gid: 0,
nlinks: 0,
mtime: backup.start_time,
size: 0,
inode: 0,
bpc_digest: BpcDigest {
len: 0,
digest: Vec::new(),
},
xattr_num_entries: 0,
xattrs: Vec::new(),
}
}
#[must_use]
pub fn from_share(share: &[u8]) -> Self {
Self {
name: share.to_vec(),
type_: FileType::Dir,
compress: 0,
mode: 0,
uid: 0,
gid: 0,
nlinks: 0,
mtime: 0,
size: 0,
inode: 0,
bpc_digest: BpcDigest {
len: 0,
digest: Vec::new(),
},
xattr_num_entries: 0,
xattrs: Vec::new(),
}
}
}
impl FileAttributes {
pub fn read_from<R: Read + VarintRead>(reader: &mut R) -> io::Result<Self> {
let filename_len: usize = reader.read_varint()?;
let mut name = vec![0u8; filename_len];
reader.read_exact(&mut name)?;
let xattr_num_entries: u64 = reader.read_varint().unwrap_or_default();
let type_: FileType = match reader.read_varint().unwrap_or(9) {
0 => FileType::File,
1 => FileType::Hardlink,
2 => FileType::Symlink,
3 => FileType::Chardev,
4 => FileType::Blockdev,
5 => FileType::Dir,
6 => FileType::Fifo,
8 => FileType::Socket,
7 | 9 => FileType::Unknown,
10 => FileType::Deleted,
other => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid file type {other}"),
))
}
};
let mtime: u64 = reader.read_varint().unwrap_or_default();
let mode: u16 = reader.read_varint().unwrap_or_default();
let uid: u32 = reader.read_varint().unwrap_or_default();
let gid: u32 = reader.read_varint().unwrap_or_default();
let size: u64 = reader.read_varint().unwrap_or_default();
let inode: u64 = reader.read_varint().unwrap_or_default();
let compress: u64 = reader.read_varint().unwrap_or_default();
let nlinks: u32 = reader.read_varint().unwrap_or_default();
let digest_len: usize = reader.read_varint().unwrap_or_default();
let mut digest = vec![0u8; digest_len];
if digest_len > 0 {
reader.read_exact(&mut digest)?;
}
let mut xattrs = Vec::new();
for _ in 0..xattr_num_entries {
let key_len: usize = reader.read_varint().unwrap_or_default();
let mut key = vec![0u8; key_len];
reader.read_exact(&mut key)?;
let key = String::from_utf8(key).unwrap_or_default();
let value_len: usize = reader.read_varint().unwrap_or_default();
let mut value = vec![0u8; value_len];
reader.read_exact(&mut value)?;
let value = String::from_utf8(value).unwrap_or_default();
xattrs.push(XattrEntry { key, value });
}
Ok(Self {
name,
xattr_num_entries,
type_,
mtime,
mode,
uid,
gid,
size,
inode,
compress,
nlinks,
bpc_digest: BpcDigest {
len: digest_len as u64,
digest,
},
xattrs,
})
}
}
#[derive(Debug)]
pub struct AttributeFile {
pub attributes: Vec<FileAttributes>,
}
impl AttributeFile {
pub fn read_from<R: Read + VarintRead>(reader: &mut R) -> Result<Self, Box<dyn Error>> {
let magic: u32 = reader.read_u32::<BigEndian>()?;
if magic != BPC_ATTRIB_TYPE_XATTR {
return Err("Invalid magic number".into());
}
let mut attributes = Vec::new();
loop {
match FileAttributes::read_from(reader) {
Ok(attr) => attributes.push(attr),
Err(e) => {
if e.kind() == io::ErrorKind::UnexpectedEof {
break;
}
eprintln!("Error reading file attributes: {e}");
}
}
}
Ok(Self { attributes })
}
}