This commit is contained in:
Horror Proton 2025-07-06 09:59:58 +02:00 committed by GitHub
commit 138bf13d41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 57 additions and 1 deletions

View file

@ -410,6 +410,46 @@ impl<'dir> File<'dir> {
.is_some_and(|p| all_mounts().contains_key(p))
}
/// Whether the directory represents a Btrfs subvolume
#[cfg(target_os = "linux")]
pub fn is_btrfs_subvolume(&self) -> bool {
// Listing all subvolumes with ioctl(BTRFS_IOC_TREE_SEARCH) requires CAP_SYS_ADMIN, normal users can't do that.
// So we test the inode number, directory representing a subvolume has always inode number 256
const BTRFS_FIRST_FREE_OBJECTID: u64 = 256;
self.is_directory()
&& self.inode().0 == BTRFS_FIRST_FREE_OBJECTID
&& self.is_btrfs().unwrap_or(false)
}
#[cfg(not(target_os = "linux"))]
pub fn is_btrfs_subvolume(&self) -> bool {
false
}
#[cfg(target_os = "linux")]
fn is_btrfs(&self) -> io::Result<bool> {
use std::os::unix::ffi::OsStrExt;
const BTRFS_FSTYPE_NAME: &str = "btrfs";
for part in self.absolute_path().unwrap_or(&self.path).ancestors() {
if let Some(mount) = all_mounts().get(part) {
return Ok(mount.fstype == BTRFS_FSTYPE_NAME);
}
}
// /proc/mounts not working? fallback to statfs
let file_path = &self.path;
let mut out = std::mem::MaybeUninit::<libc::statfs>::uninit();
let path = std::ffi::CString::new(file_path.as_os_str().as_bytes()).unwrap();
match unsafe { libc::statfs(path.as_ptr(), out.as_mut_ptr()) } {
0 => Ok(unsafe { out.assume_init() }.f_type == libc::BTRFS_SUPER_MAGIC),
_ => Err(io::Error::last_os_error()),
// eprintln!("eza: statfs {:?}: {}", self.path, os_err);
}
}
/// The filesystem device and type for a mount point
pub fn mount_point_info(&self) -> Option<&MountedFs> {
if cfg!(any(target_os = "linux", target_os = "macos")) {

View file

@ -273,6 +273,7 @@ pub struct FileKindsOverride {
pub special: Option<StyleOverride>, // sp
pub executable: Option<StyleOverride>, // ex
pub mount_point: Option<StyleOverride>, // mp
pub btrfs_subvol: Option<StyleOverride>, // sv
}
impl FromOverride<FileKindsOverride> for FileKinds {
@ -288,6 +289,7 @@ impl FromOverride<FileKindsOverride> for FileKinds {
special: FromOverride::from(value.special, default.special),
executable: FromOverride::from(value.executable, default.executable),
mount_point: FromOverride::from(value.mount_point, default.mount_point),
btrfs_subvol: FromOverride::from(value.btrfs_subvol, default.btrfs_subvol),
}
}
}

View file

@ -477,6 +477,7 @@ impl<C: Colours> FileName<'_, '_, C> {
#[rustfmt::skip]
return match self.file {
f if f.is_mount_point() => self.colours.mount_point(),
f if f.is_btrfs_subvolume() => self.colours.btrfs_subvol(),
f if f.is_directory() => self.colours.directory(),
#[cfg(unix)]
f if f.is_executable_file() => self.colours.executable_file(),
@ -531,6 +532,9 @@ pub trait Colours: FiletypeColours {
/// The style to paint a directory that has a filesystem mounted on it.
fn mount_point(&self) -> Style;
/// The style to paint a directory representing a Btrfs subvolume.
fn btrfs_subvol(&self) -> Style;
fn colour_file(&self, file: &File<'_>) -> Style;
fn style_override(&self, file: &File<'_>) -> Option<FileNameStyle>;

View file

@ -40,6 +40,7 @@ impl Default for UiStyles {
special: Some(Yellow.normal()),
executable: Some(Green.bold()),
mount_point: Some(Blue.bold().underline()),
btrfs_subvol: Some(Blue.underline()),
}),
#[rustfmt::skip]

View file

@ -471,6 +471,9 @@ impl FileNameColours for Theme {
fn broken_control_char(&self) -> Style { apply_overlay(self.ui.control_char(), self.ui.broken_path_overlay()) }
fn executable_file(&self) -> Style { self.ui.filekinds.unwrap_or_default().executable() }
fn mount_point(&self) -> Style { self.ui.filekinds.unwrap_or_default().mount_point() }
fn btrfs_subvol(&self) -> Style {
self.ui.filekinds.unwrap_or_default().btrfs_subvol()
}
fn colour_file(&self, file: &File<'_>) -> Style {
self.exts
@ -728,6 +731,7 @@ mod customs_test {
test!(exa_bo: ls "", exa "bO=4" => colours c -> { c.broken_path_overlay = Some(Style::default().underline()); });
test!(exa_mp: ls "", exa "mp=1;34;4" => colours c -> { c.filekinds().mount_point = Some(Blue.bold().underline()); });
test!(exa_sv: ls "", exa "sv=0;34;4" => colours c -> { c.filekinds().btrfs_subvol = Some(Blue.underline()); });
test!(exa_sp: ls "", exa "sp=1;35;4" => colours c -> { c.filekinds().special = Some(Purple.bold().underline()); });
test!(exa_im: ls "", exa "im=38;5;128" => colours c -> { c.file_type().image = Some(Fixed(128).normal()); });

View file

@ -128,6 +128,7 @@ pub struct FileKinds {
pub special: Option<Style>, // sp
pub executable: Option<Style>, // ex
pub mount_point: Option<Style>, // mp
pub btrfs_subvol: Option<Style>, // sv
}
impl Default for FileKinds {
@ -143,6 +144,7 @@ impl Default for FileKinds {
special: Some(Yellow.normal()),
executable: Some(Green.bold()),
mount_point: Some(Blue.bold().underline()),
btrfs_subvol: Some(Blue.underline()),
}
}
}
@ -157,7 +159,8 @@ field_accessors!(
socket: Option<Style>,
special: Option<Style>,
executable: Option<Style>,
mount_point: Option<Style>
mount_point: Option<Style>,
btrfs_subvol: Option<Style>
);
#[rustfmt::skip]
@ -402,6 +405,7 @@ impl UiStyles {
special: Some(Style::default()),
executable: Some(Style::default()),
mount_point: Some(Style::default()),
btrfs_subvol: Some(Style::default()),
}),
#[rustfmt::skip]
@ -601,6 +605,7 @@ impl UiStyles {
"bO" => self.broken_path_overlay = Some(pair.to_style()),
"mp" => self.filekinds().mount_point = Some(pair.to_style()),
"sv" => self.filekinds().btrfs_subvol = Some(pair.to_style()),
"sp" => self.filekinds().special = Some(pair.to_style()), // Catch-all for unrecognized file kind
"im" => self.file_type().image = Some(pair.to_style()),