Skip to content

Commit

Permalink
Merge pull request #2 from Speedy37/dev
Browse files Browse the repository at this point in the history
mountinfos
  • Loading branch information
Speedy37 authored Apr 20, 2021
2 parents 0f8d216 + dd64fd2 commit b9d71f0
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 47 deletions.
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ List mount points (windows, linux, ...)
"""

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "handleapi", "fileapi", "winerror"] }
winapi = { version = "0.3", features = [
"errhandlingapi",
"handleapi",
"fileapi",
"winerror",
"impl-default",
] }

[target.'cfg(target_os = "macos")'.dependencies]
[target.'cfg(not(target_os = "windows"))'.dependencies]
libc = { version = "0.2.93" }
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
## Example

```rust
use mountpoints::mount_points;
use mountpoints::mountpaths;

fn main() {
for mount_point in mount_points().unwrap() {
println!("{}", mount_point.display());
for mountpath in mountpaths().unwrap() {
println!("{}", mountpath);
}
}
```
Expand Down
40 changes: 34 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,50 @@ mod macos;
mod windows;

#[cfg(target_os = "linux")]
pub use linux::{mount_points, Error};
use linux as sys;
#[cfg(target_os = "macos")]
pub use macos::{mount_points, Error};
use macos as sys;
#[cfg(target_os = "windows")]
pub use windows::{mount_points, Error};
use windows as sys;

#[derive(Debug, Clone)]
pub struct MountInfo {
/// Mount path
pub path: String,
/// Available bytes to current user
pub avail: Option<u64>,
/// Free bytes
pub free: Option<u64>,
/// Size in bytes
pub size: Option<u64>,
/// Name
pub name: Option<String>,
/// Format (NTFS, FAT, ext4, ...)
pub format: Option<String>,
/// Read only
pub readonly: Option<bool>,
/// True if this mount point is likely to not be important
pub dummy: bool,
__priv: (),
}

pub use sys::{mountinfos, mountpaths, Error};
impl std::error::Error for Error {}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
for mount_point in mount_points().unwrap() {
eprintln!("{}", mount_point.display());
fn mountpaths_works() {
for mountpath in mountpaths().unwrap() {
eprintln!("{}", mountpath);
}
}
#[test]
fn mountinfosworks() {
for mountinfo in mountinfos().unwrap() {
eprintln!("{:?}", mountinfo);
}
}
}
69 changes: 57 additions & 12 deletions src/linux.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,82 @@
use crate::MountInfo;
use std::ffi::CString;
use std::fmt;
use std::fs;
use std::path::PathBuf;
use std::mem::MaybeUninit;
use std::os::raw::c_int;

#[derive(Debug)]
pub enum Error {
IoError(std::io::Error),
StatError(c_int),
NulError,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::IoError(err) => write!(f, "failed to read /proc/mounts: {}", err),
Error::StatError(err) => write!(f, "statvfs failed: {}", err),
Error::NulError => write!(f, "mount path contains NUL"),
}
}
}

pub fn mount_points() -> Result<Vec<PathBuf>, Error> {
fn _mounts(
mut cb: impl FnMut(String, bool, Option<&str>) -> Result<(), Error>,
) -> Result<(), Error> {
let mounts = fs::read_to_string("/proc/mounts").map_err(|err| Error::IoError(err))?;
let mut mount_points = Vec::new();
for mount in mounts.split('\n') {
if mount.starts_with('#') {
continue;
}
let mut it = mount.split(&[' ', '\t'][..]);
let fs = it.next();
if let Some(mount_point) = it.next() {
mount_points.push(
mount_point
.replace("\\040", " ")
.replace("\\011", "\t")
.into(),
);
let _fsname = it.next();
if let Some(mountpath) = it.next() {
let fstype = it.next();
let dummy = match fstype.unwrap_or("") {
"autofs" | "proc" | "subfs" | "debugfs" | "devpts" | "fusectl" | "mqueue"
| "rpc_pipefs" | "sysfs" | "devfs" | "kernfs" | "ignore" => true,
_ => false,
};
let path = mountpath.replace("\\040", " ").replace("\\011", "\t");
cb(path, dummy, fstype)?;
}
}
Ok(mount_points)
Ok(())
}

pub fn mountinfos() -> Result<Vec<MountInfo>, Error> {
let mut mountinfos = Vec::new();
_mounts(|path, dummy, fstype| {
let cpath = CString::new(path.as_str()).map_err(|_| Error::NulError)?;
let mut stat = MaybeUninit::<libc::statvfs>::zeroed();
let r = unsafe { libc::statvfs(cpath.as_ptr(), stat.as_mut_ptr()) };
if r != 0 {
return Err(Error::StatError(unsafe { *libc::__errno_location() }));
}
let stat = unsafe { stat.assume_init() };
mountinfos.push(MountInfo {
path,
avail: Some(stat.f_bavail.saturating_mul(u64::from(stat.f_bsize))),
free: Some(stat.f_bfree.saturating_mul(u64::from(stat.f_bsize))),
size: Some(stat.f_blocks.saturating_mul(u64::from(stat.f_frsize))),
name: None,
format: fstype.map(|s| s.to_string()),
readonly: Some((stat.f_flag & libc::ST_RDONLY) == libc::ST_RDONLY),
dummy,
__priv: (),
});
Ok(())
})?;
Ok(mountinfos)
}

pub fn mountpaths() -> Result<Vec<String>, Error> {
let mut mountpaths = Vec::new();
_mounts(|mountpath, _, _| {
mountpaths.push(mountpath);
Ok(())
})?;
Ok(mountpaths)
}
45 changes: 39 additions & 6 deletions src/macos.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::MountInfo;
use std::ffi::CStr;
use std::fmt;
use std::os::raw::{c_char, c_int};
use std::path::PathBuf;

#[allow(non_camel_case_types)]
type uid_t = u32;
Expand All @@ -14,6 +14,7 @@ struct fsid_t {
const MFSTYPENAMELEN: usize = 16;
const MAXPATHLEN: usize = 1024;
const MNT_NOWAIT: c_int = 2;
const MNT_RDONLY: u32 = 1;

#[repr(C)]
struct statfs64 {
Expand Down Expand Up @@ -70,21 +71,53 @@ impl fmt::Display for Error {
}
}

pub fn mount_points() -> Result<Vec<PathBuf>, Error> {
fn _mounts(mut cb: impl FnMut(&statfs64, String) -> Result<(), Error>) -> Result<(), Error> {
let mut mntbuf: *const statfs64 = std::ptr::null_mut();
let mut n = unsafe { getmntinfo64(&mut mntbuf, MNT_NOWAIT) };
if n <= 0 {
return Err(Error::GetMntInfo64(unsafe { *libc::__error() }));
}

let mut mount_points = Vec::with_capacity(n as usize);
while n > 0 {
let p: &statfs64 = unsafe { &*mntbuf };
let mount_point = unsafe { CStr::from_ptr(p.f_mntonname.as_ptr() as *const c_char) };
mount_points.push(mount_point.to_str().map_err(|_| Error::Utf8Error)?.into());
let mountpath = unsafe { CStr::from_ptr(p.f_mntonname.as_ptr() as *const c_char) };
cb(p, mountpath.to_str().map_err(|_| Error::Utf8Error)?.into())?;
mntbuf = unsafe { mntbuf.add(1) };
n -= 1;
}

Ok(mount_points)
Ok(())
}

pub fn mountinfos() -> Result<Vec<MountInfo>, Error> {
let mut mountinfos = Vec::new();
_mounts(|stat, path| {
mountinfos.push(MountInfo {
path,
avail: Some(stat.f_bavail.saturating_mul(u64::from(stat.f_bsize))),
free: Some(stat.f_bfree.saturating_mul(u64::from(stat.f_bsize))),
size: Some(stat.f_blocks.saturating_mul(u64::from(stat.f_bsize))),
name: None,
format: Some(
unsafe { CStr::from_ptr(stat.f_fstypename.as_ptr() as *const c_char) }
.to_str()
.map_err(|_| Error::Utf8Error)?
.into(),
),
readonly: Some((stat.f_flags & MNT_RDONLY) == MNT_RDONLY),
dummy: false,
__priv: (),
});
Ok(())
})?;
Ok(mountinfos)
}

pub fn mountpaths() -> Result<Vec<String>, Error> {
let mut mountpaths = Vec::new();
_mounts(|_, mountpath| {
mountpaths.push(mountpath);
Ok(())
})?;
Ok(mountpaths)
}
Loading

0 comments on commit b9d71f0

Please sign in to comment.