5
0
mirror of git://git.proxmox.com/git/pxar.git synced 2025-01-09 05:17:38 +03:00
pxar/src/lib.rs
Fabian Grünbichler 8de202d4c4 clippy: use matches! instead of match
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
2021-01-11 14:48:15 +01:00

487 lines
13 KiB
Rust

//! Proxmox backup archive format handling.
//!
//! This implements a reader and writer for the proxmox archive format (.pxar).
use std::ffi::OsStr;
use std::mem;
use std::path::{Path, PathBuf};
#[macro_use]
mod macros;
pub mod format;
pub(crate) mod util;
mod poll_fn;
pub mod accessor;
pub mod binary_tree_array;
pub mod decoder;
pub mod encoder;
#[doc(inline)]
pub use format::{mode, Stat};
/// File metadata found in pxar archives.
///
/// This includes the usual data you'd get from `stat()` as well as ACLs, extended attributes, file
/// capabilities and more.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
pub struct Metadata {
/// Data typically found in a `stat()` call.
pub stat: Stat,
/// Extended attributes.
pub xattrs: Vec<format::XAttr>,
/// ACLs.
pub acl: Acl,
/// File capabilities.
pub fcaps: Option<format::FCaps>,
/// Quota project id.
pub quota_project_id: Option<format::QuotaProjectId>,
}
impl From<Stat> for Metadata {
fn from(stat: Stat) -> Self {
Self {
stat,
..Default::default()
}
}
}
impl From<&std::fs::Metadata> for Metadata {
fn from(meta: &std::fs::Metadata) -> Metadata {
// NOTE: fill the remaining metadata via feature flags?
Self::from(Stat::from(meta))
}
}
impl From<std::fs::Metadata> for Metadata {
fn from(meta: std::fs::Metadata) -> Metadata {
Self::from(&meta)
}
}
/// Convenience helpers.
impl Metadata {
/// Get the file type (`mode & mode::IFMT`).
#[inline]
pub fn file_type(&self) -> u64 {
self.stat.file_type()
}
/// Get the file mode bits (`mode & !mode::IFMT`).
#[inline]
pub fn file_mode(&self) -> u64 {
self.stat.file_mode()
}
/// Check whether this is a directory.
#[inline]
pub fn is_dir(&self) -> bool {
self.stat.is_dir()
}
/// Check whether this is a symbolic link.
#[inline]
pub fn is_symlink(&self) -> bool {
self.stat.is_symlink()
}
/// Check whether this is a device node.
#[inline]
pub fn is_device(&self) -> bool {
self.stat.is_device()
}
/// Check whether this is a regular file.
#[inline]
pub fn is_regular_file(&self) -> bool {
self.stat.is_regular_file()
}
/// Check whether this is a named pipe (FIFO).
#[inline]
pub fn is_fifo(&self) -> bool {
self.stat.is_fifo()
}
/// Check whether this is a named socket.
#[inline]
pub fn is_socket(&self) -> bool {
self.stat.is_socket()
}
/// Get the mtime as duration since the epoch. an `Ok` value is a positive duration, an `Err`
/// value is a negative duration.
pub fn mtime_as_duration(&self) -> format::SignedDuration {
self.stat.mtime_as_duration()
}
/// A more convenient way to create metadata for a regular file.
pub fn file_builder(mode: u64) -> MetadataBuilder {
Self::builder(mode::IFREG | (mode & !mode::IFMT))
}
/// A more convenient way to create metadata for a directory file.
pub fn dir_builder(mode: u64) -> MetadataBuilder {
Self::builder(mode::IFDIR | (mode & !mode::IFMT))
}
/// A more convenient way to create generic metadata.
pub const fn builder(mode: u64) -> MetadataBuilder {
MetadataBuilder::new(mode)
}
#[cfg(all(test, target_os = "linux"))]
/// A more convenient way to create metadata starting from `libc::stat`.
pub fn builder_from_stat(stat: &libc::stat) -> MetadataBuilder {
MetadataBuilder::new(0).fill_from_stat(stat)
}
}
impl From<MetadataBuilder> for Metadata {
fn from(builder: MetadataBuilder) -> Self {
builder.build()
}
}
pub struct MetadataBuilder {
inner: Metadata,
}
impl MetadataBuilder {
pub const fn new(type_and_mode: u64) -> Self {
Self {
inner: Metadata {
stat: Stat {
mode: type_and_mode,
flags: 0,
uid: 0,
gid: 0,
mtime: format::StatxTimestamp::zero(),
},
xattrs: Vec::new(),
acl: Acl {
users: Vec::new(),
groups: Vec::new(),
group_obj: None,
default: None,
default_users: Vec::new(),
default_groups: Vec::new(),
},
fcaps: None,
quota_project_id: None,
},
}
}
pub fn build(self) -> Metadata {
self.inner
}
/// Set the file mode (complete `stat.st_mode` with file type and permission bits).
pub const fn st_mode(mut self, mode: u64) -> Self {
self.inner.stat.mode = mode;
self
}
/// Set the file type (`mode & mode::IFMT`).
pub const fn file_type(mut self, mode: u64) -> Self {
self.inner.stat.mode = (self.inner.stat.mode & !mode::IFMT) | (mode & mode::IFMT);
self
}
/// Set the file mode bits (`mode & !mode::IFMT`).
pub const fn file_mode(mut self, mode: u64) -> Self {
self.inner.stat.mode = (self.inner.stat.mode & mode::IFMT) | (mode & !mode::IFMT);
self
}
/// Set the modification time from a statx timespec value.
pub fn mtime_full(mut self, mtime: format::StatxTimestamp) -> Self {
self.inner.stat.mtime = mtime;
self
}
/// Set the modification time from a duration since the epoch (`SystemTime::UNIX_EPOCH`).
pub fn mtime_unix(self, mtime: std::time::Duration) -> Self {
self.mtime_full(format::StatxTimestamp::from_duration_since_epoch(mtime))
}
/// Set the modification time from a system time.
pub fn mtime(self, mtime: std::time::SystemTime) -> Self {
self.mtime_full(mtime.into())
}
/// Set the ownership information.
pub const fn owner(self, uid: u32, gid: u32) -> Self {
self.uid(uid).gid(gid)
}
/// Set the owning user id.
pub const fn uid(mut self, uid: u32) -> Self {
self.inner.stat.uid = uid;
self
}
/// Set the owning user id.
pub const fn gid(mut self, gid: u32) -> Self {
self.inner.stat.gid = gid;
self
}
/// Add an extended attribute.
pub fn xattr<N: AsRef<[u8]>, V: AsRef<[u8]>>(mut self, name: N, value: V) -> Self {
self.inner.xattrs.push(format::XAttr::new(name, value));
self
}
/// Add a user ACL entry.
pub fn acl_user(mut self, entry: format::acl::User) -> Self {
self.inner.acl.users.push(entry);
self
}
/// Add a group ACL entry.
pub fn acl_group(mut self, entry: format::acl::Group) -> Self {
self.inner.acl.groups.push(entry);
self
}
/// Add a user default-ACL entry.
pub fn default_acl_user(mut self, entry: format::acl::User) -> Self {
self.inner.acl.default_users.push(entry);
self
}
/// Add a group default-ACL entry.
pub fn default_acl_group(mut self, entry: format::acl::Group) -> Self {
self.inner.acl.default_groups.push(entry);
self
}
/// Set the default ACL entry for a directory.
pub const fn default_acl(mut self, entry: Option<format::acl::Default>) -> Self {
self.inner.acl.default = entry;
self
}
/// Set the quota project id.
pub fn quota_project_id(mut self, id: Option<u64>) -> Self {
self.inner.quota_project_id = id.map(|projid| format::QuotaProjectId { projid });
self
}
/// Set the raw file capability data.
pub fn fcaps(mut self, fcaps: Option<Vec<u8>>) -> Self {
self.inner.fcaps = fcaps.map(|data| format::FCaps { data });
self
}
#[cfg(all(test, target_os = "linux"))]
/// Fill the metadata with information from `struct stat`.
pub fn fill_from_stat(self, stat: &libc::stat) -> Self {
self.st_mode(u64::from(stat.st_mode))
.owner(stat.st_uid, stat.st_gid)
.mtime_full(format::StatxTimestamp::from_stat(
stat.st_mtime,
stat.st_mtime_nsec as u32,
))
}
}
/// ACL entries of a pxar archive.
///
/// This contains all the various ACL entry types supported by the pxar archive format.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
pub struct Acl {
/// User ACL list.
pub users: Vec<format::acl::User>,
/// Group ACL list.
pub groups: Vec<format::acl::Group>,
/// Group object ACL entry.
pub group_obj: Option<format::acl::GroupObject>,
/// Default permissions.
pub default: Option<format::acl::Default>,
/// Default user permissions.
pub default_users: Vec<format::acl::User>,
/// Default group permissions.
pub default_groups: Vec<format::acl::Group>,
}
impl Acl {
pub fn is_empty(&self) -> bool {
self.users.is_empty()
&& self.groups.is_empty()
&& self.group_obj.is_none()
&& self.default.is_none()
&& self.default_users.is_empty()
&& self.default_groups.is_empty()
}
}
/// Pxar archive entry kind.
///
/// Identifies whether the entry is a file, symlink, directory, etc.
#[derive(Clone, Debug)]
pub enum EntryKind {
/// Symbolic links.
Symlink(format::Symlink),
/// Hard links, relative to the root of the current archive.
Hardlink(format::Hardlink),
/// Device node.
Device(format::Device),
/// Named unix socket.
Socket,
/// Named pipe.
Fifo,
/// Regular file.
File { size: u64, offset: Option<u64> },
/// Directory entry. When iterating through an archive, the contents follow next.
Directory,
/// End of a directory. This is for internal use to remember the goodbye-table of a directory
/// entry. Will not occur during normal iteration.
GoodbyeTable,
}
/// A pxar archive entry. This contains the current path, file metadata and entry type specific
/// information.
#[derive(Clone, Debug)]
pub struct Entry {
path: PathBuf,
metadata: Metadata,
kind: EntryKind,
}
/// General accessors.
impl Entry {
/// Clear everything except for the path.
fn clear_data(&mut self) {
self.metadata = Metadata::default();
self.kind = EntryKind::GoodbyeTable;
}
fn internal_default() -> Self {
Self {
path: PathBuf::default(),
metadata: Metadata::default(),
kind: EntryKind::GoodbyeTable,
}
}
fn take(&mut self) -> Self {
let this = mem::replace(self, Self::internal_default());
self.path = this.path.clone();
this
}
/// Get the full path of this file within the current pxar directory structure.
#[inline]
pub fn path(&self) -> &Path {
&self.path
}
/// Convenience method to get just the file name portion of the current path.
#[inline]
pub fn file_name(&self) -> &OsStr {
self.path.file_name().unwrap_or_else(|| OsStr::new(""))
}
/// Get the file metadata.
#[inline]
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
/// Take out the metadata.
#[inline]
pub fn into_metadata(self) -> Metadata {
self.metadata
}
/// Get the entry-type specific information.
pub fn kind(&self) -> &EntryKind {
&self.kind
}
/// Get the value of the symbolic link if it is one.
pub fn get_symlink(&self) -> Option<&OsStr> {
match &self.kind {
EntryKind::Symlink(link) => Some(link.as_ref()),
_ => None,
}
}
/// Get the value of the hard link if it is one.
pub fn get_hardlink(&self) -> Option<&OsStr> {
match &self.kind {
EntryKind::Hardlink(link) => Some(link.as_ref()),
_ => None,
}
}
/// Get the value of the device node if it is one.
pub fn get_device(&self) -> Option<format::Device> {
match &self.kind {
EntryKind::Device(dev) => Some(dev.clone()),
_ => None,
}
}
}
/// Convenience helpers.
impl Entry {
/// Check whether this is a directory.
pub fn is_dir(&self) -> bool {
matches!(self.kind, EntryKind::Directory)
}
/// Check whether this is a symbolic link.
pub fn is_symlink(&self) -> bool {
matches!(self.kind, EntryKind::Symlink(_))
}
/// Check whether this is a hard link.
pub fn is_hardlink(&self) -> bool {
matches!(self.kind, EntryKind::Hardlink(_))
}
/// Check whether this is a device node.
pub fn is_device(&self) -> bool {
matches!(self.kind, EntryKind::Device(_))
}
/// Check whether this is a regular file.
pub fn is_regular_file(&self) -> bool {
matches!(self.kind, EntryKind::File { .. })
}
/// Get the file size if this is a regular file, or `None`.
pub fn file_size(&self) -> Option<u64> {
match self.kind {
EntryKind::File { size, .. } => Some(size),
_ => None,
}
}
}