335 lines
9.4 KiB
Bash
335 lines
9.4 KiB
Bash
#!/bin/bash
|
|
# ssd-safe-eject.sh - Safe Eject Extension für USB-C SSDs
|
|
# Zweck: Sichere Entfernung von USB-C SSDs mit vollständigem Sync und Hardware-Eject
|
|
|
|
set -e
|
|
|
|
# Konfiguration
|
|
MOUNT_POINT="/mnt/ssd-storage"
|
|
LOG_FILE="/var/log/ssd-mount.log"
|
|
|
|
# Farben für Ausgabe
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Logging-Funktion
|
|
log_message() {
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - SAFE-EJECT: $1" | sudo tee -a "$LOG_FILE"
|
|
echo -e "${BLUE}[SAFE-EJECT]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - SUCCESS: $1" | sudo tee -a "$LOG_FILE"
|
|
echo -e "${GREEN}[✓]${NC} $1"
|
|
}
|
|
|
|
log_warning() {
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - WARNING: $1" | sudo tee -a "$LOG_FILE"
|
|
echo -e "${YELLOW}[!]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: $1" | sudo tee -a "$LOG_FILE"
|
|
echo -e "${RED}[✗]${NC} $1"
|
|
}
|
|
|
|
# Schritt 1: Pre-Eject Validierung
|
|
validate_pre_eject() {
|
|
log_message "Validiere Pre-Eject Status..."
|
|
|
|
if ! mountpoint -q "$MOUNT_POINT"; then
|
|
log_warning "Keine SSD auf $MOUNT_POINT gemountet"
|
|
return 1
|
|
fi
|
|
|
|
mounted_device=$(df "$MOUNT_POINT" | tail -1 | awk '{print $1}')
|
|
log_success "Gemountetes Device identifiziert: $mounted_device"
|
|
|
|
# Prüfe ob Device USB-Device ist
|
|
device_base=$(echo "$mounted_device" | sed 's/[0-9]*$//')
|
|
if lsblk -o TRAN "$device_base" 2>/dev/null | grep -q usb; then
|
|
log_success "USB-Device bestätigt: $device_base"
|
|
echo "$mounted_device"
|
|
return 0
|
|
else
|
|
log_error "Device ist kein USB-Device - Safe-Eject nicht anwendbar"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Schritt 2: Aktive Prozesse prüfen
|
|
check_active_processes() {
|
|
local device="$1"
|
|
log_message "Prüfe aktive Prozesse auf $MOUNT_POINT..."
|
|
|
|
# Prozesse finden die das Mount-Point verwenden
|
|
active_processes=$(lsof "$MOUNT_POINT" 2>/dev/null || true)
|
|
|
|
if [ -n "$active_processes" ]; then
|
|
log_warning "Aktive Prozesse gefunden:"
|
|
echo "$active_processes"
|
|
echo ""
|
|
echo "Prozesse beenden? (y/n/s für skip)"
|
|
read -r response
|
|
|
|
case "$response" in
|
|
"y"|"Y")
|
|
log_message "Beende Prozesse..."
|
|
lsof -t "$MOUNT_POINT" 2>/dev/null | xargs -r sudo kill -TERM
|
|
sleep 2
|
|
# Prüfe erneut
|
|
remaining=$(lsof "$MOUNT_POINT" 2>/dev/null || true)
|
|
if [ -n "$remaining" ]; then
|
|
log_warning "Einige Prozesse laufen noch - Force Kill..."
|
|
lsof -t "$MOUNT_POINT" 2>/dev/null | xargs -r sudo kill -KILL
|
|
fi
|
|
log_success "Prozesse beendet"
|
|
;;
|
|
"s"|"S")
|
|
log_warning "Prozesse werden nicht beendet - Fortfahren auf eigene Gefahr"
|
|
;;
|
|
*)
|
|
log_message "Safe-Eject abgebrochen"
|
|
return 1
|
|
;;
|
|
esac
|
|
else
|
|
log_success "Keine aktiven Prozesse gefunden"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Schritt 3: Vollständiger Daten-Sync
|
|
perform_full_sync() {
|
|
log_message "Führe vollständigen Daten-Sync durch..."
|
|
|
|
# Globaler Sync
|
|
log_message "Globaler Sync..."
|
|
sync
|
|
|
|
# Warte auf Dirty Pages Flush
|
|
log_message "Warte auf Buffer-Flush..."
|
|
local timeout=30
|
|
local count=0
|
|
|
|
while [ $count -lt $timeout ]; do
|
|
dirty_pages=$(cat /proc/meminfo | grep "Dirty:" | awk '{print $2}')
|
|
writeback_pages=$(cat /proc/meminfo | grep "Writeback:" | awk '{print $2}')
|
|
|
|
if [ "$dirty_pages" -le 4 ] && [ "$writeback_pages" -le 4 ]; then
|
|
log_success "Buffer-Flush abgeschlossen (Dirty: ${dirty_pages}kB, Writeback: ${writeback_pages}kB)"
|
|
break
|
|
fi
|
|
|
|
if [ $((count % 5)) -eq 0 ]; then
|
|
log_message "Warte auf Buffer-Flush... (Dirty: ${dirty_pages}kB, Writeback: ${writeback_pages}kB)"
|
|
fi
|
|
|
|
sleep 1
|
|
count=$((count + 1))
|
|
done
|
|
|
|
if [ $count -ge $timeout ]; then
|
|
log_warning "Buffer-Flush Timeout erreicht - Fortfahren..."
|
|
fi
|
|
|
|
# Finaler Sync
|
|
log_message "Finaler Sync..."
|
|
sync
|
|
sleep 2
|
|
|
|
log_success "Vollständiger Sync abgeschlossen"
|
|
}
|
|
|
|
# Schritt 4: Filesystem Unmount
|
|
perform_unmount() {
|
|
local device="$1"
|
|
log_message "Unmounte Dateisystem: $device"
|
|
|
|
# Lazy Unmount falls normal unmount fehlschlägt
|
|
if ! sudo umount "$MOUNT_POINT" 2>/dev/null; then
|
|
log_warning "Normaler Unmount fehlgeschlagen - versuche Lazy Unmount..."
|
|
|
|
if sudo umount -l "$MOUNT_POINT" 2>/dev/null; then
|
|
log_success "Lazy Unmount erfolgreich"
|
|
else
|
|
log_error "Unmount fehlgeschlagen"
|
|
return 1
|
|
fi
|
|
else
|
|
log_success "Unmount erfolgreich"
|
|
fi
|
|
|
|
# Verifikation
|
|
if mountpoint -q "$MOUNT_POINT"; then
|
|
log_error "Mount-Point noch aktiv nach Unmount"
|
|
return 1
|
|
else
|
|
log_success "Mount-Point erfolgreich getrennt"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Schritt 5: Hardware-Eject
|
|
perform_hardware_eject() {
|
|
local device="$1"
|
|
local device_base=$(echo "$device" | sed 's/[0-9]*$//')
|
|
|
|
log_message "Hardware-Eject für Device: $device_base"
|
|
|
|
# Prüfe ob eject verfügbar ist
|
|
if ! command -v eject >/dev/null; then
|
|
log_warning "eject Befehl nicht verfügbar - installiere eject..."
|
|
sudo apt update && sudo apt install -y eject
|
|
fi
|
|
|
|
# Hardware-Eject durchführen
|
|
if sudo eject "$device_base" 2>/dev/null; then
|
|
log_success "Hardware-Eject erfolgreich: $device_base"
|
|
else
|
|
log_warning "Hardware-Eject fehlgeschlagen - möglicherweise nicht unterstützt"
|
|
|
|
# Alternative: USB-Device über sysfs deaktivieren
|
|
usb_device=$(udevadm info --query=path --name="$device_base" 2>/dev/null | sed 's|/block/.*||')
|
|
if [ -n "$usb_device" ]; then
|
|
log_message "Versuche USB-Device Deaktivierung über sysfs..."
|
|
if echo 1 | sudo tee "/sys$usb_device/remove" >/dev/null 2>&1; then
|
|
log_success "USB-Device über sysfs deaktiviert"
|
|
else
|
|
log_warning "USB-Device Deaktivierung fehlgeschlagen"
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Schritt 6: Post-Eject Validierung
|
|
validate_post_eject() {
|
|
local device="$1"
|
|
local device_base=$(echo "$device" | sed 's/[0-9]*$//')
|
|
|
|
log_message "Post-Eject Validierung..."
|
|
|
|
# Prüfe ob Mount-Point noch aktiv
|
|
if mountpoint -q "$MOUNT_POINT"; then
|
|
log_error "Mount-Point noch aktiv - Safe-Eject unvollständig"
|
|
return 1
|
|
fi
|
|
|
|
# Prüfe ob Device noch sichtbar
|
|
sleep 3 # Kurze Wartezeit für System-Update
|
|
|
|
if [ -b "$device_base" ]; then
|
|
log_warning "Device noch im System sichtbar: $device_base"
|
|
log_message "Device kann möglicherweise manuell entfernt werden"
|
|
else
|
|
log_success "Device nicht mehr im System sichtbar"
|
|
fi
|
|
|
|
log_success "Post-Eject Validierung abgeschlossen"
|
|
return 0
|
|
}
|
|
|
|
# Safe-Eject Status anzeigen
|
|
show_eject_status() {
|
|
echo ""
|
|
echo "=== Safe-Eject Status ==="
|
|
|
|
if mountpoint -q "$MOUNT_POINT"; then
|
|
mounted_device=$(df "$MOUNT_POINT" | tail -1 | awk '{print $1}')
|
|
device_base=$(echo "$mounted_device" | sed 's/[0-9]*$//')
|
|
|
|
echo "✓ Gemountetes Device: $mounted_device"
|
|
echo "✓ Hardware Device: $device_base"
|
|
|
|
# USB-Status prüfen
|
|
if lsblk -o TRAN "$device_base" 2>/dev/null | grep -q usb; then
|
|
echo "✓ USB-Device: Ja"
|
|
echo "✓ Safe-Eject: Verfügbar"
|
|
else
|
|
echo "✗ USB-Device: Nein"
|
|
echo "✗ Safe-Eject: Nicht verfügbar"
|
|
fi
|
|
|
|
# Aktive Prozesse prüfen
|
|
active_count=$(lsof "$MOUNT_POINT" 2>/dev/null | wc -l)
|
|
if [ "$active_count" -gt 0 ]; then
|
|
echo "! Aktive Prozesse: $active_count"
|
|
else
|
|
echo "✓ Aktive Prozesse: Keine"
|
|
fi
|
|
|
|
else
|
|
echo "✗ Kein Device gemountet"
|
|
echo "✗ Safe-Eject: Nicht erforderlich"
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
# Hauptfunktion Safe-Eject
|
|
safe_eject_main() {
|
|
echo "=== USB-C SSD Safe-Eject ==="
|
|
echo "Version: 1.0"
|
|
echo ""
|
|
|
|
# Pre-Validierung
|
|
device=$(validate_pre_eject)
|
|
if [ $? -ne 0 ]; then
|
|
return 1
|
|
fi
|
|
|
|
echo "=== Safe-Eject Prozess gestartet für: $device ==="
|
|
echo ""
|
|
|
|
# Aktive Prozesse prüfen
|
|
if ! check_active_processes "$device"; then
|
|
return 1
|
|
fi
|
|
|
|
# Vollständiger Sync
|
|
perform_full_sync
|
|
|
|
# Unmount
|
|
if ! perform_unmount "$device"; then
|
|
return 1
|
|
fi
|
|
|
|
# Hardware-Eject
|
|
perform_hardware_eject "$device"
|
|
|
|
# Post-Validierung
|
|
validate_post_eject "$device"
|
|
|
|
echo ""
|
|
echo "=== Safe-Eject abgeschlossen ==="
|
|
echo "USB-C SSD kann jetzt sicher entfernt werden."
|
|
echo ""
|
|
|
|
return 0
|
|
}
|
|
|
|
# Hauptfunktion
|
|
main() {
|
|
case "${1:-eject}" in
|
|
"eject")
|
|
safe_eject_main
|
|
;;
|
|
"status")
|
|
show_eject_status
|
|
;;
|
|
*)
|
|
echo "Verwendung: $0 {eject|status}"
|
|
echo ""
|
|
echo "Befehle:"
|
|
echo " eject - Sichere SSD-Entfernung durchführen"
|
|
echo " status - Safe-Eject Status anzeigen"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Script ausführen
|
|
main "$@"
|