diff --git a/Cargo.lock b/Cargo.lock index 320f5ad..1f68e68 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" @@ -1016,6 +1022,7 @@ dependencies = [ "textwrap", "uu_ctrlaltdel", "uu_dmesg", + "uu_fsfreeze", "uu_last", "uu_lscpu", "uu_lsmem", @@ -1047,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 36510ca..0feea1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ default = ["feat_common_core"] uudoc = [] feat_common_core = [ + "fsfreeze", "mountpoint", "lscpu", "lsmem", @@ -55,6 +56,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 } @@ -66,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> <MOUNTPOINT> +``` + +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::<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.", + )) +} 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;