mirror of
https://github.com/rustic-rs/rustic.git
synced 2025-10-26 11:18:51 +00:00
Merge pull request #785 from rustic-rs/linktarget
handle non-unicode link-targets
This commit is contained in:
commit
1eafb722b7
@ -6,6 +6,7 @@ Bugs fixed:
|
||||
- prune did abort when no time was set for a pack-do-delete. This case is now handled correctly.
|
||||
- retrying backend access didn't work for long operations. This has been fixed (and retries are now customizable)
|
||||
- The zstd compression library led to data corruption in very unlikely cases. This has been fixed by a dependency update.
|
||||
- Non-unicode link targets are now correctly handled on Unix (after this has been added to the restic repo format).
|
||||
|
||||
New features:
|
||||
- New global configuration paths are available, located at /etc/rustic/*.toml or %PROGRAMDATA%/rustic/config/*.toml, depending on your platform.
|
||||
|
||||
@ -313,15 +313,7 @@ fn map_entry(
|
||||
Node::new_node(name, NodeType::Dir, meta)
|
||||
} else if m.is_symlink() {
|
||||
let target = read_link(entry.path()).map_err(IgnoreErrorKind::FromIoError)?;
|
||||
let node_type = NodeType::Symlink {
|
||||
linktarget: target
|
||||
.to_str()
|
||||
.ok_or(IgnoreErrorKind::TargetIsNotValidUnicode {
|
||||
file: entry.path().to_path_buf(),
|
||||
target: target.clone(),
|
||||
})?
|
||||
.to_string(),
|
||||
};
|
||||
let node_type = NodeType::from_link(&target);
|
||||
Node::new_node(name, node_type, meta)
|
||||
} else {
|
||||
Node::new_node(name, NodeType::File, meta)
|
||||
@ -441,15 +433,7 @@ fn map_entry(
|
||||
Node::new_node(name, NodeType::Dir, meta)
|
||||
} else if m.is_symlink() {
|
||||
let target = read_link(entry.path()).map_err(IgnoreErrorKind::FromIoError)?;
|
||||
let node_type = NodeType::Symlink {
|
||||
linktarget: target
|
||||
.to_str()
|
||||
.ok_or_else(|| IgnoreErrorKind::TargetIsNotValidUnicode {
|
||||
file: entry.path().to_path_buf(),
|
||||
target: target.clone(),
|
||||
})?
|
||||
.to_string(),
|
||||
};
|
||||
let node_type = NodeType::from_link(&target);
|
||||
Node::new_node(name, node_type, meta)
|
||||
} else if filetype.is_block_device() {
|
||||
let node_type = NodeType::Dev { device: m.rdev() };
|
||||
|
||||
@ -488,12 +488,14 @@ impl LocalDestination {
|
||||
let filename = self.path(item);
|
||||
|
||||
match &node.node_type {
|
||||
NodeType::Symlink { linktarget } => symlink(linktarget.clone(), filename.clone())
|
||||
.map_err(|err| LocalErrorKind::SymlinkingFailed {
|
||||
linktarget: linktarget.to_string(),
|
||||
NodeType::Symlink { .. } => {
|
||||
let linktarget = node.node_type.to_link();
|
||||
symlink(linktarget, &filename).map_err(|err| LocalErrorKind::SymlinkingFailed {
|
||||
linktarget: linktarget.to_path_buf(),
|
||||
filename,
|
||||
source: err,
|
||||
})?,
|
||||
})?;
|
||||
}
|
||||
NodeType::Dev { device } => {
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
|
||||
@ -2,6 +2,7 @@ use std::{
|
||||
cmp::Ordering,
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::Debug,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
@ -17,9 +18,11 @@ use chrono::{DateTime, Local};
|
||||
use derive_more::{Constructor, IsVariant};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_aux::prelude::*;
|
||||
use serde_with::base64::{Base64, Standard};
|
||||
use serde_with::formats::Padded;
|
||||
use serde_with::{DeserializeAs, SerializeAs};
|
||||
use serde_with::{
|
||||
base64::{Base64, Standard},
|
||||
formats::Padded,
|
||||
serde_as, DeserializeAs, SerializeAs,
|
||||
};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use crate::error::NodeErrorKind;
|
||||
@ -39,6 +42,7 @@ pub struct Node {
|
||||
pub subtree: Option<Id>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, IsVariant)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
pub enum NodeType {
|
||||
@ -46,6 +50,9 @@ pub enum NodeType {
|
||||
Dir,
|
||||
Symlink {
|
||||
linktarget: String,
|
||||
#[serde_as(as = "Option<Base64>")]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
linktarget_raw: Option<Vec<u8>>,
|
||||
},
|
||||
Dev {
|
||||
#[serde(default)]
|
||||
@ -59,6 +66,60 @@ pub enum NodeType {
|
||||
Socket,
|
||||
}
|
||||
|
||||
impl NodeType {
|
||||
#[cfg(not(windows))]
|
||||
pub fn from_link(target: &Path) -> Self {
|
||||
let (linktarget, linktarget_raw) = target.to_str().map_or_else(
|
||||
|| {
|
||||
(
|
||||
target.as_os_str().to_string_lossy().to_string(),
|
||||
Some(target.as_os_str().as_bytes().to_vec()),
|
||||
)
|
||||
},
|
||||
|t| (t.to_string(), None),
|
||||
);
|
||||
Self::Symlink {
|
||||
linktarget,
|
||||
linktarget_raw,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
// Windows doen't support non-unicode link targets, so we assume unicode here.
|
||||
// TODO: Test and check this!
|
||||
pub fn from_link(target: &Path) -> Self {
|
||||
Self::Symlink {
|
||||
linktarget: target.as_os_str().to_string_lossy().to_string(),
|
||||
linktarget_raw: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Must be only called on NodeType::Symlink!
|
||||
#[cfg(not(windows))]
|
||||
pub fn to_link(&self) -> &Path {
|
||||
match self {
|
||||
Self::Symlink {
|
||||
linktarget,
|
||||
linktarget_raw,
|
||||
} => linktarget_raw.as_ref().map_or_else(
|
||||
|| Path::new(linktarget),
|
||||
|t| Path::new(OsStr::from_bytes(t)),
|
||||
),
|
||||
_ => panic!("called method to_link on non-symlink!"),
|
||||
}
|
||||
}
|
||||
|
||||
// Must be only called on NodeType::Symlink!
|
||||
// TODO: Implement non-unicode link targets correctly for windows
|
||||
#[cfg(windows)]
|
||||
pub fn to_link(&self) -> &Path {
|
||||
match self {
|
||||
Self::Symlink { linktarget, .. } => Path::new(linktarget),
|
||||
_ => panic!("called method to_link on non-symlink!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NodeType {
|
||||
fn default() -> Self {
|
||||
Self::File
|
||||
@ -136,9 +197,9 @@ impl Node {
|
||||
pub const fn is_special(&self) -> bool {
|
||||
matches!(
|
||||
self.node_type,
|
||||
NodeType::Symlink { linktarget: _ }
|
||||
| NodeType::Dev { device: _ }
|
||||
| NodeType::Chardev { device: _ }
|
||||
NodeType::Symlink { .. }
|
||||
| NodeType::Dev { .. }
|
||||
| NodeType::Chardev { .. }
|
||||
| NodeType::Fifo
|
||||
| NodeType::Socket
|
||||
)
|
||||
@ -356,4 +417,10 @@ mod tests {
|
||||
let expected = OsStr::from_bytes(expected);
|
||||
assert_eq!(expected, unescape_filename(input).unwrap());
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn from_link_to_link_is_identity(bytes: Vec<u8>) -> bool {
|
||||
let path = Path::new(OsStr::from_bytes(&bytes));
|
||||
path == NodeType::from_link(path).to_link()
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,17 +225,7 @@ impl RestoreOpts {
|
||||
// process existing node
|
||||
if (node.is_dir() && !dst.file_type().unwrap().is_dir())
|
||||
|| (node.is_file() && !dst.metadata().unwrap().is_file())
|
||||
|| {
|
||||
let this = &node;
|
||||
matches!(
|
||||
this.node_type,
|
||||
NodeType::Symlink { linktarget: _ }
|
||||
| NodeType::Dev { device: _ }
|
||||
| NodeType::Chardev { device: _ }
|
||||
| NodeType::Fifo
|
||||
| NodeType::Socket
|
||||
)
|
||||
}
|
||||
|| node.is_special()
|
||||
{
|
||||
// if types do not match, first remove the existing file
|
||||
process_existing(dst)?;
|
||||
|
||||
@ -669,7 +669,7 @@ pub enum LocalErrorKind {
|
||||
/// failed to symlink target {linktarget:?} from {filename:?} with {source:?}
|
||||
#[cfg(not(any(windows, target_os = "openbsd")))]
|
||||
SymlinkingFailed {
|
||||
linktarget: String,
|
||||
linktarget: PathBuf,
|
||||
filename: PathBuf,
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
|
||||
@ -194,14 +194,9 @@ fn diff(
|
||||
NodeType::File if metadata && node1.meta != node2.meta => {
|
||||
println!("U {path:?}");
|
||||
}
|
||||
NodeType::Symlink { linktarget } => {
|
||||
if let NodeType::Symlink {
|
||||
linktarget: linktarget2,
|
||||
} = &node2.node_type
|
||||
{
|
||||
if *linktarget != *linktarget2 {
|
||||
println!("U {path:?}");
|
||||
}
|
||||
NodeType::Symlink { .. } => {
|
||||
if node1.node_type.to_link() != node1.node_type.to_link() {
|
||||
println!("U {path:?}");
|
||||
}
|
||||
}
|
||||
_ => {} // no difference to show
|
||||
|
||||
@ -137,8 +137,8 @@ fn print_node(node: &Node, path: &Path) {
|
||||
.mtime
|
||||
.map(|t| t.format("%_d %b %H:%M").to_string())
|
||||
.unwrap_or_else(|| "?".to_string()),
|
||||
if let NodeType::Symlink { linktarget } = &node.node_type {
|
||||
["->", linktarget].join(" ")
|
||||
if let NodeType::Symlink { .. } = &node.node_type {
|
||||
["->", &node.node_type.to_link().to_string_lossy()].join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user