Skip to content

Commit

Permalink
feat: Add highlighting of mounted directories (Linux only)
Browse files Browse the repository at this point in the history
  • Loading branch information
daviessm committed Sep 5, 2023
1 parent 51cdb54 commit 67262c2
Show file tree
Hide file tree
Showing 19 changed files with 191 additions and 12 deletions.
43 changes: 41 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ default-features = false
# See: https://github.com/eza-community/eza/pull/192
features = ["vendored-libgit2"]

[target.'cfg(target_os = "linux")'.dependencies]
proc-mounts = "0.3"

[target.'cfg(unix)'.dependencies]
uzers = "0.11.2"

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ By deliberately making some decisions differently, eza attempts to be a more fea

- Fixes [“The Grid Bug”](https://github.com/eza-community/eza/issues/66#issuecomment-1656758327) introduced in exa 2021.
- Hyperlink support.
- Mount point details.
- Selinux context output.
- Git repo status output.
- Human readable relative dates.
Expand Down Expand Up @@ -170,6 +171,7 @@ These options are available when running with `--long` (`-l`):
- **-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 only).
- **-S**, **--blocksize**: show size of allocated file system blocks
- **-t**, **--time=(field)**: which timestamp field to use
- **-u**, **--accessed**: use the accessed timestamp field
Expand Down
1 change: 1 addition & 0 deletions completions/fish/eza.fish
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ complete -c eza -s o -l octal-permissions -d "List each file's permission in oct
complete -c eza -l no-filesize -d "Suppress the filesize field"
complete -c eza -l no-user -d "Suppress the user field"
complete -c eza -l no-time -d "Suppress the time field"
complete -c eza -s M -l mounts -d "Show mount details"

# Optional extras
complete -c eza -l git -d "List each file's Git status, if tracked"
Expand Down
1 change: 1 addition & 0 deletions completions/zsh/_eza
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ __eza() {
--git-repos-no-status"[List each git-repos branch name (much faster)]" \
{-@,--extended}"[List each file's extended attributes and sizes]" \
{-Z,--context}"[List each file's security context]" \
{-M,--mounts}"[Show mount details (long mode only)]" \
'*:filename:_files'
}

Expand Down
3 changes: 3 additions & 0 deletions man/eza.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ These options are available when running with `--long` (`-l`):
`-m`, `--modified`
: Use the modified timestamp field.

`-M`, `--mounts`
: Show mount details (Linux only)

`-n`, `--numeric`
: List numeric user and group IDs.

Expand Down
43 changes: 38 additions & 5 deletions src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ use std::time::{Duration, UNIX_EPOCH};

use log::*;

use crate::ALL_MOUNTS;
use crate::fs::dir::Dir;
use crate::fs::feature::xattr;
use crate::fs::feature::xattr::{FileAttributes, Attribute};
use crate::fs::fields as f;

use super::mounts::MountedFs;


/// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with
/// associated data about the file.
Expand Down Expand Up @@ -79,7 +82,9 @@ pub struct File<'dir> {
pub deref_links: bool,
/// The extended attributes of this file.
pub extended_attributes: Vec<Attribute>,
}

/// The absolute value of this path, used to look up mount points.
pub absolute_path: PathBuf,}

impl<'dir> File<'dir> {
pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN, deref_links: bool) -> io::Result<File<'dir>>
Expand All @@ -94,8 +99,9 @@ impl<'dir> File<'dir> {
let metadata = std::fs::symlink_metadata(&path)?;
let is_all_all = false;
let extended_attributes = File::gather_extended_attributes(&path);
let absolute_path = std::fs::canonicalize(&path)?;

Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, extended_attributes })
Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, extended_attributes, absolute_path })
}

pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
Expand All @@ -107,8 +113,9 @@ impl<'dir> File<'dir> {
let is_all_all = true;
let parent_dir = Some(parent_dir);
let extended_attributes = File::gather_extended_attributes(&path);
let absolute_path = std::fs::canonicalize(&path)?;

Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false, extended_attributes })
Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false, extended_attributes, absolute_path })
}

pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
Expand All @@ -119,8 +126,9 @@ impl<'dir> File<'dir> {
let is_all_all = true;
let parent_dir = Some(parent_dir);
let extended_attributes = File::gather_extended_attributes(&path);
let absolute_path = std::fs::canonicalize(&path)?;

Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false, extended_attributes })
Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false, extended_attributes, absolute_path })
}

/// A file’s name is derived from its string. This needs to handle directories
Expand Down Expand Up @@ -243,6 +251,21 @@ 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 cfg!(target_os = "linux") && self.is_directory() {
return ALL_MOUNTS.contains_key(&self.absolute_path);
}
false
}

