diff --git a/Cargo.lock b/Cargo.lock index 47b33f13..1fe5c1b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -75,9 +81,11 @@ dependencies = [ "anyhow", "bincode", "camino", + "cap-std-ext", "chrono", "clap", "env_logger", + "fail", "fn-error-context", "fs2", "hex", @@ -88,6 +96,7 @@ dependencies = [ "openat-ext", "openssl", "os-release", + "regex", "rustix", "serde", "serde_json", @@ -108,6 +117,58 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +[[package]] +name = "cap-primitives" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d00bd8d26c4270d950eaaa837387964a2089a1c3c349a690a1fa03221d29531" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix", + "windows-sys 0.52.0", + "winx", +] + +[[package]] +name = "cap-std" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19eb8e3d71996828751c1ed3908a439639752ac6bdc874e41469ef7fc15fbd7f" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix", +] + +[[package]] +name = "cap-std-ext" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0279cf1f7b6cbeeb98e6946e8fea58136f691d4d0aa8c775f4439a05030a481" +dependencies = [ + "cap-primitives", + "cap-tempfile", + "rustix", +] + +[[package]] +name = "cap-tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53880047c3f37cd64947775f0526795498d614182603a718c792616b762ce777" +dependencies = [ + "cap-std", + "rand", + "rustix", + "uuid", +] + [[package]] name = "cc" version = "1.0.83" @@ -234,6 +295,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fail" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5e43d0f78a42ad591453aedb1d7ae631ce7ee445c7643691055a9ed8d3b01c" +dependencies = [ + "log", + "once_cell", + "rand", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -266,6 +338,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fs-set-times" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" +dependencies = [ + "io-lifetimes", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fs2" version = "0.4.3" @@ -369,6 +452,28 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-extras" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9f046b9af244f13b3bd939f55d16830ac3a201e8a9ba9661bfcb03e2be72b9b" +dependencies = [ + "io-lifetimes", + "windows-sys 0.52.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "is-terminal" version = "0.4.9" @@ -437,6 +542,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "memchr" version = "2.6.4" @@ -712,8 +823,10 @@ checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.4.1", "errno", + "itoa", "libc", "linux-raw-sys", + "once_cell", "windows-sys 0.52.0", ] @@ -873,6 +986,7 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ + "getrandom", "serde", ] @@ -1135,3 +1249,13 @@ name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winx" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346" +dependencies = [ + "bitflags 2.4.1", + "windows-sys 0.52.0", +] diff --git a/Cargo.toml b/Cargo.toml index 9fdcccbe..a37090b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,12 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" bincode = "1.3.2" +cap-std-ext = "4.0.0" camino = "1.1.7" chrono = { version = "0.4.38", features = ["serde"] } clap = { version = "3.2", default-features = false, features = ["cargo", "derive", "std", "suggestions"] } env_logger = "0.10" +fail = { version = "0.5", features = ["failpoints"] } fn-error-context = "0.2.1" fs2 = "0.4.3" hex = "0.4.3" @@ -35,6 +37,7 @@ openat = "0.1.20" openat-ext = ">= 0.2.2, < 0.3.0" openssl = "^0.10" os-release = "0.1.0" +regex = "1.10.4" rustix = { version = "0.38.34", features = ["process", "fs"] } serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" diff --git a/src/bootupd.rs b/src/bootupd.rs index 72d025d0..4f029e83 100644 --- a/src/bootupd.rs +++ b/src/bootupd.rs @@ -396,6 +396,7 @@ pub(crate) fn print_status(status: &Status) -> Result<()> { } pub(crate) fn client_run_update() -> Result<()> { + crate::try_fail_point!("update"); let status: Status = status()?; if status.components.is_empty() && status.adoptable.is_empty() { println!("No components installed."); @@ -489,3 +490,17 @@ pub(crate) fn client_run_validate() -> Result<()> { } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_failpoint_update() { + let guard = fail::FailScenario::setup(); + fail::cfg("update", "return").unwrap(); + let r = client_run_update(); + assert_eq!(r.is_err(), true); + guard.teardown(); + } +} diff --git a/src/failpoints.rs b/src/failpoints.rs new file mode 100644 index 00000000..78dce44f --- /dev/null +++ b/src/failpoints.rs @@ -0,0 +1,21 @@ +//! Wrappers and utilities on top of the `fail` crate. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/// TODO: Use https://github.com/tikv/fail-rs/pull/68 once it merges +/// copy from https://github.com/coreos/rpm-ostree/commit/aa8d7fb0ceaabfaf10252180e2ddee049d07aae3#diff-adcc419e139605fae34d17b31418dbaf515af2fe9fb766fcbdb2eaad862b3daa +#[macro_export] +macro_rules! try_fail_point { + ($name:expr) => {{ + if let Some(e) = fail::eval($name, |msg| { + let msg = msg.unwrap_or_else(|| "synthetic failpoint".to_string()); + anyhow::Error::msg(msg) + }) { + return Err(From::from(e)); + } + }}; + ($name:expr, $cond:expr) => {{ + if $cond { + $crate::try_fail_point!($name); + } + }}; +} diff --git a/src/filetree.rs b/src/filetree.rs index 047ca384..8234dcba 100644 --- a/src/filetree.rs +++ b/src/filetree.rs @@ -395,6 +395,7 @@ pub(crate) fn apply_diff( .local_rename(tmp, dst) .with_context(|| format!("rename for {} and {:?}", tmp, dst))?; } + crate::try_fail_point!("update::exchange"); } // Ensure all of the updates & changes are written persistently to disk if !opts.skip_sync { @@ -705,7 +706,6 @@ mod tests { let b_btime_foo_new = fs::metadata(pb.join(foo))?.created()?; assert_eq!(b_btime_foo_new, b_btime_foo); } - Ok(()) } } diff --git a/src/main.rs b/src/main.rs index b075bde0..7c7cb40c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ mod component; mod coreos; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] mod efi; +mod failpoints; mod filesystem; mod filetree; #[cfg(any( @@ -43,6 +44,7 @@ use clap::crate_name; /// Binary entrypoint, for both daemon and client logic. fn main() { + let _scenario = fail::FailScenario::setup(); let exit_code = run_cli(); std::process::exit(exit_code); } diff --git a/tests/e2e-update/e2e-update-in-vm.sh b/tests/e2e-update/e2e-update-in-vm.sh index fd969cad..0956bdfa 100755 --- a/tests/e2e-update/e2e-update-in-vm.sh +++ b/tests/e2e-update/e2e-update-in-vm.sh @@ -67,7 +67,12 @@ tmpefimount=$(mount_tmp_efi) assert_not_has_file ${tmpefimount}/EFI/fedora/test-bootupd.efi -bootupctl update | tee out.txt +if env FAILPOINTS='update::exchange=return' bootupctl update -vvv 2>err.txt; then + fatal "should have errored" +fi +assert_file_has_content err.txt "error: .*synthetic failpoint" + +bootupctl update -vvv | tee out.txt assert_file_has_content out.txt "Previous EFI: .*" assert_file_has_content out.txt "Updated EFI: ${TARGET_GRUB_PKG}.*,test-bootupd-payload-1.0" diff --git a/tests/e2e-update/e2e-update.sh b/tests/e2e-update/e2e-update.sh index e45623f7..5fc19e87 100755 --- a/tests/e2e-update/e2e-update.sh +++ b/tests/e2e-update/e2e-update.sh @@ -24,10 +24,14 @@ export test_tmpdir=${testtmp} # This is new content for our update test_bootupd_payload_file=/boot/efi/EFI/fedora/test-bootupd.efi +test_bootupd_payload_file1=/boot/efi/EFI/BOOT/test-bootupd1.efi build_rpm test-bootupd-payload \ - files ${test_bootupd_payload_file} \ + files "${test_bootupd_payload_file} + ${test_bootupd_payload_file1}" \ install "mkdir -p %{buildroot}/$(dirname ${test_bootupd_payload_file}) - echo test-payload > %{buildroot}/${test_bootupd_payload_file}" + echo test-payload > %{buildroot}/${test_bootupd_payload_file} + mkdir -p %{buildroot}/$(dirname ${test_bootupd_payload_file1}) + echo test-payload1 > %{buildroot}/${test_bootupd_payload_file1}" # Start in cosa dir cd ${COSA_DIR}