diff options
| author | Leonard Kugis <leonard@kug.is> | 2026-01-19 21:30:17 +0100 |
|---|---|---|
| committer | Leonard Kugis <leonard@kug.is> | 2026-01-19 21:30:17 +0100 |
| commit | cf472dd80aedab34e7907f86b5d64ea268a4127d (patch) | |
| tree | ae8d3386d162dff96dff4e537c7a348ab8f3c5fe | |
| parent | 7df2fb341747003cee73cf8e819e6cec824edaba (diff) | |
| download | squashr-cf472dd80aedab34e7907f86b5d64ea268a4127d.tar.gz | |
Implemented unify operation
| -rwxr-xr-x | src/main.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs index 3b6f2cf..a2c8b5d 100755 --- a/src/main.rs +++ b/src/main.rs @@ -107,6 +107,7 @@ enum Cmd { Delete { #[arg(short = 's')] s: usize }, /// Hängt SquashR-relevante Mounts aus (ohne Argument: alle; mit Pfad: nur diesen). Umount { target: Option<PathBuf> }, + Unify { #[arg(short = 's')] s: usize, target: PathBuf }, } #[derive(Debug, Clone, Deserialize)] @@ -377,6 +378,7 @@ fn main() -> Result<()> { Cmd::Mount { s, target } => cmd_mount(&mut ctx, s, &target), Cmd::Delete { s } => cmd_delete(&mut ctx, s), Cmd::Umount { target } => cmd_umount(&mut ctx, target.as_deref()), + Cmd::Unify { s, target } => cmd_unify(&mut ctx, s, &target), } } @@ -1405,6 +1407,169 @@ fn cmd_mount(ctx:&mut Ctx, s:Option<usize>, target:&Path)->Result<()>{ Ok(()) } +fn target_is_luks(p: &Path) -> bool { + p.file_name() + .and_then(|s| s.to_str()) + .map(|s| s.ends_with(".luks")) + .unwrap_or(false) +} + +fn mapper_name_for_mount_image(img: &Path) -> Option<String> { + // Muss exakt zur Logik in mount_image_ro passen: + // mapper = "squashr_mount_<file_stem>" + // Bei "*.squashfs.luks" ist file_stem typischerweise "0001.squashfs" + let stem = img.file_stem()?.to_string_lossy(); + Some(format!("squashr_mount_{}", stem)) +} + +fn close_mount_mapper_best_effort(img: &Path) { + if img.extension().and_then(|e| e.to_str()) == Some("luks") { + if let Some(mapper) = mapper_name_for_mount_image(img) { + let _ = Command::new("cryptsetup").arg("close").arg(&mapper).status(); + } + } +} + +fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> { + require_root("unify (mounts / overlay)")?; + + let snaps = ctx.list_snapshots()?; + if snaps.is_empty() { + bail!("No snapshots found."); + } + let max = snaps.len(); + if s == 0 || s > max { + bail!("Snapshot -s {} is invalid (1..={}).", s, max); + } + + // Zielpfad absolut/kanonisch (best effort) + let target_abs = abspath(target); + + // temporäre Arbeitsverzeichnisse + let view = abspath(&ctx.temp_path("unify.view")); + fs::create_dir_all(&view)?; + + // Mounts für lowerdirs + let mut lowers: Vec<PathBuf> = Vec::new(); + let mut mounted_imgs: Vec<PathBuf> = Vec::new(); // für mapper-close cleanup + + // Sonderfall: nur Snapshot 1 → direkt mounten (kein Overlay nötig) + if s == 1 { + let img = find_snapshot_file(ctx, 1)?; + mounted_imgs.push(img.clone()); + mount_image_ro(ctx, &img, &view)?; + + // SquashFS aus dem View bauen + let tmp_plain = ctx.temp_path("unify.plain.squashfs"); + if tmp_plain.exists() { let _ = fs::remove_file(&tmp_plain); } + + let mut cmd = Command::new("mksquashfs"); + cmd.arg(&view).arg(&tmp_plain).arg("-no-progress").arg("-no-recovery"); + if ctx.comp_enable { + cmd.arg("-comp").arg(&ctx.comp_algo); + if let Some(extra) = ctx.comp_args.as_ref() { + for tok in shell_split(extra) { cmd.arg(tok); } + } + } + run(&mut cmd, "mksquashfs (unify)")?; + + // aushängen + mapper schließen + let _ = umount(&view); + close_mount_mapper_best_effort(&img); + + // Output schreiben (plain oder luks anhand target) + if target_abs.exists() { fs::remove_file(&target_abs)?; } + + if target_is_luks(&target_abs) { + encrypt_into_luks(ctx, &tmp_plain, &target_abs)?; + let _ = fs::remove_file(&tmp_plain); + } else { + fs::rename(&tmp_plain, &target_abs)?; + } + + // best-effort cleanup + let _ = fs::remove_dir_all(&view); + + println!("Unified snapshot up to {:04} → {}", s, target_abs.display()); + return Ok(()); + } + + // MULTI-LAYER unify: 1..=s mounten → overlay → deletes → mksquashfs + for i in 1..=s { + let img = find_snapshot_file(ctx, i)?; + let mnt = ctx.mounts_dir.join(format!("unify_{:04}_{}", i, chrono::Local::now().format("%Y%m%d%H%M%S"))); + fs::create_dir_all(&mnt)?; + mount_image_ro(ctx, &img, &mnt)?; + lowers.push(abspath(&mnt)); + mounted_imgs.push(img); + } + + let upper = abspath(&ctx.temp_path("unify.overlay.upper")); + let work = abspath(&ctx.temp_path("unify.overlay.work")); + fs::create_dir_all(&upper)?; + fs::create_dir_all(&work)?; + + let loweropt = lowers.iter().rev().map(|p| p.display().to_string()).join(":"); // neuester zuerst + mount_overlay(&loweropt, &upper, &work, &view)?; + + // Deletes/Whiteouts wie bei cmd_mount + let deletes = create_whiteouts_unlink_list(ctx, s)?; + if !deletes.is_empty() { + apply_whiteouts_via_unlink(&view, &deletes)?; + } + + // neues SquashFS aus dem View + let tmp_plain = ctx.temp_path("unify.plain.squashfs"); + if tmp_plain.exists() { let _ = fs::remove_file(&tmp_plain); } + + let mut cmd = Command::new("mksquashfs"); + cmd.arg(&view).arg(&tmp_plain).arg("-no-progress").arg("-no-recovery"); + if ctx.comp_enable { + cmd.arg("-comp").arg(&ctx.comp_algo); + if let Some(extra) = ctx.comp_args.as_ref() { + for tok in shell_split(extra) { cmd.arg(tok); } + } + } + run(&mut cmd, "mksquashfs (unify)")?; + + // unmount overlay + lowers + let _ = umount(&view); + + // Lower mounts aushängen (reverse depth egal, sind eigenständige mountpoints) + for mnt in &lowers { + let _ = umount(mnt); + } + + // Mapper schließen (nur falls LUKS) + for img in mounted_imgs { + close_mount_mapper_best_effort(&img); + } + + // Output schreiben + if target_abs.exists() { + fs::remove_file(&target_abs) + .with_context(|| format!("remove existing target: {}", target_abs.display()))?; + } + + if target_is_luks(&target_abs) { + encrypt_into_luks(ctx, &tmp_plain, &target_abs)?; + let _ = fs::remove_file(&tmp_plain); + } else { + fs::rename(&tmp_plain, &target_abs)?; + } + + // best-effort cleanup + let _ = fs::remove_dir_all(&view); + let _ = fs::remove_dir_all(&upper); + let _ = fs::remove_dir_all(&work); + for mnt in lowers { + let _ = fs::remove_dir_all(mnt); + } + + println!("Unified snapshot up to {:04} → {}", s, target_abs.display()); + Ok(()) +} + /* ---------- Delete / New ---------- */ fn cmd_delete(ctx:&mut Ctx, s:usize)->Result<()>{ |
