feat(flags): Add BSD file flags

This commit is contained in:
Robert Minsk 2023-11-30 23:43:15 -08:00 committed by Christina Sørensen
parent c7950dc9bd
commit 458a6f5b1e
14 changed files with 131 additions and 2 deletions

View file

@ -200,6 +200,9 @@ These options are available when running with `--long` (`-l`):
`-n`, `--numeric`
: List numeric user and group IDs.
`-O`, `--flags`
: List file flags. See chflags(1) for a list of file flags and their meanings. (Mac and BSD only)
`-S`, `--blocksize`
: List each files size of allocated file system blocks.

View file

@ -300,6 +300,9 @@ LIST OF CODES
`Sl`
: SELinux level
`ff`
: BSD file flags
Values in `EXA_COLORS` override those given in `LS_COLORS`, so you dont need to re-write an existing `LS_COLORS` variable with proprietary extensions.

View file

@ -29,6 +29,9 @@ pub type time_t = i64;
/// The type of a files user ID.
pub type uid_t = u32;
/// The type of user file flags
pub type flag_t = u32;
/// The files base type, which gets displayed in the very first column of the
/// details output.
///
@ -274,3 +277,7 @@ impl Default for SubdirGitRepo {
}
}
}
/// The user file flags on the file. This will only ever be a number;
/// looking up the flags is done in the `display` module.
pub struct Flags(pub flag_t);

View file

@ -875,6 +875,39 @@ impl<'dir> File<'dir> {
context: SecurityContextType::None,
}
}
/// User file flags.
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
pub fn flags(&self) -> f::Flags {
#[cfg(target_os = "dragonfly")]
use std::os::dragonfly::fs::MetadataExt;
#[cfg(target_os = "freebsd")]
use std::os::freebsd::fs::MetadataExt;
#[cfg(target_os = "macos")]
use std::os::macos::fs::MetadataExt;
#[cfg(target_os = "netbsd")]
use std::os::netbsd::fs::MetadataExt;
#[cfg(target_os = "openbsd")]
use std::os::openbsd::fs::MetadataExt;
f::Flags(self.metadata.st_flags())
}
#[cfg(not(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
)))]
pub fn flags(&self) -> f::Flags {
f::Flags(0)
}
}
impl<'a> AsRef<File<'a>> for File<'a> {

View file

@ -81,6 +81,7 @@ pub static EXTENDED: Arg = Arg { short: Some(b'@'), long: "extended",
pub static OCTAL: Arg = Arg { short: Some(b'o'), long: "octal-permissions", takes_value: TakesValue::Forbidden };
pub static SECURITY_CONTEXT: Arg = Arg { short: Some(b'Z'), long: "context", takes_value: TakesValue::Forbidden };
pub static STDIN: Arg = Arg { short: None, long: "stdin", takes_value: TakesValue::Forbidden };
pub static FILE_FLAGS: Arg = Arg { short: Some(b'O'), long: "flags", takes_value: TakesValue::Forbidden };
pub static ALL_ARGS: Args = Args(&[
&VERSION, &HELP,
@ -97,5 +98,5 @@ pub static ALL_ARGS: Args = Args(&[
&NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &SMART_GROUP,
&GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
&EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN,
&EXTENDED, &OCTAL, &SECURITY_CONTEXT, &STDIN, &FILE_FLAGS
]);

View file

@ -59,8 +59,9 @@ LONG VIEW OPTIONS
-H, --links list each file's number of hard links
-i, --inode list each file's inode number
-m, --modified use the modified timestamp field
-M, --mounts show mount details (Linux and MacOS only)
-M, --mounts show mount details (Linux and Mac only)
-n, --numeric list numeric user and group IDs
-O, --flags list file flags (Mac and BSD only)
-S, --blocksize show size of allocated file system blocks
-t, --time FIELD which timestamp field to list (modified, accessed, created)
-u, --accessed use the accessed timestamp field

View file

@ -270,6 +270,7 @@ impl Columns {
let links = matches.has(&flags::LINKS)?;
let octal = matches.has(&flags::OCTAL)?;
let security_context = xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?;
let file_flags = matches.has(&flags::FILE_FLAGS)?;
let permissions = !matches.has(&flags::NO_PERMISSIONS)?;
let filesize = !matches.has(&flags::NO_FILESIZE)?;
@ -286,6 +287,7 @@ impl Columns {
subdir_git_repos_no_stat,
octal,
security_context,
file_flags,
permissions,
filesize,
user,

View file

@ -0,0 +1,10 @@
use ansiterm::Style;
use crate::fs::fields as f;
use crate::output::cell::TextCell;
impl f::Flags {
pub fn render(self, style: Style) -> TextCell {
TextCell::paint(style, "-".to_string())
}
}

View file

@ -0,0 +1,39 @@
use ansiterm::Style;
use std::ffi::CStr;
use crate::fs::fields as f;
use crate::output::cell::TextCell;
extern "C" {
fn fflagstostr(flags: libc::c_ulong) -> *const libc::c_char;
}
/// Wrapper around the C library call fflagstostr. If returned string is NULL
/// or empty a "-" is returned
fn flags_to_string(flags: f::flag_t) -> String {
// SAFETY: Calling external "C" function
let flags_c_str = unsafe { fflagstostr(libc::c_ulong::from(flags)) };
if flags_c_str.is_null() {
"-".to_string()
} else {
let flags_str = unsafe { CStr::from_ptr(flags_c_str) };
let flags = flags_str
.to_str()
.map_or("-", |s| if s.is_empty() { "-" } else { s })
.to_string();
// SAFETY: Calling external "C" function to free memory allocated by fflagstostr
unsafe {
libc::free(flags_c_str.cast_mut().cast());
}
flags
}
}
impl f::Flags {
pub fn render(self, style: Style) -> TextCell {
TextCell::paint(style, flags_to_string(self.0))
}
}

View file

@ -45,3 +45,21 @@ pub use self::octal::Render as OctalPermissionsRender;
mod securityctx;
pub use self::securityctx::Colours as SecurityCtxColours;
#[cfg(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
))]
mod flags_bsd;
#[cfg(not(any(
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly"
)))]
mod flags;