/// The filesystem device and type for a mount point
pub fn mount_point_info(&self) -> Option<&MountedFs> {
if cfg!(target_os = "linux") {
return ALL_MOUNTS.get(&self.absolute_path);
}
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
Expand Down Expand Up @@ -293,7 +316,17 @@ impl<'dir> File<'dir> {
let ext = File::ext(&path);
let name = File::filename(&path);
let extended_attributes = File::gather_extended_attributes(&absolute_path);
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, deref_links: self.deref_links, extended_attributes };
let file = File {
parent_dir: None,
path,
ext,
metadata,
name,
is_all_all: false,
deref_links: self.deref_links,
extended_attributes,
absolute_path
};
FileTarget::Ok(Box::new(file))
}
Err(e) => {
Expand Down
1 change: 1 addition & 0 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ pub mod dir_action;
pub mod feature;
pub mod fields;
pub mod filter;
pub mod mounts;
6 changes: 6 additions & 0 deletions src/fs/mounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Details of a mounted filesystem.
pub struct MountedFs {
pub dest: String,
pub fstype: String,
pub source: String,
}
29 changes: 29 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wildcard_imports)]

use std::collections::HashMap;
use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{self, Write, ErrorKind};
Expand All @@ -31,6 +32,13 @@ use ansi_term::{ANSIStrings, Style};

use log::*;

#[cfg(target_os = "linux")]
use proc_mounts::MountList;

#[macro_use]
extern crate lazy_static;

use crate::fs::mounts::MountedFs;
use crate::fs::{Dir, File};
use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
Expand All @@ -45,6 +53,27 @@ mod options;
mod output;
mod theme;

lazy_static! {
static ref ALL_MOUNTS: HashMap<PathBuf, MountedFs> = {
#[cfg(target_os = "linux")]
match MountList::new() {
Ok(mount_list) => {
let mut m = HashMap::new();
mount_list.0.iter().for_each(|mount| {
m.insert(mount.dest.clone(), MountedFs {
dest: mount.dest.to_string_lossy().into_owned(),
fstype: mount.fstype.clone(),
source: mount.source.to_string_lossy().into(),
});
});
m
}
Err(_) => HashMap::new()
}
#[cfg(not(target_os = "linux"))]
HashMap::new()
};
}

fn main() {
use std::process::exit;
Expand Down
5 changes: 3 additions & 2 deletions src/options/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ pub static TIME: Arg = Arg { short: Some(b't'), long: "time", takes_
pub static ACCESSED: Arg = Arg { short: Some(b'u'), long: "accessed", takes_value: TakesValue::Forbidden };
pub static CREATED: Arg = Arg { short: Some(b'U'), long: "created", takes_value: TakesValue::Forbidden };
pub static TIME_STYLE: Arg = Arg { short: None, long: "time-style", takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };
pub static HYPERLINK: Arg = Arg { short: None, long: "hyperlink", takes_value: TakesValue::Forbidden};
pub static HYPERLINK: Arg = Arg { short: None, long: "hyperlink", takes_value: TakesValue::Forbidden };
pub static MOUNTS: Arg = Arg { short: Some(b'M'), long: "mounts", takes_value: TakesValue::Forbidden };
const TIMES: Values = &["modified", "changed", "accessed", "created"];
const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"];

Expand Down Expand Up @@ -85,7 +86,7 @@ pub static ALL_ARGS: Args = Args(&[
&IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS,

&BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
&BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK,
&BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
&NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,

&GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,
Expand Down
1 change: 1 addition & 0 deletions src/options/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ 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 only)
-n, --numeric list numeric user and group IDs
-S, --blocksize show size of allocated file system blocks
-t, --time FIELD which timestamp field to list (modified, accessed, created)
Expand Down
5 changes: 4 additions & 1 deletion src/options/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ impl Mode {
// user about flags that won’t have any effect.
if matches.is_strict() {
for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
&flags::HEADER, &flags::BLOCKSIZE, &flags::TIME, &flags::GROUP, &flags::NUMERIC ] {
&flags::HEADER, &flags::BLOCKSIZE, &flags::TIME, &flags::GROUP, &flags::NUMERIC,
&flags::MOUNTS ] {
if matches.has(option)? {
return Err(OptionsError::Useless(option, false, &flags::LONG));
}
Expand Down Expand Up @@ -119,6 +120,7 @@ impl details::Options {
header: false,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
mounts: matches.has(&flags::MOUNTS)?,
};

Ok(details)
Expand All @@ -139,6 +141,7 @@ impl details::Options {
header: matches.has(&flags::HEADER)?,
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
mounts: matches.has(&flags::MOUNTS)?,
})
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/output/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ use crate::theme::Theme;
///
/// Almost all the heavy lifting is done in a Table object, which handles the
/// columns for each row.
#[allow(clippy::struct_excessive_bools)] /// This clearly isn't a state machine
#[derive(PartialEq, Eq, Debug)]
pub struct Options {

Expand All @@ -109,6 +110,9 @@ pub struct Options {

/// Whether to show each file's security attribute.
pub secattr: bool,

/// Whether to show a directory's mounted filesystem details
pub mounts: bool,
}


Expand Down Expand Up @@ -288,6 +292,7 @@ impl<'a> Render<'a> {

let file_name = self.file_style.for_file(egg.file, self.theme)
.with_link_paths()
.with_mount_details(self.opts.mounts)
.paint()
.promote();

Expand Down
Loading

0 comments on commit 67262c2

Please sign in to comment.