Skip to content

Commit

Permalink
Restore extended file attributes after dedupe
Browse files Browse the repository at this point in the history
Fixes #152
  • Loading branch information
pkolaczk committed Sep 4, 2022
1 parent 932f1b6 commit 428951d
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 9 deletions.
12 changes: 11 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fclones"
version = "0.27.1"
version = "0.27.2"
description = "Finds duplicate, unique, under- or over-replicated files"
authors = ["Piotr Kołaczkowski <pkolaczk@gmail.com>"]
homepage = "https://github.com/pkolaczk/fclones"
Expand Down Expand Up @@ -63,8 +63,9 @@ uuid = { version = "0.8", features = ["v4"] }
fiemap = "0.1"

[target.'cfg(unix)'.dependencies]
nix = "0.21"
libc = "0.2"
nix = "0.21"
xattr = "0.2"

[target.'cfg(windows)'.dependencies]
winapi = "0.3"
Expand Down
59 changes: 53 additions & 6 deletions src/reflink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ use filetime::FileTime;
use crate::dedupe::{FsCommand, PathAndMetadata};
use crate::log::Log;

#[cfg(unix)]
struct XAttr {
name: std::ffi::OsString,
value: Option<Vec<u8>>,
}

/// Calls OS-specific reflink implementations with an option to call the more generic
/// one during testing one on Linux ("crosstesting").
/// The destination file is allowed to exist.
pub fn reflink(src: &PathAndMetadata, dest: &PathAndMetadata, log: &Log) -> io::Result<()> {
// Remember the original metadata of the parent directory of the destination file:
// Remember original metadata of the destination file and its parent directory:
let dest_parent = dest.path.parent();
let dest_parent_metadata = dest_parent.map(|p| p.to_path_buf().metadata());
#[cfg(unix)]
let dest_xattrs = get_xattrs(&dest.path.to_path_buf());

// Call reflink:
let result = {
Expand All @@ -30,15 +38,25 @@ pub fn reflink(src: &PathAndMetadata, dest: &PathAndMetadata, log: &Log) -> io::
};

// Restore the original metadata of the deduplicated file:
if let Err(e) = restore_some_metadata(&dest.path.to_path_buf(), &dest.metadata) {
log.warn(format!("Failed keep metadata for {}: {}", dest, e))
let dest_path_buf = dest.path.to_path_buf();
if let Err(e) = restore_metadata(&dest_path_buf, &dest.metadata) {
log.warn(format!("Failed to keep metadata for {}: {}", dest, e))
}
#[cfg(unix)]
if xattr::SUPPORTED_PLATFORM {
if let Err(e) = restore_xattrs(&dest_path_buf, dest_xattrs) {
log.warn(format!(
"Failed to keep extended attributes for {}: {}",
dest, e
))
}
}

// Restore the original metadata of the deduplicated files's parent directory:
if let Some(parent) = dest_parent {
if let Some(metadata) = dest_parent_metadata {
let result = metadata
.and_then(|metadata| restore_some_metadata(&parent.to_path_buf(), &metadata));
let result =
metadata.and_then(|metadata| restore_metadata(&parent.to_path_buf(), &metadata));
if let Err(e) = result {
log.warn(format!(
"Failed keep metadata for {}: {}",
Expand Down Expand Up @@ -148,7 +166,7 @@ fn reflink_overwrite(target: &std::path::Path, link: &std::path::Path) -> io::Re
}

// Not kept: owner, xattrs, ACLs, etc.
fn restore_some_metadata(path: &std::path::Path, metadata: &Metadata) -> io::Result<()> {
fn restore_metadata(path: &std::path::Path, metadata: &Metadata) -> io::Result<()> {
let atime = FileTime::from_last_access_time(metadata);
let mtime = FileTime::from_last_modification_time(metadata);

Expand All @@ -160,6 +178,35 @@ fn restore_some_metadata(path: &std::path::Path, metadata: &Metadata) -> io::Res
}
}

#[cfg(unix)]
fn get_xattrs(path: &std::path::Path) -> io::Result<Vec<XAttr>> {
use itertools::Itertools;
use xattr::FileExt;

let file = fs::File::open(path)?;
file.list_xattr()?
.into_iter()
.map(|name| {
Ok(XAttr {
value: file.get_xattr(name.as_os_str())?,
name,
})
})
.try_collect()
}

#[cfg(unix)]
fn restore_xattrs(path: &std::path::Path, xattrs: io::Result<Vec<XAttr>>) -> io::Result<()> {
use xattr::FileExt;
let file = fs::File::open(path)?;
for attr in xattrs? {
if let Some(value) = attr.value {
file.set_xattr(attr.name, &value)?;
}
}
Ok(())
}

// Reflink which expects the destination to not exist.
#[cfg(any(not(any(target_os = "linux", target_os = "android")), test))]
fn copy_by_reflink(src: &crate::path::Path, dest: &crate::path::Path) -> io::Result<()> {
Expand Down

0 comments on commit 428951d

Please sign in to comment.