diff --git a/src/uu/ls/src/colors.rs b/src/uu/ls/src/colors.rs index a50418cd98..7cd9a22bf4 100644 --- a/src/uu/ls/src/colors.rs +++ b/src/uu/ls/src/colors.rs @@ -530,7 +530,7 @@ pub(crate) fn color_name( let has_capabilities = style_manager .colors .has_explicit_style_for(Indicator::Capabilities) - && uucore::fsxattr::has_security_cap_acl(&path.p_buf); + && uucore::fsxattr::has_security_cap_acl_with_deref(&path.p_buf, path.must_dereference); // If the file has capabilities, use a specific style for `ca` (capabilities) if has_capabilities { diff --git a/src/uu/ls/src/display.rs b/src/uu/ls/src/display.rs index b875462435..da0af2190f 100644 --- a/src/uu/ls/src/display.rs +++ b/src/uu/ls/src/display.rs @@ -34,7 +34,7 @@ use term_grid::{DEFAULT_SEPARATOR_SIZE, Direction, Filling, Grid, GridOptions}; #[cfg(unix)] use uucore::entries; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] -use uucore::fsxattr::has_acl; +use uucore::fsxattr::has_acl_with_deref; #[cfg(unix)] use uucore::libc::{dev_t, major, minor}; use uucore::{ @@ -934,7 +934,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 = has_acl(item.path()); + let is_acl_set = has_acl_with_deref(item.path(), item.must_dereference); state .display_buf .extend(display_permissions(md, true).as_bytes()); @@ -1339,7 +1339,7 @@ fn calculate_padding_collection( // 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 = has_acl(item.path()); + let is_acl_set = has_acl_with_deref(item.path(), item.must_dereference); if context_len > 1 || is_acl_set { padding_collections.permissions = PERMISSIONS_WIDTH + 1; } diff --git a/src/uucore/src/lib/features/fsxattr.rs b/src/uucore/src/lib/features/fsxattr.rs index 23b3684f9a..deb959bffb 100644 --- a/src/uucore/src/lib/features/fsxattr.rs +++ b/src/uucore/src/lib/features/fsxattr.rs @@ -156,30 +156,49 @@ pub fn apply_xattrs>( /// # Arguments /// /// * `file` - A reference to the path of the file. +/// * `dereference` - Whether to inspect the target of a symlink instead of the link itself. /// /// # Returns /// /// `true` if the file has extended attributes (indicating an ACL), `false` otherwise. -pub fn has_acl>(file: P) -> bool { +pub fn has_acl_with_deref>(file: P, dereference: bool) -> bool { // don't use exacl here, it is doing more getxattr call then needed - xattr::list_deref(file).is_ok_and(|acl| { + let attrs = if dereference { + xattr::list_deref(file) + } else { + xattr::list(file) + }; + + attrs.is_ok_and(|acl| { // if we have extra attributes, we have an acl acl.count() > 0 }) } +/// Checks if a file has an Access Control List (ACL) based on its extended attributes. +pub fn has_acl>(file: P) -> bool { + has_acl_with_deref(file, true) +} + /// Checks if a file has an Access Control List (ACL) named "security.capability" based on its extended attributes. /// /// # Arguments /// /// * `file` - A reference to the path of the file. +/// * `dereference` - Whether to inspect the target of a symlink instead of the link itself. /// /// # Returns /// /// `true` if the file has an extended attribute named "security.capability", `false` otherwise. -pub fn has_security_cap_acl>(file: P) -> bool { +pub fn has_security_cap_acl_with_deref>(file: P, dereference: bool) -> bool { // don't use exacl here, it is doing more getxattr call then needed - xattr::list_deref(file).is_ok_and(|mut acl| { + let attrs = if dereference { + xattr::list_deref(file) + } else { + xattr::list(file) + }; + + attrs.is_ok_and(|mut acl| { #[cfg(unix)] return acl.contains(OsStr::from_bytes(b"security.capability")); @@ -188,6 +207,11 @@ pub fn has_security_cap_acl>(file: P) -> bool { }) } +/// Checks if a file has an Access Control List (ACL) named "security.capability" based on its extended attributes. +pub fn has_security_cap_acl>(file: P) -> bool { + has_security_cap_acl_with_deref(file, true) +} + /// Returns the permissions bits of a file or directory which has Access Control List (ACL) entries based on its /// extended attributes (Only works for linux) /// diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 98001ef16b..0d3f230a09 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -6985,6 +6985,44 @@ fn test_acl_display_symlink() { assert!(iter.all(|i| i == first)); } +#[cfg(all(unix, not(target_os = "macos")))] +#[test] +fn test_acl_display_symlink_without_dereference() { + use std::process::Command; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let dir_name = "dir"; + let link_name = "link"; + at.mkdir(dir_name); + + match Command::new("setfacl") + .args(["-d", "-m", "u:bin:rwx", &at.plus_as_string(dir_name)]) + .status() + .map(|status| status.code()) + { + Ok(Some(0)) => {} + Ok(_) => { + println!("test skipped: setfacl failed"); + return; + } + Err(e) => { + println!("test skipped: setfacl failed with {e}"); + return; + } + } + + at.symlink_dir(dir_name, link_name); + + scene + .ucmd() + .arg("-ld") + .arg(link_name) + .succeeds() + .stdout_does_not_contain("+"); +} + #[test] fn ls_emoji_alignment() { let scene = TestScenario::new(util_name!());