From 42eacbd57a4b73948aebbcbe3edc0d312abe4cb3 Mon Sep 17 00:00:00 2001 From: Cao Yuhang Date: Tue, 14 Jul 2026 15:16:17 +0000 Subject: [PATCH 1/3] ci: pin CapOS dependency baseline --- .github/workflows/capos-full-qemu.yml | 2 +- .github/workflows/weekly-randomized-model.yml | 2 +- scripts/qemu/build_capos_image.sh | 2 +- scripts/qemu/capos_build.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/capos-full-qemu.yml b/.github/workflows/capos-full-qemu.yml index a0c543cc..b1e9c9e9 100644 --- a/.github/workflows/capos-full-qemu.yml +++ b/.github/workflows/capos-full-qemu.yml @@ -24,7 +24,7 @@ permissions: env: CARGO_TERM_COLOR: always - ARGOSFS_CAPOS_REF: 168f01fc44f627ab07f23dd9590453f0ba5fe7fc + ARGOSFS_CAPOS_REF: 3903b2968a692fdc77e05f63eb3a1cfa9a739999 jobs: capos-full-build: diff --git a/.github/workflows/weekly-randomized-model.yml b/.github/workflows/weekly-randomized-model.yml index c9727bce..0f6092a9 100644 --- a/.github/workflows/weekly-randomized-model.yml +++ b/.github/workflows/weekly-randomized-model.yml @@ -49,7 +49,7 @@ permissions: env: CARGO_TERM_COLOR: always - ARGOSFS_CAPOS_REF: 168f01fc44f627ab07f23dd9590453f0ba5fe7fc + ARGOSFS_CAPOS_REF: 3903b2968a692fdc77e05f63eb3a1cfa9a739999 jobs: prepare-matrix: diff --git a/scripts/qemu/build_capos_image.sh b/scripts/qemu/build_capos_image.sh index 1c04224a..83a21895 100755 --- a/scripts/qemu/build_capos_image.sh +++ b/scripts/qemu/build_capos_image.sh @@ -38,7 +38,7 @@ ARGOSFS_CAPOS_MAKE_JOBS="$make_jobs" \ ARGOSFS_CAPOS_MAKE_TARGET="$make_target" \ ARGOSFS_CAPOS_MAKE_V="${ARGOSFS_CAPOS_MAKE_V:-}" \ CAPOS_REPO="${CAPOS_REPO:-https://github.com/fwerkor/capos.git}" \ -CAPOS_REF="${CAPOS_REF:-168f01fc44f627ab07f23dd9590453f0ba5fe7fc}" \ +CAPOS_REF="${CAPOS_REF:-3903b2968a692fdc77e05f63eb3a1cfa9a739999}" \ CAPOS_LOCAL_SOURCE="${CAPOS_LOCAL_SOURCE:-}" \ "$repo/scripts/qemu/capos_build.sh" diff --git a/scripts/qemu/capos_build.sh b/scripts/qemu/capos_build.sh index 77c8ba3b..9c250c6f 100755 --- a/scripts/qemu/capos_build.sh +++ b/scripts/qemu/capos_build.sh @@ -4,7 +4,7 @@ set -euo pipefail repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" artifacts="${ARGOSFS_TEST_ARTIFACTS:-$repo/target/argosfs-test-artifacts/capos-build}" capos_repo="${CAPOS_REPO:-https://github.com/fwerkor/capos.git}" -capos_ref="${CAPOS_REF:-168f01fc44f627ab07f23dd9590453f0ba5fe7fc}" +capos_ref="${CAPOS_REF:-3903b2968a692fdc77e05f63eb3a1cfa9a739999}" capos_local_source="${CAPOS_LOCAL_SOURCE:-}" capos_full_compile="${ARGOSFS_CAPOS_FULL_COMPILE:-1}" capos_make_jobs="${ARGOSFS_CAPOS_MAKE_JOBS:-$(nproc 2>/dev/null || echo 2)}" From 28b0cf712ea1c049465060bf9ad2c19e8c1b118b Mon Sep 17 00:00:00 2001 From: Cao Yuhang Date: Tue, 14 Jul 2026 17:47:36 +0000 Subject: [PATCH 2/3] ci: stream QEMU guest scripts reliably --- scripts/qemu/crash_recovery.sh | 25 ++++---------- scripts/qemu/hotplug.sh | 38 ++++++++------------- scripts/qemu/lib/common.sh | 55 ++++++++++++++++++++++++++++++ scripts/qemu/mixed_chaos.sh | 41 +++++++--------------- scripts/qemu/reboot_persistence.sh | 13 +++---- 5 files changed, 92 insertions(+), 80 deletions(-) diff --git a/scripts/qemu/crash_recovery.sh b/scripts/qemu/crash_recovery.sh index a2afbe7d..c8f602d7 100755 --- a/scripts/qemu/crash_recovery.sh +++ b/scripts/qemu/crash_recovery.sh @@ -17,7 +17,6 @@ commands2="$artifacts/qemu-crash-recovery-phase2.commands" reject="${ARGOSFS_QEMU_REJECT:-Kernel panic|Bad file descriptor|argosfs-initrd: emergency|Oops:|BUG:|segfault}" timeout_s="${ARGOSFS_QEMU_TIMEOUT:-1800}" console_timeout_s="${ARGOSFS_QEMU_CRASH_CONSOLE_TIMEOUT:-600}" -command_delay_s="${ARGOSFS_QEMU_CRASH_COMMAND_DELAY:-1}" done_marker="ARGOSFS_QEMU_CRASH_RECOVERY_DONE" disks=() @@ -111,18 +110,11 @@ run_phase1_until_kill_marker() { # shellcheck disable=SC2094 ( argosfs_qemu_wait_console_prompt "$log1" 1 "$console_timeout_s" "$reject" "crash-recovery phase1 console prompt" || exit $? - printf '\r' - sleep "$command_delay_s" - while IFS= read -r line; do - printf '%s\r' "$line" || exit 0 - sleep "$command_delay_s" - if [ "$line" = "echo ARGOSFS_WAIT_CRASH_HOTPLUG" ]; then - argosfs_qemu_wait_log_marker "$log1" ARGOSFS_WAIT_CRASH_HOTPLUG 180 - for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done - idx=0 - for disk in "${disks[@]}"; do qemu_device_add "$idx" "$disk"; idx=$((idx + 1)); done - fi - done <"$commands1" + argosfs_qemu_stream_script "$commands1" 1 /tmp/argosfs-qemu-crash-phase1.sh "$log1" + argosfs_qemu_wait_log_marker "$log1" ARGOSFS_WAIT_CRASH_HOTPLUG 180 + for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done + idx=0 + for disk in "${disks[@]}"; do qemu_device_add "$idx" "$disk"; idx=$((idx + 1)); done ) | timeout "$timeout_s" "$qemu_bin" "${qemu_args[@]}" >"$log1" 2>&1 & qemu_pid=$! deadline=$((SECONDS + timeout_s)) @@ -149,12 +141,7 @@ run_phase2() { # shellcheck disable=SC2094 ( argosfs_qemu_wait_console_prompt "$log2" 1 "$console_timeout_s" "$reject" "crash-recovery phase2 console prompt" || exit $? - printf '\r' - sleep "$command_delay_s" - while IFS= read -r line; do - printf '%s\r' "$line" || exit 0 - sleep "$command_delay_s" - done <"$commands2" + argosfs_qemu_stream_script "$commands2" 1 /tmp/argosfs-qemu-crash-phase2.sh "$log2" ) | timeout "$timeout_s" "$qemu_bin" "${qemu_args[@]}" >"$log2" 2>&1 status=${PIPESTATUS[1]} set -e diff --git a/scripts/qemu/hotplug.sh b/scripts/qemu/hotplug.sh index 9a58e91b..8b7ed904 100755 --- a/scripts/qemu/hotplug.sh +++ b/scripts/qemu/hotplug.sh @@ -19,7 +19,6 @@ commands="$artifacts/qemu-hotplug.commands" reject="${ARGOSFS_QEMU_REJECT:-Kernel panic|Bad file descriptor|argosfs-initrd: emergency|Oops:|BUG:|I/O error|missing device}" timeout_s="${ARGOSFS_QEMU_TIMEOUT:-260}" console_timeout_s="${ARGOSFS_QEMU_HOTPLUG_CONSOLE_TIMEOUT:-420}" -command_delay_s="${ARGOSFS_QEMU_HOTPLUG_COMMAND_DELAY:-1}" done_marker="ARGOSFS_QEMU_HOTPLUG_DONE" cat >"$commands" <<'CMDS' @@ -66,29 +65,20 @@ set +e # shellcheck disable=SC2094 ( argosfs_qemu_wait_console_prompt "$log" 1 "$console_timeout_s" "$reject" "hotplug console prompt" || exit $? - printf '\r' - sleep "$command_delay_s" - while IFS= read -r line; do - printf '%s\r' "$line" - sleep "$command_delay_s" - if [ "$line" = "echo ARGOSFS_WAIT_HOTPLUG" ]; then - argosfs_qemu_wait_log_marker "$log" ARGOSFS_WAIT_HOTPLUG 180 - for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done - argosfs_qemu_monitor_command "$monitor" \ - "drive_add 0 if=none,file=$hotplug_disk,format=raw,id=hot0" "$monitor_log" - bus_arg="$(argosfs_qemu_hotplug_bus_arg hot 0)" - rom_arg="" - [ "$arch" != "arm64" ] || rom_arg=",romfile=" - argosfs_qemu_monitor_command "$monitor" \ - "device_add virtio-blk-pci,drive=hot0,id=hotdisk0${bus_arg}${rom_arg}" "$monitor_log" - fi - if [ "$line" = "echo ARGOSFS_WAIT_UNPLUG" ]; then - argosfs_qemu_wait_log_marker "$log" ARGOSFS_WAIT_UNPLUG 600 - argosfs_qemu_monitor_command "$monitor" "device_del hotdisk0" "$monitor_log" - sleep 2 - argosfs_qemu_monitor_command "$monitor" "drive_del hot0" "$monitor_log" "Device '[^']+' not found" - fi - done <"$commands" + argosfs_qemu_stream_script "$commands" 1 /tmp/argosfs-qemu-hotplug.sh "$log" + argosfs_qemu_wait_log_marker "$log" ARGOSFS_WAIT_HOTPLUG 180 + for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done + argosfs_qemu_monitor_command "$monitor" \ + "drive_add 0 if=none,file=$hotplug_disk,format=raw,id=hot0" "$monitor_log" + bus_arg="$(argosfs_qemu_hotplug_bus_arg hot 0)" + rom_arg="" + [ "$arch" != "arm64" ] || rom_arg=",romfile=" + argosfs_qemu_monitor_command "$monitor" \ + "device_add virtio-blk-pci,drive=hot0,id=hotdisk0${bus_arg}${rom_arg}" "$monitor_log" + argosfs_qemu_wait_log_marker "$log" ARGOSFS_WAIT_UNPLUG 600 + argosfs_qemu_monitor_command "$monitor" "device_del hotdisk0" "$monitor_log" + sleep 2 + argosfs_qemu_monitor_command "$monitor" "drive_del hot0" "$monitor_log" "Device '[^']+' not found" ) | timeout "$timeout_s" "$qemu_bin" "${qemu_args[@]}" >"$log" 2>&1 status=${PIPESTATUS[1]} set -e diff --git a/scripts/qemu/lib/common.sh b/scripts/qemu/lib/common.sh index ed8dbe73..fbdadaed 100755 --- a/scripts/qemu/lib/common.sh +++ b/scripts/qemu/lib/common.sh @@ -152,6 +152,61 @@ argosfs_qemu_wait_log_marker() { return 1 } +argosfs_qemu_stream_script() { + local script="$1" + local fd="${2:-1}" + local remote="${3:-/tmp/argosfs-qemu-script.sh}" + local log="${4:-}" + local line_delay="${ARGOSFS_QEMU_SCRIPT_LINE_DELAY:-0.01}" + local delimiter="ARGOSFS_QEMU_SCRIPT_EOF" + local marker_id + local shell_marker + local upload_marker + local deadline + local line + + if grep -Fxq "$delimiter" "$script"; then + echo "QEMU guest script contains reserved delimiter: $delimiter" >&2 + return 2 + fi + marker_id="$(printf '%s' "$remote" | cksum | awk '{print $1}')" + shell_marker="ARGOSFS_QEMU_SHELL_READY_$marker_id" + upload_marker="ARGOSFS_QEMU_UPLOAD_READY_$marker_id" + + printf '\r' >&"$fd" + if [ -n "$log" ]; then + deadline=$((SECONDS + ${ARGOSFS_QEMU_SHELL_READY_TIMEOUT:-60})) + while [ "$SECONDS" -lt "$deadline" ]; do + if awk -v marker="$shell_marker" \ + '{ sub(/\r$/, ""); if ($0 == marker) found = 1 } END { exit !found }' "$log" 2>/dev/null; then + break + fi + printf 'echo %s\r' "$shell_marker" >&"$fd" + sleep 1 + done + if ! awk -v marker="$shell_marker" \ + '{ sub(/\r$/, ""); if ($0 == marker) found = 1 } END { exit !found }' "$log" 2>/dev/null; then + echo "timed out waiting for QEMU guest shell: $shell_marker" >&2 + return 1 + fi + else + sleep "${ARGOSFS_QEMU_SCRIPT_START_DELAY:-1}" + fi + + printf 'stty -echo; echo %s\r' "$upload_marker" >&"$fd" + if [ -n "$log" ]; then + argosfs_qemu_wait_log_marker "$log" "$upload_marker" "${ARGOSFS_QEMU_SCRIPT_READY_TIMEOUT:-30}" || return $? + else + sleep "${ARGOSFS_QEMU_SCRIPT_STTY_DELAY:-0.2}" + fi + printf "cat >'%s' <<'%s'\r" "$remote" "$delimiter" >&"$fd" + while IFS= read -r line || [ -n "$line" ]; do + printf '%s\r' "$line" >&"$fd" + sleep "$line_delay" + done <"$script" + printf '%s\r' "$delimiter" >&"$fd" + printf "stty echo; sh '%s'\r" "$remote" >&"$fd" +} argosfs_qemu_wait_console_prompt() { local log="$1" local min_count="${2:-1}" diff --git a/scripts/qemu/mixed_chaos.sh b/scripts/qemu/mixed_chaos.sh index 2869926f..c377ee49 100755 --- a/scripts/qemu/mixed_chaos.sh +++ b/scripts/qemu/mixed_chaos.sh @@ -17,7 +17,6 @@ commands2="$artifacts/qemu-mixed-chaos-phase2.commands" reject="${ARGOSFS_QEMU_REJECT:-Kernel panic|Bad file descriptor|argosfs-initrd: emergency|Oops:|BUG:|segfault}" timeout_s="${ARGOSFS_QEMU_TIMEOUT:-3000}" console_timeout_s="${ARGOSFS_QEMU_CHAOS_CONSOLE_TIMEOUT:-600}" -command_delay_s="${ARGOSFS_QEMU_CHAOS_COMMAND_DELAY:-1}" worker_count="${ARGOSFS_QEMU_CHAOS_WORKERS:-6}" file_count="${ARGOSFS_QEMU_CHAOS_FILES:-160}" if [ "$file_count" -lt 150 ]; then @@ -203,18 +202,11 @@ run_phase1_until_kill_marker() { # shellcheck disable=SC2094 ( argosfs_qemu_wait_console_prompt "$log1" 1 "$console_timeout_s" "$reject" "mixed-chaos phase1 console prompt" || exit $? - printf '\r' - sleep "$command_delay_s" - while IFS= read -r line; do - printf '%s\r' "$line" - sleep "$command_delay_s" - if [ "$line" = "echo ARGOSFS_WAIT_CHAOS_HOTPLUG" ]; then - argosfs_qemu_wait_log_marker "$log1" ARGOSFS_WAIT_CHAOS_HOTPLUG 180 - for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done - idx=0 - for disk in "${disks[@]}"; do qemu_device_add chaos "$idx" "$disk"; idx=$((idx + 1)); done - fi - done <"$commands1" + argosfs_qemu_stream_script "$commands1" 1 /tmp/argosfs-qemu-mixed-phase1.sh "$log1" + argosfs_qemu_wait_log_marker "$log1" ARGOSFS_WAIT_CHAOS_HOTPLUG 180 + for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done + idx=0 + for disk in "${disks[@]}"; do qemu_device_add chaos "$idx" "$disk"; idx=$((idx + 1)); done ) | timeout "$timeout_s" "$qemu_bin" "${qemu_args[@]}" >"$log1" 2>&1 & qemu_pid=$! deadline=$((SECONDS + timeout_s)) @@ -248,21 +240,14 @@ run_phase2() { # shellcheck disable=SC2094 ( argosfs_qemu_wait_console_prompt "$log2" 1 "$console_timeout_s" "$reject" "mixed-chaos phase2 console prompt" || exit $? - printf '\r' - sleep "$command_delay_s" - while IFS= read -r line; do - printf '%s\r' "$line" - sleep "$command_delay_s" - if [ "$line" = "echo ARGOSFS_WAIT_CHAOS_REATTACH" ]; then - argosfs_qemu_wait_log_marker "$log2" ARGOSFS_WAIT_CHAOS_REATTACH 180 - for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done - qemu_device_add recover 0 "${disks[0]}" - qemu_device_add recover 1 "${disks[2]}" - qemu_device_add recover 2 "${disks[3]}" - qemu_device_add recover 3 "${disks[4]}" - qemu_device_add recover 4 "${disks[5]}" - fi - done <"$commands2" + argosfs_qemu_stream_script "$commands2" 1 /tmp/argosfs-qemu-mixed-phase2.sh "$log2" + argosfs_qemu_wait_log_marker "$log2" ARGOSFS_WAIT_CHAOS_REATTACH 180 + for _ in $(seq 1 30); do [ -S "$monitor" ] && break; sleep 1; done + qemu_device_add recover 0 "${disks[0]}" + qemu_device_add recover 1 "${disks[2]}" + qemu_device_add recover 2 "${disks[3]}" + qemu_device_add recover 3 "${disks[4]}" + qemu_device_add recover 4 "${disks[5]}" ) | timeout "$timeout_s" "$qemu_bin" "${qemu_args[@]}" >"$log2" 2>&1 status=${PIPESTATUS[1]} set -e diff --git a/scripts/qemu/reboot_persistence.sh b/scripts/qemu/reboot_persistence.sh index dd36b388..c0572bce 100755 --- a/scripts/qemu/reboot_persistence.sh +++ b/scripts/qemu/reboot_persistence.sh @@ -17,7 +17,6 @@ reject="${ARGOSFS_QEMU_REJECT:-Kernel panic|Bad file descriptor|argosfs-initrd: timeout_s="${ARGOSFS_QEMU_TIMEOUT:-600}" login_delay_s="${ARGOSFS_QEMU_REBOOT_LOGIN_DELAY:-420}" reboot_delay_s="${ARGOSFS_QEMU_REBOOT_DELAY:-600}" -command_delay_s="${ARGOSFS_QEMU_REBOOT_COMMAND_DELAY:-1}" done_marker="ARGOSFS_QEMU_REBOOT_DONE" cat >"$commands1" <<'CMDS' @@ -51,12 +50,8 @@ argosfs_qemu_build_args send_command_file() { local file="$1" - printf '\r' >&3 - sleep "$command_delay_s" - while IFS= read -r line; do - printf '%s\r' "$line" >&3 - sleep "$command_delay_s" - done <"$file" + local remote="$2" + argosfs_qemu_stream_script "$file" 3 "$remote" "$log" } wait_for_log_count() { @@ -94,14 +89,14 @@ exec 3>"$stdin_fifo" wait_status=0 wait_for_log_count 'Please press Enter to activate this console\.' 1 "$login_delay_s" 'first login prompt' || wait_status=$? if [ "$wait_status" -eq 0 ]; then - send_command_file "$commands1" + send_command_file "$commands1" /tmp/argosfs-qemu-reboot-phase1.sh wait_for_log_count 'ARGOSFS_REBOOT_REQUESTED' 1 60 'phase1 reboot request' || wait_status=$? fi if [ "$wait_status" -eq 0 ]; then wait_for_log_count 'Please press Enter to activate this console\.' 2 "$reboot_delay_s" 'second login prompt' || wait_status=$? fi if [ "$wait_status" -eq 0 ]; then - send_command_file "$commands2" + send_command_file "$commands2" /tmp/argosfs-qemu-reboot-phase2.sh wait_for_log_count "$done_marker" 1 120 'reboot persistence completion' || wait_status=$? fi exec 3>&- From 2fd709fe34f0badf9d8931103e330d6b475e68a0 Mon Sep 17 00:00:00 2001 From: Cao Yuhang Date: Wed, 15 Jul 2026 03:31:49 +0000 Subject: [PATCH 3/3] fs: preserve block handles across switch_root --- src/volume/mod.rs | 80 +++++++++++++++++++----------- tests/integration/block_backend.rs | 36 ++++++++++++++ 2 files changed, 87 insertions(+), 29 deletions(-) diff --git a/src/volume/mod.rs b/src/volume/mod.rs index 2b0d9154..4ee9c6b8 100644 --- a/src/volume/mod.rs +++ b/src/volume/mod.rs @@ -527,6 +527,9 @@ impl ArgosFs { let meta = self.meta.read(); if meta.backend != BackendKind::Host { let superblocks = self.active_superblocks_locked(&meta)?; + if self.open_backend_covers_superblocks(&superblocks) { + return raw_store::audit(&*self.backend, &superblocks); + } let backend = self.active_block_backend_locked(&meta, false)?; return raw_store::audit(&backend, &superblocks); } @@ -558,9 +561,14 @@ impl ArgosFs { return Ok(()); } let superblocks = self.active_superblocks_locked(&meta)?; - let backend = self.active_block_backend_locked(&meta, true)?; - raw_store::write_metadata_copies(&backend, &superblocks, &meta)?; - backend.flush_all()?; + if self.open_backend_covers_superblocks(&superblocks) { + raw_store::write_metadata_copies(&*self.backend, &superblocks, &meta)?; + self.backend.flush_all()?; + } else { + let backend = self.active_block_backend_locked(&meta, true)?; + raw_store::write_metadata_copies(&backend, &superblocks, &meta)?; + backend.flush_all()?; + } return Ok(()); } @@ -593,13 +601,20 @@ impl ArgosFs { } self.ensure_block_backend_writable_locked(&meta)?; let superblocks = self.active_superblocks_locked(&meta)?; - let backend = self.active_block_backend_locked(&meta, true)?; - if meta.config.defer_metadata_commit { - raw_store::write_metadata_copies(&backend, &superblocks, &meta)?; - backend.flush_all()?; + let mark_clean = |backend: &dyn StorageBackend| -> Result<()> { + if meta.config.defer_metadata_commit { + raw_store::write_metadata_copies(backend, &superblocks, &meta)?; + backend.flush_all()?; + } + raw_store::write_superblock_clean_state(backend, &superblocks, true)?; + backend.flush_all() + }; + if self.open_backend_covers_superblocks(&superblocks) { + mark_clean(&*self.backend) + } else { + let backend = self.active_block_backend_locked(&meta, true)?; + mark_clean(&backend) } - raw_store::write_superblock_clean_state(&backend, &superblocks, true)?; - backend.flush_all() } fn sync_dirty_host_shards(&self) -> Result<()> { @@ -1445,28 +1460,35 @@ impl ArgosFs { } journal::prepare_metadata_integrity_with_previous(meta, previous_meta_hash.clone())?; let superblocks = self.active_superblocks_locked(meta)?; - let backend = self.active_block_backend_locked(meta, true)?; - if meta.config.defer_data_flush || bulk_import_enabled() { - backend.flush_all()?; - journal::inject_crash(FaultPoint::AfterDataFlushBeforeJournalCommit.as_str())?; - } - if checkpoint { - raw_store::write_metadata_copies(&backend, &superblocks, meta)?; + let commit = |backend: &dyn StorageBackend| -> Result<()> { + if meta.config.defer_data_flush || bulk_import_enabled() { + backend.flush_all()?; + journal::inject_crash(FaultPoint::AfterDataFlushBeforeJournalCommit.as_str())?; + } + if checkpoint { + raw_store::write_metadata_copies(backend, &superblocks, meta)?; + } else { + raw_store::append_transaction_with_previous( + backend, + &superblocks, + meta, + Some(&previous), + "group-commit", + json!({ + "transactions": dirty_transactions, + "previous_txid": previous.txid, + "txid": meta.txid, + }), + )?; + } + backend.flush_all() + }; + if self.open_backend_covers_superblocks(&superblocks) { + commit(&*self.backend) } else { - raw_store::append_transaction_with_previous( - &backend, - &superblocks, - meta, - Some(&previous), - "group-commit", - json!({ - "transactions": dirty_transactions, - "previous_txid": previous.txid, - "txid": meta.txid, - }), - )?; + let backend = self.active_block_backend_locked(meta, true)?; + commit(&backend) } - backend.flush_all() })(); match result { diff --git a/tests/integration/block_backend.rs b/tests/integration/block_backend.rs index c11b8384..15ac0a25 100644 --- a/tests/integration/block_backend.rs +++ b/tests/integration/block_backend.rs @@ -798,6 +798,42 @@ fn raw_batched_metadata_commit_persists_after_sync() { assert!(reopened.fsck(true, true).unwrap().errors.is_empty()); } +#[test] +fn raw_group_commit_reuses_open_device_after_path_disappears() { + let tmp = TempDir::new().unwrap(); + let original = tmp.path().join("root.img"); + let moved = tmp.path().join("root-after-switch-root.img"); + let images = vec![original.clone()]; + let mut cfg = config(1, 0); + cfg.defer_journal_flush = true; + cfg.defer_metadata_commit = true; + cfg.defer_data_flush = true; + cfg.deferred_commit_interval_ms = 60_000; + cfg.deferred_commit_max_transactions = 1_000; + cfg.compression = Compression::None; + let fs = + ArgosFs::create_loop(&images, cfg, 32 * 1024 * 1024, "switch-root-handles", false).unwrap(); + fs.write_file("/before", &vec![b'b'; 2048], 0o644).unwrap(); + fs.sync().unwrap(); + + std::fs::rename(&original, &moved).unwrap(); + fs.write_file("/after", &vec![b'a'; 2048], 0o644).unwrap(); + assert!(fs.sync_deferred_if_dirty().unwrap()); + assert!(fs.transaction_report().unwrap().errors.is_empty()); + drop(fs); + + let reopened = ArgosFs::open_loop(&[moved], false).unwrap(); + assert_eq!( + reopened.read_file("/before", true).unwrap(), + vec![b'b'; 2048] + ); + assert_eq!( + reopened.read_file("/after", true).unwrap(), + vec![b'a'; 2048] + ); + assert!(reopened.transaction_report().unwrap().errors.is_empty()); +} + #[test] fn raw_group_commit_batches_transactions_into_one_durable_record() { let tmp = TempDir::new().unwrap();