improve throughout.
This commit is contained in:
251
cleanup.sh
251
cleanup.sh
@@ -67,63 +67,180 @@ show_user_loop_devices() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# Helper to list processes blocking a device (if possible)
|
||||
list_blocking_processes() {
|
||||
local dev="$1"
|
||||
if command -v fuser >/dev/null 2>&1; then
|
||||
fuser -v "$dev" 2>/dev/null || true
|
||||
elif command -v lsof >/dev/null 2>&1; then
|
||||
lsof "$dev" 2>/dev/null || true
|
||||
else
|
||||
print_info "Install 'psmisc' (provides fuser) or 'lsof' to list blocking processes"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to unmount all partitions of a loop device
|
||||
unmount_loop_partitions() {
|
||||
local loop_device=$1
|
||||
local unmounted=0
|
||||
|
||||
# Try both naming conventions
|
||||
for part in ${loop_device}p* ${loop_device}[0-9]*; do
|
||||
if [ -e "$part" ]; then
|
||||
local mount_point=$(findmnt -n -o TARGET "$part" 2>/dev/null || true)
|
||||
if [ -n "$mount_point" ]; then
|
||||
print_info "Unmounting $part from $mount_point"
|
||||
if umount "$part"; then
|
||||
print_success "Unmounted $part"
|
||||
unmounted=$((unmounted + 1))
|
||||
else
|
||||
print_warning "Failed to unmount $part"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build a list of partition nodes for this loop device
|
||||
local parts=()
|
||||
for p in "${loop_device}p"* "${loop_device}"[0-9]*; do
|
||||
# Only consider block device nodes that actually exist
|
||||
if [ -b "$p" ]; then
|
||||
parts+=("$p")
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [ ${#parts[@]} -eq 0 ]; then
|
||||
print_info "No partition block devices found for $loop_device"
|
||||
return 0
|
||||
fi
|
||||
|
||||
for part in "${parts[@]}"; do
|
||||
# Determine if the partition is mounted
|
||||
local mount_point
|
||||
mount_point=$(findmnt -n -o TARGET --source "$part" 2>/dev/null || true)
|
||||
if [ -n "$mount_point" ]; then
|
||||
print_info "Unmounting $part from $mount_point"
|
||||
if umount "$part" 2>/dev/null; then
|
||||
print_success "Unmounted $part"
|
||||
unmounted=$((unmounted + 1))
|
||||
else
|
||||
print_warning "Regular umount failed for $part - attempting lazy unmount"
|
||||
if umount -l "$part" 2>/dev/null; then
|
||||
print_success "Lazy-unmounted $part"
|
||||
unmounted=$((unmounted + 1))
|
||||
else
|
||||
print_warning "Failed to unmount $part even with lazy unmount"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
print_info "Partition $part does not appear to be mounted"
|
||||
fi
|
||||
done
|
||||
|
||||
# Attempt to remove partition mappings so kernel releases device nodes
|
||||
if command -v partx >/dev/null 2>&1; then
|
||||
partx -d "$loop_device" 2>/dev/null || true
|
||||
elif command -v kpartx >/dev/null 2>&1; then
|
||||
kpartx -d "$loop_device" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Give udev a moment to remove stale device nodes
|
||||
if command -v udevadm >/dev/null 2>&1; then
|
||||
udevadm settle 2>/dev/null || true
|
||||
fi
|
||||
sleep 1
|
||||
|
||||
return $unmounted
|
||||
}
|
||||
|
||||
# Function to detach loop device
|
||||
detach_loop_device() {
|
||||
local loop_device=$1
|
||||
|
||||
local force=${2:-false} # second optional argument: "true" to forcibly kill blockers
|
||||
|
||||
print_info "Detaching $loop_device"
|
||||
|
||||
# Check if device still exists in losetup output
|
||||
if ! losetup -l | grep -q "^$loop_device "; then
|
||||
|
||||
# If the device is not present in losetup listing, consider it already detached
|
||||
if ! losetup -l -n -O NAME 2>/dev/null | awk '{print $1}' | grep -qxF "$loop_device"; then
|
||||
print_success "Loop device already detached"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if losetup -d "$loop_device"; then
|
||||
|
||||
# Try a normal detach first
|
||||
if losetup -d "$loop_device" 2>/dev/null; then
|
||||
print_success "Detached $loop_device"
|
||||
return 0
|
||||
else
|
||||
print_error "Failed to detach $loop_device"
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_warning "Initial detach failed for $loop_device; attempting recovery steps"
|
||||
|
||||
# Try removing partition mappings and let udev settle
|
||||
if command -v partx >/dev/null 2>&1; then
|
||||
partx -d "$loop_device" 2>/dev/null || true
|
||||
elif command -v kpartx >/dev/null 2>&1; then
|
||||
kpartx -d "$loop_device" 2>/dev/null || true
|
||||
fi
|
||||
if command -v udevadm >/dev/null 2>&1; then
|
||||
udevadm settle 2>/dev/null || true
|
||||
fi
|
||||
sleep 1
|
||||
|
||||
# Second detach attempt
|
||||
if losetup -d "$loop_device" 2>/dev/null; then
|
||||
print_success "Detached $loop_device"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# If allowed, try to kill processes referencing the loop device (SIGTERM then SIGKILL)
|
||||
if [ "$force" = "true" ]; then
|
||||
if command -v fuser >/dev/null 2>&1; then
|
||||
print_info "Killing processes using $loop_device (SIGTERM)"
|
||||
fuser -k -TERM "$loop_device" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Try detach again
|
||||
if losetup -d "$loop_device" 2>/dev/null; then
|
||||
print_success "Detached $loop_device after killing blockers"
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_info "Killing any remaining processes using $loop_device (SIGKILL)"
|
||||
fuser -k -KILL "$loop_device" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
if losetup -d "$loop_device" 2>/dev/null; then
|
||||
print_success "Detached $loop_device after force-killing blockers"
|
||||
return 0
|
||||
fi
|
||||
elif command -v lsof >/dev/null 2>&1; then
|
||||
print_info "Listing processes using $loop_device via lsof"
|
||||
lsof "$loop_device" 2>/dev/null || true
|
||||
local pids
|
||||
pids=$(lsof -t "$loop_device" 2>/dev/null || true)
|
||||
if [ -n "$pids" ]; then
|
||||
print_info "Sending SIGTERM to: $pids"
|
||||
kill -TERM $pids 2>/dev/null || true
|
||||
sleep 1
|
||||
if losetup -d "$loop_device" 2>/dev/null; then
|
||||
print_success "Detached $loop_device after killing blockers"
|
||||
return 0
|
||||
fi
|
||||
print_info "Sending SIGKILL to: $pids"
|
||||
kill -KILL $pids 2>/dev/null || true
|
||||
sleep 1
|
||||
if losetup -d "$loop_device" 2>/dev/null; then
|
||||
print_success "Detached $loop_device after force-killing blockers"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
else
|
||||
print_warning "No 'fuser' or 'lsof' available; cannot automatically kill blocking processes"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_error "Failed to detach $loop_device. The device is likely still referenced by processes or the kernel"
|
||||
print_info "Processes referencing $loop_device (if any):"
|
||||
list_blocking_processes "$loop_device"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Automatic mode
|
||||
auto_cleanup() {
|
||||
local devices=$(get_user_loop_devices)
|
||||
|
||||
|
||||
if [ -z "$devices" ]; then
|
||||
print_info "No user loop devices to clean up"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
echo "The following loop devices will be cleaned up:"
|
||||
echo ""
|
||||
|
||||
|
||||
local count=0
|
||||
while IFS= read -r line; do
|
||||
local device=$(echo "$line" | awk '{print $1}')
|
||||
@@ -131,34 +248,35 @@ auto_cleanup() {
|
||||
echo " [$((count+1))] $device -> $file"
|
||||
count=$((count+1))
|
||||
done <<< "$devices"
|
||||
|
||||
|
||||
echo ""
|
||||
read -p "Clean up all $count device(s)? (yes/no): " confirm
|
||||
|
||||
|
||||
if [ "$confirm" != "yes" ]; then
|
||||
print_info "Cancelled"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
echo ""
|
||||
local success=0
|
||||
local failed=0
|
||||
|
||||
|
||||
while IFS= read -r line; do
|
||||
local device=$(echo "$line" | awk '{print $1}')
|
||||
local file=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ *//')
|
||||
|
||||
|
||||
echo "Processing: $device"
|
||||
unmount_loop_partitions "$device"
|
||||
|
||||
if detach_loop_device "$device"; then
|
||||
unmount_loop_partitions "$device" || true
|
||||
|
||||
# In automatic cleanup assume we are allowed to force-kill blockers when necessary
|
||||
if detach_loop_device "$device" "true"; then
|
||||
success=$((success+1))
|
||||
else
|
||||
failed=$((failed+1))
|
||||
fi
|
||||
echo ""
|
||||
done <<< "$devices"
|
||||
|
||||
|
||||
echo "=========================================="
|
||||
print_success "Cleaned up: $success device(s)"
|
||||
if [ $failed -gt 0 ]; then
|
||||
@@ -170,27 +288,35 @@ auto_cleanup() {
|
||||
# Manual mode - specific file
|
||||
manual_cleanup() {
|
||||
local target=$1
|
||||
|
||||
|
||||
if [ ! -f "$target" ]; then
|
||||
print_error "File not found: $target"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
# Find loop device associated with this file
|
||||
local loop_device=$(losetup -l -n -O NAME,BACK-FILE | grep "$(realpath $target)" | awk '{print $1}')
|
||||
|
||||
|
||||
if [ -z "$loop_device" ]; then
|
||||
print_warning "No loop device found for: $target"
|
||||
print_info "The file may already be detached"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
print_info "Found loop device: $loop_device"
|
||||
echo ""
|
||||
|
||||
unmount_loop_partitions "$loop_device"
|
||||
detach_loop_device "$loop_device"
|
||||
|
||||
|
||||
unmount_loop_partitions "$loop_device" || true
|
||||
|
||||
# Ask user whether to force if needed
|
||||
read -p "Force-kill processes using $loop_device if necessary? (y/N): " force_ans
|
||||
force_ans=${force_ans:-N}
|
||||
if [ "$force_ans" = "y" ] || [ "$force_ans" = "Y" ]; then
|
||||
detach_loop_device "$loop_device" "true"
|
||||
else
|
||||
detach_loop_device "$loop_device"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Cleanup complete for: $target"
|
||||
}
|
||||
@@ -198,19 +324,19 @@ manual_cleanup() {
|
||||
# Interactive mode
|
||||
interactive_cleanup() {
|
||||
local devices=$(get_user_loop_devices)
|
||||
|
||||
|
||||
if [ -z "$devices" ]; then
|
||||
print_info "No user loop devices to clean up"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
echo "Select a device to clean up:"
|
||||
echo ""
|
||||
|
||||
|
||||
local -a device_array
|
||||
local -a file_array
|
||||
local count=0
|
||||
|
||||
|
||||
while IFS= read -r line; do
|
||||
local device=$(echo "$line" | awk '{print $1}')
|
||||
local file=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ *//')
|
||||
@@ -219,41 +345,48 @@ interactive_cleanup() {
|
||||
echo " [$((count+1))] $device -> $file"
|
||||
count=$((count+1))
|
||||
done <<< "$devices"
|
||||
|
||||
|
||||
echo " [a] Clean up ALL"
|
||||
echo " [q] Quit"
|
||||
echo ""
|
||||
|
||||
|
||||
read -p "Enter selection: " selection
|
||||
|
||||
|
||||
if [ "$selection" = "q" ]; then
|
||||
print_info "Cancelled"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
if [ "$selection" = "a" ]; then
|
||||
echo ""
|
||||
auto_cleanup
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
# Validate numeric input
|
||||
if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt $count ]; then
|
||||
print_error "Invalid selection"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
local idx=$((selection-1))
|
||||
local device="${device_array[$idx]}"
|
||||
local file="${file_array[$idx]}"
|
||||
|
||||
|
||||
echo ""
|
||||
print_info "Cleaning up: $device -> $file"
|
||||
echo ""
|
||||
|
||||
unmount_loop_partitions "$device"
|
||||
detach_loop_device "$device"
|
||||
|
||||
|
||||
unmount_loop_partitions "$device" || true
|
||||
|
||||
read -p "Force-kill processes using $device if necessary? (y/N): " force_ans
|
||||
force_ans=${force_ans:-N}
|
||||
if [ "$force_ans" = "y" ] || [ "$force_ans" = "Y" ]; then
|
||||
detach_loop_device "$device" "true"
|
||||
else
|
||||
detach_loop_device "$device"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Cleanup complete"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user