Skip to content

Commit f18c3c1

Browse files
authored
Merge pull request eza-community#167 from daviessm/mount_point
Add mounted filesystem info
2 parents cc25f2d + fe1ac47 commit f18c3c1

20 files changed

+209
-12
lines changed

Cargo.lock

+41-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ default-features = false
6767
# See: https://github.com/eza-community/eza/pull/192
6868
features = ["vendored-libgit2"]
6969

70+
[target.'cfg(target_os = "linux")'.dependencies]
71+
proc-mounts = "0.3"
72+
7073
[target.'cfg(unix)'.dependencies]
7174
uzers = "0.11.2"
7275

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ By deliberately making some decisions differently, eza attempts to be a more fea
3434

3535
- Fixes [“The Grid Bug”](https://github.com/eza-community/eza/issues/66#issuecomment-1656758327) introduced in exa 2021.
3636
- Hyperlink support.
37+
- Mount point details.
3738
- Selinux context output.
3839
- Git repo status output.
3940
- Human readable relative dates.
@@ -189,6 +190,7 @@ These options are available when running with `--long` (`-l`):
189190
- **-H**, **--links**: list each file’s number of hard links
190191
- **-i**, **--inode**: list each file’s inode number
191192
- **-m**, **--modified**: use the modified timestamp field
193+
- **-M**, **--mounts**: Show mount details (Linux only).
192194
- **-S**, **--blocksize**: show size of allocated file system blocks
193195
- **-t**, **--time=(field)**: which timestamp field to use
194196
- **-u**, **--accessed**: use the accessed timestamp field

completions/fish/eza.fish

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ complete -c eza -s o -l octal-permissions -d "List each file's permission in oct
8989
complete -c eza -l no-filesize -d "Suppress the filesize field"
9090
complete -c eza -l no-user -d "Suppress the user field"
9191
complete -c eza -l no-time -d "Suppress the time field"
92+
complete -c eza -s M -l mounts -d "Show mount details"
9293

9394
# Optional extras
9495
complete -c eza -l git -d "List each file's Git status, if tracked"

completions/zsh/_eza

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ __eza() {
6060
--git-repos-no-status"[List each git-repos branch name (much faster)]" \
6161
{-@,--extended}"[List each file's extended attributes and sizes]" \
6262
{-Z,--context}"[List each file's security context]" \
63+
{-M,--mounts}"[Show mount details (long mode only)]" \
6364
'*:filename:_files'
6465
}
6566

man/eza.1.md

+3
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ These options are available when running with `--long` (`-l`):
149149
`-m`, `--modified`
150150
: Use the modified timestamp field.
151151

152+
`-M`, `--mounts`
153+
: Show mount details (Linux only)
154+
152155
`-n`, `--numeric`
153156
: List numeric user and group IDs.
154157

man/eza_colors.5.md

+3
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ LIST OF CODES
220220
`bO`
221221
: the overlay style for broken symlink paths
222222

223+
`mp`
224+
: a mount point
225+
223226
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.
224227

225228

src/fs/file.rs

+38-5
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ use std::time::{Duration, UNIX_EPOCH};
1212

1313
use log::*;
1414

15+
use crate::ALL_MOUNTS;
1516
use crate::fs::dir::Dir;
1617
use crate::fs::feature::xattr;
1718
use crate::fs::feature::xattr::{FileAttributes, Attribute};
1819
use crate::fs::fields as f;
1920

21+
use super::mounts::MountedFs;
22+
2023

2124
/// A **File** is a wrapper around one of Rust’s `PathBuf` values, along with
2225
/// associated data about the file.
@@ -79,7 +82,9 @@ pub struct File<'dir> {
7982
pub deref_links: bool,
8083
/// The extended attributes of this file.
8184
pub extended_attributes: Vec<Attribute>,
82-
}
85+
86+
/// The absolute value of this path, used to look up mount points.
87+
pub absolute_path: PathBuf,}
8388

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

98-
Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, extended_attributes })
104+
Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, extended_attributes, absolute_path })
99105
}
100106

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

111-
Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false, extended_attributes })
118+
Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false, extended_attributes, absolute_path })
112119
}
113120

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

123-
Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false, extended_attributes })
131+
Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false, extended_attributes, absolute_path })
124132
}
125133

