#!/usr/bin/env bash
DW_THEME_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "$DW_THEME_DIR/Theme.sh" ]]; then source "$DW_THEME_DIR/Theme.sh"
elif [[ -f "$DW_THEME_DIR/../share/dw-rettung/Theme.sh" ]]; then source "$DW_THEME_DIR/../share/dw-rettung/Theme.sh"
else C_RESET=""; C_BOLD=""; C_INFO=""; C_GOLD=""; C_OK=""; C_WARN=""; C_DANGER=""; C_MUTED=""; fi
: "${C_DANGER:=}" "${C_WARN:=}" "${C_INFO:=}" "${C_BOLD:=}" "${C_RESET:=}" "${C_OK:=}" "${C_GOLD:=}" "${C_MUTED:=}"
# DW Rettungs-Assistent 1.1.1
# Geführte Nutzung von ddrescue (gddrescue) und PhotoRec. Die Quelle wird nur gelesen, nie repariert.
# Standard ist ein Probelauf. Keine Netzwerkverbindung, keine Telemetrie.
# MIT-Lizenz, (c) 2026 Michael Bergfeld, DoMeZos-Ware
set -u
export LC_ALL=C.UTF-8 2>/dev/null || export LC_ALL=C

VERSION="1.1.1"
PROG="dw-rettung"

AUSFUEHREN=0
OPT_QUELLE=""; OPT_ZIEL=""; OPT_NAME=""; OPT_IMAGE=""; OPT_AUSGABE=""; OPT_MAPFILE=""
OPT_MAX=""; OPT_WIEDERHOLUNGEN=3; OPT_TAKT=30
TMPDIRS=()
SUDO_BENUTZT=0
LAUFENDE_PID=""; UNTERBROCHEN=0

usage() {
cat <<'EOF'
DW Rettungs-Assistent 1.1.1 - Daten von defekten Datenträgern geführt sichern

Aufruf:
  dw-rettung BEFEHL [Optionen]

Befehle:
  geraete     Datenträger auflisten (nur Anzeige)
  retten      Abbild der Quelle mit ddrescue erstellen (geführt, wenn --quelle fehlt)
  status      Fortschritt aus der Mapfile in Klartext anzeigen
  hinweise    Hinweise zum schreibgeschützten Einhängen eines Abbilds (loop,ro) und zu PhotoRec
  photorec    PhotoRec auf einem Abbild starten (nie direkt auf dem Original)

Optionen:
  --quelle PFAD        Datenträger (z. B. /dev/sdb) oder Image-Datei, die gelesen wird
  --ziel ORDNER        Ordner für Abbild und Mapfile (auf einem ANDEREN Datenträger als die Quelle)
  --name NAME          Dateiname ohne Endung (Abbild NAME.img, Mapfile NAME.map)
  --mapfile DATEI      bei "status": Mapfile direkt angeben
  --image DATEI        bei "hinweise" und "photorec": das Abbild
  --ausgabe ORDNER     bei "photorec": Ordner für die wiederhergestellten Dateien
  --max-groesse GR     nur die ersten GR Bytes kopieren (z. B. 100M, 2G); später ohne Angabe fortsetzen
  --wiederholungen N   Wiederholungen für defekte Stellen in Phase 2 (Standard 3)
  --takt SEKUNDEN      Abstand der Fortschrittsmeldungen (Standard 5)
  --ausfuehren         wirklich ausführen (ohne diese Option nur Probelauf)
  --version            Version anzeigen
  -h, --help, --hilfe  diese Hilfe

Ablauf von "retten --ausfuehren":
  1. Prüfungen: Quelle nicht beschreibbar eingehängt, Ziel hat genug Platz, Ziel liegt nicht auf der Quelle.
  2. Doppelbestätigung: Sie tippen den Pfad der Quelle und den des Abbilds ein.
  3. Phase 1 (schnell, überspringt defekte Stellen), Phase 2 (Wiederholungen). Fortschritt in Klartext.
  4. Pausieren mit Strg+C; Fortsetzen mit demselben Aufruf (die Mapfile merkt sich den Stand).
  5. Ausgabe: Hinweise zum Einhängen (loop,ro) und zu PhotoRec auf dem Abbild.

Beispiele:
  dw-rettung geraete
  dw-rettung retten --quelle /dev/sdb --ziel /media/rettung --name stick
  dw-rettung retten --quelle /dev/sdb --ziel /media/rettung --name stick --ausfuehren
  dw-rettung status --ziel /media/rettung --name stick
  dw-rettung hinweise --image /media/rettung/stick.img
  dw-rettung photorec --image /media/rettung/stick.img --ausgabe /media/rettung/dateien --ausfuehren

Rückgabewerte: 0 vollständig gerettet, 4 fertig, aber mit defekten Stellen, 3 pausiert,
               1 Fehler oder Prüfung nicht bestanden, 2 falsche Bedienung.

Das Programm repariert nichts auf der Quelle und garantiert keine Datenrettung.
Ausführliche Hinweise: NUTZUNG.txt (unter /usr/share/doc/dw-rettung/) und "man dw-rettung".
EOF
}

# ---------------------------------------------------------------- Ausgabe
say()  { printf '%s\n' "$*"; }
warn() { printf 'Warnung: %s\n' "$*" >&2; }
err()  { printf 'Fehler: %s\n' "$*" >&2; }
die()  { err "$*"; exit 1; }
usage_fehler() { err "$*"; printf 'Hilfe: %s --hilfe\n' "$PROG" >&2; exit 2; }
hat() { command -v "$1" >/dev/null 2>&1; }

mktmp() {
  local d; d="$(mktemp -d)" || die "Temporärer Ordner konnte nicht angelegt werden."
  TMPDIRS+=("$d"); printf '%s' "$d"
}
cleanup() { local d; for d in "${TMPDIRS[@]+"${TMPDIRS[@]}"}"; do rm -rf -- "$d"; done; }

# Root-Rechte nur für den Einzelschritt (Lesen eines Blockgeräts, Eigentümer des Abbilds)
priv() {
  if [ "$(id -u)" -eq 0 ]; then "$@"; return $?; fi
  hat sudo || { err "sudo fehlt. Bitte als Root ausführen oder sudo installieren."; return 1; }
  SUDO_BENUTZT=1
  sudo -- "$@"
}

