소스 검색

Merge pull request #291 from cfxegbert/mac-mounts

feat: Support --mount option on Mac
Christina Sørensen 2 년 전
부모
커밋
5bd9e73738
10개의 변경된 파일154개의 추가작업 그리고 106개의 파일을 삭제
  1. 1 1
      man/eza.1.md
  2. 6 10
      src/fs/file.rs
  3. 1 1
      src/fs/mod.rs
  4. 0 6
      src/fs/mounts.rs
  5. 16 0
      src/fs/mounts/linux.rs
  6. 54 0
      src/fs/mounts/macos.rs
  7. 75 0
      src/fs/mounts/mod.rs
  8. 0 46
      src/lib.rs
  9. 0 41
      src/main.rs
  10. 1 1
      src/options/help.rs

+ 1 - 1
man/eza.1.md

@@ -150,7 +150,7 @@ These options are available when running with `--long` (`-l`):
 : Use the modified timestamp field.
 : Use the modified timestamp field.
 
 
 `-M`, `--mounts`
 `-M`, `--mounts`
-: Show mount details (Linux only)
+: Show mount details (Linux and Mac only)
 
 
 `-n`, `--numeric`
 `-n`, `--numeric`
 : List numeric user and group IDs.
 : List numeric user and group IDs.

+ 6 - 10
src/fs/file.rs

@@ -11,12 +11,12 @@ use chrono::prelude::*;
 
 
 use log::*;
 use log::*;
 
 
-use crate::ALL_MOUNTS;
 use crate::fs::dir::Dir;
 use crate::fs::dir::Dir;
 use crate::fs::feature::xattr;
 use crate::fs::feature::xattr;
 use crate::fs::feature::xattr::{FileAttributes, Attribute};
 use crate::fs::feature::xattr::{FileAttributes, Attribute};
 use crate::fs::fields as f;
 use crate::fs::fields as f;
 
 
+use super::mounts::all_mounts;
 use super::mounts::MountedFs;
 use super::mounts::MountedFs;
 
 
 
 