126134
/// A file’s name is derived from its string. This needs to handle directories
@@ -243,6 +251,21 @@ impl<'dir> File<'dir> {
243251
self.metadata.file_type().is_socket()
244252
}
245253

254+
/// Whether this file is a mount point
255+
pub fn is_mount_point(&self) -> bool {
256+
if cfg!(target_os = "linux") && self.is_directory() {
257+
return ALL_MOUNTS.contains_key(&self.absolute_path);
258+
}
259+
false
260+
}
261+
262+
/// The filesystem device and type for a mount point
263+
pub fn mount_point_info(&self) -> Option<&MountedFs> {
264+
if cfg!(target_os = "linux") {
265+
return ALL_MOUNTS.get(&self.absolute_path);
266+
}
267+
None
268+
}
246269

247270
/// Re-prefixes the path pointed to by this file, if it’s a symlink, to
248271
/// make it an absolute path that can be accessed from whichever
@@ -293,7 +316,17 @@ impl<'dir> File<'dir> {
293316
let ext = File::ext(&path);
294317
let name = File::filename(&path);
295318
let extended_attributes = File::gather_extended_attributes(&absolute_path);
296-
let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, deref_links: self.deref_links, extended_attributes };
319+
let file = File {
320+
parent_dir: None,
321+
path,
322+
ext,
323+
metadata,
324+
name,
325+
is_all_all: false,
326+
deref_links: self.deref_links,
327+
extended_attributes,
328+
absolute_path
329+
};
297330
FileTarget::Ok(Box::new(file))
298331
}
299332
Err(e) => {

src/fs/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ pub mod dir_action;
88
pub mod feature;
99
pub mod fields;
1010
pub mod filter;
11+
pub mod mounts;

src/fs/mounts.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// Details of a mounted filesystem.
2+
pub struct MountedFs {
3+
pub dest: String,
4+
pub fstype: String,
5+
pub source: String,
6+
}

src/main.rs

+40
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#![allow(clippy::upper_case_acronyms)]
2323
#![allow(clippy::wildcard_imports)]
2424

25+
use std::collections::HashMap;
2526
use std::env;
2627
use std::ffi::{OsStr, OsString};
2728
use std::io::{self, Write, ErrorKind};
@@ -31,6 +32,13 @@ use ansiterm::{ANSIStrings, Style};
3132

3233
use log::*;
3334

35+
#[cfg(target_os = "linux")]
36+
use proc_mounts::MountList;
37+
38+
#[macro_use]
39+
extern crate lazy_static;
40+
41+
use crate::fs::mounts::MountedFs;
3442
use crate::fs::{Dir, File};
3543
use crate::fs::feature::git::GitCache;
3644
use crate::fs::filter::GitIgnore;
@@ -45,6 +53,38 @@ mod options;
4553
mod output;
4654
mod theme;
4755

56+
// A lazily initialised static map of all mounted file systems.
57+
//
58+
// The map contains a mapping from the mounted directory path to the
59+
// corresponding mount information. On Linux systems, this map is populated
60+
// using the `proc-mounts` crate. If there's an error retrieving the mount
61+
// list or if we're not running on Linux, the map will be empty.
62+
//
63+
// Initialise this at application start so we don't have to look the details
64+
// up for every directory. Ideally this would only be done if the --mounts
65+
// option is specified which will be significantly easier once the move
66+
// to `clap` is complete.
67+
lazy_static! {
68+
static ref ALL_MOUNTS: HashMap<PathBuf, MountedFs> = {
69+
#[cfg(target_os = "linux")]
70+
match MountList::new() {
71+
Ok(mount_list) => {
72+
let mut m = HashMap::new();
73+
mount_list.0.iter().for_each(|mount| {
74+
m.insert(mount.dest.clone(), MountedFs {
75+
dest: mount.dest.to_string_lossy().into_owned(),
76+
fstype: mount.fstype.clone(),
77+
source: mount.source.to_string_lossy().into(),
78+
});
79+
});
80+
m
81+
}
82+
Err(_) => HashMap::new()
83+
}
84+
#[cfg(not(target_os = "linux"))]
85+
HashMap::new()
86+
};
87+
}
4888

4989
fn main() {
5090
use std::process::exit;

src/options/flags.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ pub static TIME: Arg = Arg { short: Some(b't'), long: "time", takes_
5454
pub static ACCESSED: Arg = Arg { short: Some(b'u'), long: "accessed", takes_value: TakesValue::Forbidden };
5555
pub static CREATED: Arg = Arg { short: Some(b'U'), long: "created", takes_value: TakesValue::Forbidden };
5656
pub static TIME_STYLE: Arg = Arg { short: None, long: "time-style", takes_value: TakesValue::Necessary(Some(TIME_STYLES)) };
57-
pub static HYPERLINK: Arg = Arg { short: None, long: "hyperlink", takes_value: TakesValue::Forbidden};
57+
pub static HYPERLINK: Arg = Arg { short: None, long: "hyperlink", takes_value: TakesValue::Forbidden };
58+
pub static MOUNTS: Arg = Arg { short: Some(b'M'), long: "mounts", takes_value: TakesValue::Forbidden };
5859
const TIMES: Values = &["modified", "changed", "accessed", "created"];
5960
const TIME_STYLES: Values = &["default", "long-iso", "full-iso", "iso", "relative"];
6061

@@ -85,7 +86,7 @@ pub static ALL_ARGS: Args = Args(&[
8586
&IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS,
8687

8788
&BINARY, &BYTES, &GROUP, &NUMERIC, &HEADER, &ICONS, &INODE, &LINKS, &MODIFIED, &CHANGED,
88-
&BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK,
89+
&BLOCKSIZE, &TIME, &ACCESSED, &CREATED, &TIME_STYLE, &HYPERLINK, &MOUNTS,
8990
&NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,
9091

9192
&GIT, &NO_GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT,

src/options/help.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ LONG VIEW OPTIONS
5353
-H, --links list each file's number of hard links
5454
-i, --inode list each file's inode number
5555
-m, --modified use the modified timestamp field
56+
-M, --mounts show mount details (Linux only)
5657
-n, --numeric list numeric user and group IDs
5758
-S, --blocksize show size of allocated file system blocks
5859
-t, --time FIELD which timestamp field to list (modified, accessed, created)

src/options/view.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ impl Mode {
8282
// user about flags that won’t have any effect.
8383
if matches.is_strict() {
8484
for option in &[ &flags::BINARY, &flags::BYTES, &flags::INODE, &flags::LINKS,
85-
&flags::HEADER, &flags::BLOCKSIZE, &flags::TIME, &flags::GROUP, &flags::NUMERIC ] {
85+
&flags::HEADER, &flags::BLOCKSIZE, &flags::TIME, &flags::GROUP, &flags::NUMERIC,
86+
&flags::MOUNTS ] {
8687
if matches.has(option)? {
8788
return Err(OptionsError::Useless(option, false, &flags::LONG));
8889
}
@@ -119,6 +120,7 @@ impl details::Options {
119120
header: false,
120121
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
121122
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
123+
mounts: matches.has(&flags::MOUNTS)?,
122124
};
123125

124126
Ok(details)
@@ -139,6 +141,7 @@ impl details::Options {
139141
header: matches.has(&flags::HEADER)?,
140142
xattr: xattr::ENABLED && matches.has(&flags::EXTENDED)?,
141143
secattr: xattr::ENABLED && matches.has(&flags::SECURITY_CONTEXT)?,
144+
mounts: matches.has(&flags::MOUNTS)?,
142145
})
143146
}
144147
}

src/output/details.rs

+5
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ use crate::theme::Theme;
9292
///
9393
/// Almost all the heavy lifting is done in a Table object, which handles the
9494
/// columns for each row.
95+
#[allow(clippy::struct_excessive_bools)] /// This clearly isn't a state machine
9596
#[derive(PartialEq, Eq, Debug)]
9697
pub struct Options {
9798

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

110111
/// Whether to show each file's security attribute.
111112
pub secattr: bool,
113+
114+
/// Whether to show a directory's mounted filesystem details
115+
pub mounts: bool,
112116
}
113117

114118

@@ -288,6 +292,7 @@ impl<'a> Render<'a> {
288292

289293
let file_name = self.file_style.for_file(egg.file, self.theme)
290294
.with_link_paths()
295+
.with_mount_details(self.opts.mounts)
291296
.paint()
292297
.promote();
293298

0 commit comments

Comments
 (0)