From ebebf2b4e52d999d41a20cc5534abfee8aad03c5 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Wed, 8 Feb 2023 16:20:25 -0500 Subject: [PATCH] Add `swapoff` applet --- README.md | 6 +++ hashbox/commands/sha224sum/mod.rs | 4 +- hashbox/main.rs | 2 +- mount/src/lib.rs | 9 ++++ mount/src/mntent.rs | 88 ++++++++++++------------------- utilbox/commands/mod.rs | 10 +++- utilbox/commands/swapoff/mod.rs | 76 ++++++++++++++++++++++++++ 7 files changed, 137 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 37aae6f..df1496f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ code between applets, making for an overall smaller binary. - sha512sum - sleep - swaplabel +- swapoff - sync - true - umount @@ -80,6 +81,11 @@ for the project include: The code is not intended to be portable across different architectures, only Linux is supported. +Full compatability with GNU coreutils is not intended. Compliance with POSIX is +the main goal, with extensions provided by GNU or BSD where they make sense, and +certain tweaks to improve consistency of behavior where they do not interfere +with POSIX compliance. + ## Installation Building is done using the official Rust toolchain. It is recommended that you install your toolchain using Rustup rather than distro packages, as old compiler diff --git a/hashbox/commands/sha224sum/mod.rs b/hashbox/commands/sha224sum/mod.rs index 626bff5..c654a86 100644 --- a/hashbox/commands/sha224sum/mod.rs +++ b/hashbox/commands/sha224sum/mod.rs @@ -35,8 +35,8 @@ impl Cmd for Sha224sum { } } if erred > 0 { - println!("sha224sum: WARNING: {erred} computed checksum did NOT match"); - process::exit(1); + let msg = format!("WARNING: {erred} computed checksums did NOT match"); + return Err(io::Error::new(io::ErrorKind::Other, msg).into()); } } Ok(()) diff --git a/hashbox/main.rs b/hashbox/main.rs index 17536cc..cb00dbb 100644 --- a/hashbox/main.rs +++ b/hashbox/main.rs @@ -12,7 +12,7 @@ fn main() { process::exit(1); } } else { - eprintln!("shitbox: Error: unknown command {progname}"); + eprintln!("hashbox: Error: unknown command {progname}"); process::exit(1); } } diff --git a/mount/src/lib.rs b/mount/src/lib.rs index c422024..3226451 100644 --- a/mount/src/lib.rs +++ b/mount/src/lib.rs @@ -55,6 +55,15 @@ pub fn swapon(dev: &str, flags: usize) -> io::Result<()> { } } +pub fn swapoff(path: &str) -> io::Result<()> { + let ret = unsafe { syscall!(SWAPOFF, CString::new(path)?.as_ptr()) }; + if ret == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + pub fn umount(special: &str, flags: u32) -> io::Result<()> { let ret = unsafe { syscall!(UMOUNT2, CString::new(special)?.as_ptr(), flags) }; if ret == 0 { diff --git a/mount/src/mntent.rs b/mount/src/mntent.rs index 9785f91..6f23413 100644 --- a/mount/src/mntent.rs +++ b/mount/src/mntent.rs @@ -8,8 +8,7 @@ use blkid::{ use std::{ fmt::{self, Write}, fs::File, - io, - os::unix::fs::MetadataExt, + io::{self, BufRead, BufReader}, path::PathBuf, str::FromStr, }; @@ -48,6 +47,30 @@ impl MntEntry { false } + pub fn is_swapped(&self) -> bool { + if self.fstype != "swap" { + return false; + } + let dev = self.device().map(|x| x.to_str().map(|x| x.to_string())); + let Ok(Some(dev)) = dev else { return false }; + let Ok(fd) = File::open("/proc/swaps") else { + return false; + }; + let reader = BufReader::new(fd); + for line in reader.lines() { + let Ok(line) = line else { + return false; + }; + let swap = line.split_whitespace().next(); + if let Some(swap) = swap { + if swap == &dev { + return true; + } + } + } + false + } + pub fn parse_opts(&self) -> Result<(u32, String), fmt::Error> { let mut res = (0, String::new()); let splitopts = self.opts.split(','); @@ -93,21 +116,7 @@ impl MntEntry { s if s.starts_with("LABEL=") => dev_from_label(s), s if s.starts_with("PARTUUID=") => dev_from_partuuid(s), s if s.starts_with("PARTLABEL=") => dev_from_partlabel(s), - s => { - let mut p = PathBuf::from(s); - if p.exists() { - p = p.canonicalize()?; - let fd = File::open(&p)?; - let meta = fd.metadata()?; - if meta.rdev() != 0 { - Ok(p) - } else { - Err(io::Error::new(io::ErrorKind::Other, "not a block device")) - } - } else { - Err(io::Error::new(io::ErrorKind::NotFound, "no such file")) - } - } + s => PathBuf::from(s).canonicalize(), } } } @@ -162,22 +171,17 @@ fn dev_from_uuid(s: &str) -> io::Result { "this should never happen", ))?; let cache = Cache::new().map_err(|x| io::Error::new(io::ErrorKind::Other, x))?; + cache + .probe_all() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let devs = cache.devs(); for dev in devs { let tags = dev.tags(); for tag in tags { match tag.typ() { TagType::Superblock(SuperblockTag::Uuid) => { - if uuid.to_lowercase().as_str() == tag.value() { - let devname = dev.name(); - let devfile = devname.canonicalize()?; - let fd = File::open(&devfile)?; - let meta = fd.metadata()?; - if meta.rdev() != 0 { - return Ok(devfile); - } else { - return Err(io::Error::new(io::ErrorKind::Other, "not a block device")); - } + if uuid.to_lowercase().as_str().trim() == tag.value().trim() { + return dev.name().canonicalize(); } } _ => {} @@ -200,15 +204,7 @@ fn dev_from_label(s: &str) -> io::Result { match tag.typ() { TagType::Superblock(SuperblockTag::Label) => { if label == tag.value() { - let devname = dev.name(); - let devfile = devname.canonicalize()?; - let fd = File::open(&devfile)?; - let meta = fd.metadata()?; - if meta.rdev() != 0 { - return Ok(devfile); - } else { - return Err(io::Error::new(io::ErrorKind::Other, "not a block device")); - } + return dev.name().canonicalize(); } } _ => {} @@ -231,15 +227,7 @@ fn dev_from_partuuid(s: &str) -> io::Result { match tag.typ() { TagType::Partition(PartitionTag::PartEntryUuid) => { if partlabel == tag.value() { - let devname = dev.name(); - let devfile = devname.canonicalize()?; - let fd = File::open(&devfile)?; - let meta = fd.metadata()?; - if meta.rdev() != 0 { - return Ok(devfile); - } else { - return Err(io::Error::new(io::ErrorKind::Other, "not a block device")); - } + return dev.name().canonicalize(); } } _ => {} @@ -262,15 +250,7 @@ fn dev_from_partlabel(s: &str) -> io::Result { match tag.typ() { TagType::Partition(PartitionTag::PartEntryName) => { if partlabel == tag.value() { - let devname = dev.name(); - let devfile = devname.canonicalize()?; - let fd = File::open(&devfile)?; - let meta = fd.metadata()?; - if meta.rdev() != 0 { - return Ok(devfile); - } else { - return Err(io::Error::new(io::ErrorKind::Other, "not a block device")); - } + return dev.name().canonicalize(); } } _ => {} diff --git a/utilbox/commands/mod.rs b/utilbox/commands/mod.rs index 03b926a..35d573a 100644 --- a/utilbox/commands/mod.rs +++ b/utilbox/commands/mod.rs @@ -18,10 +18,18 @@ pub fn get(name: &str) -> Option> { "clear" => Some(Box::new(clear::Clear::default())), "mountpoint" => Some(Box::new(mountpoint::Mountpoint::default())), "swaplabel" => Some(Box::new(swaplabel::Swaplabel::default())), + "swapoff" => Some(Box::new(swapoff::Swapoff::default())), "umount" => Some(Box::new(umount::Umount::default())), "utilbox" => Some(Box::new(utilbox::Utilbox::default())), _ => None, } } -pub static COMMANDS: [&str; 5] = ["clear", "mountpoint", "swaplabel", "umount", "utilbox"]; +pub static COMMANDS: [&str; 6] = [ + "clear", + "mountpoint", + "swaplabel", + "swapoff", + "umount", + "utilbox", +]; diff --git a/utilbox/commands/swapoff/mod.rs b/utilbox/commands/swapoff/mod.rs index 8b13789..e9d0a2a 100644 --- a/utilbox/commands/swapoff/mod.rs +++ b/utilbox/commands/swapoff/mod.rs @@ -1 +1,77 @@ +use clap::{Arg, ArgAction, Command}; +use mount::MntEntries; +use shitbox::{args, Cmd}; +use std::{ + fs::File, + io::{self, BufRead, BufReader}, +}; +#[derive(Debug, Default)] +pub struct Swapoff; + +impl Cmd for Swapoff { + fn cli(&self) -> clap::Command { + Command::new("swapoff") + .about("disable devices and files for paging and swapping") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([ + Arg::new("all") + .help("Disable swapping on all known swap devices and files as found in /etc/fstab.") + .short('a') + .long("all") + .action(ArgAction::SetTrue), + args::verbose(), + Arg::new("device") + .num_args(1..) + .conflicts_with("all") + .required_unless_present("all"), + ]) + } + + fn run(&self, matches: &clap::ArgMatches) -> Result<(), Box> { + if matches.get_flag("all") { + if let Ok(entries) = MntEntries::new("/etc/fstab") { + for e in entries.filter(|x| &x.fstype == "swap") { + if e.is_swapped() { + let dev = e + .device()? + .to_str() + .ok_or(io::Error::new(io::ErrorKind::Other, "utf8 error"))? + .to_string(); + mount::swapoff(&dev)?; + if matches.get_flag("verbose") { + println!("swapoff {dev}"); + } + } + } + } + } else if let Some(devices) = matches.get_many::("device") { + for d in devices { + let fd = File::open("/proc/swaps")?; + let reader = BufReader::new(fd); + for line in reader.lines() { + let line = line?; + let swap = line.split_whitespace().next().ok_or(io::Error::new( + io::ErrorKind::Other, + "Error reading /proc/swaps", + ))?; + if swap == d { + mount::swapoff(d)?; + if matches.get_flag("verbose") { + println!("swapoff {d}"); + } + return Ok(()); + } + } + let msg = format!("{d} is not swapped"); + return Err(Box::new(io::Error::new(io::ErrorKind::Other, msg))); + } + } + Ok(()) + } + + fn path(&self) -> Option { + Some(shitbox::Path::Sbin) + } +}