From efac7ffd1c2ec51ed60542fc5f3147c68a2ecfc9 Mon Sep 17 00:00:00 2001 From: Hadi Chokr Date: Sun, 26 Apr 2026 15:50:02 +0200 Subject: [PATCH] try handeling nested subvols better --- btrfs-migrator/src/main.rs | 61 +++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/btrfs-migrator/src/main.rs b/btrfs-migrator/src/main.rs index c79dd02..ed3048d 100644 --- a/btrfs-migrator/src/main.rs +++ b/btrfs-migrator/src/main.rs @@ -13,7 +13,7 @@ use std::{ extern crate scopeguard; use dialoguer::{self, Confirm}; use fstab::FsTab; -use libbtrfsutil::{CreateSnapshotOptions, CreateSubvolumeOptions, DeleteSubvolumeOptions}; +use libbtrfsutil::{CreateSnapshotOptions, CreateSubvolumeOptions, DeleteSubvolumeOptions, is_subvolume}; fn find_rootfs_v1(root: &Path) -> Option { let subvols = fs::read_dir(root).ok()?; @@ -240,10 +240,11 @@ If nothing critically important is managed by fstab you can let the auto-migrati fn run_v3(root: &Path) -> Result<(), Box> { let system_home = root.join("@system/home"); let system_home_tmp = root.join("@system/home.v3tmp"); + let system_home_old = root.join("@system/home.v3old"); let _ = Command::new("plymouth") - .arg("display-message") - .arg("--text=Migrating to v3 rootfs. Can take a while.") - .status(); + .arg("display-message") + .arg("--text=Migrating to v3 rootfs. Can take a while.") + .status(); println!("Migrating @system/home from subvolume to regular directory with per-user subvolumes"); // Stage into a sibling directory so that if we crash mid-way, @system/home is still a subvolume // and the next boot will retry the migration cleanly. @@ -257,18 +258,18 @@ fn run_v3(root: &Path) -> Result<(), Box> { let dst = system_home_tmp.join(entry.file_name()); println!("Creating user subvolume at {dst:?}"); CreateSubvolumeOptions::new() - .create(&dst) - .map_err(|e| format!("Failed to create subvolume {dst:?}: {e:?}"))?; + .create(&dst) + .map_err(|e| format!("Failed to create subvolume {dst:?}: {e:?}"))?; - // Copy everything except nested subvolume contents (leaves empty placeholder dirs) + // Copy everything except nested subvolume contents (leaves empty dirs) let cp_result = Command::new("cp") - .arg("--recursive") - .arg("--archive") - .arg("--reflink=auto") - .arg(format!("{}/.", src.display())) - .arg(format!("{}/.", dst.display())) - .status() - .expect("Failed to copy user home"); + .arg("--recursive") + .arg("--archive") + .arg("--reflink=auto") + .arg(format!("{}/.", src.display())) + .arg(format!("{}/.", dst.display())) + .status() + .expect("Failed to copy user home"); if !cp_result.success() { return Err(format!("Failed to copy {src:?} to {dst:?}").into()); } @@ -282,16 +283,36 @@ fn run_v3(root: &Path) -> Result<(), Box> { println!("Snapshotting nested subvolume {nested_src:?} -> {nested_dst:?}"); fs::remove_dir(&nested_dst)?; CreateSnapshotOptions::new() - .snapshot(&nested_src, &nested_dst) - .map_err(|e| format!("Failed to snapshot {nested_src:?} to {nested_dst:?}: {e:?}"))?; + .recursive(true) + .create(&nested_src, &nested_dst) + .map_err(|e| format!("Failed to snapshot {nested_src:?} to {nested_dst:?}: {e:?}"))?; } } } - DeleteSubvolumeOptions::new() - .delete(&system_home) - .map_err(|e| format!("Failed to delete @system/home subvolume: {e:?}"))?; + + // the original subvolume is still intact at home.v3old and can be recovered. + println!("Renaming {system_home:?} to {system_home_old:?}"); + fs::rename(&system_home, &system_home_old)?; + println!("Renaming {system_home_tmp:?} to {system_home:?}"); - fs::rename(&system_home_tmp, &system_home)?; // fatal problem + if let Err(e) = fs::rename(&system_home_tmp, &system_home) { + // Rename failed + eprintln!("Fatal: failed to rename {system_home_tmp:?} to {system_home:?}: {e}"); + eprintln!("Restoring original home subvolume from {system_home_old:?}"); + fs::rename(&system_home_old, &system_home) + .map_err(|re| format!("CRITICAL: failed to restore original home: {re}. System may be unbootable. Original home is at {system_home_old:?}"))?; + return Err(format!("Migration failed, original home restored: {e}").into()); + } + + println!("Deleting old home subvolume {system_home_old:?}"); + let _ = DeleteSubvolumeOptions::new() + .recursive(true) + .delete(&system_home_old) + .map_err(|e| { + eprintln!("Warning: failed to delete old home subvolume {system_home_old:?}: {e}"); + eprintln!("The migration succeeded but {system_home_old:?} can be manually deleted."); + }); + Ok(()) }