diff --git a/src/backend/local.rs b/src/backend/local.rs index 33c406f..e08e138 100644 --- a/src/backend/local.rs +++ b/src/backend/local.rs @@ -146,7 +146,39 @@ impl WriteBackend for LocalBackend { } } -impl LocalBackend { +#[derive(Clone)] +pub struct LocalDestination { + path: PathBuf, + is_file: bool, +} + +impl LocalDestination { + pub fn new(path: &str, create: bool, expect_file: bool) -> Result { + let is_dir = path.ends_with('/'); + let path: PathBuf = path.into(); + let is_file = path.is_file() || (!path.is_dir() && !is_dir && expect_file); + + if create { + if is_file { + if let Some(path) = path.parent() { + fs::create_dir_all(path)?; + } + } else { + fs::create_dir_all(&path)?; + } + } + + Ok(Self { path, is_file }) + } + + fn path(&self, item: impl AsRef) -> PathBuf { + if self.is_file { + self.path.clone() + } else { + self.path.join(item) + } + } + pub fn remove_dir(&self, dirname: impl AsRef) -> Result<()> { Ok(fs::remove_dir_all(dirname)?) } @@ -162,7 +194,7 @@ impl LocalBackend { } pub fn set_times(&self, item: impl AsRef, meta: &Metadata) -> Result<()> { - let filename = self.path.join(item); + let filename = self.path(item); if let Some(mtime) = meta.mtime.map(|t| FileTime::from_system_time(t.into())) { set_file_mtime(&filename, mtime)?; } @@ -173,7 +205,7 @@ impl LocalBackend { } pub fn set_user_group(&self, item: impl AsRef, meta: &Metadata) -> Result<()> { - let filename = self.path.join(item); + let filename = self.path(item); let user = meta .user @@ -195,7 +227,7 @@ impl LocalBackend { } pub fn set_uid_gid(&self, item: impl AsRef, meta: &Metadata) -> Result<()> { - let filename = self.path.join(item); + let filename = self.path(item); let uid = meta.uid.map(Uid::from_raw); let gid = meta.gid.map(Gid::from_raw); @@ -205,7 +237,7 @@ impl LocalBackend { } pub fn set_permission(&self, item: impl AsRef, meta: &Metadata) -> Result<()> { - let filename = self.path.join(item); + let filename = self.path(item); if let Some(mode) = meta.mode() { let mode = map_mode_from_go(*mode); @@ -216,7 +248,7 @@ impl LocalBackend { // set_length sets the length of the given file. If it doesn't exist, create a new (empty) one with given length pub fn set_length(&self, item: impl AsRef, size: u64) -> Result<()> { - let filename = self.path.join(item); + let filename = self.path(item); OpenOptions::new() .create(true) .write(true) @@ -226,7 +258,7 @@ impl LocalBackend { } pub fn create_special(&self, item: impl AsRef, node: &Node) -> Result<()> { - let filename = self.path.join(item); + let filename = self.path(item); match node.node_type() { NodeType::Symlink { linktarget } => { @@ -270,7 +302,7 @@ impl LocalBackend { } pub fn read_at(&self, item: impl AsRef, offset: u64, length: u64) -> Result { - let filename = self.path.join(item); + let filename = self.path(item); let mut file = File::open(filename)?; file.seek(SeekFrom::Start(offset))?; let mut vec = vec![0; length.try_into()?]; @@ -279,7 +311,7 @@ impl LocalBackend { } pub fn get_matching_file(&self, item: impl AsRef, size: u64) -> Option { - let filename = self.path.join(item); + let filename = self.path(item); match fs::symlink_metadata(&filename) { Ok(meta) => { if meta.is_file() && meta.len() == size { @@ -293,7 +325,7 @@ impl LocalBackend { } pub fn write_at(&self, item: impl AsRef, offset: u64, data: &[u8]) -> Result<()> { - let filename = self.path.join(item); + let filename = self.path(item); let file = fs::OpenOptions::new() .create(true) .write(true) diff --git a/src/commands/diff.rs b/src/commands/diff.rs index 47e2eec..26bcb47 100644 --- a/src/commands/diff.rs +++ b/src/commands/diff.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context, Result}; use clap::Parser; use super::{progress_counter, RusticConfig}; -use crate::backend::{LocalBackend, LocalSource, LocalSourceOptions}; +use crate::backend::{LocalDestination, LocalSource, LocalSourceOptions}; use crate::blob::{Node, NodeStreamer, NodeType, Tree}; use crate::commands::helpers::progress_spinner; use crate::crypto::hash; @@ -79,7 +79,7 @@ pub(super) fn execute( let index = IndexBackend::new(be, progress_counter(""))?; let node1 = Tree::node_from_path(&index, snap1.tree, Path::new(path1))?; - let local = LocalBackend::new(path2)?; + let local = LocalDestination::new(path2, false, !node1.is_dir())?; let path2 = PathBuf::from(path2); let is_dir = path2 .metadata() @@ -123,7 +123,7 @@ fn arg_to_snap_path<'a>(arg: &'a str, default_path: &'a str) -> (Option<&'a str> } fn identical_content_local( - local: &LocalBackend, + local: &LocalDestination, index: &impl ReadIndex, path: &Path, node: &Node, diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 93eeca0..1904b7f 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -15,7 +15,7 @@ use rayon::ThreadPoolBuilder; use super::rustic_config::RusticConfig; use super::{bytes, progress_bytes, progress_counter, warm_up_wait}; -use crate::backend::{DecryptReadBackend, FileType, LocalBackend}; +use crate::backend::{DecryptReadBackend, FileType, LocalDestination}; use crate::blob::{Node, NodeStreamer, NodeType, Tree}; use crate::commands::helpers::progress_spinner; use crate::crypto::hash; @@ -89,7 +89,7 @@ pub(super) fn execute( let index = IndexBackend::new(be, progress_counter(""))?; let node = Tree::node_from_path(&index, snap.tree, Path::new(path))?; - let dest = LocalBackend::new(&opts.dest)?; + let dest = LocalDestination::new(&opts.dest, true, !node.is_dir())?; let p = progress_spinner("collecting file information..."); let (file_infos, stats) = allocate_and_collect(&dest, index.clone(), &node, &opts)?; @@ -150,7 +150,7 @@ struct RestoreStats { /// collect restore information, scan existing files and allocate non-existing files fn allocate_and_collect( - dest: &LocalBackend, + dest: &LocalDestination, index: impl IndexedBackend + Unpin, node: &Node, opts: &Opts, @@ -332,7 +332,7 @@ fn allocate_and_collect( /// using the [`DecryptReadBackend`] `be` and writing them into the [`LocalBackend`] `dest`. fn restore_contents( be: &impl DecryptReadBackend, - dest: &LocalBackend, + dest: &LocalDestination, file_infos: FileInfos, ) -> Result<()> { let (filenames, restore_info, total_size, _) = file_infos.dissolve(); @@ -400,7 +400,7 @@ fn restore_contents( } fn restore_metadata( - dest: &LocalBackend, + dest: &LocalDestination, index: impl IndexedBackend + Unpin, node: &Node, opts: &Opts, @@ -435,7 +435,7 @@ fn restore_metadata( Ok(()) } -fn set_metadata(dest: &LocalBackend, path: &PathBuf, node: &Node, opts: &Opts) { +fn set_metadata(dest: &LocalDestination, path: &PathBuf, node: &Node, opts: &Opts) { debug!("setting metadata for {:?}", path); dest.create_special(path, node) .unwrap_or_else(|_| warn!("restore {:?}: creating special file failed.", path)); @@ -512,7 +512,7 @@ impl FileInfos { /// Returns the computed length of the file fn add_file( &mut self, - dest: &LocalBackend, + dest: &LocalDestination, file: &Node, name: PathBuf, index: &impl IndexedBackend,