Skip to content
Merged
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
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 @@ -189,6 +190,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
3 changes: 3 additions & 0 deletions man/eza_colors.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ LIST OF CODES
`bO`
: the overlay style for broken symlink paths

`mp`
: a mount point

Values in `EXA_COLORS` override those given in `LS_COLORS`, so you don’t need to re-write an existing `LS_COLORS` variable with proprietary extensions.


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,
}
40 changes: 40 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 ansiterm::{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,38 @@ mod options;
mod output;
mod theme;

// A lazily initialised static map of all mounted file systems.
//
// The map contains a mapping from the mounted directory path to the
// corresponding mount information. On Linux systems, this map is populated
// using the `proc-mounts` crate. If there's an error retrieving the mount
// list or if we're not running on Linux, the map will be empty.
//
// Initialise this at application start so we don't have to look the details
// up for every directory. Ideally this would only be done if the --mounts
// option is specified which will be significantly easier once the move
// to `clap` is complete.
lazy_static! {
Comment thread
daviessm marked this conversation as resolved.
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