diff --git a/Cargo.lock b/Cargo.lock index 72420cd21..c7887ff0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3318,13 +3318,13 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" dependencies = [ "base64 0.22.1", "indexmap 2.9.0", - "quick-xml 0.32.0", + "quick-xml 0.37.5", "serde", "time", ] @@ -3482,7 +3482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.102", @@ -3532,15 +3532,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quick-xml" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - [[package]] name = "quick-xml" version = "0.37.5" @@ -4191,7 +4182,7 @@ dependencies = [ [[package]] name = "rustic_backend" version = "0.5.2" -source = "git+https://github.com/rustic-rs/rustic_core.git#40261b4af6a93cd41dc9200916eadc892c42dbaa" +source = "git+https://github.com/rustic-rs/rustic_core.git?branch=typed_path#e4242902c4f5c8e53ecd1528762fda67c81397e9" dependencies = [ "aho-corasick", "backon", @@ -4230,7 +4221,7 @@ checksum = "fbcebf2228827bc4b61cb54dfd84cf43aacf06ca2dfe4c014b136a0e32b876e2" [[package]] name = "rustic_core" version = "0.7.3" -source = "git+https://github.com/rustic-rs/rustic_core.git#40261b4af6a93cd41dc9200916eadc892c42dbaa" +source = "git+https://github.com/rustic-rs/rustic_core.git?branch=typed_path#e4242902c4f5c8e53ecd1528762fda67c81397e9" dependencies = [ "aes256ctr_poly1305aes", "binrw", @@ -4253,6 +4244,7 @@ dependencies = [ "enumset", "filetime", "gethostname 1.0.2", + "globset", "hex", "humantime", "ignore", @@ -4277,6 +4269,7 @@ dependencies = [ "shell-words", "strum 0.27.1", "thiserror 2.0.12", + "typed-path", "walkdir", "xattr", "zstd", @@ -4285,7 +4278,7 @@ dependencies = [ [[package]] name = "rustic_testing" version = "0.3.2" -source = "git+https://github.com/rustic-rs/rustic_core.git#40261b4af6a93cd41dc9200916eadc892c42dbaa" +source = "git+https://github.com/rustic-rs/rustic_core.git?branch=typed_path#e4242902c4f5c8e53ecd1528762fda67c81397e9" dependencies = [ "aho-corasick", "anyhow", @@ -5865,7 +5858,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index df3388540..c6c7b3fb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,8 +57,8 @@ rustdoc-args = ["--document-private-items", "--generate-link-to-definition"] [dependencies] abscissa_core = { version = "0.8.1", default-features = false, features = ["application"] } -rustic_backend = { git = "https://github.com/rustic-rs/rustic_core.git", features = ["cli"] } -rustic_core = { git = "https://github.com/rustic-rs/rustic_core.git", features = ["cli"] } +rustic_backend = { git = "https://github.com/rustic-rs/rustic_core.git", branch = "typed_path", features = ["cli"] } +rustic_core = { git = "https://github.com/rustic-rs/rustic_core.git", branch = "typed_path", features = ["cli"] } # allocators jemallocator-global = { version = "0.3.2", optional = true } @@ -144,7 +144,7 @@ pretty_assertions = "1.4" quickcheck = "1" quickcheck_macros = "1" rstest = "0.23" -rustic_testing = { git = "https://github.com/rustic-rs/rustic_core.git" } +rustic_testing = { git = "https://github.com/rustic-rs/rustic_core.git", branch = "typed_path" } tar = "0.4.43" tempfile = "3.14" toml = "0.8" diff --git a/src/commands/diff.rs b/src/commands/diff.rs index f9521fff3..a9a2e2200 100644 --- a/src/commands/diff.rs +++ b/src/commands/diff.rs @@ -6,10 +6,7 @@ use abscissa_core::{Command, Runnable, Shutdown}; use clap::ValueHint; use log::debug; -use std::{ - fmt::Display, - path::{Path, PathBuf}, -}; +use std::{fmt::Display, path::PathBuf}; use anyhow::{Context, Result, bail}; @@ -17,6 +14,8 @@ use rustic_core::{ IndexedFull, LocalDestination, LocalSource, LocalSourceFilterOptions, LocalSourceSaveOptions, LsOptions, ReadSource, ReadSourceEntry, Repository, RusticResult, repofile::{Node, NodeType}, + typed_path::{UnixPath, UnixPathBuf}, + util::path_to_unix_path, }; /// `diff` subcommand @@ -97,6 +96,7 @@ impl DiffCmd { let node1 = repo.node_from_snapshot_and_path(&snap1, path1)?; let local = LocalDestination::new(path2, false, !node1.is_dir())?; let path2 = PathBuf::from(path2); + let path2_unix = path_to_unix_path(&path2)?; let is_dir = path2 .metadata() .with_context(|| format!("Error accessing {path2:?}"))? @@ -111,10 +111,10 @@ impl DiffCmd { let ReadSourceEntry { path, node, .. } = item?; let path = if is_dir { // remove given path prefix for dirs as local path - path.strip_prefix(&path2).unwrap().to_path_buf() + path.strip_prefix(&path2_unix).unwrap().to_path_buf() } else { // ensure that we really get the filename if local path is a file - path2.file_name().unwrap().into() + path2_unix.file_name().unwrap().into() }; Ok((path, node)) }); @@ -190,7 +190,7 @@ fn arg_to_snap_path<'a>(arg: &'a str, default_path: &'a str) -> (Option<&'a str> fn identical_content_local( local: &LocalDestination, repo: &Repository, - path: &Path, + path: &UnixPath, node: &Node, ) -> Result { let Some(mut open_file) = local.get_matching_file(path, node.meta.size) else { @@ -317,10 +317,10 @@ impl Display for DiffStatistics { /// // TODO!: add errors! fn diff( - mut tree_streamer1: impl Iterator>, - mut tree_streamer2: impl Iterator>, + mut tree_streamer1: impl Iterator>, + mut tree_streamer2: impl Iterator>, no_content: bool, - file_identical: impl Fn(&Path, &Node, &Node) -> Result, + file_identical: impl Fn(&UnixPath, &Node, &Node) -> Result, metadata: bool, ) -> Result<()> { let mut item1 = tree_streamer1.next().transpose()?; @@ -393,9 +393,9 @@ fn diff( } fn diff_identical( - mut tree_streamer1: impl Iterator>, - mut tree_streamer2: impl Iterator>, - file_identical: impl Fn(&Path, &Node, &Node) -> Result, + mut tree_streamer1: impl Iterator>, + mut tree_streamer2: impl Iterator>, + file_identical: impl Fn(&UnixPath, &Node, &Node) -> Result, ) -> Result<()> { let mut item1 = tree_streamer1.next().transpose()?; let mut item2 = tree_streamer2.next().transpose()?; diff --git a/src/commands/dump.rs b/src/commands/dump.rs index a4c018c5f..eaa14c246 100644 --- a/src/commands/dump.rs +++ b/src/commands/dump.rs @@ -16,6 +16,7 @@ use log::warn; use rustic_core::{ LsOptions, repofile::{Node, NodeType}, + util::{typed_path_to_path, unix_path_to_path}, vfs::OpenFile, }; use tar::{Builder, EntryType, Header}; @@ -203,7 +204,7 @@ fn dump_tar( // handle special files if node.is_symlink() { - header.set_link_name(node.node_type.to_link())?; + header.set_link_name(typed_path_to_path(&node.node_type.to_link())?)?; } match node.node_type { NodeType::Dev { device } | NodeType::Chardev { device } => { @@ -213,6 +214,7 @@ fn dump_tar( _ => {} } + let path = unix_path_to_path(&path)?; if node.is_file() { // write file content if this is a regular file let open_file = OpenFileReader { @@ -280,6 +282,7 @@ fn write_zip_contents( options = options.last_modified_time(mtime.naive_local().try_into().unwrap_or_default()); } + let path = unix_path_to_path(&path)?; if node.is_file() { zip.start_file_from_path(path, options)?; repo.dump(&node, zip)?; diff --git a/src/commands/find.rs b/src/commands/find.rs index 66eb5b044..0a12dd843 100644 --- a/src/commands/find.rs +++ b/src/commands/find.rs @@ -1,7 +1,5 @@ //! `find` subcommand -use std::path::{Path, PathBuf}; - use crate::{Application, RUSTIC_APP, repository::CliIndexedRepo, status_err}; use abscissa_core::{Command, Runnable, Shutdown}; @@ -13,6 +11,8 @@ use itertools::Itertools; use rustic_core::{ FindMatches, FindNode, SnapshotGroupCriterion, repofile::{Node, SnapshotFile}, + typed_path::{UnixPath, UnixPathBuf}, + util::u8_to_path, }; use super::ls::print_node; @@ -30,7 +30,7 @@ pub(crate) struct FindCmd { /// exact path to find #[clap(long, value_name = "PATH", value_hint = ValueHint::AnyPath)] - path: Option, + path: Option, /// Snapshots to search in. If none is given, use filter options to filter from all snapshots #[clap(value_name = "ID")] @@ -105,8 +105,11 @@ impl FindCmd { _ = builder.add(GlobBuilder::new(glob).case_insensitive(true).build()?); } let globset = builder.build()?; - let matches = |path: &Path, _: &Node| { - globset.is_match(path) || path.file_name().is_some_and(|f| globset.is_match(f)) + let matches = |path: &UnixPath, _: &Node| { + globset.is_match(u8_to_path(path)) + || path + .file_name() + .is_some_and(|f| globset.is_match(u8_to_path(f))) }; let FindMatches { paths, diff --git a/src/commands/ls.rs b/src/commands/ls.rs index a89fab2e1..6b9c7c966 100644 --- a/src/commands/ls.rs +++ b/src/commands/ls.rs @@ -1,7 +1,5 @@ //! `ls` subcommand -use std::path::Path; - use crate::{Application, RUSTIC_APP, repository::CliIndexedRepo, status_err}; use abscissa_core::{Command, Runnable, Shutdown}; @@ -10,6 +8,7 @@ use anyhow::Result; use rustic_core::{ LsOptions, repofile::{Node, NodeType}, + typed_path::UnixPath, }; mod constants { @@ -153,7 +152,7 @@ impl LsCmd { if !first_item { print!(","); } - print!("{}", serde_json::to_string(&path)?); + print!("{}", path.display()); } else if self.long { print_node(&node, &path, self.numeric_id); } else { @@ -183,7 +182,7 @@ impl LsCmd { /// /// * `node` - the node to print /// * `path` - the path of the node -pub fn print_node(node: &Node, path: &Path, numeric_uid_gid: bool) { +pub fn print_node(node: &Node, path: &UnixPath, numeric_uid_gid: bool) { println!( "{:>10} {:>8} {:>8} {:>9} {:>17} {path:?} {}", node.mode_str(), diff --git a/src/commands/mount/fusefs.rs b/src/commands/mount/fusefs.rs index e65f10aa7..db9732c97 100644 --- a/src/commands/mount/fusefs.rs +++ b/src/commands/mount/fusefs.rs @@ -1,8 +1,7 @@ -#[cfg(not(windows))] -use std::os::unix::prelude::OsStrExt; use std::{ collections::BTreeMap, ffi::{CString, OsStr}, + os::unix::ffi::OsStrExt, path::Path, sync::RwLock, time::{Duration, SystemTime}, @@ -11,6 +10,8 @@ use std::{ use rustic_core::{ IndexedFull, Repository, repofile::{Node, NodeType}, + typed_path::UnixPathBuf, + util::{path_to_unix_path, typed_path_to_unix_path}, vfs::{FilePolicy, OpenFile, Vfs}, }; @@ -43,13 +44,19 @@ impl FuseFS { fn node_from_path(&self, path: &Path) -> Result { self.vfs - .node_from_path(&self.repo, path) + .node_from_path( + &self.repo, + &path_to_unix_path(path).map_err(|_| libc::ENOENT)?, + ) .map_err(|_| libc::ENOENT) } fn dir_entries_from_path(&self, path: &Path) -> Result, i32> { self.vfs - .dir_entries_from_path(&self.repo, path) + .dir_entries_from_path( + &self.repo, + &path_to_unix_path(path).map_err(|_| libc::ENOENT)?, + ) .map_err(|_| libc::ENOENT) } } @@ -74,9 +81,9 @@ fn node_type_to_rdev(tpe: &NodeType) -> u32 { .unwrap() } -fn node_to_linktarget(node: &Node) -> Option<&OsStr> { +fn node_to_linktarget(node: &Node) -> Option { if node.is_symlink() { - Some(node.node_type.to_link().as_os_str()) + Some(typed_path_to_unix_path(&node.node_type.to_link()).to_path_buf()) } else { None } @@ -187,7 +194,7 @@ impl FilesystemMT for FuseFS { let result = nodes .into_iter() .map(|node| DirectoryEntry { - name: node.name(), + name: OsStr::from_bytes(&node.name()).to_os_string(), kind: node_to_filetype(&node), }) .collect(); diff --git a/src/commands/tui/ls.rs b/src/commands/tui/ls.rs index 2622ab813..80d2ae11a 100644 --- a/src/commands/tui/ls.rs +++ b/src/commands/tui/ls.rs @@ -1,5 +1,3 @@ -use std::path::{Path, PathBuf}; - use anyhow::Result; use crossterm::event::{Event, KeyCode, KeyEventKind}; use ratatui::{ @@ -9,6 +7,7 @@ use ratatui::{ use rustic_core::{ IndexedFull, ProgressBars, Repository, repofile::{Node, SnapshotFile, Tree}, + typed_path::{UnixPath, UnixPathBuf}, }; use style::palette::tailwind; @@ -55,7 +54,7 @@ pub(crate) struct Snapshot<'a, P, S> { table: WithBlock, repo: &'a Repository, snapshot: SnapshotFile, - path: PathBuf, + path: UnixPathBuf, trees: Vec<(Tree, usize)>, // Stack of parent trees with position tree: Tree, } @@ -80,7 +79,7 @@ impl<'a, P: ProgressBars, S: IndexedFull> Snapshot<'a, P, S> { table: WithBlock::new(SelectTable::new(header), Block::new()), repo, snapshot, - path: PathBuf::new(), + path: UnixPathBuf::new(), trees: Vec::new(), tree, }; @@ -104,7 +103,7 @@ impl<'a, P: ProgressBars, S: IndexedFull> Snapshot<'a, P, S> { node.meta.group.clone().unwrap_or_else(|| "?".to_string()), ) }; - let name = node.name().to_string_lossy().to_string(); + let name = String::from_utf8_lossy(&node.name()).to_string(); let size = node.meta.size.to_string(); let mtime = node.meta.mtime.map_or_else( || "?".to_string(), @@ -243,7 +242,7 @@ impl<'a, P: ProgressBars, S: IndexedFull> Snapshot<'a, P, S> { .snapshot .paths .iter() - .any(|p| Path::new(p).is_absolute()); + .any(|p| UnixPath::new(p).is_absolute()); let path = self.path.join(node.name()); let path = path.display(); let default_target = if is_absolute { diff --git a/src/commands/webdav/webdavfs.rs b/src/commands/webdav/webdavfs.rs index db68fb54b..838c240b2 100644 --- a/src/commands/webdav/webdavfs.rs +++ b/src/commands/webdav/webdavfs.rs @@ -1,5 +1,3 @@ -#[cfg(not(windows))] -use std::os::unix::ffi::OsStrExt; use std::{ fmt::{Debug, Formatter}, io::SeekFrom, @@ -19,6 +17,7 @@ use futures::FutureExt; use rustic_core::{ IndexedFull, Repository, repofile::Node, + typed_path::UnixPath, vfs::{FilePolicy, OpenFile, Vfs}, }; use tokio::task::spawn_blocking; @@ -96,7 +95,7 @@ impl WebDavFS< /// [`Tree`]: crate::repofile::Tree async fn node_from_path(&self, path: &DavPath) -> Result { let inner = self.inner.clone(); - let path = path.as_pathbuf(); + let path = UnixPath::new(path.as_bytes()).to_path_buf(); spawn_blocking(move || { inner .vfs @@ -124,7 +123,7 @@ impl WebDavFS< /// [`Tree`]: crate::repofile::Tree async fn dir_entries_from_path(&self, path: &DavPath) -> Result, FsError> { let inner = self.inner.clone(); - let path = path.as_pathbuf(); + let path = UnixPath::new(path.as_bytes()).to_path_buf(); spawn_blocking(move || { inner .vfs @@ -233,19 +232,8 @@ impl DavDirEntry for DavFsDirEntry { .boxed() } - #[cfg(not(windows))] fn name(&self) -> Vec { - self.0.name().as_bytes().to_vec() - } - - #[cfg(windows)] - fn name(&self) -> Vec { - self.0 - .name() - .as_os_str() - .to_string_lossy() - .to_string() - .into_bytes() + self.0.name().to_vec() } }