aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonard Kugis <leonard@kug.is>2026-01-19 21:39:25 +0100
committerLeonard Kugis <leonard@kug.is>2026-01-19 21:39:25 +0100
commit3b34a2be2aab19ff4ed80cb58b956c6db5725e20 (patch)
treede96c82c7da6bbaee54022760ad1830321f8ca11
parentcf472dd80aedab34e7907f86b5d64ea268a4127d (diff)
downloadsquashr-3b34a2be2aab19ff4ed80cb58b956c6db5725e20.tar.gz
Removed unneccessary comments, unified english languageHEADmaster
-rwxr-xr-xsrc/main.rs171
1 files changed, 23 insertions, 148 deletions
diff --git a/src/main.rs b/src/main.rs
index a2c8b5d..6f9239a 100755
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,7 +29,6 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use walkdir::WalkDir;
-// kleine Helfer für Verbose-Logs
macro_rules! vlog {
($ctx:expr, $($arg:tt)*) => {
if $ctx.verbose {
@@ -82,15 +81,12 @@ struct Cli {
squashr_tar_args: Option<String>,
- /// Passphrase für cryptsetup; ACHTUNG: Sichtbar im Prozesslisting.
#[arg(long)]
squashr_cryptsetup_pass: Option<String>,
- /// Datei mit Passphrase (roh, binär möglich); hat Vorrang, wenn beides gesetzt ist.
#[arg(long, value_hint=ValueHint::FilePath)]
squashr_cryptsetup_pass_file: Option<PathBuf>,
- /// Mehr Ausgaben, inkl. Auflistung gesicherter Dateien
#[arg(short, long)]
verbose: bool,
@@ -105,7 +101,6 @@ enum Cmd {
New,
Mount { #[arg(short = 's')] s: Option<usize>, target: PathBuf },
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 },
}
@@ -167,7 +162,7 @@ fn load_config(path: Option<&Path>) -> Result<Config> {
let mut cfg = Config::default();
if let Some(p) = path {
if p.exists() {
- let text = fs::read_to_string(p).with_context(|| format!("Konfig lesen: {}", p.display()))?;
+ let text = fs::read_to_string(p).with_context(|| format!("Reading config: {}", p.display()))?;
let mut map: HashMap<String, String> = HashMap::new();
for line in text.lines() {
let l = line.trim();
@@ -247,7 +242,7 @@ struct Ctx {
state_dir: PathBuf,
mounts_dir: PathBuf,
work_dir: PathBuf,
- tar_snapshot: PathBuf, // GNU-tar Snapshot-Datei (materialisiert für tar)
+ tar_snapshot: PathBuf,
min_keep: usize,
max_keep: Option<usize>,
include: Vec<PathBuf>,
@@ -280,11 +275,10 @@ impl Ctx {
let max_raw = cfg.SQUASHR_N_SNAPSHOTS_MAX.unwrap_or(0);
let max_keep = if max_raw == 0 { None } else { Some(max_raw) };
- // Passphrase ermitteln (CLI-String hat Vorrang vor Datei)
let luks_pass = if let Some(p) = cfg.SQUASHR_CRYPTSETUP_PASS {
Some(p.into_bytes())
} else if let Some(f) = cfg.SQUASHR_CRYPTSETUP_PASS_FILE {
- Some(fs::read(&f).with_context(|| format!("Pass-Datei lesen: {}", f.display()))?)
+ Some(fs::read(&f).with_context(|| format!("Reading key file: {}", f.display()))?)
} else {
None
};
@@ -337,7 +331,6 @@ impl Ctx {
.unwrap_or(false)
})
.filter(|p| {
- // meta-Container NICHT als "Snapshot" zählen
let fname = p.file_name().and_then(|s| s.to_str()).unwrap_or("");
fname != "meta.squashfs" && fname != "meta.squashfs.luks"
})
@@ -394,7 +387,6 @@ fn abs_key(p: &Path) -> Result<String> {
fn parse_snap_index(p:&Path)->Result<usize>{
let fname = p.file_name().and_then(|s| s.to_str()).ok_or_else(||anyhow!("Invalid filename: {}", p.display()))?;
- // robust: nur führende 4 Ziffern extrahieren
let re = Regex::new(r"^(\d{4})").unwrap();
let caps = re.captures(fname).ok_or_else(|| anyhow!("No index found: {}", p.display()))?;
Ok(caps.get(1).unwrap().as_str().parse::<usize>().unwrap())
@@ -465,7 +457,6 @@ fn build_prune_set_for_root(abs_root: &Path, ctx: &Ctx) -> Vec<PathBuf> {
v.push(ex_abs);
}
}
- // stets interne Verzeichnisse ausschließen
for auto in [&ctx.state_dir, &ctx.mounts_dir, &ctx.work_dir] {
let ex_abs = canonicalize_or_same(auto);
if ex_abs.starts_with(abs_root) { v.push(ex_abs); }
@@ -517,8 +508,6 @@ fn collect_manifest(ctx: &Ctx) -> Result<(HashMap<String, PathBuf>, BTreeSet<Str
Ok((roots, manifest))
}
-/* ---------- TAR Streaming Filter: GNU 'D' dumpdir strip ---------- */
-
fn is_zero_block(b: &[u8]) -> bool {
b.iter().all(|&x| x == 0)
}
@@ -535,7 +524,6 @@ fn parse_tar_size_octal(field: &[u8]) -> u64 {
fn parse_tar_size(field: &[u8]) -> u64 {
if field.is_empty() { return 0; }
- // GNU base-256?
if (field[0] & 0x80) != 0 {
let mut v: u128 = 0;
let mut first = true;
@@ -546,7 +534,6 @@ fn parse_tar_size(field: &[u8]) -> u64 {
}
return v as u64;
}
- // Standard: oktal bis Space/NUL
let mut s = 0u64;
for &c in field {
match c {
@@ -593,14 +580,14 @@ fn is_valid_tar_header(h: &[u8; 512]) -> bool {
fn copy_blocks_resync<R: Read, W: Write>(
r: &mut R,
- mut out: Option<&mut W>, // <- mut
+ mut out: Option<&mut W>,
mut bytes: u64,
) -> Result<[u8; 512]> {
let mut buf = vec![0u8; 128 * 1024];
while bytes > 0 {
let take = (bytes as usize).min(buf.len());
r.read_exact(&mut buf[..take])?;
- if let Some(w) = out.as_mut() { // <- as_mut statt as_ref
+ if let Some(w) = out.as_mut() {
w.write_all(&buf[..take])?;
}
bytes -= take as u64;
@@ -612,7 +599,7 @@ fn copy_blocks_resync<R: Read, W: Write>(
if is_valid_tar_header(&next) {
return Ok(next);
}
- if let Some(w) = out.as_mut() { // <- as_mut statt as_ref
+ if let Some(w) = out.as_mut() {
w.write_all(&next)?;
}
}
@@ -620,18 +607,14 @@ fn copy_blocks_resync<R: Read, W: Write>(
fn forward_tar_strip_gnu_dumpdirs<R: Read, W: Write>(mut r: R, mut w: W) -> Result<()> {
let mut header = [0u8; 512];
- let mut pending: Vec<Vec<u8>> = Vec::new(); // komplette Records L/K/x/g puffern
+ let mut pending: Vec<Vec<u8>> = Vec::new();
- // erstes Header-Block lesen
r.read_exact(&mut header).with_context(|| "read tar header")?;
loop {
- // Trailer?
if is_zero_block(&header) {
- // zweiter Zero-Block gehört dazu
let mut zero2 = [0u8; 512];
r.read_exact(&mut zero2).with_context(|| "read tar trailing zero")?;
- // evtl. pending verwerfen (sollte leer sein)
pending.clear();
w.write_all(&header)?;
w.write_all(&zero2)?;
@@ -648,7 +631,6 @@ fn forward_tar_strip_gnu_dumpdirs<R: Read, W: Write>(mut r: R, mut w: W) -> Resu
let padded = (size + 511) & !511;
match typeflag {
- // L/K/x/g: vollständig puffern (Header + Payload (+ evtl. Spill))
'L' | 'K' | 'x' | 'g' => {
let mut rec: Vec<u8> = Vec::with_capacity(512);
rec.extend_from_slice(&header);
@@ -658,14 +640,12 @@ fn forward_tar_strip_gnu_dumpdirs<R: Read, W: Write>(mut r: R, mut w: W) -> Resu
continue; // weiter mit neuem 'header'
}
- // GNU dumpdir → komplett wegwerfen, inkl. evtl. vorausgehender L/K/x/g
'D' => {
header = copy_blocks_resync(&mut r, None::<&mut W>, padded)?;
pending.clear();
continue;
}
- // alle „normalen“ Einträge: erst pending ausgeben, dann diesen Record
_ => {
for rec in pending.drain(..) { w.write_all(&rec)?; }
// aktuellen Header schreiben
@@ -680,8 +660,6 @@ fn forward_tar_strip_gnu_dumpdirs<R: Read, W: Write>(mut r: R, mut w: W) -> Resu
Ok(())
}
-/* ---------- Meta-Container (Manifeste + tar.snapshot) ---------- */
-
fn meta_existing(ctx: &Ctx) -> Option<PathBuf> {
let luks = ctx.meta_luks_path();
let plain = ctx.meta_plain_path();
@@ -731,7 +709,6 @@ fn extract_file_from_meta(ctx:&Ctx, filename:&str, dest_path:&Path) -> Result<bo
if let Some((src, mapper)) = open_meta_read(ctx)? {
let tmp = ctx.temp_path("meta.extract");
fs::create_dir_all(&tmp)?;
- // nur die gewünschte Datei extrahieren
let mut cmd = Command::new("unsquashfs");
cmd.arg("-d").arg(&tmp).arg(&src).arg(filename);
let st = run_ok_status(&mut cmd).with_context(|| "run unsquashfs")?;
@@ -743,7 +720,6 @@ fn extract_file_from_meta(ctx:&Ctx, filename:&str, dest_path:&Path) -> Result<bo
fs::rename(&srcf, dest_path)?;
present = true;
}
- // aufräumen
let _ = fs::remove_dir_all(&tmp);
close_meta_mapper(mapper);
Ok(present)
@@ -753,7 +729,6 @@ fn extract_file_from_meta(ctx:&Ctx, filename:&str, dest_path:&Path) -> Result<bo
}
fn cleanup_plain_meta_files(ctx:&Ctx) {
- // tar.snapshot + alle manifest_* im state_dir entfernen, falls vorhanden
let _ = fs::remove_file(&ctx.tar_snapshot);
if let Ok(rd) = fs::read_dir(&ctx.work_dir) {
for e in rd.flatten() {
@@ -767,7 +742,6 @@ fn cleanup_plain_meta_files(ctx:&Ctx) {
}
fn rebuild_meta_from_staging(ctx:&Ctx, staging:&Path) -> Result<()> {
- // meta tmp plain
let tmp_plain = ctx.temp_path("meta.plain.squashfs");
if tmp_plain.exists() { let _ = fs::remove_file(&tmp_plain); }
let mut cmd = Command::new("mksquashfs");
@@ -778,11 +752,9 @@ fn rebuild_meta_from_staging(ctx:&Ctx, staging:&Path) -> Result<()> {
}
run(&mut cmd, "mksquashfs (meta)")?;
- // Ziel festlegen (verschlüsselt/unkryptiert)
let plain = ctx.meta_plain_path();
let luks = ctx.meta_luks_path();
- // vorhandene Varianten löschen, um Inkonsistenzen zu vermeiden
let _ = fs::remove_file(&plain);
let _ = fs::remove_file(&luks);
@@ -793,7 +765,6 @@ fn rebuild_meta_from_staging(ctx:&Ctx, staging:&Path) -> Result<()> {
fs::rename(&tmp_plain, &plain)?;
}
- // Klartextreste entfernen
cleanup_plain_meta_files(ctx);
Ok(())
}
@@ -801,7 +772,7 @@ fn rebuild_meta_from_staging(ctx:&Ctx, staging:&Path) -> Result<()> {
fn save_manifest_to_dir(dest_dir:&Path, idx: usize, manifest: &BTreeSet<String>) -> Result<()> {
fs::create_dir_all(dest_dir)?;
let path = dest_dir.join(Ctx::manifest_name(idx));
- let mut f = fs::File::create(&path).with_context(|| format!("manifest schreiben: {}", path.display()))?;
+ let mut f = fs::File::create(&path).with_context(|| format!("Writing manifest: {}", path.display()))?;
for line in manifest {
writeln!(f, "{line}")?;
}
@@ -809,22 +780,20 @@ fn save_manifest_to_dir(dest_dir:&Path, idx: usize, manifest: &BTreeSet<String>)
}
fn load_manifest(ctx:&Ctx, idx: usize) -> Result<BTreeSet<String>> {
- // Primär aus Meta-Container lesen
let fname = Ctx::manifest_name(idx);
let tmp = ctx.temp_path("manifest.read");
fs::create_dir_all(&tmp)?;
let out = tmp.join(&fname);
if extract_file_from_meta(ctx, &fname, &out)? {
let text = fs::read_to_string(&out)
- .with_context(|| format!("manifest lesen: {}", out.display()))?;
+ .with_context(|| format!("Reading manifest: {}", out.display()))?;
let _ = fs::remove_dir_all(&tmp);
return Ok(text.lines().map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect());
}
- // Fallback (falls sehr früher Zustand): Klartextdatei
let legacy = ctx.state_dir.join(&fname);
if legacy.exists() {
let text = fs::read_to_string(&legacy)
- .with_context(|| format!("manifest lesen: {}", legacy.display()))?;
+ .with_context(|| format!("Reading manifest: {}", legacy.display()))?;
let _ = fs::remove_dir_all(&tmp);
return Ok(text.lines().map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect());
}
@@ -832,8 +801,6 @@ fn load_manifest(ctx:&Ctx, idx: usize) -> Result<BTreeSet<String>> {
bail!("manifest {} not found", fname);
}
-/* ---------- TAR → SQFSTAR ---------- */
-
fn build_squash_image_tar_sqfstar(
ctx: &Ctx,
out: &Path,
@@ -846,7 +813,6 @@ fn build_squash_image_tar_sqfstar(
.with_context(|| format!("remove old output: {}", out.display()))?;
}
- // --- sqfstar: liest Tar von stdin, schreibt SquashFS nach `out`
let mut sq = std::process::Command::new("sqfstar");
if ctx.comp_enable {
sq.arg("-comp").arg(&ctx.comp_algo);
@@ -860,10 +826,8 @@ fn build_squash_image_tar_sqfstar(
.take()
.ok_or_else(|| anyhow::anyhow!("cannot open sqfstar stdin"))?;
- // --- tar: POSIX/PAX + listed-incremental, KEINE Filterei
let mut tar = std::process::Command::new("tar");
- // Excludes relativ zu /
let mut all_excludes: Vec<std::path::PathBuf> = ctx.exclude.clone();
for auto in [&ctx.state_dir, &ctx.mounts_dir, &ctx.work_dir] {
all_excludes.push(auto.clone());
@@ -876,8 +840,6 @@ fn build_squash_image_tar_sqfstar(
}
tar.arg("--format=posix")
- // Entfernt das GNU.dumpdir-Attribut aus PAX-XHeaders → keine Warnungen mehr
- //.arg("--pax-option").arg("delete=GNU.dumpdir,exthdr.name=%d/PaxHeaders/%f")
.arg("-C").arg("/")
.arg("--null")
.arg("--files-from=-")
@@ -886,7 +848,6 @@ fn build_squash_image_tar_sqfstar(
.arg("--numeric-owner")
.arg("--ignore-failed-read");
- // Benutzerdefinierte TAR-Args (am Ende → überschreiben Defaults, "last wins")
if let Some(extra) = ctx.tar_args.as_ref() {
for tok in shell_split(extra) {
tar.arg(tok);
@@ -898,7 +859,6 @@ fn build_squash_image_tar_sqfstar(
tar.stderr(std::process::Stdio::inherit());
let mut tar_child = tar.spawn().with_context(|| "Unable to start tar")?;
- // Pfadliste (roots) in tar stdin schreiben, 0-terminiert
{
let mut w = std::io::BufWriter::new(
tar_child.stdin.take().ok_or_else(|| anyhow::anyhow!("cannot open tar stdin"))?
@@ -912,7 +872,6 @@ fn build_squash_image_tar_sqfstar(
w.flush()?;
}
- // tar stdout *direkt* zu sqfstar stdin pumpen (ohne Filter)
{
let mut tar_stdout = tar_child.stdout
.take()
@@ -923,7 +882,6 @@ fn build_squash_image_tar_sqfstar(
}
drop(sq_stdin);
- // Exit-Codes prüfen
let tar_status = tar_child.wait().with_context(|| "waiting for tar failed")?;
let tar_code = tar_status.code().unwrap_or(-1);
let tar_nonzero = tar_code != 0;
@@ -979,8 +937,6 @@ fn encrypt_into_luks(ctx:&Ctx, plain:&Path, out_luks:&Path)->Result<()>{
Ok(())
}
-/* ---------- Mount Helfer / FUSE Fallback ---------- */
-
fn try_mount_kernel_squashfs(source:&Path, mnt:&Path, use_loop:bool) -> Result<bool> {
let mut cmd = Command::new("mount");
cmd.arg("-t").arg("squashfs");
@@ -1003,8 +959,6 @@ fn try_mount_fuse_squashfs(source:&Path, mnt:&Path) -> Result<bool> {
}
}
-/* ---------- Mount-Info Utilities ---------- */
-
#[derive(Debug)]
struct MountEntry { src:String, tgt:PathBuf, fstype:String, opts:String }
@@ -1012,7 +966,6 @@ fn read_proc_mounts() -> Result<Vec<MountEntry>> {
let text = fs::read_to_string("/proc/mounts").context("read /proc/mounts")?;
let mut out = Vec::new();
for line in text.lines() {
- // /proc/mounts: src tgt fstype opts 0 0
let mut it = line.split_whitespace();
let (Some(src), Some(tgt), Some(fstype), Some(opts)) = (it.next(), it.next(), it.next(), it.next()) else { continue };
out.push(MountEntry{
@@ -1041,7 +994,6 @@ fn sort_paths_deep_first(mut v: Vec<PathBuf>) -> Vec<PathBuf> {
}
fn loop_backing_file(dev: &str) -> Option<PathBuf> {
- // Erwartet "/dev/loopX"
let name = Path::new(dev).file_name()?.to_string_lossy().to_string();
let candidates = [
format!("/sys/block/{}/loop/backing_file", name),
@@ -1099,10 +1051,7 @@ fn purge_dir(dir: &Path) {
}
}
-/* ---------- Backup ---------- */
-
fn ensure_tar_snapshot_materialized(ctx:&Ctx) -> Result<()> {
- // Falls in Meta enthalten → herausziehen, sonst tar lässt neu schreiben
let _ = extract_file_from_meta(ctx, "tar.snapshot", &ctx.tar_snapshot)?;
Ok(())
}
@@ -1115,12 +1064,10 @@ fn truncate_logs(ctx:&Ctx)->Result<()>{
let p = entry.path();
if p.is_file() {
let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("");
- // komprimierte Logs löschen
if name.ends_with(".gz") || name.ends_with(".xz") || name.ends_with(".zst") || name.ends_with(".bz2") {
let _ = fs::remove_file(p);
continue;
}
- // unkomprimierte Logs auf Länge 0 setzen
let _ = fs::OpenOptions::new()
.write(true)
.open(p)
@@ -1135,25 +1082,22 @@ fn cmd_backup(ctx:&mut Ctx)->Result<()>{
ensure_includes_nonempty(ctx)?;
truncate_logs(ctx)?;
- // Vorbereitungen: tar.snapshot bereitstellen
ensure_tar_snapshot_materialized(ctx)?;
let snaps = ctx.list_snapshots()?;
let next_idx = snaps.len() + 1;
- // 1) Manifest aufnehmen (inkl. Excludes & interne Verzeichnisse)
let (roots, manifest_now) = collect_manifest(ctx)?;
let manifest_prev = if next_idx > 1 {
load_manifest(ctx, next_idx - 1).ok()
} else { None };
- // 2) Falls keine Änderung: minimalistisches Image erstellen (wie bisher)
let no_changes = manifest_prev.as_ref().map_or(false, |m| m == &manifest_now);
let plain_img = ctx.temp_path("snapshot.plain.squashfs");
let mut tar_warn = false;
if no_changes {
- vlog!(ctx, "[backup] no changes → creating minimal image {}", plain_img.display());
+ vlog!(ctx, "[backup] no changes -> creating minimal image {}", plain_img.display());
let empty_src = ctx.temp_path("empty.src");
fs::create_dir_all(&empty_src)?;
let mut cmd = Command::new("mksquashfs");
@@ -1164,11 +1108,9 @@ fn cmd_backup(ctx:&mut Ctx)->Result<()>{
}
run(&mut cmd, "mksquashfs (empty)")?;
} else {
- // 3) tar (listed-incremental) → Filter (strip 'D') → sqfstar
tar_warn = build_squash_image_tar_sqfstar(ctx, &plain_img, &roots)?;
}
- // 4) Optional: LUKS-Container schreiben
let final_path = if ctx.luks_enable {
let out = ctx.snapshot_path(next_idx, true);
encrypt_into_luks(ctx, &plain_img, &out)?;
@@ -1180,7 +1122,6 @@ fn cmd_backup(ctx:&mut Ctx)->Result<()>{
out
};
- // 5) Meta-Container neu bauen (alle Manifeste + tar.snapshot)
let staging = ctx.temp_path("meta.staging");
extract_all_meta_to(ctx, &staging)?;
// neues Manifest hinein
@@ -1215,8 +1156,6 @@ fn rotate_if_needed(ctx:&mut Ctx)->Result<()>{
Ok(())
}
-/* ---------- Whiteouts / Mount / Overlay ---------- */
-
fn create_whiteouts_unlink_list(ctx:&Ctx, upto:usize)->Result<Vec<String>>{
if upto == 0 { return Ok(vec![]); }
let present = load_manifest(ctx, upto).unwrap_or_default();
@@ -1249,14 +1188,12 @@ fn mount_image_ro(ctx:&Ctx, img:&Path, mnt:&Path)->Result<()>{
let mnt_abs = abspath(mnt);
if img.extension().and_then(|e| e.to_str()) == Some("luks") {
- // LUKS → /dev/mapper/<mapper>
let mapper = format!(
"squashr_mount_{}",
img.file_stem().and_then(|s| s.to_str()).unwrap_or("img")
);
let dev = format!("/dev/mapper/{}", mapper);
- // evtl. Alt-Mapping schließen
let _ = Command::new("cryptsetup").arg("close").arg(&mapper).status();
let mut o = Command::new("cryptsetup");
@@ -1264,45 +1201,41 @@ fn mount_image_ro(ctx:&Ctx, img:&Path, mnt:&Path)->Result<()>{
o.arg("open").arg(img).arg(&mapper);
cryptsetup_run(&mut o, ctx.luks_pass.as_deref(), "cryptsetup open (mount)")?;
- // 1) Kernel-Mount versuchen
match try_mount_kernel_squashfs(Path::new(&dev), &mnt_abs, false) {
Ok(true) => return Ok(()),
Ok(false) => {
- eprintln!("[warn] Kernel-SquashFS-Mount fehlgeschlagen – versuche FUSE (squashfuse).");
+ eprintln!("[warn] Kernel mount failed, attempting FUSE (squashfuse).");
}
Err(e) => {
- eprintln!("[warn] Kernel-SquashFS-Mount Fehler: {e} – versuche FUSE (squashfuse).");
+ eprintln!("[warn] Kernel mount failed: {e} – attempting FUSE (squashfuse).");
}
}
- // 2) FUSE-Fallback versuchen
match try_mount_fuse_squashfs(Path::new(&dev), &mnt_abs) {
Ok(true) => return Ok(()),
Ok(false) => {
- // Mapping sauber schließen
let _ = Command::new("cryptsetup").arg("close").arg(&mapper).status();
- bail!("FUSE-Mount (squashfuse) fehlgeschlagen. Prüfe Kernelmodul 'squashfs' oder installiere 'squashfuse'.");
+ bail!("FUSE mount (squashfuse) failed.");
}
Err(e) => {
let _ = Command::new("cryptsetup").arg("close").arg(&mapper).status();
- bail!("{e}. Prüfe Kernelmodul 'squashfs' oder installiere 'squashfuse'.");
+ bail!("{e}. FUSE mount failed.");
}
}
} else {
- // Plain SquashFS-Datei
match try_mount_kernel_squashfs(img, &mnt_abs, true) {
Ok(true) => return Ok(()),
Ok(false) => {
- eprintln!("[warn] Kernel-SquashFS-Mount fehlgeschlagen – versuche FUSE (squashfuse).");
+ eprintln!("[warn] Kernel mount failed, attempting FUSE (squashfuse).");
}
Err(e) => {
- eprintln!("[warn] Kernel-SquashFS-Mount Fehler: {e} – versuche FUSE (squashfuse).");
+ eprintln!("[warn] Kernel mount failed: {e} – attempting FUSE (squashfuse).");
}
}
match try_mount_fuse_squashfs(img, &mnt_abs) {
Ok(true) => Ok(()),
- Ok(false) => bail!("FUSE-Mount (squashfuse) fehlgeschlagen. Prüfe Kernelmodul 'squashfs' oder installiere 'squashfuse'."),
- Err(e) => bail!("{e}. Prüfe Kernelmodul 'squashfs' oder installiere 'squashfuse'."),
+ Ok(false) => bail!("FUSE mount failed (squashfuse)."),
+ Err(e) => bail!("{e}. FUSE mount failed."),
}
}
}
@@ -1323,32 +1256,26 @@ fn mount_overlay(lowerdirs:&str, upper:&Path, work:&Path, target:&Path)->Result<
Ok(())
}
-/* ---------- Umount (robust, idempotent) ---------- */
-
fn umount(path:&Path)->Result<()>{
require_root("umount")?;
- // Wenn nicht gemountet → OK (idempotent)
if !is_target_mounted(path) {
return Ok(());
}
- // Erst normal versuchen
let mut cmd = Command::new("umount");
cmd.arg(path);
- let status = cmd.status().with_context(|| "umount aufrufen")?;
+ let status = cmd.status().with_context(|| "calling umount")?;
if status.success() {
return Ok(());
}
- // FUSE-Fälle: fusermount3/fusermount
let _ = Command::new("fusermount3").arg("-u").arg(path).status();
let status2 = Command::new("fusermount").arg("-u").arg(path).status();
if let Ok(s) = status2 {
if s.success() { return Ok(()); }
}
- // Letzter Versuch: lazy
let _ = Command::new("umount").arg("-l").arg(path).status();
if !is_target_mounted(path) { return Ok(()); }
- bail!("umount/fusermount fehlgeschlagen für {}", path.display());
+ bail!("umount/fusermount failed for {}", path.display());
}
fn find_snapshot_file(ctx:&Ctx, idx:usize)->Result<PathBuf>{
@@ -1378,7 +1305,6 @@ fn cmd_mount(ctx:&mut Ctx, s:Option<usize>, target:&Path)->Result<()>{
return Ok(());
}
- // MULTI-LAYER
let mut lowers: Vec<PathBuf> = vec![];
for i in 1..=upto {
let img = find_snapshot_file(ctx, i)?;
@@ -1415,9 +1341,6 @@ fn target_is_luks(p: &Path) -> bool {
}
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))
}
@@ -1442,24 +1365,19 @@ fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> {
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
+ let mut mounted_imgs: Vec<PathBuf> = Vec::new();
- // 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); }
@@ -1473,11 +1391,9 @@ fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> {
}
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) {
@@ -1487,14 +1403,12 @@ fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> {
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")));
@@ -1512,13 +1426,11 @@ fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> {
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); }
@@ -1532,20 +1444,16 @@ fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> {
}
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()))?;
@@ -1558,7 +1466,6 @@ fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> {
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);
@@ -1570,8 +1477,6 @@ fn cmd_unify(ctx: &mut Ctx, s: usize, target: &Path) -> Result<()> {
Ok(())
}
-/* ---------- Delete / New ---------- */
-
fn cmd_delete(ctx:&mut Ctx, s:usize)->Result<()>{
let path_plain = ctx.snapshot_path(s, false);
let path_luks = ctx.snapshot_path(s, true);
@@ -1579,7 +1484,6 @@ fn cmd_delete(ctx:&mut Ctx, s:usize)->Result<()>{
if path_plain.exists(){ fs::remove_file(&path_plain)?; }
if path_luks.exists(){ fs::remove_file(&path_luks)?; }
- // Alle nachfolgenden Snapshots umnummerieren
let snaps = ctx.list_snapshots()?;
for p in snaps {
let n = parse_snap_index(&p)?;
@@ -1590,12 +1494,10 @@ fn cmd_delete(ctx:&mut Ctx, s:usize)->Result<()>{
}
}
- // Meta-Container anpassen: manifest_s löschen, >s dekrementieren
let staging = ctx.temp_path("meta.staging");
extract_all_meta_to(ctx, &staging)?;
let to_del = staging.join(Ctx::manifest_name(s));
let _ = fs::remove_file(&to_del);
- // renumber
let mut k = s + 1;
loop {
let from = staging.join(Ctx::manifest_name(k));
@@ -1614,7 +1516,6 @@ fn cmd_delete(ctx:&mut Ctx, s:usize)->Result<()>{
fn cmd_new(ctx:&mut Ctx)->Result<()>{
for s in ctx.list_snapshots()? { fs::remove_file(s)?; }
- // Meta-Container & Klartextreste löschen
let _ = fs::remove_file(ctx.meta_plain_path());
let _ = fs::remove_file(ctx.meta_luks_path());
cleanup_plain_meta_files(ctx);
@@ -1623,8 +1524,6 @@ fn cmd_new(ctx:&mut Ctx)->Result<()>{
cmd_backup(ctx)
}
-/* ---------- Umount Command ---------- */
-
fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
require_root("umount")?;
@@ -1645,13 +1544,11 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
}
} else {
- // 1) Alle Mounts unter mounts_dir
for m in &mounts_before {
if m.tgt.starts_with(&ctx.mounts_dir) {
todo.push(m.tgt.clone());
}
}
- // 2) Overlay-Ziele mit upperdir in work_dir
for m in &mounts_before {
if m.fstype == "overlay" && m.opts.contains("upperdir=") {
if let Some(start) = m.opts.find("upperdir=") {
@@ -1663,7 +1560,6 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
}
}
- // 3) Mapper-SquashFS
for m in &mounts_before {
if m.fstype == "squashfs" && m.src.starts_with("/dev/mapper/squashr_") {
todo.push(m.tgt.clone());
@@ -1671,7 +1567,6 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
mappers_in_use.insert(mapper);
}
}
- // 4) Loop-SquashFS aus state_dir
for m in &mounts_before {
if m.fstype == "squashfs" && m.src.starts_with("/dev/loop") {
if let Some(back) = loop_backing_file(&m.src) {
@@ -1682,7 +1577,6 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
}
}
- // 5) FUSE mounts auf state_dir-Quellen oder Mapper
for m in &mounts_before {
if m.fstype.starts_with("fuse") {
let src_path = PathBuf::from(&m.src);
@@ -1699,13 +1593,10 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
if target.is_none() {
- // 6) Alle /dev/mapper/squashr_* aufnehmen (auch wenn nirgendwo gemountet)
for m in list_squashr_mappers() { mappers_in_use.insert(m); }
- // 7) Alle Loop-Devices, deren backing_file im state_dir liegt (auch ohne Mount)
for l in list_loops_on_state(ctx) { loops_in_use.insert(l); }
}
- // Nur aktuell gemountete Ziele behalten
let mounted_now: HashSet<PathBuf> = read_proc_mounts()?
.into_iter()
.map(|m| abspath(&m.tgt))
@@ -1719,7 +1610,6 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
let mut did_something = !todo.is_empty();
let todo = sort_paths_deep_first(todo);
- // Aushängen
let mut errors = Vec::new();
for mpt in &todo {
match umount(mpt) {
@@ -1731,10 +1621,8 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
}
- // Nach dem Aushängen erneut Mounts einlesen
let mounts_after = read_proc_mounts().unwrap_or_default();
- // LUKS-Mapper schließen (nur, wenn nicht mehr gemountet)
for mapper in mappers_in_use {
let devpath = format!("/dev/mapper/{mapper}");
let still_mounted = mounts_after.iter().any(|m| m.src == devpath);
@@ -1748,7 +1636,6 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
}
- // Loop-Devices lösen
for loopdev in loops_in_use {
let still_mounted = mounts_after.iter().any(|m| m.src == loopdev);
if !still_mounted {
@@ -1763,10 +1650,8 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
}
- // Workdir radikal leeren (temporäre Klartext-/Overlay-Reste)
if target.is_none() {
purge_dir(&ctx.work_dir);
- // leere Mount-Unterverzeichnisse entfernen (best effort)
let _ = purge_dir(&ctx.mounts_dir);
}
@@ -1785,8 +1670,6 @@ fn cmd_umount(ctx:&mut Ctx, target: Option<&Path>) -> Result<()> {
}
}
-/* ---------- Minimize ---------- */
-
fn cmd_minimize(ctx:&mut Ctx, n_opt:Option<usize>)->Result<()>{
let target = n_opt.unwrap_or(ctx.min_keep);
if target < ctx.min_keep {
@@ -1801,8 +1684,6 @@ fn cmd_minimize(ctx:&mut Ctx, n_opt:Option<usize>)->Result<()>{
Ok(())
}
-/* ---------- Merge ---------- */
-
fn merge_first_two(ctx:&mut Ctx)->Result<()>{
require_root("merge (mounts / overlay)")?;
let snaps = ctx.list_snapshots()?;
@@ -1826,10 +1707,9 @@ fn merge_first_two(ctx:&mut Ctx)->Result<()>{
fs::create_dir_all(&upper)?;
fs::create_dir_all(&work)?;
fs::create_dir_all(&view)?;
- let loweropt = format!("{}:{}", abspath(&m1).display(), abspath(&m2).display()); // s2 über s1
+ let loweropt = format!("{}:{}", abspath(&m1).display(), abspath(&m2).display());
mount_overlay(&loweropt, &upper, &work, &view)?;
- // Unlinks gemäß manifest_2 (Zielzustand) gegenüber manifest_1
let present = load_manifest(ctx, idx2).unwrap_or_default();
let past = load_manifest(ctx, idx1).unwrap_or_default();
let deletes: Vec<String> = past.difference(&present).cloned().collect();
@@ -1837,7 +1717,6 @@ fn merge_first_two(ctx:&mut Ctx)->Result<()>{
apply_whiteouts_via_unlink(&abspath(&view), &deletes)?;
}
- // neues SquashFS aus dem View
let tmp_plain = ctx.temp_path("merge.plain.sqsh");
let mut cmd = Command::new("mksquashfs");
cmd.arg(&view).arg(&tmp_plain).arg("-no-progress").arg("-no-recovery");
@@ -1862,7 +1741,6 @@ fn merge_first_two(ctx:&mut Ctx)->Result<()>{
}
if s2.exists(){ fs::remove_file(s2)?; }
- // Indizes > idx2 runterzählen (Dateien)
let rest = ctx.list_snapshots()?;
for p in rest {
let n = parse_snap_index(&p)?;
@@ -1873,15 +1751,12 @@ fn merge_first_two(ctx:&mut Ctx)->Result<()>{
}
}
- // Meta-Container anpassen: manifest_1 := manifest_2; danach alle >2 dekrementieren
let staging = ctx.temp_path("meta.staging");
extract_all_meta_to(ctx, &staging)?;
- // manifest_2 -> manifest_1 (overwrite)
let m1p = staging.join(Ctx::manifest_name(1));
let m2p = staging.join(Ctx::manifest_name(2));
if m1p.exists(){ let _ = fs::remove_file(&m1p); }
if m2p.exists(){ fs::rename(&m2p, &m1p).ok(); }
- // ab 3 dekrementieren
let mut k = 3usize;
loop {
let from = staging.join(Ctx::manifest_name(k));