Merge pull request #785 from rustic-rs/linktarget

handle non-unicode link-targets
This commit is contained in:
aawsome 2023-08-01 09:53:58 +02:00 committed by GitHub
commit 1eafb722b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 50 deletions

View File

@ -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.

View File

@ -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() };

View File

@ -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",

View File

@ -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()
}
}

View File

@ -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)?;

View File

@ -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,

View File

@ -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

View File

@ -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()
}