@@ -254,19 +254,15 @@ impl<'dir> File<'dir> {
 
 
     /// Whether this file is a mount point
     /// Whether this file is a mount point
     pub fn is_mount_point(&self) -> bool {
     pub fn is_mount_point(&self) -> bool {
-        if cfg!(target_os = "linux") && self.is_directory() {
-            return match self.absolute_path.as_ref() {
-                Some(path) => ALL_MOUNTS.contains_key(path),
-                None => false,
-            }
-        }
-        false
+        cfg!(any(target_os = "linux", target_os = "macos")) &&
+            self.is_directory() &&
+            self.absolute_path.as_ref().is_some_and(|p| all_mounts().contains_key(p))
     }
     }
 
 
     /// The filesystem device and type for a mount point
     /// The filesystem device and type for a mount point
     pub fn mount_point_info(&self) -> Option<&MountedFs> {
     pub fn mount_point_info(&self) -> Option<&MountedFs> {
-        if cfg!(target_os = "linux") {
-            return self.absolute_path.as_ref().and_then(|p|ALL_MOUNTS.get(p));
+        if cfg!(any(target_os = "linux",target_os = "macos")) {
+            return self.absolute_path.as_ref().and_then(|p| all_mounts().get(p));
         }
         }
         None
         None
     }
     }

+ 1 - 1
src/fs/mod.rs

@@ -8,4 +8,4 @@ pub mod dir_action;
 pub mod feature;
 pub mod feature;
 pub mod fields;
 pub mod fields;
 pub mod filter;
 pub mod filter;
-pub mod mounts;
+pub mod mounts;

+ 0 - 6
src/fs/mounts.rs

@@ -1,6 +0,0 @@
-/// Details of a mounted filesystem.
-pub struct MountedFs {
-    pub dest: String,
-    pub fstype: String,
-    pub source: String,
-}

+ 16 - 0
src/fs/mounts/linux.rs

@@ -0,0 +1,16 @@
+use proc_mounts::MountList;
+use crate::fs::mounts::{Error, MountedFs};
+
+/// Get a list of all mounted filesystems
+pub fn mounts() -> Result<Vec<MountedFs>, Error> {
+    Ok(MountList::new()
+           .map_err(Error::IOError)?
+           .0.iter()
+           .map(|mount| MountedFs {
+               dest: mount.dest.clone(),
+               fstype: mount.fstype.clone(),
+               source: mount.source.to_string_lossy().into()
+           })
+           .collect()
+    )
+}

+ 54 - 0
src/fs/mounts/macos.rs

@@ -0,0 +1,54 @@
+use std::{mem, ptr};
+use std::ffi::{CStr, OsStr};
+use std::os::raw::{c_char, c_int};
+use std::os::unix::ffi::OsStrExt;
+use std::path::PathBuf;
+use libc::{__error, getfsstat, MNT_NOWAIT, statfs};
+use crate::fs::mounts::{Error, MountedFs};
+
+/// Get a list of all mounted filesystem
+pub fn mounts() -> Result<Vec<MountedFs>, Error> {
+    // SAFETY:
+    // Calling external "C" function getfsstat.  Passing a null pointer and zero
+    // bufsize will return the number of mounts.
+    let mut count: i32 = unsafe { getfsstat(ptr::null_mut(), 0, MNT_NOWAIT) };
+    let mut mntbuf = Vec::<statfs>::new();
+    if count > 0 {
+        // SAFETY: Zero out buffer memory as we allocate.
+        mntbuf.resize_with(count as usize, || unsafe { mem::zeroed() });
+        let bufsize = mntbuf.len() * mem::size_of::<statfs>();
+        // SAFETY:
+        // Calling external "C" function getfsstate with actual buffer now.  The
+        // function takes a buffer size to not overflow.  If the mount table
+        // changes size between calls we are protected by bufsize
+        count = unsafe { getfsstat(mntbuf.as_mut_ptr(), bufsize as c_int, MNT_NOWAIT) };
+        // Resize if the mount table has shrunk since last call
+        if count >= 0 {
+            mntbuf.truncate(count as usize);
+        }
+    }
+    if count < 0 {
+        // SAFETY: Calling external "C" errno function to get the error number
+        return Err(Error::GetFSStatError(unsafe { *__error() }));
+    }
+
+    let mut mounts = Vec::with_capacity(count as usize);
+    for mnt in &mntbuf {
+        let mount_point = OsStr::from_bytes(
+            // SAFETY: Converting null terminated "C" string
+            unsafe { CStr::from_ptr(mnt.f_mntonname.as_ptr().cast::<c_char>()) }.to_bytes()
+        );
+        let dest = PathBuf::from(mount_point);
+        // SAFETY: Converting null terminated "C" string
+        let fstype = unsafe { CStr::from_ptr(mnt.f_fstypename.as_ptr().cast::<c_char>()) }
+            .to_string_lossy()
+            .into();
+        // SAFETY: Converting null terminated "C" string
+        let source = unsafe { CStr::from_ptr(mnt.f_mntfromname.as_ptr().cast::<c_char>()) }
+            .to_string_lossy()
+            .into();
+        mounts.push(MountedFs { dest, fstype, source });
+    }
+
+    Ok(mounts)
+}

+ 75 - 0
src/fs/mounts/mod.rs

@@ -0,0 +1,75 @@
+use std::collections::HashMap;
+use std::path::PathBuf;
+use std::sync::OnceLock;
+
+#[cfg(target_os = "linux")]
+mod linux;
+#[cfg(target_os = "macos")]
+mod macos;
+
+#[cfg(target_os = "linux")]
+use linux::mounts;
+#[cfg(target_os = "macos")]
+use macos::mounts;
+
+/// Details of a mounted filesystem.
+#[derive(Clone)]
+pub struct MountedFs {
+    pub dest: PathBuf,
+    pub fstype: String,
+    pub source: String,
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum Error {
+    #[cfg(target_os = "macos")]
+    GetFSStatError(i32),
+    #[cfg(target_os = "linux")]
+    IOError(std::io::Error)
+}
+
+impl std::error::Error for Error {}
+
+impl std::fmt::Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        // Allow unreachable_patterns for windows build
+        #[allow(unreachable_patterns)]
+        match self {
+            #[cfg(target_os = "macos")]
+            Error::GetFSStatError(err) => write!(f, "getfsstat failed: {err}"),
+            #[cfg(target_os = "linux")]
+            Error::IOError(err)        => write!(f, "failed to read /proc/mounts: {err}"),
+            _                          => write!(f, "Unknown error"),
+        }
+    }
+}
+
+// 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. If there's an error retrieving the mount
+// list or if we're not running on Linux or Mac, 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.
+pub(super) fn all_mounts() -> &'static HashMap<PathBuf, MountedFs> {
+    static ALL_MOUNTS: OnceLock<HashMap<PathBuf, MountedFs>> = OnceLock::new();
+
+    ALL_MOUNTS.get_or_init(|| {
+        // Allow unused_mut for windows build
+        #[allow(unused_mut)]
+        let mut mount_map: HashMap<PathBuf, MountedFs> = HashMap::new();
+
+        #[cfg(any(target_os = "linux", target_os = "macos"))]
+        if let Ok(mounts)  = mounts() {
+            for mount in mounts {
+                mount_map.insert(mount.dest.clone(), mount);
+            }
+        }
+
+        mount_map
+    })
+}

