From 257d142a5b0434a800ab6ecddb59fa12997b3de8 Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Sat, 4 Jan 2025 14:24:57 +0200 Subject: [PATCH 1/2] Cargo.toml: Add linux-raw-sys This is needed for ioctl numbers for many of the tools. --- Cargo.lock | 6 ++++++ Cargo.toml | 1 + 2 files changed, 7 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4cea903..e623184 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,6 +393,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + [[package]] name = "log" version = "0.4.22" diff --git a/Cargo.toml b/Cargo.toml index 36510ca..21ba05a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.122" tabled = "0.17.0" dns-lookup = "2.0.4" +linux-raw-sys = { version = "0.6.5", features = ["ioctl"] } [dependencies] clap = { workspace = true } From ca574b49d372253e702f793ee7aff0e832f79a2d Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Sat, 4 Jan 2025 14:27:06 +0200 Subject: [PATCH 2/2] 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. --- Cargo.lock | 12 +++++ Cargo.toml | 2 + README.md | 1 + src/uu/fsfreeze/Cargo.toml | 18 +++++++ src/uu/fsfreeze/fsfreeze.md | 7 +++ src/uu/fsfreeze/src/fsfreeze.rs | 85 +++++++++++++++++++++++++++++++++ src/uu/fsfreeze/src/main.rs | 1 + tests/by-util/test_fsfreeze.rs | 62 ++++++++++++++++++++++++ tests/tests.rs | 4 ++ 9 files changed, 192 insertions(+) create mode 100644 src/uu/fsfreeze/Cargo.toml create mode 100644 src/uu/fsfreeze/fsfreeze.md create mode 100644 src/uu/fsfreeze/src/fsfreeze.rs create mode 100644 src/uu/fsfreeze/src/main.rs create mode 100644 tests/by-util/test_fsfreeze.rs diff --git a/Cargo.lock b/Cargo.lock index e623184..e677068 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 21ba05a..0feea1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/README.md b/README.md index 7b251d2..ff1162f 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/uu/fsfreeze/Cargo.toml b/src/uu/fsfreeze/Cargo.toml new file mode 100644 index 0000000..d96e2f0 --- /dev/null +++ b/src/uu/fsfreeze/Cargo.toml @@ -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 } diff --git a/src/uu/fsfreeze/fsfreeze.md b/src/uu/fsfreeze/fsfreeze.md new file mode 100644 index 0000000..6728b4c --- /dev/null +++ b/src/uu/fsfreeze/fsfreeze.md @@ -0,0 +1,7 @@ +# fsfreeze + +``` +fsfreeze <--freeze|--unfreeze> +``` + +suspends or resumes modifications to a mounted filesystem diff --git a/src/uu/fsfreeze/src/fsfreeze.rs b/src/uu/fsfreeze/src/fsfreeze.rs new file mode 100644 index 0000000..820d9c2 --- /dev/null +++ b/src/uu/fsfreeze/src/fsfreeze.rs @@ -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::("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.", + )) +} diff --git a/src/uu/fsfreeze/src/main.rs b/src/uu/fsfreeze/src/main.rs new file mode 100644 index 0000000..5ab60d7 --- /dev/null +++ b/src/uu/fsfreeze/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_fsfreeze); diff --git a/tests/by-util/test_fsfreeze.rs b/tests/by-util/test_fsfreeze.rs new file mode 100644 index 0000000..b55daa8 --- /dev/null +++ b/tests/by-util/test_fsfreeze.rs @@ -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"); + } +} diff --git a/tests/tests.rs b/tests/tests.rs index 4532383..824a096 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -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;