Skip to content
Open
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: 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
65 changes: 55 additions & 10 deletions src/uu/ls/src/ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
#[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;
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
use std::sync::Once;
use std::{borrow::Cow, cell::RefCell};
use std::{
cell::{LazyCell, OnceCell},
cmp::Reverse,
Expand Down Expand Up @@ -42,7 +43,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 +64,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 @@ -1939,6 +1939,10 @@ struct PathData {
p_buf: PathBuf,
must_dereference: bool,
command_line: bool,
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
has_acl: OnceCell<bool>,
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
has_security_cap: OnceCell<bool>,
}

impl PathData {
Expand Down Expand Up @@ -2011,6 +2015,10 @@ impl PathData {
p_buf,
must_dereference,
command_line,
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
has_acl: OnceCell::new(),
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
has_security_cap: OnceCell::new(),
}
}

Expand Down Expand Up @@ -2051,6 +2059,43 @@ impl PathData {
.as_ref()
}

#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
pub fn has_acl(&self) -> bool {
self.init_xattrs_once();

self.has_acl.get().is_some_and(|inner| *inner)
}

#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
pub fn has_security_cap(&self) -> bool {
self.init_xattrs_once();

self.has_security_cap.get().is_some_and(|inner| *inner)
}

#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
pub fn init_xattrs_once(&self) {
static START: Once = Once::new();

START.call_once(|| {
let _ = retrieve_xattr_list(self.path()).map(|inner| {
self.has_security_cap.get_or_init(|| {
let cap_key = OsStr::new("security.capability");

!self.file_type().is_some_and(FileType::is_symlink) && inner.contains(cap_key)
});

self.has_acl.get_or_init(|| {
!self.file_type().is_some_and(FileType::is_symlink)
&& inner
.iter()
.filter_map(|key| key.to_str().map(str::to_lowercase))
.any(|xattr| xattr.contains("acl"))
});
});
});
}

fn file_type(&self) -> Option<&FileType> {
self.ft
.get_or_init(|| self.metadata().map(Metadata::file_type))
Expand Down Expand Up @@ -3018,7 +3063,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 +3782,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