fsfreeze: Add tool

Add fsfreeze, a Linux-specific tool used to freeze/thaw writes to a
filesystem. It's an extremely simple tool so this should have full
feature-parity.
This commit is contained in:
Tuomas Tynkkynen 2025-01-04 14:27:06 +02:00
parent 257d142a5b
commit ca574b49d3
9 changed files with 192 additions and 0 deletions

12
Cargo.lock generated

@ -1022,6 +1022,7 @@ dependencies = [
"textwrap",
"uu_ctrlaltdel",
"uu_dmesg",
"uu_fsfreeze",
"uu_last",
"uu_lscpu",
"uu_lsmem",
@ -1053,6 +1054,17 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_fsfreeze"
version = "0.0.1"
dependencies = [
"clap",
"linux-raw-sys 0.6.5",
"regex",
"sysinfo",
"uucore",
]
[[package]]
name = "uu_last"
version = "0.0.1"

@ -26,6 +26,7 @@ default = ["feat_common_core"]
uudoc = []
feat_common_core = [
"fsfreeze",
"mountpoint",
"lscpu",
"lsmem",
@ -67,6 +68,7 @@ textwrap = { workspace = true }
dns-lookup = { workspace = true }
#
fsfreeze = { optional = true, version = "0.0.1", package = "uu_fsfreeze", path = "src/uu/fsfreeze" }
lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" }
lsmem = { optional = true, version = "0.0.1", package = "uu_lsmem", path = "src/uu/lsmem" }
mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", path = "src/uu/mountpoint" }

@ -35,6 +35,7 @@ First, reimplement the most important tools from util-linux:
Started
- `fsck`: Checks and repairs filesystems.
- `fsfreeze`: Freezes/unfreezes filesystems.
Done
- `fstrim`: Discards unused blocks on filesystems.
- `wipefs`: Wipes filesystem signatures.

@ -0,0 +1,18 @@
[package]
name = "uu_fsfreeze"
version = "0.0.1"
edition = "2021"
[lib]
path = "src/fsfreeze.rs"
[[bin]]
name = "fsfreeze"
path = "src/main.rs"
[dependencies]
clap = { workspace = true }
linux-raw-sys = { workspace = true }
regex = { workspace = true }
sysinfo = { workspace = true }
uucore = { workspace = true }

@ -0,0 +1,7 @@
# fsfreeze
```
fsfreeze <--freeze|--unfreeze> <MOUNTPOINT>
```
suspends or resumes modifications to a mounted filesystem

@ -0,0 +1,85 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use clap::{crate_version, Arg, ArgAction, ArgGroup, Command};
#[cfg(target_os = "linux")]
use std::{fs::File, io, os::fd::AsRawFd};
#[cfg(target_os = "linux")]
use uucore::{error::UIoError, libc};
use uucore::{error::UResult, format_usage, help_about, help_usage};
const ABOUT: &str = help_about!("fsfreeze.md");
const USAGE: &str = help_usage!("fsfreeze.md");
#[cfg(target_os = "linux")]
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
let mountpoint = matches.get_one::<String>("mountpoint").unwrap();
let file = File::open(mountpoint)?;
let metadata = file.metadata()?;
if !metadata.is_dir() {
return Err(UIoError::new(io::ErrorKind::InvalidData, "not a directory"));
}
let (op_name, op_code) = if matches.get_flag("freeze") {
("freeze", linux_raw_sys::ioctl::FIFREEZE)
} else {
("unfreeze", linux_raw_sys::ioctl::FITHAW)
};
if unsafe { libc::ioctl(file.as_raw_fd(), op_code.into(), 0) } < 0 {
uucore::show_error!(
"failed to {} the filesystem: {}",
op_name,
UIoError::from(io::Error::last_os_error())
);
}
Ok(())
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new("freeze")
.short('f')
.long("freeze")
.help("freeze the filesystem")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("unfreeze")
.short('u')
.long("unfreeze")
.help("unfreeze the filesystem")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("mountpoint")
.help("mountpoint of the filesystem")
.required(true)
.action(ArgAction::Set),
)
.group(
ArgGroup::new("action")
.required(true)
.args(["freeze", "unfreeze"]),
)
}
#[cfg(not(target_os = "linux"))]
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let _matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?;
Err(uucore::error::USimpleError::new(
1,
"`fsfreeze` is available only on Linux.",
))
}

@ -0,0 +1 @@
uucore::bin!(uu_fsfreeze);

@ -0,0 +1,62 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use crate::common::util::TestScenario;
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}
#[test]
fn test_operations_mutually_exclusive() {
new_ucmd!()
.arg("--freeze")
.arg("--unfreeze")
.arg("/foo")
.fails()
.code_is(1)
.stderr_contains("the argument '--freeze' cannot be used with '--unfreeze'");
}
#[cfg(target_os = "linux")]
mod linux {
use crate::common::util::TestScenario;
#[test]
fn test_fails_on_non_existing_path() {
new_ucmd!()
.arg("--unfreeze")
.arg("/non/existing")
.fails()
.code_is(1)
.stderr_contains("No such file or directory");
}
#[test]
fn test_fails_on_non_directory() {
new_ucmd!()
.arg("--unfreeze")
.arg("/dev/null")
.fails()
.code_is(1)
.stderr_contains("not a directory");
}
}
#[cfg(not(target_os = "linux"))]
mod non_linux {
use crate::common::util::TestScenario;
#[test]
fn test_fails_on_unsupported_platforms() {
new_ucmd!()
.arg("--unfreeze")
.arg("/non/existing")
.fails()
.code_is(1)
.stderr_is("fsfreeze: `fsfreeze` is available only on Linux.\n");
}
}

@ -36,3 +36,7 @@ mod test_last;
#[cfg(feature = "dmesg")]
#[path = "by-util/test_dmesg.rs"]
mod test_dmesg;
#[cfg(feature = "fsfreeze")]
#[path = "by-util/test_fsfreeze.rs"]
mod test_fsfreeze;