Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .vscode/cspell.dictionaries/jargon.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fileio
filesystem
filesystems
flamegraph
fsxattr
fullblock
getfacl
gibi
Expand Down Expand Up @@ -133,6 +134,7 @@ urand
whitespace
wordlist
wordlists
xattrs

# * abbreviations
consts
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

4 changes: 1 addition & 3 deletions src/uu/ls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,14 @@ uucore = { workspace = true, features = [
"colors",
"entries",
"fs",
"fsxattr",
"quoting-style",
"version-cmp",
] }
once_cell = { workspace = true }
selinux = { workspace = true, optional = true }
hostname = { workspace = true }

[target.'cfg(unix)'.dependencies]
xattr = { workspace = true }

[[bin]]
name = "ls"
path = "src/main.rs"
Expand Down
19 changes: 4 additions & 15 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm getxattr
// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm

use clap::{
builder::{NonEmptyStringValueParser, ValueParser},
Expand Down Expand Up @@ -36,7 +36,8 @@ use std::{
};
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use unicode_width::UnicodeWidthStr;

#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
use uucore::fsxattr::has_acl;
#[cfg(any(
target_os = "linux",
target_os = "macos",
Expand Down Expand Up @@ -2621,18 +2622,6 @@ fn display_grid(
Ok(())
}

#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
fn file_has_acl<P: AsRef<Path>>(file: P) -> bool {
// don't use exacl here, it is doing more getxattr call then needed
match xattr::list(file) {
Ok(acl) => {
// if we have extra attributes, we have an acl
acl.count() > 0
}
Err(_) => false,
}
}

/// This writes to the BufWriter out a single string of the output of `ls -l`.
///
/// It writes the following keys, in order:
Expand Down Expand Up @@ -2680,7 +2669,7 @@ fn display_item_long(
// TODO: See how Mac should work here
let is_acl_set = false;
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
let is_acl_set = file_has_acl(item.display_name.as_os_str());
let is_acl_set = has_acl(item.display_name.as_os_str());
write!(
output_display,
"{}{}{} {}",
Expand Down
1 change: 1 addition & 0 deletions src/uu/mv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ indicatif = { workspace = true }
uucore = { workspace = true, features = [
"backup-control",
"fs",
"fsxattr",
"update-control",
] }

Expand Down
15 changes: 15 additions & 0 deletions src/uu/mv/src/mv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ use uucore::fs::{
are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file,
path_ends_with_terminator,
};
#[cfg(all(unix, not(target_os = "macos")))]
use uucore::fsxattr;
use uucore::update_control;

// These are exposed for projects (e.g. nushell) that want to create an `Options` value, which
// requires these enums
pub use uucore::{backup_control::BackupMode, update_control::UpdateMode};
Expand Down Expand Up @@ -631,6 +634,10 @@ fn rename_with_fallback(
None
};

#[cfg(all(unix, not(target_os = "macos")))]
let xattrs =
fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| std::collections::HashMap::new());
Comment on lines +637 to +639
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some reason why xattrs is set here and not after the following if-block? Otherwise you could put this snippet, and the one on line 652, into one block and save one cfg (and show that they belong together).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I would have to duplicate the declaration as the xattr detection needs to happen with and without the progress bar

and you can't do at the same time because "from" gets removed/moved :)

so, to rephrase

  1. get the attributes on the file
  2. move the file
  3. set the attributes on the "new" file


let result = if let Some(ref pb) = progress_bar {
move_dir_with_progress(from, to, &options, |process_info: TransitProcess| {
pb.set_position(process_info.copied_bytes);
Expand All @@ -641,6 +648,9 @@ fn rename_with_fallback(
move_dir(from, to, &options)
};

#[cfg(all(unix, not(target_os = "macos")))]
fsxattr::apply_xattrs(to, xattrs).unwrap();

if let Err(err) = result {
return match err.kind {
fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new(
Expand All @@ -651,6 +661,11 @@ fn rename_with_fallback(
};
}
} else {
#[cfg(all(unix, not(target_os = "macos")))]
fs::copy(from, to)
.and_then(|_| fsxattr::copy_xattrs(&from, &to))
.and_then(|_| fs::remove_file(from))?;
#[cfg(any(target_os = "macos", not(unix)))]
fs::copy(from, to).and_then(|_| fs::remove_file(from))?;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/uucore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ sm3 = { workspace = true, optional = true }
[target.'cfg(unix)'.dependencies]
walkdir = { workspace = true, optional = true }
nix = { workspace = true, features = ["fs", "uio", "zerocopy", "signal"] }
xattr = { workspace = true, optional = true }

[dev-dependencies]
clap = { workspace = true }
Expand All @@ -77,6 +78,7 @@ encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
entries = ["libc"]
fs = ["dunce", "libc", "winapi-util", "windows-sys"]
fsext = ["libc", "time", "windows-sys"]
fsxattr = ["xattr"]
lines = []
format = ["itertools"]
mode = ["libc"]
Expand Down
2 changes: 2 additions & 0 deletions src/uucore/src/lib/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub mod pipes;
#[cfg(all(unix, feature = "process"))]
pub mod process;

#[cfg(all(unix, not(target_os = "macos"), feature = "fsxattr"))]
pub mod fsxattr;
#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
pub mod signals;
#[cfg(all(
Expand Down
154 changes: 154 additions & 0 deletions src/uucore/src/lib/features/fsxattr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore getxattr

//! Set of functions to manage xattr on files and dirs
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::Path;

/// Copies extended attributes (xattrs) from one file or directory to another.
///
/// # Arguments
///
/// * `source` - A reference to the source path.
/// * `dest` - A reference to the destination path.
///
/// # Returns
///
/// A result indicating success or failure.
pub fn copy_xattrs<P: AsRef<Path>>(source: P, dest: P) -> std::io::Result<()> {
for attr_name in xattr::list(&source)? {
if let Some(value) = xattr::get(&source, &attr_name)? {
xattr::set(&dest, &attr_name, &value)?;
}
}
Ok(())
}

/// Retrieves the extended attributes (xattrs) of a given file or directory.
///
/// # Arguments
///
/// * `source` - A reference to the path of the file or directory.
///
/// # Returns
///
/// A result containing a HashMap of attributes names and values, or an error.
pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<HashMap<OsString, Vec<u8>>> {
let mut attrs = HashMap::new();
for attr_name in xattr::list(&source)? {
if let Some(value) = xattr::get(&source, &attr_name)? {
attrs.insert(attr_name, value);
}
}
Ok(attrs)
}

/// Applies extended attributes (xattrs) to a given file or directory.
///
/// # Arguments
///
/// * `dest` - A reference to the path of the file or directory.
/// * `xattrs` - A HashMap containing attribute names and their corresponding values.
///
/// # Returns
///
/// A result indicating success or failure.
pub fn apply_xattrs<P: AsRef<Path>>(
dest: P,
xattrs: HashMap<OsString, Vec<u8>>,
) -> std::io::Result<()> {
for (attr, value) in xattrs {
xattr::set(&dest, &attr, &value)?;
}
Ok(())
}

/// Checks if a file has an Access Control List (ACL) based on its extended attributes.
///
/// # Arguments
///
/// * `file` - A reference to the path of the file.
///
/// # Returns
///
/// `true` if the file has extended attributes (indicating an ACL), `false` otherwise.
pub fn has_acl<P: AsRef<Path>>(file: P) -> bool {
// don't use exacl here, it is doing more getxattr call then needed
match xattr::list(file) {
Ok(acl) => {
// if we have extra attributes, we have an acl
acl.count() > 0
}
Err(_) => false,
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use tempfile::tempdir;

#[test]
fn test_copy_xattrs() {
let temp_dir = tempdir().unwrap();
let source_path = temp_dir.path().join("source.txt");
let dest_path = temp_dir.path().join("dest.txt");

File::create(&source_path).unwrap();
File::create(&dest_path).unwrap();

let test_attr = "user.test";
let test_value = b"test value";
xattr::set(&source_path, test_attr, test_value).unwrap();

copy_xattrs(&source_path, &dest_path).unwrap();

let copied_value = xattr::get(&dest_path, test_attr).unwrap().unwrap();
assert_eq!(copied_value, test_value);
}

#[test]
fn test_apply_and_retrieve_xattrs() {
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("test_file.txt");

File::create(&file_path).unwrap();

let mut test_xattrs = HashMap::new();
let test_attr = "user.test_attr";
let test_value = b"test value";
test_xattrs.insert(OsString::from(test_attr), test_value.to_vec());
apply_xattrs(&file_path, test_xattrs).unwrap();

let retrieved_xattrs = retrieve_xattrs(&file_path).unwrap();
assert!(retrieved_xattrs.contains_key(OsString::from(test_attr).as_os_str()));
assert_eq!(
retrieved_xattrs
.get(OsString::from(test_attr).as_os_str())
.unwrap(),
test_value
);
}

#[test]
fn test_file_has_acl() {
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("test_file.txt");

File::create(&file_path).unwrap();

assert!(!has_acl(&file_path));

let test_attr = "user.test_acl";
let test_value = b"test value";
xattr::set(&file_path, test_attr, test_value).unwrap();

assert!(has_acl(&file_path));
}
}
8 changes: 6 additions & 2 deletions src/uucore/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ pub use crate::features::encoding;
pub use crate::features::format;
#[cfg(feature = "fs")]
pub use crate::features::fs;
#[cfg(feature = "fsext")]
pub use crate::features::fsext;
#[cfg(feature = "lines")]
pub use crate::features::lines;
#[cfg(feature = "quoting-style")]
Expand Down Expand Up @@ -89,6 +87,12 @@ pub use crate::features::utmpx;
#[cfg(all(windows, feature = "wide"))]
pub use crate::features::wide;

#[cfg(feature = "fsext")]
pub use crate::features::fsext;

#[cfg(all(unix, not(target_os = "macos"), feature = "fsxattr"))]
pub use crate::features::fsxattr;

//## core functions

use std::ffi::OsString;
Expand Down
17 changes: 1 addition & 16 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt";
#[cfg(unix)]
static TEST_NONEXISTENT_FILE: &str = "nonexistent_file.txt";
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
use xattr;
use crate::common::util::compare_xattrs;

/// Assert that mode, ownership, and permissions of two metadata objects match.
#[cfg(all(not(windows), not(target_os = "freebsd")))]
Expand Down Expand Up @@ -3739,21 +3739,6 @@ fn test_cp_no_such() {
.stderr_is("cp: 'no-such/' is not a directory\n");
}

#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
fn compare_xattrs<P: AsRef<Path>>(path1: P, path2: P) -> bool {
let get_sorted_xattrs = |path: P| {
xattr::list(path)
.map(|attrs| {
let mut attrs = attrs.collect::<Vec<_>>();
attrs.sort();
attrs
})
.unwrap_or_else(|_| Vec::new())
};

get_sorted_xattrs(path1) == get_sorted_xattrs(path2)
}

#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
#[test]
fn test_acl_preserve() {
Expand Down
Loading