+ 0 - 46
src/lib.rs

@@ -1,49 +1,3 @@
-#[macro_use]
-extern crate lazy_static;
-
-use crate::fs::mounts::MountedFs;
-
-#[cfg(target_os = "linux")]
-use proc_mounts::MountList;
-use std::collections::HashMap;
-use std::path::PathBuf;
-
-// 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! {
-    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()
-    };
-}
-
 #[allow(unused)]
 #[allow(unused)]
 pub mod fs;
 pub mod fs;
 #[allow(unused)]
 #[allow(unused)]

+ 0 - 41
src/main.rs

@@ -22,7 +22,6 @@
 #![allow(clippy::upper_case_acronyms)]
 #![allow(clippy::upper_case_acronyms)]
 #![allow(clippy::wildcard_imports)]
 #![allow(clippy::wildcard_imports)]
 
 
-use std::collections::HashMap;
 use std::env;
 use std::env;
 use std::ffi::{OsStr, OsString};
 use std::ffi::{OsStr, OsString};
 use std::io::{self, Write, ErrorKind};
 use std::io::{self, Write, ErrorKind};
@@ -33,13 +32,6 @@ use ansiterm::{ANSIStrings, Style};
 
 
 use log::*;
 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::{Dir, File};
 use crate::fs::feature::git::GitCache;
 use crate::fs::feature::git::GitCache;
 use crate::fs::filter::GitIgnore;
 use crate::fs::filter::GitIgnore;
@@ -54,39 +46,6 @@ mod options;
 mod output;
 mod output;
 mod theme;
 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! {
-    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() {
 fn main() {
     #[cfg(unix)]
     #[cfg(unix)]
     unsafe {
     unsafe {

+ 1 - 1
src/options/help.rs

@@ -53,7 +53,7 @@ LONG VIEW OPTIONS
   -H, --links              list each file's number of hard links
   -H, --links              list each file's number of hard links
   -i, --inode              list each file's inode number
   -i, --inode              list each file's inode number
   -m, --modified           use the modified timestamp field
   -m, --modified           use the modified timestamp field
-  -M, --mounts             show mount details (Linux only)
+  -M, --mounts             show mount details (Linux and MacOS only)
   -n, --numeric            list numeric user and group IDs
   -n, --numeric            list numeric user and group IDs
   -S, --blocksize          show size of allocated file system blocks
   -S, --blocksize          show size of allocated file system blocks
   -t, --time FIELD         which timestamp field to list (modified, accessed, created)
   -t, --time FIELD         which timestamp field to list (modified, accessed, created)