From 55df1effb3820f8a8d58b0f16e9d20d6795b5772 Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Sat, 21 Aug 2021 15:19:03 +0100 Subject: [PATCH 1/8] Underline mount points --- Cargo.lock | 117 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/fs/file.rs | 14 +++++ src/output/file_name.rs | 4 ++ src/theme/default_theme.rs | 1 + src/theme/mod.rs | 1 + src/theme/ui_styles.rs | 1 + 7 files changed, 140 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index f62bbc9a0..e0777464b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "err-derive" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn", + "synstructure", +] + [[package]] name = "exa" version = "0.10.1" @@ -72,6 +86,7 @@ dependencies = [ "natord", "num_cpus", "number_prefix", + "proc-mounts", "scoped_threadpool", "term_grid", "terminal_size", @@ -253,6 +268,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "partition-identity" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec13ba9a0eec5c10a89f6ec1b6e9e2ef7d29b810d771355abbd1c43cae003ed6" +dependencies = [ + "err-derive", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -265,18 +289,99 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "proc-mounts" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad7e9c8d1b8c20f16a84d61d7c4c0325a5837c1307a2491b509cd92fb4e4442" +dependencies = [ + "lazy_static", + "partition-identity", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + [[package]] name = "scoped_threadpool" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" +[[package]] +name = "syn" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "term_grid" version = "0.1.7" @@ -335,6 +440,12 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + [[package]] name = "url" version = "2.2.1" @@ -363,6 +474,12 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index abb484de1..5c6d1be76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ default = [ "git" ] git = [ "git2" ] vendored-openssl = ["git2/vendored-openssl"] +[target.'cfg(unix)'.dependencies] +proc-mounts = "0.2" # make dev builds faster by excluding debug symbols [profile.dev] diff --git a/src/fs/file.rs b/src/fs/file.rs index ea83f08b1..00bbda8d2 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -7,6 +7,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use log::*; +use proc_mounts::MOUNTS; + use crate::fs::dir::Dir; use crate::fs::fields as f; @@ -204,6 +206,18 @@ impl<'dir> File<'dir> { self.metadata.file_type().is_socket() } + // Whether this file is a mount point + pub fn is_mount_point(&self) -> bool { + if self.is_directory() { + let mounts = &MOUNTS.read().unwrap().0; + for mount in mounts { + if self.path.eq(&mount.dest) { + return true; + } + } + } + return false; + } /// Re-prefixes the path pointed to by this file, if it’s a symlink, to /// make it an absolute path that can be accessed from whichever diff --git a/src/output/file_name.rs b/src/output/file_name.rs index b6a38c0ee..3ffb8bb44 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -300,6 +300,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { } match self.file { + f if f.is_mount_point() => self.colours.mount_point(), f if f.is_directory() => self.colours.directory(), f if f.is_executable_file() => self.colours.executable_file(), f if f.is_link() => self.colours.symlink(), @@ -342,6 +343,9 @@ pub trait Colours: FiletypeColours { /// The style to paint a file that has its executable bit set. fn executable_file(&self) -> Style; + /// The style to paint a directory that has a filesystem mounted on it. + fn mount_point(&self) -> Style; + fn colour_file(&self, file: &File<'_>) -> Style; } diff --git a/src/theme/default_theme.rs b/src/theme/default_theme.rs index b4269b73b..953d18ae1 100644 --- a/src/theme/default_theme.rs +++ b/src/theme/default_theme.rs @@ -20,6 +20,7 @@ impl UiStyles { socket: Red.bold(), special: Yellow.normal(), executable: Green.bold(), + mount_point: Blue.bold().underline(), }, perms: Permissions { diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 255f054d0..66966164f 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -300,6 +300,7 @@ impl FileNameColours for Theme { fn control_char(&self) -> Style { self.ui.control_char } fn symlink_path(&self) -> Style { self.ui.symlink_path } fn executable_file(&self) -> Style { self.ui.filekinds.executable } + fn mount_point(&self) -> Style { self.ui.filekinds.mount_point } fn colour_file(&self, file: &File<'_>) -> Style { self.exts.colour_file(file).unwrap_or(self.ui.filekinds.normal) diff --git a/src/theme/ui_styles.rs b/src/theme/ui_styles.rs index f92c5442c..c85f607cd 100644 --- a/src/theme/ui_styles.rs +++ b/src/theme/ui_styles.rs @@ -38,6 +38,7 @@ pub struct FileKinds { pub socket: Style, pub special: Style, pub executable: Style, + pub mount_point: Style, } #[derive(Clone, Copy, Debug, Default, PartialEq)] From 494e32c5a15859fd538406e52f506411ebece976 Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Sat, 21 Aug 2021 15:30:19 +0100 Subject: [PATCH 2/8] Always return false on non-*nix systems --- src/fs/file.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index 00bbda8d2..462af8921 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -208,11 +208,13 @@ impl<'dir> File<'dir> { // Whether this file is a mount point pub fn is_mount_point(&self) -> bool { - if self.is_directory() { - let mounts = &MOUNTS.read().unwrap().0; - for mount in mounts { - if self.path.eq(&mount.dest) { - return true; + if cfg!(unix) { + if self.is_directory() { + let mounts = &MOUNTS.read().unwrap().0; + for mount in mounts { + if self.path.eq(&mount.dest) { + return true; + } } } } From c6bfe17e79416632b2ae06baae16f26ba82f17ad Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Sat, 21 Aug 2021 17:00:15 +0100 Subject: [PATCH 3/8] Show mounted fs and type in list view --- src/fs/file.rs | 24 +++++++++++++++++++++++ src/fs/mod.rs | 2 +- src/output/details.rs | 1 + src/output/file_name.rs | 42 +++++++++++++++++++++++++++++++++++++++-- src/output/lines.rs | 1 + 5 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index 462af8921..4da50c20c 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -221,6 +221,23 @@ impl<'dir> File<'dir> { return false; } + // The filesystem device and type for a mount point + pub fn mount_point_info(&self) -> Option { + if self.is_mount_point() { + let mounts = &MOUNTS.read().unwrap().0; + for mount_point in mounts { + if self.path.eq(&mount_point.dest) { + return Some(MountedFs { + dest: mount_point.dest.to_string_lossy().into_owned(), + fstype: mount_point.fstype.clone(), + source: mount_point.source.to_string_lossy().into_owned(), + }) + } + } + } + None + } + /// Re-prefixes the path pointed to by this file, if it’s a symlink, to /// make it an absolute path that can be accessed from whichever /// directory exa is being run from. @@ -495,6 +512,13 @@ impl<'dir> FileTarget<'dir> { } } +/// Details of a mounted filesystem. +pub struct MountedFs { + pub dest: String, + pub fstype: String, + pub source: String, +} + /// More readable aliases for the permission bits exposed by libc. #[allow(trivial_numeric_casts)] diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 1188f615a..fcbe39f8d 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -2,7 +2,7 @@ mod dir; pub use self::dir::{Dir, DotFilter}; mod file; -pub use self::file::{File, FileTarget}; +pub use self::file::{File, FileTarget, MountedFs}; pub mod dir_action; pub mod feature; diff --git a/src/output/details.rs b/src/output/details.rs index 9dca7d408..5e703cd78 100644 --- a/src/output/details.rs +++ b/src/output/details.rs @@ -284,6 +284,7 @@ impl<'a> Render<'a> { let file_name = self.file_style.for_file(egg.file, self.theme) .with_link_paths() + .with_mount_details() .paint() .promote(); diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 3ffb8bb44..b54214d5b 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -3,7 +3,7 @@ use std::path::Path; use ansi_term::{ANSIString, Style}; -use crate::fs::{File, FileTarget}; +use crate::fs::{File, FileTarget, MountedFs}; use crate::output::cell::TextCellContents; use crate::output::escape; use crate::output::icons::{icon_for_file, iconify_style}; @@ -32,7 +32,9 @@ impl Options { link_style: LinkStyle::JustFilenames, options: self, target: if file.is_link() { Some(file.link_target()) } - else { None } + else { None }, + mount_style: MountStyle::JustDirectoryNames, + mounted_fs: file.mount_point_info(), } } } @@ -52,6 +54,19 @@ enum LinkStyle { FullLinkPaths, } +/// When displaying a directory name, there needs to be some way to handle +/// mount details, depending on how long the resulting Cell can be. +#[derive(PartialEq, Debug, Copy, Clone)] +enum MountStyle { + + /// Just display the directory names. + JustDirectoryNames, + + /// Display mount points as directories and include information about + /// the filesystem that's mounted there. + MountInfo, +} + /// Whether to append file class characters to the file names. #[derive(PartialEq, Debug, Copy, Clone)] @@ -102,6 +117,12 @@ pub struct FileName<'a, 'dir, C> { link_style: LinkStyle, options: Options, + + /// The filesystem details for a mounted filesystem. + mounted_fs: Option, + + /// How to handle displaying a mounted filesystem. + mount_style: MountStyle, } impl<'a, 'dir, C> FileName<'a, 'dir, C> { @@ -112,6 +133,13 @@ impl<'a, 'dir, C> FileName<'a, 'dir, C> { self.link_style = LinkStyle::FullLinkPaths; self } + + /// Sets the flag on this file name to display mounted filesystem + ///details. + pub fn with_mount_details(mut self) -> Self { + self.mount_style = MountStyle::MountInfo; + self + } } impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { @@ -179,6 +207,8 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { target: None, link_style: LinkStyle::FullLinkPaths, options: target_options, + mounted_fs: None, + mount_style: MountStyle::JustDirectoryNames, }; for bit in target_name.coloured_file_name() { @@ -216,6 +246,14 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { bits.push(Style::default().paint(class)); } } + else if let (MountStyle::MountInfo, Some(mount_details)) = (self.mount_style, self.mounted_fs.as_ref()) { + // This is a filesystem mounted on the directory, output its details + bits.push(Style::default().paint(" {")); + bits.push(Style::default().paint(mount_details.source.clone())); + bits.push(Style::default().paint(" (")); + bits.push(Style::default().paint(mount_details.fstype.clone())); + bits.push(Style::default().paint(")}")); + } bits.into() } diff --git a/src/output/lines.rs b/src/output/lines.rs index 2343ce506..f90003a0a 100644 --- a/src/output/lines.rs +++ b/src/output/lines.rs @@ -32,6 +32,7 @@ impl<'a> Render<'a> { self.file_style .for_file(file, self.theme) .with_link_paths() + .with_mount_details() .paint() } } From 6e17b067b4b0f34e3767f62ffba7799d93a40c9e Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Wed, 25 Aug 2021 11:42:42 +0100 Subject: [PATCH 4/8] Use absolute path for looking up mounts --- src/fs/file.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index 4da50c20c..c0becc314 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -65,6 +65,9 @@ pub struct File<'dir> { /// directory’s children, and are in fact added specifically by exa; this /// means that they should be skipped when recursing. pub is_all_all: bool, + + /// The absolute value of this path, used to look up mount points. + pub absolute_path: PathBuf, } impl<'dir> File<'dir> { @@ -79,8 +82,9 @@ impl<'dir> File<'dir> { debug!("Statting file {:?}", &path); let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = false; + let absolute_path = std::fs::canonicalize(&path)?; - Ok(File { name, ext, path, metadata, parent_dir, is_all_all }) + Ok(File { name, ext, path, metadata, parent_dir, is_all_all, absolute_path }) } pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result> { @@ -91,8 +95,9 @@ impl<'dir> File<'dir> { let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = true; let parent_dir = Some(parent_dir); + let absolute_path = std::fs::canonicalize(&path)?; - Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all }) + Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, absolute_path }) } pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result> { @@ -102,8 +107,9 @@ impl<'dir> File<'dir> { let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = true; let parent_dir = Some(parent_dir); + let absolute_path = std::fs::canonicalize(&path)?; - Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all }) + Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, absolute_path }) } /// A file’s name is derived from its string. This needs to handle directories @@ -212,7 +218,7 @@ impl<'dir> File<'dir> { if self.is_directory() { let mounts = &MOUNTS.read().unwrap().0; for mount in mounts { - if self.path.eq(&mount.dest) { + if self.absolute_path.eq(&mount.dest) { return true; } } @@ -226,7 +232,7 @@ impl<'dir> File<'dir> { if self.is_mount_point() { let mounts = &MOUNTS.read().unwrap().0; for mount_point in mounts { - if self.path.eq(&mount_point.dest) { + if self.absolute_path.eq(&mount_point.dest) { return Some(MountedFs { dest: mount_point.dest.to_string_lossy().into_owned(), fstype: mount_point.fstype.clone(), @@ -286,7 +292,7 @@ impl<'dir> File<'dir> { Ok(metadata) => { let ext = File::ext(&path); let name = File::filename(&path); - let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false }; + let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, absolute_path }; FileTarget::Ok(Box::new(file)) } Err(e) => { From 8b10a8c8ba6cb5f8d7ec099d8e8b4e969b6ef110 Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Wed, 25 Aug 2021 12:26:39 +0100 Subject: [PATCH 5/8] Use a static HashMap to speed up mount lookups --- src/fs/file.rs | 29 +++++++---------------------- src/main.rs | 24 +++++++++++++++++++++++- src/output/file_name.rs | 2 +- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index c0becc314..b19f9831f 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -7,8 +7,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use log::*; -use proc_mounts::MOUNTS; - +use crate::ALL_MOUNTS; use crate::fs::dir::Dir; use crate::fs::fields as f; @@ -212,34 +211,20 @@ impl<'dir> File<'dir> { self.metadata.file_type().is_socket() } - // Whether this file is a mount point + /// Whether this file is a mount point pub fn is_mount_point(&self) -> bool { if cfg!(unix) { if self.is_directory() { - let mounts = &MOUNTS.read().unwrap().0; - for mount in mounts { - if self.absolute_path.eq(&mount.dest) { - return true; - } - } + return ALL_MOUNTS.contains_key(&self.absolute_path); } } return false; } - // The filesystem device and type for a mount point - pub fn mount_point_info(&self) -> Option { - if self.is_mount_point() { - let mounts = &MOUNTS.read().unwrap().0; - for mount_point in mounts { - if self.absolute_path.eq(&mount_point.dest) { - return Some(MountedFs { - dest: mount_point.dest.to_string_lossy().into_owned(), - fstype: mount_point.fstype.clone(), - source: mount_point.source.to_string_lossy().into_owned(), - }) - } - } + /// The filesystem device and type for a mount point + pub fn mount_point_info(&self) -> Option<&MountedFs> { + if cfg!(unix) { + return ALL_MOUNTS.get(&self.absolute_path); } None } diff --git a/src/main.rs b/src/main.rs index 67309931d..9f2d7b368 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,16 +23,21 @@ #![allow(clippy::upper_case_acronyms)] #![allow(clippy::wildcard_imports)] +#[macro_use] +extern crate lazy_static; + +use std::collections::HashMap; use std::env; use std::ffi::{OsStr, OsString}; use std::io::{self, Write, ErrorKind}; use std::path::{Component, PathBuf}; use ansi_term::{ANSIStrings, Style}; +use proc_mounts::MOUNTS; use log::*; -use crate::fs::{Dir, File}; +use crate::fs::{Dir, File, MountedFs}; use crate::fs::feature::git::GitCache; use crate::fs::filter::GitIgnore; use crate::options::{Options, Vars, vars, OptionsResult}; @@ -46,6 +51,23 @@ mod options; mod output; mod theme; +lazy_static! { + static ref ALL_MOUNTS: HashMap = { + let mut m = HashMap::new(); + if cfg!(unix) { + for mount_point in &MOUNTS.read().unwrap().0 { + let mount = MountedFs { + dest: mount_point.dest.to_string_lossy().into_owned(), + fstype: mount_point.fstype.clone(), + source: mount_point.source.to_string_lossy().into_owned(), + }; + m.insert(mount_point.dest.clone(), mount); + } + } + m + }; +} + fn main() { use std::process::exit; diff --git a/src/output/file_name.rs b/src/output/file_name.rs index b54214d5b..2b5b9555c 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -119,7 +119,7 @@ pub struct FileName<'a, 'dir, C> { options: Options, /// The filesystem details for a mounted filesystem. - mounted_fs: Option, + mounted_fs: Option<&'a MountedFs>, /// How to handle displaying a mounted filesystem. mount_style: MountStyle, From 1ad1105160b00321f1423b6de838ca2b47038706 Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Wed, 25 Aug 2021 13:22:17 +0100 Subject: [PATCH 6/8] Detect btrfs subvolumes --- src/fs/file.rs | 26 ++++++++++++++++++++++++++ src/output/file_name.rs | 8 ++++++++ src/theme/default_theme.rs | 21 +++++++++++---------- src/theme/mod.rs | 1 + src/theme/ui_styles.rs | 1 + 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index b19f9831f..b8327992d 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -229,6 +229,32 @@ impl<'dir> File<'dir> { None } + /// Whether this file (directory) is a `btrfs` subvolume + pub fn is_btrfs_subvolume(&self) -> bool { + if cfg!(unix) { + if self.is_directory() && + (self.metadata.ino() == 2 || self.metadata.ino() == 256) { + //This directory matches the characteristics of a btrfs + //subvolume root. Check it's actually on a btrfs fs. + let mut ancestors: Vec = Vec::new(); + for ancestor in self.absolute_path.ancestors() { + ancestors.push(ancestor.to_path_buf()); + } + ancestors.reverse(); + let mut is_on_btrfs = false; + // Start at / and work downwards + for ancestor in ancestors { + is_on_btrfs = match ALL_MOUNTS.get(&ancestor) { + None => is_on_btrfs, + Some(mount) => mount.fstype.eq("btrfs"), + }; + } + return is_on_btrfs; + } + } + return false; + } + /// Re-prefixes the path pointed to by this file, if it’s a symlink, to /// make it an absolute path that can be accessed from whichever /// directory exa is being run from. diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 2b5b9555c..4e667a968 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -252,6 +252,10 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { bits.push(Style::default().paint(mount_details.source.clone())); bits.push(Style::default().paint(" (")); bits.push(Style::default().paint(mount_details.fstype.clone())); + if mount_details.fstype.eq("btrfs") { + bits.push(Style::default().paint(" [")); + bits.push(Style::default().paint(" ]")); + } bits.push(Style::default().paint(")}")); } @@ -339,6 +343,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { match self.file { f if f.is_mount_point() => self.colours.mount_point(), + f if f.is_btrfs_subvolume() => self.colours.btrfs_subvolume(), f if f.is_directory() => self.colours.directory(), f if f.is_executable_file() => self.colours.executable_file(), f if f.is_link() => self.colours.symlink(), @@ -384,6 +389,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 that is the root of a btrfs subvolume. + fn btrfs_subvolume(&self) -> Style; + fn colour_file(&self, file: &File<'_>) -> Style; } diff --git a/src/theme/default_theme.rs b/src/theme/default_theme.rs index 953d18ae1..f19ee9a5f 100644 --- a/src/theme/default_theme.rs +++ b/src/theme/default_theme.rs @@ -11,16 +11,17 @@ impl UiStyles { colourful: true, filekinds: FileKinds { - normal: Style::default(), - directory: Blue.bold(), - symlink: Cyan.normal(), - pipe: Yellow.normal(), - block_device: Yellow.bold(), - char_device: Yellow.bold(), - socket: Red.bold(), - special: Yellow.normal(), - executable: Green.bold(), - mount_point: Blue.bold().underline(), + normal: Style::default(), + directory: Blue.bold(), + symlink: Cyan.normal(), + pipe: Yellow.normal(), + block_device: Yellow.bold(), + char_device: Yellow.bold(), + socket: Red.bold(), + special: Yellow.normal(), + executable: Green.bold(), + mount_point: Blue.bold().underline(), + btrfs_subvolume: Blue.underline(), }, perms: Permissions { diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 66966164f..9e8b5d7d5 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -301,6 +301,7 @@ impl FileNameColours for Theme { fn symlink_path(&self) -> Style { self.ui.symlink_path } fn executable_file(&self) -> Style { self.ui.filekinds.executable } fn mount_point(&self) -> Style { self.ui.filekinds.mount_point } + fn btrfs_subvolume(&self) -> Style { self.ui.filekinds.btrfs_subvolume } fn colour_file(&self, file: &File<'_>) -> Style { self.exts.colour_file(file).unwrap_or(self.ui.filekinds.normal) diff --git a/src/theme/ui_styles.rs b/src/theme/ui_styles.rs index c85f607cd..0702a6f66 100644 --- a/src/theme/ui_styles.rs +++ b/src/theme/ui_styles.rs @@ -39,6 +39,7 @@ pub struct FileKinds { pub special: Style, pub executable: Style, pub mount_point: Style, + pub btrfs_subvolume: Style, } #[derive(Clone, Copy, Debug, Default, PartialEq)] From c8400f397f9d89600bd29f34263d84fde141611b Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Wed, 25 Aug 2021 16:09:48 +0100 Subject: [PATCH 7/8] Show btrfs subvolume names on mount points --- src/fs/file.rs | 1 + src/main.rs | 13 +++++++++++++ src/output/file_name.rs | 7 ++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index b8327992d..25c32ff89 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -534,6 +534,7 @@ pub struct MountedFs { pub dest: String, pub fstype: String, pub source: String, + pub subvolume: Option, } diff --git a/src/main.rs b/src/main.rs index 9f2d7b368..88febb09d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,6 +60,19 @@ lazy_static! { dest: mount_point.dest.to_string_lossy().into_owned(), fstype: mount_point.fstype.clone(), source: mount_point.source.to_string_lossy().into_owned(), + subvolume: match mount_point.fstype.as_str() { + "btrfs" => { + let mut subvol = None; + for option in &mount_point.options { + if option.starts_with("subvol=") { + subvol = Some(option[7..].to_string()); + break; + } + } + subvol + }, + _ => None, + } }; m.insert(mount_point.dest.clone(), mount); } diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 4e667a968..695ac4637 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -252,9 +252,10 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { bits.push(Style::default().paint(mount_details.source.clone())); bits.push(Style::default().paint(" (")); bits.push(Style::default().paint(mount_details.fstype.clone())); - if mount_details.fstype.eq("btrfs") { - bits.push(Style::default().paint(" [")); - bits.push(Style::default().paint(" ]")); + if mount_details.fstype.eq("btrfs") && mount_details.subvolume.is_some() { + bits.push(Style::default().paint(" @[")); + bits.push(Style::default().paint(mount_details.subvolume.clone().unwrap())); + bits.push(Style::default().paint("]")); } bits.push(Style::default().paint(")}")); } From 823764aaa9e6098a35b4d98ce6547fc4e2a0eeba Mon Sep 17 00:00:00 2001 From: Steven Davies Date: Fri, 10 Sep 2021 08:59:01 +0100 Subject: [PATCH 8/8] Fix Windows compilation --- src/fs/file.rs | 1 + src/main.rs | 42 ++++++++++++++++++++--------------------- src/output/file_name.rs | 1 + 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/fs/file.rs b/src/fs/file.rs index 25c32ff89..9e753d1bc 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -230,6 +230,7 @@ impl<'dir> File<'dir> { } /// Whether this file (directory) is a `btrfs` subvolume + #[cfg(unix)] pub fn is_btrfs_subvolume(&self) -> bool { if cfg!(unix) { if self.is_directory() && diff --git a/src/main.rs b/src/main.rs index 88febb09d..831a84752 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ use std::io::{self, Write, ErrorKind}; use std::path::{Component, PathBuf}; use ansi_term::{ANSIStrings, Style}; +#[cfg(unix)] use proc_mounts::MOUNTS; use log::*; @@ -54,28 +55,27 @@ mod theme; lazy_static! { static ref ALL_MOUNTS: HashMap = { let mut m = HashMap::new(); - if cfg!(unix) { - for mount_point in &MOUNTS.read().unwrap().0 { - let mount = MountedFs { - dest: mount_point.dest.to_string_lossy().into_owned(), - fstype: mount_point.fstype.clone(), - source: mount_point.source.to_string_lossy().into_owned(), - subvolume: match mount_point.fstype.as_str() { - "btrfs" => { - let mut subvol = None; - for option in &mount_point.options { - if option.starts_with("subvol=") { - subvol = Some(option[7..].to_string()); - break; - } + #[cfg(unix)] + for mount_point in &MOUNTS.read().unwrap().0 { + let mount = MountedFs { + dest: mount_point.dest.to_string_lossy().into_owned(), + fstype: mount_point.fstype.clone(), + source: mount_point.source.to_string_lossy().into_owned(), + subvolume: match mount_point.fstype.as_str() { + "btrfs" => { + let mut subvol = None; + for option in &mount_point.options { + if option.starts_with("subvol=") { + subvol = Some(option[7..].to_string()); + break; } - subvol - }, - _ => None, - } - }; - m.insert(mount_point.dest.clone(), mount); - } + } + subvol + }, + _ => None, + } + }; + m.insert(mount_point.dest.clone(), mount); } m }; diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 695ac4637..698ed0290 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -344,6 +344,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> { match self.file { f if f.is_mount_point() => self.colours.mount_point(), + #[cfg(unix)] f if f.is_btrfs_subvolume() => self.colours.btrfs_subvolume(), f if f.is_directory() => self.colours.directory(), f if f.is_executable_file() => self.colours.executable_file(),