View file

@ -49,6 +49,7 @@ pub struct Columns {
pub subdir_git_repos_no_stat: bool,
pub octal: bool,
pub security_context: bool,
pub file_flags: bool,
// Defaults to true:
pub permissions: bool,
@ -98,6 +99,10 @@ impl Columns {
columns.push(Column::Group);
}
if self.file_flags {
columns.push(Column::FileFlags);
}
#[cfg(target_os = "linux")]
if self.security_context {
columns.push(Column::SecurityContext);
@ -157,6 +162,7 @@ pub enum Column {
Octal,
#[cfg(unix)]
SecurityContext,
FileFlags,
}
/// Each column can pick its own **Alignment**. Usually, numbers are
@ -214,6 +220,7 @@ impl Column {
Self::Octal => "Octal",
#[cfg(unix)]
Self::SecurityContext => "Security Context",
Self::FileFlags => "Flags",
}
}
}
@ -512,6 +519,7 @@ impl<'a> Table<'a> {
),
#[cfg(unix)]
Column::SecurityContext => file.security_context().render(self.theme),
Column::FileFlags => file.flags().render(self.theme.ui.flags),
Column::GitStatus => self.git_status(file).render(self.theme),
Column::SubdirGitRepo(status) => self.subdir_git_repo(file, status).render(self.theme),
#[cfg(unix)]

View file

@ -113,6 +113,7 @@ impl UiStyles {
inode: Purple.normal(),
blocks: Cyan.normal(),
octal: Purple.normal(),
flags: Style::default(),
header: Style::default().underline(),
symlink_path: Cyan.normal(),

View file

@ -583,6 +583,7 @@ mod customs_test {
test!(exa_lp: ls "", exa "lp=38;5;133" => colours c -> { c.symlink_path = Fixed(133).normal(); });
test!(exa_cc: ls "", exa "cc=38;5;134" => colours c -> { c.control_char = Fixed(134).normal(); });
test!(exa_oc: ls "", exa "oc=38;5;135" => colours c -> { c.octal = Fixed(135).normal(); });
test!(exa_ff: ls "", exa "ff=38;5;136" => colours c -> { c.flags = Fixed(136).normal(); });
test!(exa_bo: ls "", exa "bO=4" => colours c -> { c.broken_path_overlay = Style::default().underline(); });
test!(exa_mp: ls "", exa "mp=1;34;4" => colours c -> { c.filekinds.mount_point = Blue.bold().underline(); });

View file

@ -23,6 +23,7 @@ pub struct UiStyles {
pub blocks: Style, // bl
pub header: Style, // hd
pub octal: Style, // oc
pub flags: Style, // ff
pub symlink_path: Style, // lp
pub control_char: Style, // cc
@ -253,6 +254,7 @@ impl UiStyles {
"bl" => self.blocks = pair.to_style(),
"hd" => self.header = pair.to_style(),
"oc" => self.octal = pair.to_style(),
"ff" => self.flags = pair.to_style(),
"lp" => self.symlink_path = pair.to_style(),
"cc" => self.control_char = pair.to_style(),
"bO" => self.broken_path_overlay = pair.to_style(),