Add `swapoff` applet

This commit is contained in:
Nathan Fisher 2023-02-08 16:20:25 -05:00
parent 565e2c52d4
commit ebebf2b4e5
7 changed files with 137 additions and 58 deletions

View File

@ -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

View File

@ -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(())

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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<PathBuf> {
"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<PathBuf> {
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<PathBuf> {
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<PathBuf> {
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();
}
}
_ => {}

View File

@ -18,10 +18,18 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
"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",
];

View File

@ -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<dyn std::error::Error>> {
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::<String>("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<shitbox::Path> {
Some(shitbox::Path::Sbin)
}
}