proxmox-backup/src/pxar/dir_stack.rs

142 lines
3.8 KiB
Rust
Raw Normal View History

use std::ffi::OsString;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
use anyhow::{bail, format_err, Error};
use nix::dir::Dir;
use nix::fcntl::OFlag;
use nix::sys::stat::{mkdirat, Mode};
use proxmox::sys::error::SysError;
use pxar::Metadata;
use crate::pxar::tools::{assert_single_path_component, perms_from_metadata};
pub struct PxarDir {
file_name: OsString,
metadata: Metadata,
dir: Option<Dir>,
}
impl PxarDir {
pub fn new(file_name: OsString, metadata: Metadata) -> Self {
Self {
file_name,
metadata,
dir: None,
}
}
pub fn with_dir(dir: Dir, metadata: Metadata) -> Self {
Self {
file_name: OsString::from("."),
metadata,
dir: Some(dir),
}
}
fn create_dir(&mut self, parent: RawFd, allow_existing_dirs: bool) -> Result<RawFd, Error> {
match mkdirat(
parent,
self.file_name.as_os_str(),
perms_from_metadata(&self.metadata)?,
) {
Ok(()) => (),
Err(err) => {
if !(allow_existing_dirs && err.already_exists()) {
return Err(err.into());
}
}
}
self.open_dir(parent)
}
fn open_dir(&mut self, parent: RawFd) -> Result<RawFd, Error> {
let dir = Dir::openat(
parent,
self.file_name.as_os_str(),
OFlag::O_DIRECTORY,
Mode::empty(),
)?;
let fd = dir.as_raw_fd();
self.dir = Some(dir);
Ok(fd)
}
pub fn try_as_raw_fd(&self) -> Option<RawFd> {
self.dir.as_ref().map(AsRawFd::as_raw_fd)
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
}
pub struct PxarDirStack {
dirs: Vec<PxarDir>,
path: PathBuf,
created: usize,
}
impl PxarDirStack {
pub fn new(root: Dir, metadata: Metadata) -> Self {
Self {
dirs: vec![PxarDir::with_dir(root, metadata)],
path: PathBuf::from("/"),
created: 1, // the root directory exists
}
}
pub fn is_empty(&self) -> bool {
self.dirs.is_empty()
}
pub fn push(&mut self, file_name: OsString, metadata: Metadata) -> Result<(), Error> {
assert_single_path_component(&file_name)?;
self.path.push(&file_name);
self.dirs.push(PxarDir::new(file_name, metadata));
Ok(())
}
pub fn pop(&mut self) -> Result<Option<PxarDir>, Error> {
let out = self.dirs.pop();
if !self.path.pop() {
if self.path.as_os_str() == "/" {
// we just finished the root directory, make sure this can only happen once:
self.path = PathBuf::new();
} else {
bail!("lost track of path");
}
}
self.created = self.created.min(self.dirs.len());
Ok(out)
}
pub fn last_dir_fd(&mut self, allow_existing_dirs: bool) -> Result<RawFd, Error> {
// should not be possible given the way we use it:
assert!(!self.dirs.is_empty(), "PxarDirStack underrun");
let mut fd = self.dirs[self.created - 1]
.try_as_raw_fd()
.ok_or_else(|| format_err!("lost track of directory file descriptors"))?;
while self.created < self.dirs.len() {
fd = self.dirs[self.created].create_dir(fd, allow_existing_dirs)?;
self.created += 1;
}
Ok(fd)
}
pub fn root_dir_fd(&self) -> Result<RawFd, Error> {
// should not be possible given the way we use it:
assert!(!self.dirs.is_empty(), "PxarDirStack underrun");
self.dirs[0]
.try_as_raw_fd()
.ok_or_else(|| format_err!("lost track of directory file descriptors"))
}
}