# Größe in Klartext, z. B. 1,5 GiB
mensch() {
  local b="$1" v i=0
  local -a u=("B" "KiB" "MiB" "GiB" "TiB" "PiB")
  if [ "$b" -lt 1024 ]; then printf '%s B' "$b"; return; fi
  v=$((b * 10))
  while [ "$v" -ge 10240 ] && [ "$i" -lt 5 ]; do v=$((v / 1024)); i=$((i + 1)); done
  printf '%s,%s %s' "$((v / 10))" "$((v % 10))" "${u[$i]}"
}
prozent() { # TEIL GESAMT
  if [ "$2" -le 0 ]; then printf '0,0 %%'; return; fi
  local p=$(( $1 * 1000 / $2 ))
  printf '%s,%s %%' "$((p / 10))" "$((p % 10))"
}
dauer() { # SEKUNDEN -> HH:MM:SS
  printf '%02d:%02d:%02d' "$(($1 / 3600))" "$(($1 % 3600 / 60))" "$(($1 % 60))"
}

# ---------------------------------------------------------------- Prüffunktionen (in Tests überschreibbar)
ist_blockgeraet() { [ -b "$1" ]; }

quelle_groesse() { # PFAD -> Bytes
  local s=""
  if ist_blockgeraet "$1"; then
    s="$(lsblk -b -d -n -o SIZE -- "$1" 2>/dev/null | head -n1 | tr -d ' ')"
    [ -n "$s" ] || s="$(priv blockdev --getsize64 "$1" 2>/dev/null)"
  else
    s="$(stat -c '%s' -- "$1" 2>/dev/null)"
  fi
  [[ "$s" =~ ^[0-9]+$ ]] && printf '%s' "$s"
}

mountinfo_datei() { printf '%s' "${DW_RETTUNG_MOUNTINFO:-/proc/self/mountinfo}"; }

# Gibt Zeilen "EINHÄNGEPUNKT<TAB>rw|ro<TAB>QUELLE" für alle Einhängungen der Quelle (inkl. Partitionen, Loop-Geräte) aus
quelle_einhaengungen() { # PFAD
  local src="$1" mi line mp opts rest msrc sopts modus l
  local -a geraete=("$src")
  mi="$(mountinfo_datei)"
  [ -r "$mi" ] || return 0
  if hat losetup && [ -f "$src" ]; then
    while IFS= read -r l; do [ -n "$l" ] && geraete+=("${l%%:*}"); done < <(losetup -j "$src" 2>/dev/null)
  fi
  while IFS= read -r line; do
    [[ "$line" == *" - "* ]] || continue
    read -r _ _ _ _ mp opts _ <<<"$line"
    rest="${line#* - }"
    read -r _ msrc sopts <<<"$rest"
    [ -n "${msrc:-}" ] || continue
    local g treffer=0 teil
    for g in "${geraete[@]}"; do
      teil="${msrc#"$g"}"
      if [ "$msrc" = "$g" ]; then treffer=1; break; fi
      [ "$teil" != "$msrc" ] || continue
      # Partitionen: /dev/sdb -> sdb1; Geräte, die auf eine Ziffer enden (loop0, nvme0n1, mmcblk0) -> p1
      if [[ "$g" =~ [0-9]$ ]]; then
        [[ "$teil" =~ ^p[0-9]+$ ]] && { treffer=1; break; }
      else
        [[ "$teil" =~ ^[0-9]+$ ]] && { treffer=1; break; }
      fi
    done
    [ "$treffer" -eq 1 ] || continue
    modus="ro"
    case ",$opts," in *,rw,*) modus="rw";; esac
    case ",${sopts:-}," in *,rw,*) modus="rw";; esac
    mp="${mp//\\040/ }"
    printf '%s\t%s\t%s\n' "$mp" "$modus" "$msrc"
  done <"$mi"
}

# Gibt den Datenträger (ohne Partitionsnummer) zu einem Gerät aus, z. B. /dev/sdb1 -> /dev/sdb
traeger_von() {
  local d="$1" p
  p="$(lsblk -no PKNAME -- "$d" 2>/dev/null | head -n1 | tr -d ' ')"
  if [ -n "$p" ]; then printf '/dev/%s' "$p"; else printf '%s' "$d"; fi
}

ziel_geraet() { # ORDNER -> Gerät, auf dem der Ordner liegt (z. B. /dev/sda2)
  local s
  s="$(findmnt -n -o SOURCE -T "$1" 2>/dev/null | head -n1)"
  printf '%s' "${s%%\[*}"
}

frei_bytes() { # ORDNER
  df -B1 --output=avail -- "$1" 2>/dev/null | tail -n1 | tr -d ' '
}

# ---------------------------------------------------------------- Mapfile lesen
MAP_OK=0; MAP_BAD=0; MAP_OFFEN=0; MAP_TOTAL=0
lese_mapfile() { # DATEI
  MAP_OK=0; MAP_BAD=0; MAP_OFFEN=0; MAP_TOTAL=0
  local pos size st
  [ -r "$1" ] || return 1
  while read -r pos size st _; do
    case "$pos" in "#"*|"") continue;; esac
    [[ "$pos" =~ ^0x[0-9a-fA-F]+$ && "$size" =~ ^0x[0-9a-fA-F]+$ ]] || continue
    pos=$((pos)); size=$((size))
    MAP_TOTAL=$((pos + size))
    case "$st" in
      +) MAP_OK=$((MAP_OK + size));;
      -) MAP_BAD=$((MAP_BAD + size));;
      *) MAP_OFFEN=$((MAP_OFFEN + size));;
    esac
  done <"$1"
  return 0
}

fortschritt_text() { # nach lese_mapfile
  printf 'Gerettet %s von %s (%s), noch offen %s, als defekt markiert %s' \
    "$(mensch "$MAP_OK")" "$(mensch "$MAP_TOTAL")" "$(prozent "$MAP_OK" "$MAP_TOTAL")" "$(mensch "$MAP_OFFEN")" "$(mensch "$MAP_BAD")"
}

# ---------------------------------------------------------------- Prüfungen
NPROB=0; NWARN=0
pr_ok()    { printf '  %s✓ %s%s\n' "$C_OK" "$*" "$C_RESET"; }
pr_fehler(){ printf '  %s✗ %s%s\n' "$C_DANGER" "$*" "$C_RESET"; NPROB=$((NPROB + 1)); }
pr_warn()  { printf '  %s⚠ %s%s\n' "$C_WARN" "$*" "$C_RESET"; NWARN=$((NWARN + 1)); }

