From e47708479c75e606d673d8948b809751a1c0de11 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Thu, 23 Mar 2023 13:57:09 +0100 Subject: [PATCH] Handle extended attributes --- Cargo.lock | 12 +++++++++++- Cargo.toml | 1 + src/backend/ignore.rs | 16 ++++++++++++++-- src/backend/local.rs | 31 ++++++++++++++++++++++++++++++- src/backend/node.rs | 12 ++++++++++++ src/commands/restore.rs | 2 ++ 6 files changed, 70 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05ef875..deed4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1882,6 +1882,7 @@ dependencies = [ "toml", "users", "walkdir", + "xattr 1.0.0", "zstd", ] @@ -2266,7 +2267,7 @@ checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" dependencies = [ "filetime", "libc", - "xattr", + "xattr 0.2.3", ] [[package]] @@ -2867,6 +2868,15 @@ dependencies = [ "libc", ] +[[package]] +name = "xattr" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea263437ca03c1522846a4ddafbca2542d0ad5ed9b784909d4b27b76f62bc34a" +dependencies = [ + "libc", +] + [[package]] name = "zeroize" version = "1.4.3" diff --git a/Cargo.toml b/Cargo.toml index fcb2244..0cfd9fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ rhai = {version = "1.13", features = ["sync", "serde", "no_optimize", "no_module [target.'cfg(not(windows))'.dependencies] users = "0.11" +xattr = "1" [dev-dependencies] rstest = "0.17" diff --git a/src/backend/ignore.rs b/src/backend/ignore.rs index 1e9b7b7..bccf68f 100644 --- a/src/backend/ignore.rs +++ b/src/backend/ignore.rs @@ -17,8 +17,8 @@ use serde_with::{serde_as, DisplayFromStr}; #[cfg(not(windows))] use users::{Groups, Users, UsersCache}; -use super::{node::Metadata, node::NodeType, Node, ReadSource}; -use super::{ReadSourceEntry, ReadSourceOpen}; +use super::node::{ExtendedAttribute, Metadata, NodeType}; +use super::{Node, ReadSource, ReadSourceEntry, ReadSourceOpen}; pub struct LocalSource { builder: WalkBuilder, @@ -259,6 +259,7 @@ fn map_entry( inode, device_id, links, + extended_attributes: Vec::new(), }; let node = if m.is_dir() { @@ -322,6 +323,16 @@ fn map_entry( let device_id = if ignore_devid { 0 } else { m.dev() }; let links = if m.is_dir() { 0 } else { m.nlink() }; + let path = entry.path(); + let extended_attributes = xattr::list(path)? + .map(|name| { + Ok(ExtendedAttribute { + name: name.to_string_lossy().to_string(), + value: xattr::get(path, name)?.unwrap(), + }) + }) + .collect::>()?; + let meta = Metadata { size, mtime, @@ -335,6 +346,7 @@ fn map_entry( inode, device_id, links, + extended_attributes, }; let filetype = m.file_type(); diff --git a/src/backend/local.rs b/src/backend/local.rs index ee20ad3..591eb12 100644 --- a/src/backend/local.rs +++ b/src/backend/local.rs @@ -22,7 +22,7 @@ use crate::repository::parse_command; use super::mapper::map_mode_from_go; #[cfg(not(windows))] use super::node::NodeType; -use super::node::{Metadata, Node}; +use super::node::{ExtendedAttribute, Metadata, Node}; use super::{FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES}; #[derive(Clone)] @@ -324,6 +324,35 @@ impl LocalDestination { Ok(()) } + #[cfg(windows)] + pub fn set_extended_attributes( + &self, + item: impl AsRef, + extended_attributes: &[ExtendedAttribute], + ) -> Result<()> { + Ok(()) + } + + #[cfg(not(windows))] + pub fn set_extended_attributes( + &self, + item: impl AsRef, + extended_attributes: &[ExtendedAttribute], + ) -> Result<()> { + let filename = self.path(item); + + for name in xattr::list(&filename)? { + if let Err(err) = xattr::remove(&filename, &name) { + warn!("error removing xattr on {filename:?}: {err}"); + } + } + + for ExtendedAttribute { name, value } in extended_attributes { + xattr::set(&filename, name, value)?; + } + Ok(()) + } + // 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(item); diff --git a/src/backend/node.rs b/src/backend/node.rs index f148161..602f92b 100644 --- a/src/backend/node.rs +++ b/src/backend/node.rs @@ -14,6 +14,8 @@ use derive_getters::Getters; use derive_more::{Constructor, IsVariant}; use serde::{Deserialize, Serialize}; use serde_aux::prelude::*; +use serde_with::base64::Base64; +use serde_with::serde_as; use crate::id::Id; @@ -68,6 +70,16 @@ pub struct Metadata { pub device_id: u64, pub size: u64, pub links: u64, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extended_attributes: Vec, +} + +#[serde_as] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExtendedAttribute { + pub name: String, + #[serde_as(as = "Base64")] + pub value: Vec, } fn is_default(t: &T) -> bool { diff --git a/src/commands/restore.rs b/src/commands/restore.rs index b4fb435..d9b7b2f 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -453,6 +453,8 @@ fn set_metadata(dest: &LocalDestination, path: &PathBuf, node: &Node, opts: &Opt } dest.set_permission(path, node.meta()) .unwrap_or_else(|_| warn!("restore {:?}: chmod failed.", path)); + dest.set_extended_attributes(path, &node.meta.extended_attributes) + .unwrap_or_else(|_| warn!("restore {:?}: setting extended attributes failed.", path)); dest.set_times(path, node.meta()) .unwrap_or_else(|_| warn!("restore {:?}: setting file times failed.", path)); }