use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Xattr {
pub name: String,
pub value: Vec<u8>,
}
#[cfg(unix)]
pub fn save_xattrs(path: &Path) -> std::io::Result<Vec<Xattr>> {
let meta = match std::fs::symlink_metadata(path) {
Ok(m) => m,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(e) => return Err(e),
};
if meta.file_type().is_symlink() {
return Ok(Vec::new());
}
let names: Vec<_> = match xattr::list(path) {
Ok(n) => n.collect(),
Err(e) => {
tracing::debug!(path = %path.display(), error = %e, "xattr::list failed (unsupported fs?)");
return Ok(Vec::new());
}
};
let mut out = Vec::with_capacity(names.len());
for name in names {
let name_str = match name.to_str() {
Some(s) => s.to_owned(),
None => {
tracing::warn!(?name, "xattr name is not valid UTF-8; skipping");
continue;
}
};
match xattr::get(path, &name_str) {
Ok(Some(value)) => out.push(Xattr {
name: name_str,
value,
}),
Ok(None) => {
tracing::debug!(name = %name_str, "xattr::get returned None (race?)");
}
Err(e) => {
tracing::warn!(name = %name_str, error = %e, "xattr::get failed; skipping");
}
}
}
Ok(out)
}
#[cfg(not(unix))]
pub fn save_xattrs(_path: &Path) -> std::io::Result<Vec<Xattr>> {
Ok(Vec::new())
}
#[cfg(unix)]
pub fn restore_xattrs(path: &Path, attrs: &[Xattr]) -> std::io::Result<u32> {
if attrs.is_empty() {
return Ok(0);
}
let mut restored = 0u32;
for attr in attrs {
match xattr::set(path, &attr.name, &attr.value) {
Ok(()) => {
restored += 1;
}
Err(e) => {
tracing::warn!(
path = %path.display(),
name = %attr.name,
error = %e,
"xattr::set failed; continuing"
);
}
}
}
Ok(restored)
}
#[cfg(not(unix))]
pub fn restore_xattrs(_path: &Path, _attrs: &[Xattr]) -> std::io::Result<u32> {
Ok(0)
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn save_xattrs_returns_empty_on_nonexistent_path() {
let result = save_xattrs(Path::new("/nonexistent/atomwrite/xattr/test")).unwrap();
assert!(result.is_empty());
}
#[test]
fn save_and_restore_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("xattr_test.txt");
let mut f = std::fs::File::create(&file).unwrap();
f.write_all(b"hello world").unwrap();
drop(f);
xattr::set(&file, "user.atomwrite_test", b"test_value_42").unwrap();
let saved = save_xattrs(&file).unwrap();
let entry = saved
.iter()
.find(|x| x.name == "user.atomwrite_test")
.expect("user.atomwrite_test must be in save result");
assert_eq!(entry.value, b"test_value_42");
let restored = restore_xattrs(&file, &saved).unwrap();
assert!(restored >= 1, "at least one xattr should be restored");
let again = xattr::get(&file, "user.atomwrite_test").unwrap();
assert_eq!(again, Some(b"test_value_42".to_vec()));
}
#[test]
fn save_xattrs_empty_for_tmpfs_file() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("plain.txt");
std::fs::write(&file, b"plain text").unwrap();
let _ = save_xattrs(&file).unwrap();
}
}