SRC=""; SRC_SIZE=0; SRC_IST_GERAET=0
pruefe_quelle() {
  local s="$1" r e mp modus msrc swap
  SRC=""; SRC_SIZE=0; SRC_IST_GERAET=0
  if [ -z "$s" ]; then pr_fehler "Keine Quelle angegeben (--quelle)."; return; fi
  if [ ! -e "$s" ]; then pr_fehler "Quelle nicht gefunden: $s"; return; fi
  r="$(realpath -e -- "$s" 2>/dev/null)" || { pr_fehler "Quelle kann nicht aufgelöst werden: $s"; return; }
  SRC="$r"
  if [ -d "$SRC" ]; then pr_fehler "Die Quelle ist ein Ordner. Angegeben werden muss ein Datenträger (z. B. /dev/sdb) oder eine Image-Datei."; return; fi
  if ist_blockgeraet "$SRC"; then
    SRC_IST_GERAET=1
    pr_ok "Quelle ist ein Blockgerät: $SRC"
  elif [ -f "$SRC" ]; then
    if [ -r "$SRC" ]; then pr_ok "Quelle ist eine Datei (z. B. Testabbild): $SRC"; else pr_fehler "Die Quelldatei ist nicht lesbar: $SRC"; return; fi
  else
    pr_fehler "Die Quelle ist weder Blockgerät noch Datei: $SRC"; return
  fi
  SRC_SIZE="$(quelle_groesse "$SRC")"
  if [ -z "$SRC_SIZE" ] || [ "$SRC_SIZE" -le 0 ]; then
    pr_fehler "Die Größe der Quelle konnte nicht ermittelt werden (oder sie ist leer)."; SRC_SIZE=0; return
  fi
  pr_ok "Größe der Quelle: $(mensch "$SRC_SIZE") ($SRC_SIZE Bytes)"
  # Einhängungen
  e=0
  while IFS=$'\t' read -r mp modus msrc; do
    [ -n "$mp" ] || continue
    e=1
    if [ "$modus" = "rw" ]; then
      pr_fehler "Die Quelle ist BESCHREIBBAR eingehängt ($msrc auf $mp). Beim Kopieren würden sich Daten ändern, das Dateisystem kann weiter beschädigt werden. Abhilfe: sudo umount '$mp' (oder sudo mount -o remount,ro '$mp')."
    else
      pr_warn "Die Quelle ist eingehängt, aber nur lesend ($msrc auf $mp). Besser aushängen: sudo umount '$mp'."
    fi
  done < <(quelle_einhaengungen "$SRC")
  [ "$e" -eq 1 ] || pr_ok "Die Quelle ist nicht eingehängt."
  if [ -r /proc/swaps ] && swap="$(awk -v s="$SRC" '$1==s{print $1}' /proc/swaps)" && [ -n "$swap" ]; then
    pr_fehler "Die Quelle wird als Auslagerungsspeicher (Swap) benutzt."
  fi
}

