shitbox/corebox/commands/df/mod.rs

150 lines
5.0 KiB
Rust

use clap::{Arg, ArgAction, ArgMatches, Command};
use mount::MntEntries;
use shitbox::Cmd;
use size_display::Size;
use std::{error::Error, fmt, io, path::PathBuf};
#[derive(Debug, Default)]
pub struct Df;
impl Cmd for Df {
fn cli(&self) -> Command {
Command::new("df")
.about("report free disk space")
.author("Nathan Fisher")
.version(env!("CARGO_PKG_VERSION"))
.disable_help_flag(true)
.args([
Arg::new("kilobytes")
.help("Use 1024-byte units, instead of the default 512-byte units, when writing space figures.")
.short('k')
.long("kilobytes")
.action(ArgAction::SetTrue),
Arg::new("human")
.help("print sizes in powers of 1024 (e.g., 1023M)")
.short('h')
.long("human-readable")
.conflicts_with("kilobytes")
.action(ArgAction::SetTrue),
Arg::new("file")
.value_name("FILE")
.num_args(1..)
])
}
fn run(&self, matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
let units = if matches.get_flag("kilobytes") {
Units::Kilo
} else if matches.get_flag("human") {
Units::Natural
} else {
Units::Posix
};
println!("Filesystem {units} Used Avail Capacity Mounted on");
if let Some(files) = matches.get_many::<String>("file") {
for f in files {
let entry = MntEntries::new("/proc/mounts")?
.find(|e| e.dir.as_str() == f)
.ok_or(io::Error::new(io::ErrorKind::Other, "no such filesystem"))?;
print_stats(&entry.fsname, &entry.dir, units)?;
}
} else {
let entries = MntEntries::new("/proc/mounts")?.filter(|x| {
x.fstype != "proc"
&& x.fstype != "sysfs"
&& x.fstype != "securityfs"
&& x.fstype != "cgroup2"
&& x.fstype != "pstore"
&& x.fstype != "bpf"
&& x.fstype != "autofs"
&& x.fstype != "hugetlbfs"
&& x.fstype != "tracefs"
&& x.fstype != "debugfs"
&& x.fstype != "configfs"
&& x.fstype != "devpts"
&& x.fstype != "efivarfs"
&& x.fstype != "ramfs"
&& x.fstype != "binfmt_misc"
&& x.fstype != "fuse.gvfsd-fuse"
&& x.fstype != "fuse.portal"
&& x.fstype != "mqueue"
&& x.fstype != "fusectl"
});
for e in entries {
print_stats(&e.fsname, &e.dir, units)?;
}
}
Ok(())
}
fn path(&self) -> Option<shitbox::Path> {
Some(shitbox::Path::UsrBin)
}
}
#[derive(Clone, Copy, PartialEq)]
enum Units {
Posix,
Kilo,
Natural,
}
impl fmt::Display for Units {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Posix => write!(f, "512-blocks"),
Self::Kilo => write!(f, " 1K-blocks"),
Self::Natural => write!(f, " Size"),
}
}
}
fn print_stats(fs: &str, mntpt: &str, units: Units) -> Result<(), Box<dyn Error>> {
let p = PathBuf::from(mntpt);
if p.exists() {
let st = stat::statfs(mntpt)?;
let bs = match units {
Units::Posix => st.f_frsize / 512,
Units::Kilo => st.f_frsize / 1024,
Units::Natural => st.f_frsize,
};
let total = st.f_blocks * bs;
let avail = st.f_bfree * bs;
let used = total - avail;
let mut capacity = if total > 0 { (used * 100) / total } else { 0 };
if used * 100 != capacity * (used + avail) {
capacity += 1;
}
if units == Units::Natural {
print_human(fs, mntpt, total, avail, used, capacity);
return Ok(());
}
println!("{fs:<12} {total:>9} {used:>9} {avail:>9} {capacity:>7}% {mntpt}");
Ok(())
} else {
Err(io::Error::new(io::ErrorKind::Other, "fs does not exist").into())
}
}
fn print_human(fs: &str, mntpt: &str, total: u64, avail: u64, used: u64, capacity: u64) {
// Unfortunately these strings must be printed separately due to the way the
// library formats size display, as 0 will be a shorter string due to having
// no suffix, throwing off column alignment
print!("{fs:<12}");
if total > 0 {
print!("{:>9.1}", Size(total));
} else {
print!("{total:>10}");
}
if used > 0 {
print!("{:>9.1}", Size(used));
} else {
print!("{used:>10}");
}
if avail > 0 {
println!("{:9.1} {capacity:>7}% {mntpt}", Size(avail));
} else {
println!("{avail:>10} {capacity:>7}% {mntpt}");
}
}