diff --git a/src/uu/ls/src/colors.rs b/src/uu/ls/src/colors.rs index 197fd2c8cec..3f8f56b3e55 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.as_path()); + && path.has_security_cap(); // If the file has capabilities, use a specific style for `ca` (capabilities) if has_capabilities { diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 507689eee41..05a25815aa4 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -9,12 +9,11 @@ #[cfg(unix)] use rustc_hash::FxHashMap; use rustc_hash::FxHashSet; -use std::borrow::Cow; -use std::cell::RefCell; #[cfg(unix)] use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; +use std::{borrow::Cow, cell::RefCell}; use std::{ cell::{LazyCell, OnceCell}, cmp::Reverse, @@ -42,7 +41,7 @@ use thiserror::Error; #[cfg(unix)] use uucore::entries; #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] -use uucore::fsxattr::has_acl; +use uucore::fsxattr::retrieve_xattr_list; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; #[cfg(any( @@ -63,14 +62,13 @@ use uucore::{ error::{UError, UResult, set_exit_code}, format::human::{SizeFormat, human_readable}, format_usage, - fs::FileInformation, - fs::display_permissions, + fs::{FileInformation, display_permissions}, fsext::{MetadataTimeField, metadata_get_time}, line_ending::LineEnding, os_str_as_bytes_lossy, - parser::parse_glob, - parser::parse_size::parse_size_non_zero_u64, - parser::shortcut_value_parser::ShortcutValueParser, + parser::{ + parse_glob, parse_size::parse_size_non_zero_u64, shortcut_value_parser::ShortcutValueParser, + }, quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name}, show, show_error, show_warning, time::{FormatSystemTimeFallback, format, format_system_time}, @@ -1932,6 +1930,8 @@ struct PathData { // can be used to avoid reading the filetype. Can be also called d_type: // https://www.gnu.org/software/libc/manual/html_node/Directory-Entries.html de: RefCell>>, + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + xattrs: OnceCell>>, security_context: OnceCell>, // Name of the file - will be empty for . or .. display_name: OsString, @@ -1983,6 +1983,8 @@ impl PathData { // nearly free compared to a metadata() call on a Path let ft: OnceCell> = OnceCell::new(); let md: OnceCell> = OnceCell::new(); + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + let xattrs: OnceCell>> = OnceCell::new(); let security_context: OnceCell> = OnceCell::new(); let de: RefCell>> = if let Some(de) = dir_entry { @@ -2006,6 +2008,8 @@ impl PathData { md, ft, de, + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + xattrs, security_context, display_name, p_buf, @@ -2051,6 +2055,41 @@ impl PathData { .as_ref() } + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + pub fn has_acl(&self) -> bool { + if self.file_type().is_some_and(FileType::is_symlink) { + return false; + } + + let opt_xattrs = self + .xattrs + .get_or_init(|| retrieve_xattr_list(self.path()).ok()); + + opt_xattrs.as_ref().is_some_and(|inner| { + inner + .iter() + .filter_map(|key| key.to_str().map(str::to_lowercase)) + .any(|xattr| xattr.contains("acl")) + }) + } + + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + pub fn has_security_cap(&self) -> bool { + if self.file_type().is_some_and(FileType::is_symlink) { + return false; + } + + let cap_key = OsStr::new("security.capability"); + + let xattrs = self + .xattrs + .get_or_init(|| retrieve_xattr_list(self.path()).ok()); + + xattrs + .as_ref() + .is_some_and(|inner| inner.get(cap_key).is_some()) + } + fn file_type(&self) -> Option<&FileType> { self.ft .get_or_init(|| self.metadata().map(Metadata::file_type)) @@ -3018,7 +3057,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 = item.has_acl(); output_display.extend(display_permissions(md, true).as_bytes()); if item.security_context(config).len() > 1 { // GNU `ls` uses a "." character to indicate a file with a security context, @@ -3737,7 +3776,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.display_name()); + let is_acl_set = item.has_acl(); if context_len > 1 || is_acl_set { padding_collections.link_count += 1; } diff --git a/src/uucore/src/lib/features/fsxattr.rs b/src/uucore/src/lib/features/fsxattr.rs index d9683264652..023bde0439b 100644 --- a/src/uucore/src/lib/features/fsxattr.rs +++ b/src/uucore/src/lib/features/fsxattr.rs @@ -6,11 +6,9 @@ // spell-checker:ignore getxattr posix_acl_default //! Set of functions to manage xattr on files and dirs -use itertools::Itertools; -use rustc_hash::FxHashMap; -use std::ffi::{OsStr, OsString}; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::ffi::OsString; #[cfg(unix)] -use std::os::unix::ffi::OsStrExt; use std::path::Path; /// Copies extended attributes (xattrs) from one file or directory to another. @@ -56,7 +54,7 @@ pub fn copy_xattrs_skip_selinux>(source: P, dest: P) -> std::io:: /// A result containing a HashMap of attributes names and values, or an error. pub fn retrieve_xattrs>(source: P) -> std::io::Result>> { let mut attrs = FxHashMap::default(); - for attr_name in xattr::list(&source)? { + for attr_name in retrieve_xattr_list(&source)? { if let Some(value) = xattr::get(&source, &attr_name)? { attrs.insert(attr_name, value); } @@ -64,6 +62,19 @@ pub fn retrieve_xattrs>(source: P) -> std::io::Result>(source: P) -> std::io::Result> { + Ok(xattr::list(&source)?.collect()) +} + /// Applies extended attributes (xattrs) to a given file or directory. /// /// # Arguments @@ -112,13 +123,10 @@ pub fn has_acl>(file: P) -> bool { /// `true` if the file has an extended attribute named "security.capability", `false` otherwise. pub fn has_security_cap_acl>(file: P) -> bool { // don't use exacl here, it is doing more getxattr call then needed - xattr::list_deref(file).is_ok_and(|mut acl| { - #[cfg(unix)] - return acl.contains(OsStr::from_bytes(b"security.capability")); - - #[cfg(not(unix))] - return false; - }) + xattr::get_deref(file, "security.capability") + .ok() + .flatten() + .is_some() } /// Returns the permissions bits of a file or directory which has Access Control List (ACL) entries based on its @@ -137,38 +145,39 @@ pub fn get_acl_perm_bits_from_xattr>(source: P) -> u32 { // Only default acl entries get inherited by objects under the path i.e. if child directories // will have their permissions modified. - if let Ok(entries) = retrieve_xattrs(source) { - let mut perm: u32 = 0; - if let Some(value) = entries.get(&OsString::from("system.posix_acl_default")) { - // value is xattr byte vector - // value follows a starts with a 4 byte header, and then has posix_acl_entries, each - // posix_acl_entry is separated by a u32 sequence i.e. 0xFFFFFFFF - // - // struct posix_acl_entries { - // e_tag: u16 - // e_perm: u16 - // e_id: u32 - // } - // - // Reference: `https://github.com/torvalds/linux/blob/master/include/uapi/linux/posix_acl_xattr.h` - // - // The value of the header is 0x0002, so we skip the first four bytes of the value and - // process the rest - - let acl_entries = value - .split_at(3) - .1 - .iter() - .filter(|&x| *x != 255) - .copied() - .collect::>(); - - for entry in acl_entries.chunks_exact(4) { - // Third byte and fourth byte will be the perm bits - perm = (perm << 3) | u32::from(entry[2]) | u32::from(entry[3]); - } - return perm; + let mut perm: u32 = 0; + if let Some(value) = xattr::get(source, "system.posix_acl_default") + .ok() + .flatten() + { + // value is xattr byte vector + // value follows a starts with a 4 byte header, and then has posix_acl_entries, each + // posix_acl_entry is separated by a u32 sequence i.e. 0xFFFFFFFF + // + // struct posix_acl_entries { + // e_tag: u16 + // e_perm: u16 + // e_id: u32 + // } + // + // Reference: `https://github.com/torvalds/linux/blob/master/include/uapi/linux/posix_acl_xattr.h` + // + // The value of the header is 0x0002, so we skip the first four bytes of the value and + // process the rest + + let acl_entries = value + .split_at(3) + .1 + .iter() + .filter(|&x| *x != 255) + .copied() + .collect::>(); + + for entry in acl_entries.chunks_exact(4) { + // Third byte and fourth byte will be the perm bits + perm = (perm << 3) | u32::from(entry[2]) | u32::from(entry[3]); } + return perm; } 0 }