IMG=""; MAP=""; ZIEL_R=""; FORTSETZEN=0
pruefe_ziel() {
  local z="$1" n="$2" tz tq avail need alloc mp modus msrc mpl
  IMG=""; MAP=""; ZIEL_R=""; FORTSETZEN=0
  if [ -z "$z" ]; then pr_fehler "Kein Zielordner angegeben (--ziel)."; return; fi
  if [ ! -d "$z" ]; then pr_fehler "Zielordner nicht gefunden: $z (bitte zuerst anlegen)."; return; fi
  ZIEL_R="$(realpath -e -- "$z" 2>/dev/null)" || { pr_fehler "Zielordner kann nicht aufgelöst werden: $z"; return; }
  case "$ZIEL_R" in
    /dev|/dev/*|/proc|/proc/*|/sys|/sys/*) pr_fehler "Der Zielordner liegt in einem Systemordner ($ZIEL_R)."; return;;
  esac
  if [ ! -w "$ZIEL_R" ]; then pr_fehler "Der Zielordner ist für Sie nicht beschreibbar: $ZIEL_R"; return; fi
  IMG="$ZIEL_R/$n.img"; MAP="$ZIEL_R/$n.map"
  if [ -n "$SRC" ] && { [ "$IMG" = "$SRC" ] || [ "$MAP" = "$SRC" ] || { [ -e "$IMG" ] && [ "$IMG" -ef "$SRC" ]; }; }; then
    pr_fehler "Abbild und Quelle sind dieselbe Datei."; return
  fi
  # Ziel nicht auf der Quelle
  if [ -n "$SRC" ] && [ "$SRC_IST_GERAET" -eq 1 ]; then
    tz="$(ziel_geraet "$ZIEL_R")"; tq="$(traeger_von "$SRC")"
    if [ -n "$tz" ] && [ "$(traeger_von "$tz")" = "$tq" ]; then
      pr_fehler "Der Zielordner liegt auf demselben Datenträger wie die Quelle ($tz auf $tq). Bitte einen anderen Datenträger wählen."; return
    fi
    while IFS=$'\t' read -r mpl modus msrc; do
      [ -n "$mpl" ] || continue
      case "$ZIEL_R/" in "$mpl"/*|"$mpl/") pr_fehler "Der Zielordner liegt innerhalb der Quelle (eingehängt auf $mpl)."; return;; esac
    done < <(quelle_einhaengungen "$SRC")
    pr_ok "Ziel liegt auf einem anderen Datenträger (${tz:-unbekannt})."
  else
    pr_ok "Zielordner: $ZIEL_R"
  fi
  # vorhandene Dateien
  if [ -e "$IMG" ] && [ -e "$MAP" ]; then
    FORTSETZEN=1
    if lese_mapfile "$MAP"; then
      if [ -n "$SRC" ] && [ "$SRC_SIZE" -gt 0 ] && [ "$MAP_TOTAL" -gt "$SRC_SIZE" ]; then
        pr_fehler "Die vorhandene Mapfile ($MAP) gehört offenbar zu einer anderen, größeren Quelle ($(mensch "$MAP_TOTAL") statt $(mensch "$SRC_SIZE")). Bitte einen anderen --name wählen."; return
      fi
      pr_ok "Vorhandene Mapfile gefunden: Der Lauf wird fortgesetzt. Stand: $(fortschritt_text)."
    else
      pr_fehler "Mapfile nicht lesbar: $MAP"; return
    fi
  elif [ -e "$IMG" ]; then
    pr_fehler "Das Abbild $IMG existiert schon, aber es gibt keine Mapfile dazu. Es wird nichts überschrieben. Anderen --name wählen oder die Datei selbst verschieben."; return
  elif [ -e "$MAP" ]; then
    pr_fehler "Die Mapfile $MAP existiert schon, aber ohne Abbild. Anderen --name wählen oder die Datei selbst verschieben."; return
  else
    pr_ok "Es gibt noch kein Abbild: ein neues wird angelegt."
  fi
  # Platz
  if [ -n "$SRC" ] && [ "$SRC_SIZE" -gt 0 ]; then
    avail="$(frei_bytes "$ZIEL_R")"
    if ! [[ "$avail" =~ ^[0-9]+$ ]]; then pr_warn "Freier Platz konnte nicht ermittelt werden."; return; fi
    alloc=0
    if [ -e "$IMG" ]; then alloc=$(( $(stat -c '%b' -- "$IMG") * $(stat -c '%B' -- "$IMG") )); fi
    [ "$alloc" -gt "$SRC_SIZE" ] && alloc="$SRC_SIZE"
    need=$((SRC_SIZE - alloc))
    local reserve=$((SRC_SIZE / 100)); [ "$reserve" -lt 16777216 ] && reserve=16777216
    if [ "$avail" -lt $((need + reserve)) ]; then
      pr_fehler "Auf dem Ziel sind nur $(mensch "$avail") frei, Quelle benötigt $(mensch "$SRC_SIZE") (inklusive Reserve etwa $(mensch $((need + reserve)))$( [ "$alloc" -gt 0 ] && printf ', davon schon belegt %s' "$(mensch "$alloc")" ))."
    else
      pr_ok "Genug Platz im Ziel: frei $(mensch "$avail"), benötigt etwa $(mensch $((need + reserve)))."
    fi
  fi
}

# ---------------------------------------------------------------- Bestätigung
bestaetige() { # BEZEICHNUNG ERWARTET
  local a=""
  printf 'Zur Bestätigung %s eintippen (genau so: %s): ' "$1" "$2" >&2
  IFS= read -r a || [ -n "$a" ] || { printf '\n' >&2; die "Eingabe beendet, Abbruch. Es wurde nichts kopiert."; }
  if [ "$a" != "$2" ]; then die "Eingabe stimmt nicht mit '$2' überein. Abbruch, es wurde nichts kopiert."; fi
}

frage() { # PROMPT [STANDARD]
  local p="$1" d="${2:-}" a=""
  if [ -n "$d" ]; then printf '%s [%s]: ' "$p" "$d" >&2; else printf '%s: ' "$p" >&2; fi
  IFS= read -r a || [ -n "$a" ] || { printf '\n' >&2; die "Eingabe beendet, Abbruch. Es wurde nichts geändert."; }
  ANTWORT="${a:-$d}"
}
ANTWORT=""

# ---------------------------------------------------------------- Kopieren
DDR_MAPINT=0; DDR_CHECKSIZE=0
erkenne_ddrescue() {
  hat ddrescue || die "ddrescue ist nicht installiert. Installation z. B.: sudo apt install gddrescue"
  local h; h="$(ddrescue --help 2>&1)"
  [[ "$h" == *mapfile-interval* ]] && DDR_MAPINT=1
  [[ "$h" == *check-input-size* ]] && DDR_CHECKSIZE=1
  return 0
}

baue_ddrescue_args() { # PHASE(1|2) -> DDR_ARGS
  DDR_ARGS=()
  if [ "$1" = "1" ]; then
    DDR_ARGS+=(-n)
  else
    [ "$SRC_IST_GERAET" -eq 1 ] && DDR_ARGS+=(-d)
    DDR_ARGS+=("-r$OPT_WIEDERHOLUNGEN")
  fi
  [ "$DDR_MAPINT" -eq 1 ] && DDR_ARGS+=("--mapfile-interval=$OPT_TAKT")
  [ "$FORTSETZEN" -eq 1 ] && [ "$DDR_CHECKSIZE" -eq 1 ] && [ -z "$OPT_MAX" ] && DDR_ARGS+=(-I)
  [ -n "$OPT_MAX" ] && DDR_ARGS+=(-s "$OPT_MAX")
  DDR_ARGS+=(-- "$SRC" "$IMG" "$MAP")
}

zeige_befehl() { # PHASE
  baue_ddrescue_args "$1"
  local pre=""
  [ "$SRC_IST_GERAET" -eq 1 ] && [ "$(id -u)" -ne 0 ] && pre="sudo "
  printf '  %sddrescue' "$pre"; printf ' %q' "${DDR_ARGS[@]}"; printf '\n'
}

bei_int()  { UNTERBROCHEN=1; }
bei_term() { UNTERBROCHEN=1; [ -n "$LAUFENDE_PID" ] && kill -TERM "$LAUFENDE_PID" 2>/dev/null; }

DDR_RC=0
fuehre_phase_aus() { # PHASE LOGDATEI
  local phase="$1" logf="$2" start t0 last="" term_gesendet=0
  baue_ddrescue_args "$phase"
  t0=$SECONDS
  : >>"$logf"
  local -a ddr_cmd=(ddrescue "${DDR_ARGS[@]}")
  if [ "$SRC_IST_GERAET" -eq 1 ] && [ "$(id -u)" -ne 0 ]; then
    hat sudo || die "sudo fehlt. Bitte als Root ausführen oder sudo installieren."
    SUDO_BENUTZT=1
    ddr_cmd=(sudo -- "${ddr_cmd[@]}")
  fi
  "${ddr_cmd[@]}" >>"$logf" 2>&1 &
  LAUFENDE_PID=$!
  local letzte_meldung=$SECONDS
  while kill -0 "$LAUFENDE_PID" 2>/dev/null; do
    sleep 0.2 &
    wait $! 2>/dev/null
    if [ "$UNTERBROCHEN" -eq 1 ] && [ "$term_gesendet" -eq 0 ]; then
      # Strg+C erreicht ddrescue nicht immer (Hintergrundprozess); dann wird es hier sauber gestoppt
      sleep 1; kill -TERM "$LAUFENDE_PID" 2>/dev/null; term_gesendet=1
    fi
    kill -0 "$LAUFENDE_PID" 2>/dev/null || break
    if [ $((SECONDS - letzte_meldung)) -ge "$OPT_TAKT" ]; then
      letzte_meldung=$SECONDS
      if lese_mapfile "$MAP" && [ "$MAP_TOTAL" -gt 0 ]; then
        start="[Phase $phase/2] $(dauer $((SECONDS - t0))) - $(fortschritt_text)"
        if [ -t 1 ] && hat tput; then
          local pct=0 eta="" elapsed
          [ "$MAP_TOTAL" -gt 0 ] && pct=$((MAP_OK * 1000 / MAP_TOTAL))
          elapsed=$((SECONDS - t0))
          if [ "$MAP_OK" -gt 0 ] && [ "$elapsed" -gt 0 ] && [ "${MAP_OFFEN:-0}" -gt 0 ]; then
            eta=" — $(dauer $(( elapsed * MAP_OFFEN / MAP_OK ))) verbleibend"
          fi
          printf '%sGerettet: %s / %s (%s,%s %%) — Fehlerhaft: %s — Laufzeit: %s%s%s\n' \
            "$C_INFO" "$(mensch "$MAP_OK")" "$(mensch "$MAP_TOTAL")" "$((pct / 10))" "$((pct % 10))" \
            "$(mensch "$MAP_BAD")" "$(dauer "$elapsed")" "$eta" "$C_RESET"
        elif [ "$start" != "$last" ]; then
          say "$start"
        fi
        last="$start"
      fi
    fi
  done
  wait "$LAUFENDE_PID" 2>/dev/null; DDR_RC=$?
  LAUFENDE_PID=""
  return 0
}

abschluss_hinweise() {
  say ""
  printf '%s✓ Schritt 4/4 abgeschlossen%s\n' "$C_OK" "$C_RESET"
  printf '%s→ Weiter mit: PhotoRec auf dem Abbild starten:%s\n' "$C_INFO" "$C_RESET"
  printf '  %s photorec --image %q --ausgabe ORDNER_AUF_ANDEREM_DATENTRAEGER --ausfuehren\n' "$PROG" "$IMG"
  printf '%s=== Nächste Schritte ===%s\n' "$C_INFO" "$C_RESET"
  hinweise_text "$IMG"
}

rettung_kasten() {
  local okpct badpct bad_color bad_mark
  okpct="$(prozent "$MAP_OK" "$MAP_TOTAL")"
  badpct="$(prozent "$MAP_BAD" "$MAP_TOTAL")"
  bad_color="$C_WARN"; bad_mark="⚠"
  if [ "$MAP_BAD" -eq 0 ]; then bad_color="$C_OK"; bad_mark="✓"; fi
  printf '%s%s╔══ Rettung abgeschlossen ═════════════════════════════╗%s\n' "$C_INFO" "$C_BOLD" "$C_RESET"
  printf '║  Gelesene Daten:   %s%s (%s)%s  %s✓%s\n' "$C_OK" "$(mensch "$MAP_OK")" "$okpct" "$C_RESET" "$C_OK" "$C_RESET"
  printf '║  Fehlerhafte Daten: %s%s (%s)%s  %s%s%s\n' "$bad_color" "$(mensch "$MAP_BAD")" "$badpct" "$C_RESET" "$bad_color" "$bad_mark" "$C_RESET"
  printf '║  Ausgabe:          %s%s%s\n' "$C_GOLD" "$IMG" "$C_RESET"
  printf '%s╚══════════════════════════════════════════════════════╝%s\n' "$C_INFO" "$C_RESET"
}

cmd_retten() {
  local logf pos resttext ok_all=0 rc phasen
  # Eingaben
  if [ -z "$OPT_QUELLE" ]; then
    say "DW Rettungs-Assistent $VERSION - geführter Ablauf"
    say "Die Quelle wird nur GELESEN. Es wird nichts repariert und nichts auf die Quelle geschrieben."
    say ""
    cmd_geraete
    say ""
    frage "Quelle (Datenträger, z. B. /dev/sdb, oder Image-Datei)"; OPT_QUELLE="$ANTWORT"
    frage "Zielordner für Abbild und Mapfile (anderer Datenträger, genug Platz)"; OPT_ZIEL="$ANTWORT"
    local vor; vor="$(basename -- "$OPT_QUELLE")"; vor="${vor//[^A-Za-z0-9._-]/_}"; vor="${vor#.}"; [ -n "$vor" ] || vor="rettung"
    [ -n "$OPT_NAME" ] || { frage "Name der Rettung (Dateiname ohne Endung)" "$vor"; OPT_NAME="$ANTWORT"; }
  fi
  if [ -z "$OPT_NAME" ]; then
    OPT_NAME="$(basename -- "$OPT_QUELLE")"; OPT_NAME="${OPT_NAME//[^A-Za-z0-9._-]/_}"; OPT_NAME="${OPT_NAME#.}"; [ -n "$OPT_NAME" ] || OPT_NAME="rettung"
  fi
  [[ "$OPT_NAME" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,59}$ ]] || usage_fehler "Name ungültig: nur Buchstaben (ohne Umlaute), Ziffern, . _ -, höchstens 60 Zeichen, nicht mit Punkt beginnend."
  say ""
  printf '%s[Schritt 1/4]%s Quelle und Ziel prüfen\n' "$C_GOLD" "$C_RESET"
  say "=== Prüfungen ==="
  pruefe_quelle "$OPT_QUELLE"
  if [ -n "$SRC" ] && [ "$SRC_SIZE" -gt 0 ] && [ "$NPROB" -eq 0 ]; then pruefe_ziel "$OPT_ZIEL" "$OPT_NAME"
  elif [ -n "$OPT_ZIEL" ]; then pruefe_ziel "$OPT_ZIEL" "$OPT_NAME"
  else pr_fehler "Kein Zielordner angegeben (--ziel)."; fi
  if ! hat ddrescue; then pr_fehler "ddrescue ist nicht installiert (sudo apt install gddrescue)."; fi
  say ""
  if [ "$NPROB" -gt 0 ]; then
    say "Ergebnis: $NPROB Problem(e) gefunden. Es wird nichts kopiert."
    exit 1
  fi
  erkenne_ddrescue
  printf '%s✓ Schritt 1/4 abgeschlossen%s\n' "$C_OK" "$C_RESET"
  printf '%s→ Weiter mit: Schritt 2 — schnelles Abbild erstellen%s\n' "$C_INFO" "$C_RESET"
  say "=== Plan ==="
  say "Quelle:    $SRC ($(mensch "$SRC_SIZE"), wird nur gelesen)"
  say "Abbild:    $IMG"
  say "Mapfile:   $MAP"
  if [ "$FORTSETZEN" -eq 1 ]; then say "Modus:     Fortsetzen (Stand: $(fortschritt_text))"; else say "Modus:     neues Abbild"; fi
  [ -n "$OPT_MAX" ] && say "Begrenzung: nur die ersten $OPT_MAX Bytes (--max-groesse)"
  say "Befehle (Phase 1 schnell, Phase 2 mit $OPT_WIEDERHOLUNGEN Wiederholungen):"
  zeige_befehl 1
  zeige_befehl 2
  [ "$SRC_IST_GERAET" -eq 1 ] && [ "$(id -u)" -ne 0 ] && say "Für das Blockgerät fragt das Programm sudo an (nur zum Lesen durch ddrescue)."
  [ "$NWARN" -gt 0 ] && say "Hinweis: $NWARN Warnung(en) oben beachten."
  if [ "$AUSFUEHREN" -ne 1 ]; then
    say ""
    say "PROBELAUF: Es wurde nichts kopiert und nichts geändert."
    say "Zum Ausführen denselben Aufruf mit --ausfuehren wiederholen:"
    say "  $PROG retten --quelle $(printf '%q' "$SRC") --ziel $(printf '%q' "$ZIEL_R") --name $(printf '%q' "$OPT_NAME")${OPT_MAX:+ --max-groesse $OPT_MAX} --ausfuehren"
    exit 0
  fi
  # Doppelbestätigung
  say ""
  say "ACHTUNG: Es wird gleich von der Quelle gelesen und in das Abbild geschrieben."
  bestaetige "den Pfad der QUELLE" "$SRC"
  bestaetige "den Pfad des ZIEL-ABBILDS" "$IMG"
  logf="$ZIEL_R/$OPT_NAME.ddrescue.log"
  trap bei_int INT
  trap 'bei_term' TERM HUP
  say ""
  say "Start. Pausieren mit Strg+C, Fortsetzen mit demselben Aufruf. Fortschrittsmeldung etwa alle $OPT_TAKT s."
  [ "$SRC_IST_GERAET" -eq 1 ] && [ "$(id -u)" -ne 0 ] && { priv true || die "sudo wurde nicht bestätigt. Es wurde nichts kopiert."; }
  for phasen in 1 2; do
    [ "$UNTERBROCHEN" -eq 1 ] && break
    say ""
    printf '%s[Schritt %s/4]%s Phase %s von 2: %s\n' "$C_GOLD" "$((phasen + 1))" "$C_RESET" "$phasen" \
      "$( [ "$phasen" = 1 ] && echo "schnelles Kopieren, defekte Stellen werden übersprungen" || echo "Wiederholungen an den defekten Stellen" )"
    # ab Phase 1 ist es eine Fortsetzung, falls Dateien schon da sind
    [ -e "$IMG" ] && [ -e "$MAP" ] && FORTSETZEN=1
    fuehre_phase_aus "$phasen" "$logf"
    rc=$DDR_RC
    if [ "$UNTERBROCHEN" -eq 1 ]; then break; fi
    if [ "$rc" -ne 0 ]; then
      err "ddrescue wurde mit Fehlercode $rc beendet. Letzte Meldungen:"
      tr -d '\033' <"$logf" | tail -n 8 >&2
      [ "$SUDO_BENUTZT" -eq 1 ] && priv chown "$(id -u):$(id -g)" -- "$IMG" "$MAP" "$logf" 2>/dev/null
      exit 1
    fi
    lese_mapfile "$MAP" && printf '%s✓ Schritt %s/4 abgeschlossen%s — %s\n' "$C_OK" "$((phasen + 1))" "$C_RESET" "$(fortschritt_text)"
    if [ -z "$OPT_MAX" ] && lese_mapfile "$MAP" && [ "$MAP_OFFEN" -eq 0 ] && [ "$MAP_BAD" -eq 0 ]; then
      ok_all=1
      printf '%s✓ Schritt 3/4 entfällt: keine defekten Stellen%s\n' "$C_OK" "$C_RESET"
      printf '%s→ Weiter mit: Schritt 4 — Ergebnis prüfen; Wiederholungen nicht nötig%s\n' "$C_INFO" "$C_RESET"
      break
    fi
    if [ "$phasen" = 1 ]; then
      printf '%s→ Weiter mit: Schritt 3 — defekte Stellen erneut lesen:%s\n' "$C_INFO" "$C_RESET"
      zeige_befehl 2
    else
      printf '%s→ Weiter mit: Schritt 4 — Ergebnis prüfen%s\n' "$C_INFO" "$C_RESET"
    fi
  done
  [ "$SUDO_BENUTZT" -eq 1 ] && priv chown "$(id -u):$(id -g)" -- "$IMG" "$MAP" "$logf" 2>/dev/null
  trap - INT TERM HUP
  lese_mapfile "$MAP"
  say ""
  if [ "$UNTERBROCHEN" -eq 1 ]; then
    say "PAUSIERT. Stand: $(fortschritt_text)."
    say "Fortsetzen mit demselben Aufruf: $PROG retten --quelle $(printf '%q' "$SRC") --ziel $(printf '%q' "$ZIEL_R") --name $(printf '%q' "$OPT_NAME") --ausfuehren"
    say "Kurzform: denselben Aufruf mit --weiter statt --ausfuehren."
    exit 3
  fi
  printf '%s[Schritt 4/4]%s Ergebnis und nächste Schritte auswerten\n' "$C_GOLD" "$C_RESET"
  if [ -n "$OPT_MAX" ] && [ "$MAP_OFFEN" -gt 0 ]; then
    say "TEILWEISE FERTIG (Begrenzung --max-groesse). Stand: $(fortschritt_text)."
    say "Rest kopieren: denselben Aufruf ohne --max-groesse wiederholen."
    exit 0
  fi
  if [ "$ok_all" -eq 1 ] || { [ "$MAP_OFFEN" -eq 0 ] && [ "$MAP_BAD" -eq 0 ]; }; then
    say "FERTIG: Die Quelle wurde vollständig kopiert ($(mensch "$MAP_OK"))."
    rettung_kasten
    abschluss_hinweise
    exit 0
  fi
  resttext="$(mensch "$((MAP_BAD + MAP_OFFEN))")"
  say "FERTIG MIT LÜCKEN: $(fortschritt_text)."
  say "Nicht lesbar blieben $resttext. Weitere Versuche (mehr Wiederholungen, anderer Anschluss/Kabel) mit --wiederholungen 10 und demselben Aufruf; die Mapfile merkt sich den Stand."
  rettung_kasten
  abschluss_hinweise
  exit 4
}

# ---------------------------------------------------------------- Weitere Befehle
cmd_geraete() {
  say "Verfügbare Datenträger:"
  if hat lsblk; then
    say "  ROTA: 1 = HDD, 0 = SSD/Flash; Modell und Typ stehen daneben."
    lsblk -d -o NAME,SIZE,TYPE,ROTA,MODEL,VENDOR,TRAN 2>/dev/null | grep -v 'loop' || lsblk -d -o NAME,SIZE,TYPE,ROTA,MODEL,TRAN 2>/dev/null | grep -v 'loop'
    say ""
    say "Partitionen (Größe und Dateisystem):"
    lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL 2>/dev/null || lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL
  else
    say "  lsblk nicht gefunden."
  fi
}

cmd_status() {
  local m="$OPT_MAPFILE" r
  if [ -z "$m" ]; then
    [ -n "$OPT_ZIEL" ] && [ -n "$OPT_NAME" ] || usage_fehler "status braucht --mapfile DATEI oder --ziel ORDNER mit --name NAME."
    m="$OPT_ZIEL/$OPT_NAME.map"
  fi
  [ -f "$m" ] || die "Mapfile nicht gefunden: $m"
  lese_mapfile "$m" || die "Mapfile nicht lesbar: $m"
  [ "$MAP_TOTAL" -gt 0 ] || die "Die Mapfile enthält noch keine Daten (der Lauf hat noch keinen Stand gespeichert)."
  say "Mapfile:          $m"
  say "Gesamtgröße:      $(mensch "$MAP_TOTAL")"
  say "Gerettet:         $(mensch "$MAP_OK") ($(prozent "$MAP_OK" "$MAP_TOTAL"))"
  say "Noch offen:       $(mensch "$MAP_OFFEN") (noch nicht versucht oder noch nicht endgültig geprüft)"
  say "Defekt markiert:  $(mensch "$MAP_BAD")"
  if [ "$MAP_OFFEN" -eq 0 ] && [ "$MAP_BAD" -eq 0 ]; then r="vollständig gerettet"
  elif [ "$MAP_OFFEN" -eq 0 ]; then r="abgeschlossen, mit unlesbaren Stellen"
  else r="nicht abgeschlossen (mit demselben Aufruf wie beim Start fortsetzen)"; fi
  say "Zustand:          $r"
}

hinweise_text() { # IMAGE
  local img="$1" fs="" q hat_pt=0 lq
  q="$(printf '%q' "$img")"
  if [ -f "$img" ]; then
    hat blkid && fs="$(blkid -p -o value -s TYPE -- "$img" 2>/dev/null | head -n1)"
    if hat sfdisk && sfdisk -l -- "$img" 2>/dev/null | grep -q "^Device"; then hat_pt=1; fi
    [ -n "$fs" ] && say "Erkannt (nur gelesen): Dateisystem $fs im Abbild."
    [ "$hat_pt" -eq 1 ] && say "Erkannt (nur gelesen): Das Abbild enthält eine Partitionstabelle (ganzer Datenträger)."
  fi
  lq="ro,loop"
  case "$fs" in ext3|ext4) lq="ro,loop,noload";; xfs) lq="ro,loop,norecovery";; esac
  say ""
  say "A) Abbild schreibgeschützt einhängen und Dateien ansehen (nichts im Abbild wird verändert)"
  if [ "$hat_pt" -eq 1 ]; then
    say "   Das Abbild hat eine Partitionstabelle:"
    say "     sudo losetup --find --show --read-only --partscan $q      # gibt z. B. /dev/loop0 aus"
    say "     sudo mkdir -p /mnt/rettung"
    say "     sudo mount -o ro /dev/loop0p1 /mnt/rettung              # Partition aus 'lsblk /dev/loop0' wählen"
    say "   Danach:  sudo umount /mnt/rettung; sudo losetup -d /dev/loop0"
  else
    say "     sudo mkdir -p /mnt/rettung"
    say "     sudo mount -o $lq $q /mnt/rettung"
    say "   Danach:  sudo umount /mnt/rettung"
    say "   (Ist es ein ganzer Datenträger mit Partitionstabelle: sudo losetup --find --show --read-only --partscan $q und dann die Partition /dev/loop0p1 mit  mount -o ro  einhängen.)"
  fi
  say "   Wichtig: immer mit 'ro' einhängen. Kopieren Sie gerettete Dateien auf einen dritten Datenträger."
  say ""
  say "B) Gelöschte oder nicht mehr auffindbare Dateien mit PhotoRec suchen (nur auf dem Abbild, nie auf dem Original)"
  say "     $PROG photorec --image $q --ausgabe ORDNER_AUF_ANDEREM_DATENTRAEGER --ausfuehren"
  say "   Hinweis: PhotoRec sortiert nach Dateityp, Ordnerstrukturen werden nicht rekonstruiert."
  say "   PhotoRec sucht nach Dateiformaten und schreibt Funde in ORDNER/recup_dir.1, recup_dir.2 usw. (ohne Dateinamen)."
  say "   In den PhotoRec-Menüs: Abbild wählen, Partition oder 'Whole disk', Dateisystemtyp (ext2/ext3/ext4 oder Other), 'Free' oder 'Whole', dann C zum Starten."
  say ""
  say "C) Fehlende Stellen: Wenn 'FERTIG MIT LÜCKEN' angezeigt wurde, können Sie den Lauf mit mehr Wiederholungen fortsetzen (--wiederholungen 10)."
}

cmd_hinweise() {
  [ -n "$OPT_IMAGE" ] || usage_fehler "hinweise braucht --image DATEI."
  [ -f "$OPT_IMAGE" ] || die "Abbild nicht gefunden: $OPT_IMAGE"
  say "Hinweise für das Abbild: $OPT_IMAGE"
  hinweise_text "$(realpath -e -- "$OPT_IMAGE")"
}

cmd_photorec() {
  local img out avail sz
  [ -n "$OPT_IMAGE" ] || usage_fehler "photorec braucht --image DATEI und --ausgabe ORDNER."
  [ -n "$OPT_AUSGABE" ] || usage_fehler "photorec braucht --ausgabe ORDNER."
  say "=== Prüfungen ==="
  if [ ! -e "$OPT_IMAGE" ]; then pr_fehler "Abbild nicht gefunden: $OPT_IMAGE"
  else
    img="$(realpath -e -- "$OPT_IMAGE")"
    if [ -b "$img" ]; then pr_fehler "PhotoRec wird nur auf Abbild-DATEIEN gestartet, nie direkt auf einem Datenträger. Erstellen Sie zuerst ein Abbild mit '$PROG retten'."
    elif [ ! -f "$img" ]; then pr_fehler "Das Abbild ist keine normale Datei: $img"
    elif [ ! -r "$img" ]; then pr_fehler "Das Abbild ist nicht lesbar: $img"
    else pr_ok "Abbild: $img"; fi
  fi
  if [ ! -d "$OPT_AUSGABE" ]; then pr_fehler "Ausgabeordner nicht gefunden: $OPT_AUSGABE (bitte zuerst anlegen)."
  else
    out="$(realpath -e -- "$OPT_AUSGABE")"
    if [ ! -w "$out" ]; then pr_fehler "Der Ausgabeordner ist nicht beschreibbar: $out"; else pr_ok "Ausgabeordner: $out"; fi
    if [ -n "${img:-}" ] && [ -f "$img" ]; then
      sz="$(stat -c '%s' -- "$img")"; avail="$(frei_bytes "$out")"
      case "$out/" in "$img"/*) pr_fehler "Der Ausgabeordner liegt innerhalb des Abbilds.";; esac
      if [[ "$avail" =~ ^[0-9]+$ ]]; then
        if [ "$avail" -lt "$sz" ]; then pr_warn "Der freie Platz im Ausgabeordner ($(mensch "$avail")) ist kleiner als das Abbild ($(mensch "$sz")). PhotoRec kann den Platz füllen; ggf. zwischendurch Funde sichern."
        else pr_ok "Freier Platz im Ausgabeordner: $(mensch "$avail")."; fi
      fi
      if [ "$(stat -c '%d' -- "$img")" = "$(stat -c '%d' -- "$out")" ]; then pr_warn "Abbild und Ausgabeordner liegen auf demselben Datenträger. Besser ein anderer Datenträger."; fi
    fi
  fi
  hat photorec || pr_fehler "photorec ist nicht installiert (sudo apt install testdisk)."
  say ""
  if [ "$NPROB" -gt 0 ]; then say "Ergebnis: $NPROB Problem(e). PhotoRec wird nicht gestartet."; exit 1; fi
  say "=== Plan ==="
  say "PhotoRec liest das Abbild nur und schreibt Funde in $out/recup_dir.N"
  printf '  photorec /d %q %q\n' "$out/" "$img"
  if [ "$AUSFUEHREN" -ne 1 ]; then
    say ""; say "PROBELAUF: PhotoRec wurde nicht gestartet. Zum Starten --ausfuehren anhängen."; exit 0
  fi
  say ""
  bestaetige "den Pfad der ABBILD-DATEI" "$img"
  bestaetige "den Pfad des AUSGABEORDNERS" "$out"
  say "PhotoRec startet mit eigener Menüführung (Pfeiltasten, Eingabe, Q zum Beenden)."
  photorec /d "$out/" "$img"
  rc=$?
  say ""
  say "PhotoRec beendet (Code $rc). Funde: $out/recup_dir.* . Prüfen Sie die Funde und kopieren Sie wichtige Dateien auf einen weiteren Datenträger."
  exit "$rc"
}

# ---------------------------------------------------------------- Start
main() {
  local cmd=""
  trap cleanup EXIT
  if [ $# -gt 0 ]; then
    case "$1" in -*) ;; *) cmd="$1"; shift;; esac
  fi
  while [ $# -gt 0 ]; do
    case "$1" in
      --ausfuehren|--weiter) AUSFUEHREN=1;;
      --quelle|--ziel|--name|--mapfile|--image|--ausgabe|--max-groesse|--wiederholungen|--takt)
        [ $# -ge 2 ] || usage_fehler "Option $1 braucht einen Wert."
        case "$1" in
          --quelle) OPT_QUELLE="$2";; --ziel) OPT_ZIEL="$2";; --name) OPT_NAME="$2";; --mapfile) OPT_MAPFILE="$2";;
          --image) OPT_IMAGE="$2";; --ausgabe) OPT_AUSGABE="$2";; --max-groesse) OPT_MAX="$2";;
          --wiederholungen) OPT_WIEDERHOLUNGEN="$2";; --takt) OPT_TAKT="$2";;
        esac
        shift;;
      --version) say "DW Rettungs-Assistent $VERSION"; exit 0;;
      -h|--help|--hilfe) usage; exit 0;;
      *) usage_fehler "Unbekannte Option: $1";;
    esac
    shift
  done
  [[ "$OPT_WIEDERHOLUNGEN" =~ ^[0-9]{1,3}$ ]] || usage_fehler "--wiederholungen: Zahl von 0 bis 999 erwartet."
  [[ "$OPT_TAKT" =~ ^[1-9][0-9]{0,3}$ ]] || usage_fehler "--takt: ganze Sekunden von 1 bis 9999 erwartet."
  if [ -n "$OPT_MAX" ] && ! [[ "$OPT_MAX" =~ ^[1-9][0-9]*([kKMGT]i?)?$ ]]; then usage_fehler "--max-groesse: Zahl mit optionaler Endung k, M, G, T oder Ki, Mi, Gi, Ti erwartet (z. B. 100M)."; fi
  if [ -n "$cmd" ]; then
    printf '%s%s╔══════════════════════════════════════════════════════╗%s\n' "$C_DANGER" "$C_BOLD" "$C_RESET"
    printf '%s%s║%s  %s⚠  WICHTIG: Beschädigtes Laufwerk NICHT schreiben%s  %s%s║%s\n' "$C_DANGER" "$C_BOLD" "$C_RESET" "$C_WARN" "$C_RESET" "$C_DANGER" "$C_BOLD" "$C_RESET"
    printf '%s%s║%s     %sQuelle wird nur gelesen — nie auf die Quelle.%s    %s%s║%s\n' "$C_DANGER" "$C_BOLD" "$C_RESET" "$C_WARN" "$C_RESET" "$C_DANGER" "$C_BOLD" "$C_RESET"
    printf '%s%s╚══════════════════════════════════════════════════════╝%s\n' "$C_DANGER" "$C_BOLD" "$C_RESET"
  fi
  case "$cmd" in
    "") usage;;
    hilfe|help) usage;;
    geraete) cmd_geraete;;
    retten) cmd_retten;;
    status) cmd_status;;
    hinweise) cmd_hinweise;;
    photorec) cmd_photorec;;
    *) usage_fehler "Unbekannter Befehl: $cmd";;
  esac
  exit 0
}

if [ "${BASH_SOURCE[0]}" = "$0" ]; then main "$@"; fi
