Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show mounted filesystems and btrfs subvolumes #941

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
117 changes: 117 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
67 changes: 63 additions & 4 deletions src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};

use log::*;

use crate::ALL_MOUNTS;
use crate::fs::dir::Dir;
use crate::fs::fields as f;

Expand Down Expand Up @@ -63,6 +64,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> {
Expand All @@ -77,8 +81,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<File<'dir>> {
Expand All @@ -89,8 +94,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<File<'dir>> {
Expand All @@ -100,8 +106,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
Expand Down Expand Up @@ -204,6 +211,50 @@ 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!(unix) {
if self.is_directory() {
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<&MountedFs> {
if cfg!(unix) {
return ALL_MOUNTS.get(&self.absolute_path);
}
None
}

/// Whether this file (directory) is a `btrfs` subvolume
#[cfg(unix)]
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<PathBuf> = 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
Expand Down Expand Up @@ -253,7 +304,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) => {
Expand Down Expand Up @@ -479,6 +530,14 @@ impl<'dir> FileTarget<'dir> {
}
}

/// Details of a mounted filesystem.
pub struct MountedFs {
pub dest: String,
pub fstype: String,
pub source: String,
pub subvolume: Option<String>,
}


/// More readable aliases for the permission bits exposed by libc.
#[allow(trivial_numeric_casts)]
Expand Down
2 changes: 1 addition & 1 deletion src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
37 changes: 36 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,22 @@
#![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};
#[cfg(unix)]
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};
Expand All @@ -45,6 +51,35 @@ mod options;
mod output;
mod theme;

lazy_static! {
static ref ALL_MOUNTS: HashMap<PathBuf, MountedFs> = {
let mut m = HashMap::new();
#[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);
}
m
};
}


fn main() {
use std::process::exit;
Expand Down
1 change: 1 addition & 0 deletions src/output/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,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();

Expand Down
Loading