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)] 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> { 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::("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 { 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> { 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}"); } }