Skip to content
2 changes: 1 addition & 1 deletion src/uu/ls/src/colors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
59 changes: 49 additions & 10 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -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},
Expand Down Expand Up @@ -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<Option<Box<DirEntry>>>,
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
xattrs: OnceCell<Option<FxHashSet<OsString>>>,
security_context: OnceCell<Box<str>>,
// Name of the file - will be empty for . or ..
display_name: OsString,
Expand Down Expand Up @@ -1983,6 +1983,8 @@ impl PathData {
// nearly free compared to a metadata() call on a Path
let ft: OnceCell<Option<FileType>> = OnceCell::new();
let md: OnceCell<Option<Metadata>> = OnceCell::new();
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
let xattrs: OnceCell<Option<FxHashSet<OsString>>> = OnceCell::new();
let security_context: OnceCell<Box<str>> = OnceCell::new();

let de: RefCell<Option<Box<DirEntry>>> = if let Some(de) = dir_entry {
Expand All @@ -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,
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
95 changes: 52 additions & 43 deletions src/uucore/src/lib/features/fsxattr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -56,14 +54,27 @@ pub fn copy_xattrs_skip_selinux<P: AsRef<Path>>(source: P, dest: P) -> std::io::
/// 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<FxHashMap<OsString, Vec<u8>>> {
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);
}
}
Ok(attrs)
}

/// Retrieves the extended attributes keys only of a given file or directory.
///
/// # Arguments
///
/// * `source` - A reference to the path of the file or directory.
///
/// # Returns
///
/// A result containing a HashSet of attributes names
pub fn retrieve_xattr_list<P: AsRef<Path>>(source: P) -> std::io::Result<FxHashSet<OsString>> {
Ok(xattr::list(&source)?.collect())
}

/// Applies extended attributes (xattrs) to a given file or directory.
///
/// # Arguments
Expand Down Expand Up @@ -112,13 +123,10 @@ pub fn has_acl<P: AsRef<Path>>(file: P) -> bool {
/// `true` if the file has an extended attribute named "security.capability", `false` otherwise.
pub fn has_security_cap_acl<P: AsRef<Path>>(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
Expand All @@ -137,38 +145,39 @@ pub fn get_acl_perm_bits_from_xattr<P: AsRef<Path>>(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::<Vec<u8>>();

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::<Vec<u8>>();

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
}
Expand Down
Loading