Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: use same paths representation for unix/windows #390

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,13 @@ enum-map = { workspace = true }
enum-map-derive = "0.17.0"
enumset = { version = "1.1.5", features = ["serde"] }
gethostname = "0.5.0"
globset = "0.4.15"
humantime = "2.1.0"
itertools = "0.13.0"
quick_cache = "0.6.9"
shell-words = "1.1.0"
strum = { version = "0.26.3", features = ["derive"] }
typed-path = "0.10.0"
zstd = "0.13.2"

[target.'cfg(not(windows))'.dependencies]
Expand All @@ -123,7 +125,6 @@ xattr = "1"
anyhow = { workspace = true }
expect-test = "1.5.0"
flate2 = "1.0.35"
globset = "0.4.15"
insta = { version = "1.41.1", features = ["redactions", "ron"] }
mockall = "0.13"
pretty_assertions = "1.4.1"
Expand Down
10 changes: 3 additions & 7 deletions crates/core/src/archiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ pub(crate) mod parent;
pub(crate) mod tree;
pub(crate) mod tree_archiver;

use std::path::{Path, PathBuf};

use chrono::Local;
use log::warn;
use pariter::{scope, IteratorExt};
use typed_path::UnixPathBuf;

use crate::{
archiver::{
Expand Down Expand Up @@ -126,8 +125,7 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> {
pub fn archive<R>(
mut self,
src: &R,
backup_path: &Path,
as_path: Option<&PathBuf>,
as_path: Option<&UnixPathBuf>,
skip_identical_parent: bool,
no_scan: bool,
p: &impl Progress,
Expand Down Expand Up @@ -157,9 +155,7 @@ impl<'a, BE: DecryptFullBackend, I: ReadGlobalIndex> Archiver<'a, BE, I> {
}
Ok(ReadSourceEntry { path, node, open }) => {
let snapshot_path = if let Some(as_path) = as_path {
as_path
.clone()
.join(path.strip_prefix(backup_path).unwrap())
as_path.clone().join(path)
} else {
path
};
Expand Down
24 changes: 8 additions & 16 deletions crates/core/src/archiver/parent.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use std::{
cmp::Ordering,
ffi::{OsStr, OsString},
};
use std::cmp::Ordering;

use log::warn;

Expand Down Expand Up @@ -124,20 +121,20 @@ impl Parent {
/// # Returns
///
/// The parent node with the given name, or `None` if the parent node is not found.
fn p_node(&mut self, name: &OsStr) -> Option<&Node> {
fn p_node(&mut self, name: &[u8]) -> Option<&Node> {
match &self.tree {
None => None,
Some(tree) => {
let p_nodes = &tree.nodes;
loop {
match p_nodes.get(self.node_idx) {
None => break None,
Some(p_node) => match p_node.name().as_os_str().cmp(name) {
Ordering::Less => self.node_idx += 1,
Some(p_node) => match name.cmp(&p_node.name()) {
Ordering::Greater => self.node_idx += 1,
Ordering::Equal => {
break Some(p_node);
}
Ordering::Greater => {
Ordering::Less => {
break None;
}
},
Expand All @@ -161,7 +158,7 @@ impl Parent {
/// # Note
///
/// TODO: This function does not check whether the given node is a directory.
fn is_parent(&mut self, node: &Node, name: &OsStr) -> ParentResult<&Node> {
fn is_parent(&mut self, node: &Node, name: &[u8]) -> ParentResult<&Node> {
// use new variables as the mutable borrow is used later
let ignore_ctime = self.ignore_ctime;
let ignore_inode = self.ignore_inode;
Expand Down Expand Up @@ -190,12 +187,7 @@ impl Parent {
///
/// * `be` - The backend to read from.
/// * `name` - The name of the parent node.
fn set_dir(
&mut self,
be: &impl DecryptReadBackend,
index: &impl ReadGlobalIndex,
name: &OsStr,
) {
fn set_dir(&mut self, be: &impl DecryptReadBackend, index: &impl ReadGlobalIndex, name: &[u8]) {
let tree = self.p_node(name).and_then(|p_node| {
p_node.subtree.map_or_else(
|| {
Expand Down Expand Up @@ -257,7 +249,7 @@ impl Parent {
&mut self,
be: &impl DecryptReadBackend,
index: &impl ReadGlobalIndex,
item: TreeType<O, OsString>,
item: TreeType<O, Vec<u8>>,
) -> Result<ItemWithParent<O>, TreeStackEmptyError> {
let result = match item {
TreeType::NewTree((path, node, tree)) => {
Expand Down
65 changes: 28 additions & 37 deletions crates/core/src/archiver/tree.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use std::{ffi::OsString, path::PathBuf};
use crate::backend::node::{Metadata, Node, NodeType};

use crate::{
backend::node::{Metadata, Node, NodeType},
blob::tree::comp_to_osstr,
};
use typed_path::{Component, UnixPathBuf};

/// `TreeIterator` turns an Iterator yielding items with paths and Nodes into an
/// Iterator which ensures that all subdirectories are visited and closed.
Expand All @@ -18,7 +15,7 @@
/// The original Iterator.
iter: I,
/// The current path.
path: PathBuf,
path: UnixPathBuf,
/// The current item.
item: Option<T>,
}
Expand All @@ -31,7 +28,7 @@
let item = iter.next();
Self {
iter,
path: PathBuf::new(),
path: UnixPathBuf::new(),
item,
}
}
Expand All @@ -49,32 +46,25 @@
#[derive(Debug)]
pub(crate) enum TreeType<T, U> {
/// New tree to be inserted.
NewTree((PathBuf, Node, U)),
NewTree((UnixPathBuf, Node, U)),
/// A pseudo item which indicates that a tree is finished.
EndTree,
/// Original item.
Other((PathBuf, Node, T)),
Other((UnixPathBuf, Node, T)),
}

impl<I, O> Iterator for TreeIterator<(PathBuf, Node, O), I>
impl<I, O> Iterator for TreeIterator<(UnixPathBuf, Node, O), I>
where
I: Iterator<Item = (PathBuf, Node, O)>,
I: Iterator<Item = (UnixPathBuf, Node, O)>,
{
type Item = TreeType<O, OsString>;
type Item = TreeType<O, Vec<u8>>;
fn next(&mut self) -> Option<Self::Item> {
match &self.item {
None => {
if self.path.pop() {
Some(TreeType::EndTree)
} else {
// Check if we still have a path prefix open...
match self.path.components().next() {
Some(std::path::Component::Prefix(..)) => {
self.path = PathBuf::new();
Some(TreeType::EndTree)
}
_ => None,
}
None
}
}
Some((path, node, _)) => {
Expand All @@ -84,24 +74,25 @@
Some(TreeType::EndTree)
}
Ok(missing_dirs) => {
for comp in missing_dirs.components() {
self.path.push(comp);
// process next normal path component - other components are simply ignored
if let Some(p) = comp_to_osstr(comp).ok().flatten() {
if node.is_dir() && path == &self.path {
let (path, node, _) = self.item.take().unwrap();
self.item = self.iter.next();
let name = node.name();
return Some(TreeType::NewTree((path, node, name)));
}
// Use mode 755 for missing dirs, so they can be accessed
let meta = Metadata {
mode: Some(0o755),
..Default::default()
};
let node = Node::new_node(&p, NodeType::Dir, meta);
return Some(TreeType::NewTree((self.path.clone(), node, p)));
if let Some(p) = missing_dirs.components().next() {
self.path.push(p);
if node.is_dir() && path == &self.path {
let (path, node, _) = self.item.take().unwrap();
self.item = self.iter.next();
let name = node.name().to_vec();
return Some(TreeType::NewTree((path, node, name)));
}
// Use mode 755 for missing dirs, so they can be accessed
let meta = Metadata {
mode: Some(0o755),

Check warning on line 87 in crates/core/src/archiver/tree.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/archiver/tree.rs#L87

Added line #L87 was not covered by tests
..Default::default()
};
let node = Node::new_node(p.as_bytes(), NodeType::Dir, meta);
return Some(TreeType::NewTree((
self.path.clone(),
node,
p.as_bytes().to_vec(),
)));
}
// there wasn't any normal path component to process - return current item
let item = self.item.take().unwrap();
Expand Down
15 changes: 9 additions & 6 deletions crates/core/src/archiver/tree_archiver.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::path::{Path, PathBuf};

use bytesize::ByteSize;
use log::{debug, trace};
use typed_path::{UnixPath, UnixPathBuf};

use crate::{
archiver::{parent::ParentResult, tree::TreeType},
Expand Down Expand Up @@ -30,7 +29,7 @@ pub(crate) struct TreeArchiver<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex>
/// The current tree.
tree: Tree,
/// The stack of trees.
stack: Vec<(PathBuf, Node, ParentResult<TreeId>, Tree)>,
stack: Vec<(UnixPathBuf, Node, ParentResult<TreeId>, Tree)>,
/// The index to read from.
index: &'a I,
/// The packer to write to.
Expand Down Expand Up @@ -129,7 +128,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> {
/// * `path` - The path of the file.
/// * `node` - The node of the file.
/// * `parent` - The parent result of the file.
fn add_file(&mut self, path: &Path, node: Node, parent: &ParentResult<()>, size: u64) {
fn add_file(&mut self, path: &UnixPath, node: Node, parent: &ParentResult<()>, size: u64) {
let filename = path.join(node.name());
match parent {
ParentResult::Matched(()) => {
Expand Down Expand Up @@ -164,7 +163,11 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> {
/// # Returns
///
/// The id of the tree.
fn backup_tree(&mut self, path: &Path, parent: &ParentResult<TreeId>) -> RusticResult<TreeId> {
fn backup_tree(
&mut self,
path: &UnixPath,
parent: &ParentResult<TreeId>,
) -> RusticResult<TreeId> {
let (chunk, id) = self.tree.serialize().map_err(|err| {
RusticError::with_source(
ErrorKind::Internal,
Expand Down Expand Up @@ -224,7 +227,7 @@ impl<'a, BE: DecryptWriteBackend, I: ReadGlobalIndex> TreeArchiver<'a, BE, I> {
parent_tree: Option<TreeId>,
) -> RusticResult<(TreeId, SnapshotSummary)> {
let parent = parent_tree.map_or(ParentResult::NotFound, ParentResult::Matched);
let id = self.backup_tree(&PathBuf::new(), &parent)?;
let id = self.backup_tree(UnixPath::new(&[]), &parent)?;
let stats = self.tree_packer.finalize()?;
stats.apply(&mut self.summary, BlobType::Tree);

Expand Down
14 changes: 8 additions & 6 deletions crates/core/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use mockall::mock;

use serde_derive::{Deserialize, Serialize};
use typed_path::UnixPathBuf;

use crate::{
backend::node::{Metadata, Node, NodeType},
Expand All @@ -31,8 +32,8 @@
#[derive(thiserror::Error, Debug, displaydoc::Display)]
#[non_exhaustive]
pub enum BackendErrorKind {
/// Path is not allowed: `{0:?}`
PathNotAllowed(PathBuf),
/// Path is not allowed: `{0}`
PathNotAllowed(String),
}

pub(crate) type BackendResult<T> = Result<T, BackendErrorKind>;
Expand Down Expand Up @@ -416,7 +417,7 @@
#[derive(Debug, Clone)]
pub struct ReadSourceEntry<O> {
/// The path of the entry.
pub path: PathBuf,
pub path: UnixPathBuf,

/// The node information of the entry.
pub node: Node,
Expand All @@ -426,10 +427,11 @@
}

impl<O> ReadSourceEntry<O> {
fn from_path(path: PathBuf, open: Option<O>) -> BackendResult<Self> {
fn from_path(path: UnixPathBuf, open: Option<O>) -> BackendResult<Self> {
let node = Node::new_node(
path.file_name()
.ok_or_else(|| BackendErrorKind::PathNotAllowed(path.clone()))?,
path.file_name().ok_or_else(|| {
BackendErrorKind::PathNotAllowed(path.to_string_lossy().to_string())

Check warning on line 433 in crates/core/src/backend.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/backend.rs#L433

Added line #L433 was not covered by tests
})?,
NodeType::File,
Metadata::default(),
);
Expand Down
9 changes: 5 additions & 4 deletions crates/core/src/backend/childstdout.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::{
iter::{once, Once},
path::PathBuf,
process::{Child, ChildStdout, Command, Stdio},
sync::Mutex,
};

use typed_path::UnixPathBuf;

use crate::{
backend::{ReadSource, ReadSourceEntry},
error::{ErrorKind, RusticError, RusticResult},
Expand All @@ -15,7 +16,7 @@ use crate::{
#[derive(Debug)]
pub struct ChildStdoutSource {
/// The path of the stdin entry.
path: PathBuf,
path: UnixPathBuf,
/// The child process
///
/// # Note
Expand All @@ -30,14 +31,14 @@ pub struct ChildStdoutSource {

impl ChildStdoutSource {
/// Creates a new `ChildSource`.
pub fn new(cmd: &CommandInput, path: PathBuf) -> RusticResult<Self> {
pub fn new(cmd: &CommandInput, path: UnixPathBuf) -> RusticResult<Self> {
let process = Command::new(cmd.command())
.args(cmd.args())
.stdout(Stdio::piped())
.spawn()
.map_err(|err| CommandInputErrorKind::ProcessExecutionFailed {
command: cmd.clone(),
path: path.clone(),
path: path.to_string_lossy().to_string(),
source: err,
});

Expand Down
Loading
Loading