From 4cc66fe47ac6c3a7f922b1660eab7590309db98c Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Mon, 20 Oct 2025 13:18:48 +0200 Subject: [PATCH] improvements to dialog --- cleanup.sh | 331 +++++++++++++++++++----- pseudodisk.sh | 686 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 834 insertions(+), 183 deletions(-) diff --git a/cleanup.sh b/cleanup.sh index 699b6fa..4618b97 100755 --- a/cleanup.sh +++ b/cleanup.sh @@ -38,79 +38,274 @@ echo " Forensic Disk Cleanup Tool" echo "==========================================" echo "" -# Show current loop devices -print_info "Current loop devices:" -losetup -l +# Function to get user loop devices (excluding system paths) +get_user_loop_devices() { + losetup -l -n -O NAME,BACK-FILE | grep -v "/var/lib/snapd" | grep -v "/snap/" | grep -v "^$" | awk '{if (NF >= 2) print $0}' +} -echo "" -read -p "Enter the disk image filename to clean up (or 'all' for all loop devices): " TARGET - -if [ "$TARGET" = "all" ]; then - print_warning "This will unmount and detach ALL loop devices!" - read -p "Are you sure? (yes/no): " CONFIRM +# Function to display user loop devices nicely +show_user_loop_devices() { + local devices=$(get_user_loop_devices) - if [ "$CONFIRM" = "yes" ]; then - # Get all loop devices - LOOP_DEVICES=$(losetup -l -n -O NAME | tail -n +2) - - for LOOP in $LOOP_DEVICES; do - print_info "Processing $LOOP..." - - # Try to unmount all partitions - for PART in ${LOOP}p* ${LOOP}[0-9]*; do - if [ -e "$PART" ]; then - MOUNT_POINT=$(findmnt -n -o TARGET "$PART" 2>/dev/null || true) - if [ -n "$MOUNT_POINT" ]; then - print_info "Unmounting $PART from $MOUNT_POINT" - umount "$PART" || print_warning "Failed to unmount $PART" - fi + if [ -z "$devices" ]; then + echo "No user loop devices found (system devices filtered out)" + return 1 + fi + + echo "Active disk images:" + echo "" + printf "%-15s %s\n" "LOOP DEVICE" "IMAGE FILE" + echo "------------------------------------------------------------" + + while IFS= read -r line; do + local device=$(echo "$line" | awk '{print $1}') + local file=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ *//') + printf "%-15s %s\n" "$device" "$file" + done <<< "$devices" + + echo "" + return 0 +} + +# 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 - done - - # Detach loop device - print_info "Detaching $LOOP" - losetup -d "$LOOP" || print_warning "Failed to detach $LOOP" - done - - print_success "Cleanup complete" - else - print_info "Cancelled" - fi -else - if [ ! -f "$TARGET" ]; then - print_error "File not found: $TARGET" - exit 1 - fi - - # Find loop device associated with this file - 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" - exit 0 - fi - - print_info "Found loop device: $LOOP_DEVICE" - - # Try to unmount all partitions - for PART in ${LOOP_DEVICE}p* ${LOOP_DEVICE}[0-9]*; do - if [ -e "$PART" ]; then - MOUNT_POINT=$(findmnt -n -o TARGET "$PART" 2>/dev/null || true) - if [ -n "$MOUNT_POINT" ]; then - print_info "Unmounting $PART from $MOUNT_POINT" - umount "$PART" || print_warning "Failed to unmount $PART" fi fi done - # Detach loop device - print_info "Detaching $LOOP_DEVICE" - losetup -d "$LOOP_DEVICE" - - print_success "Cleanup complete for $TARGET" -fi + return $unmounted +} -echo "" -print_info "Current loop devices after cleanup:" -losetup -l -echo "" \ No newline at end of file +# Function to detach loop device +detach_loop_device() { + local loop_device=$1 + + print_info "Detaching $loop_device" + + # Check if device still exists in losetup output + if ! losetup -l | grep -q "^$loop_device "; then + print_success "Loop device already detached" + return 0 + fi + + if losetup -d "$loop_device"; then + print_success "Detached $loop_device" + return 0 + else + print_error "Failed to detach $loop_device" + return 1 + fi +} + +# 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}') + local file=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ *//') + 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 + success=$((success+1)) + else + failed=$((failed+1)) + fi + echo "" + done <<< "$devices" + + echo "==========================================" + print_success "Cleaned up: $success device(s)" + if [ $failed -gt 0 ]; then + print_warning "Failed: $failed device(s)" + fi + echo "==========================================" +} + +# 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" + + echo "" + print_success "Cleanup complete for: $target" +} + +# 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/^ *//') + device_array[$count]=$device + file_array[$count]=$file + 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" + + echo "" + print_success "Cleanup complete" +} + +# Main menu +main() { + # Check if any user loop devices exist + if ! show_user_loop_devices; then + exit 0 + fi + + echo "Cleanup Options:" + echo " 1) Select specific device (interactive)" + echo " 2) Clean up all user devices (automatic)" + echo " 3) Enter filename manually" + echo " 4) Quit" + echo "" + read -p "Select option [1-4]: " option + + echo "" + + case $option in + 1) + interactive_cleanup + ;; + 2) + auto_cleanup + ;; + 3) + read -p "Enter disk image filename: " filename + echo "" + manual_cleanup "$filename" + ;; + 4) + print_info "Cancelled" + ;; + *) + print_error "Invalid option" + exit 1 + ;; + esac + + echo "" + + # Show final state + if get_user_loop_devices >/dev/null 2>&1; then + echo "Remaining loop devices:" + show_user_loop_devices + else + print_success "All user loop devices cleaned up" + fi +} + +# Run main +main \ No newline at end of file diff --git a/pseudodisk.sh b/pseudodisk.sh index 75c7207..c8fb275 100755 --- a/pseudodisk.sh +++ b/pseudodisk.sh @@ -1,7 +1,8 @@ #!/bin/bash -# Forensic Practice Disk Image Creator +# Forensic Practice Disk Image Creator - Enhanced Version # Creates disk images with various filesystems for forensic analysis practice +# Now with improved UX, sanity checks, and extended filesystem support set -e # Exit on error @@ -10,6 +11,8 @@ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' +CYAN='\033[0;36m' +MAGENTA='\033[0;35m' NC='\033[0m' # No Color # Function to print colored messages @@ -29,6 +32,45 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1" } +print_note() { + echo -e "${CYAN}[NOTE]${NC} $1" +} + +print_tip() { + echo -e "${MAGENTA}[TIP]${NC} $1" +} + +# Filesystem size constraints (in MB) +declare -A FS_MIN_SIZE +FS_MIN_SIZE["fat12"]=1 +FS_MIN_SIZE["fat16"]=5 +FS_MIN_SIZE["fat32"]=33 +FS_MIN_SIZE["exfat"]=1 +FS_MIN_SIZE["ntfs"]=7 +FS_MIN_SIZE["ext2"]=1 +FS_MIN_SIZE["ext3"]=1 +FS_MIN_SIZE["ext4"]=1 +FS_MIN_SIZE["xfs"]=16 +FS_MIN_SIZE["hfsplus"]=8 +FS_MIN_SIZE["apfs"]=1 +FS_MIN_SIZE["swap"]=1 +FS_MIN_SIZE["unallocated"]=1 + +declare -A FS_MAX_SIZE +FS_MAX_SIZE["fat12"]=16 +FS_MAX_SIZE["fat16"]=2048 +FS_MAX_SIZE["fat32"]=2097152 # 2TB in theory, but practical limit +FS_MAX_SIZE["exfat"]=16777216 # 16 TB practical +FS_MAX_SIZE["ntfs"]=16777216 # 16 TB+ +FS_MAX_SIZE["ext2"]=16777216 +FS_MAX_SIZE["ext3"]=16777216 +FS_MAX_SIZE["ext4"]=16777216 +FS_MAX_SIZE["xfs"]=16777216 +FS_MAX_SIZE["hfsplus"]=2097152 # 2TB +FS_MAX_SIZE["apfs"]=16777216 +FS_MAX_SIZE["swap"]=128000 # 128GB practical max +FS_MAX_SIZE["unallocated"]=16777216 # No real limit + # Check if running as root check_root() { if [ "$EUID" -ne 0 ]; then @@ -59,25 +101,32 @@ check_filesystem_tools() { echo "Checking filesystem tool availability..." echo "" - # NTFS - if command -v mkfs.ntfs >/dev/null 2>&1; then - echo -e " ${GREEN}✓${NC} NTFS (mkfs.ntfs available)" + # FAT12/16 + if command -v mkfs.fat >/dev/null 2>&1 || command -v mkfs.vfat >/dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} FAT12/16 (mkfs.fat available)" else - echo -e " ${YELLOW}✗${NC} NTFS (install: sudo apt-get install ntfs-3g)" + echo -e " ${YELLOW}✗${NC} FAT12/16 (install: sudo apt-get install dosfstools)" fi # FAT32 if command -v mkfs.vfat >/dev/null 2>&1; then - echo -e " ${GREEN}✓${NC} FAT32 (mkfs.vfat available)" + echo -e " ${GREEN}✓${NC} FAT32 (mkfs.vfat available)" else - echo -e " ${YELLOW}✗${NC} FAT32 (install: sudo apt-get install dosfstools)" + echo -e " ${YELLOW}✗${NC} FAT32 (install: sudo apt-get install dosfstools)" fi # exFAT if command -v mkfs.exfat >/dev/null 2>&1; then - echo -e " ${GREEN}✓${NC} exFAT (mkfs.exfat available)" + echo -e " ${GREEN}✓${NC} exFAT (mkfs.exfat available)" else - echo -e " ${YELLOW}✗${NC} exFAT (install: sudo apt-get install exfat-fuse exfat-utils)" + echo -e " ${YELLOW}✗${NC} exFAT (install: sudo apt-get install exfatprogs)" + fi + + # NTFS + if command -v mkfs.ntfs >/dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} NTFS (mkfs.ntfs available)" + else + echo -e " ${YELLOW}✗${NC} NTFS (install: sudo apt-get install ntfs-3g)" fi # ext2/3/4 @@ -89,26 +138,154 @@ check_filesystem_tools() { # XFS if command -v mkfs.xfs >/dev/null 2>&1; then - echo -e " ${GREEN}✓${NC} XFS (mkfs.xfs available)" + echo -e " ${GREEN}✓${NC} XFS (mkfs.xfs available)" else - echo -e " ${YELLOW}✗${NC} XFS (install: sudo apt-get install xfsprogs)" + echo -e " ${YELLOW}✗${NC} XFS (install: sudo apt-get install xfsprogs)" + fi + + # HFS+ + if command -v mkfs.hfsplus >/dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} HFS+ (mkfs.hfsplus available)" + else + echo -e " ${YELLOW}✗${NC} HFS+ (install: sudo apt-get install hfsprogs)" + fi + + # APFS + if command -v mkfs.apfs >/dev/null 2>&1; then + echo -e " ${GREEN}✓${NC} APFS (mkfs.apfs available)" + else + echo -e " ${YELLOW}✗${NC} APFS (limited Linux support - not recommended)" fi # swap if command -v mkswap >/dev/null 2>&1; then - echo -e " ${GREEN}✓${NC} swap (mkswap available)" + echo -e " ${GREEN}✓${NC} swap (mkswap available)" else - echo -e " ${YELLOW}✗${NC} swap (should be in util-linux)" + echo -e " ${YELLOW}✗${NC} swap (should be in util-linux)" fi echo "" } +# Validate filesystem label +validate_label() { + local fs=$1 + local label=$2 + + case $fs in + fat12|fat16|fat32|vfat) + if [ ${#label} -gt 11 ]; then + print_error "FAT label must be 11 characters or less" + return 1 + fi + # FAT labels should be uppercase and no lowercase allowed + if [[ "$label" =~ [a-z] ]]; then + print_warning "FAT labels are typically uppercase. Converting..." + echo "${label^^}" + return 0 + fi + ;; + ntfs) + if [ ${#label} -gt 32 ]; then + print_error "NTFS label must be 32 characters or less" + return 1 + fi + ;; + ext2|ext3|ext4) + if [ ${#label} -gt 16 ]; then + print_error "ext2/3/4 label must be 16 characters or less" + return 1 + fi + ;; + xfs) + if [ ${#label} -gt 12 ]; then + print_error "XFS label must be 12 characters or less" + return 1 + fi + ;; + exfat) + if [ ${#label} -gt 15 ]; then + print_error "exFAT label must be 15 characters or less" + return 1 + fi + ;; + hfsplus) + if [ ${#label} -gt 27 ]; then + print_error "HFS+ label must be 27 characters or less" + return 1 + fi + ;; + esac + + echo "$label" + return 0 +} + +# Check if size is appropriate for filesystem +validate_fs_size() { + local fs=$1 + local size=$2 + + if [ "$size" = "remaining" ]; then + return 0 + fi + + local min=${FS_MIN_SIZE[$fs]} + local max=${FS_MAX_SIZE[$fs]} + + if [ -n "$min" ] && [ "$size" -lt "$min" ]; then + print_error "Partition size ${size}MB is too small for $fs (minimum: ${min}MB)" + return 1 + fi + + if [ -n "$max" ] && [ "$size" -gt "$max" ]; then + print_warning "Partition size ${size}MB exceeds recommended maximum for $fs (${max}MB)" + read -p "Continue anyway? (y/n): " continue + if [ "$continue" != "y" ]; then + return 1 + fi + fi + + # Specific warnings + case $fs in + fat12) + if [ "$size" -gt 16 ]; then + print_error "FAT12 cannot exceed 16MB" + return 1 + fi + ;; + fat16) + if [ "$size" -lt 16 ]; then + print_warning "Partition is small enough for FAT12, but you selected FAT16" + fi + if [ "$size" -gt 2048 ]; then + print_error "FAT16 cannot exceed 2GB (2048MB)" + return 1 + fi + ;; + fat32) + if [ "$size" -lt 33 ]; then + print_error "FAT32 requires at least 33MB" + return 1 + fi + ;; + xfs) + if [ "$size" -lt 16 ]; then + print_error "XFS requires at least 16MB" + return 1 + fi + ;; + esac + + return 0 +} + # Display banner show_banner() { echo "" echo "==========================================" echo " Forensic Disk Image Creator" + echo " Enhanced Edition v2.0" echo "==========================================" echo "" } @@ -119,11 +296,23 @@ get_filename() { read -p "Enter output filename (default: forensic_disk.dd): " FILENAME FILENAME=${FILENAME:-forensic_disk.dd} + # Validate filename + if [[ "$FILENAME" =~ [^a-zA-Z0-9._-] ]]; then + print_warning "Filename contains special characters. This may cause issues." + read -p "Continue with this filename? (y/n): " continue + if [ "$continue" != "y" ]; then + get_filename + return + fi + fi + if [ -f "$FILENAME" ]; then - read -p "File already exists. Overwrite? (y/n): " OVERWRITE + print_warning "File already exists: $FILENAME" + read -p "Overwrite? (y/n): " OVERWRITE if [ "$OVERWRITE" != "y" ]; then - print_info "Exiting..." - exit 0 + print_info "Please choose a different filename" + get_filename + return fi fi } @@ -132,33 +321,45 @@ get_filename() { get_disk_size() { echo "" echo "Disk Size Options:" - echo " 1) 100 MB (small, quick testing)" - echo " 2) 500 MB (medium)" - echo " 3) 1 GB (standard)" - echo " 4) 5 GB (large)" - echo " 5) Custom size" + echo " 1) 100 MB (small, quick testing)" + echo " 2) 500 MB (medium)" + echo " 3) 1 GB (standard)" + echo " 4) 5 GB (large)" + echo " 5) 10 GB (very large)" + echo " 6) Custom size" echo "" - read -p "Select disk size [1-5]: " SIZE_CHOICE + read -p "Select disk size [1-6]: " SIZE_CHOICE case $SIZE_CHOICE in 1) DISK_SIZE_MB=100 ;; 2) DISK_SIZE_MB=500 ;; 3) DISK_SIZE_MB=1024 ;; 4) DISK_SIZE_MB=5120 ;; - 5) + 5) DISK_SIZE_MB=10240 ;; + 6) read -p "Enter size in MB: " DISK_SIZE_MB if ! [[ "$DISK_SIZE_MB" =~ ^[0-9]+$ ]] || [ "$DISK_SIZE_MB" -lt 10 ]; then print_error "Invalid size. Must be at least 10 MB" - exit 1 + get_disk_size + return + fi + if [ "$DISK_SIZE_MB" -gt 102400 ]; then + print_warning "Very large disk size (>100GB). This may take a while." + read -p "Continue? (y/n): " continue + if [ "$continue" != "y" ]; then + get_disk_size + return + fi fi ;; *) print_error "Invalid choice" - exit 1 + get_disk_size + return ;; esac - print_info "Selected disk size: ${DISK_SIZE_MB} MB" + print_info "Selected disk size: ${DISK_SIZE_MB} MB ($(echo "scale=2; $DISK_SIZE_MB/1024" | bc) GB)" } # Get initialization method @@ -166,18 +367,29 @@ get_init_method() { echo "" echo "Initialization Method:" echo " 1) /dev/zero (Fast, zeros - forensically predictable)" - echo " 2) /dev/random (Slow, random data - more realistic)" - echo " 3) fallocate (Fastest, sparse file)" + echo " 2) /dev/urandom (Slow, random data - more realistic)" + echo " 3) fallocate (Fastest, sparse file - testing only)" + echo "" + print_tip "For forensic practice, /dev/zero (option 1) is recommended" echo "" read -p "Select initialization method [1-3]: " INIT_CHOICE case $INIT_CHOICE in 1) INIT_METHOD="zero" ;; - 2) INIT_METHOD="random" ;; - 3) INIT_METHOD="fallocate" ;; + 2) + INIT_METHOD="random" + print_warning "Random initialization can be VERY slow for large disks" + estimated_time=$(echo "scale=0; $DISK_SIZE_MB/10" | bc) + print_info "Estimated time: ~${estimated_time} seconds" + ;; + 3) + INIT_METHOD="fallocate" + print_warning "Sparse files may not be suitable for all forensic scenarios" + ;; *) print_error "Invalid choice" - exit 1 + get_init_method + return ;; esac @@ -191,14 +403,25 @@ get_partition_scheme() { echo " 1) GPT (GUID Partition Table) - Modern, Windows 10/11 default" echo " 2) MBR (Master Boot Record) - Legacy, compatible with older systems" echo "" + print_tip "GPT is recommended for modern systems and disks >2TB" + echo "" read -p "Select partition scheme [1-2]: " PARTITION_CHOICE case $PARTITION_CHOICE in 1) PARTITION_SCHEME="gpt" ;; - 2) PARTITION_SCHEME="msdos" ;; + 2) + PARTITION_SCHEME="msdos" + if [ "$DISK_SIZE_MB" -gt 2097152 ]; then + print_error "MBR does not support disks larger than 2TB" + print_info "Please select GPT or reduce disk size" + get_partition_scheme + return + fi + ;; *) print_error "Invalid choice" - exit 1 + get_partition_scheme + return ;; esac @@ -212,7 +435,12 @@ get_partition_count() { if ! [[ "$PARTITION_COUNT" =~ ^[1-4]$ ]]; then print_error "Invalid number. Must be between 1 and 4" - exit 1 + get_partition_count + return + fi + + if [ "$PARTITION_COUNT" -gt 1 ]; then + print_note "The last partition will automatically use all remaining space" fi print_info "Creating $PARTITION_COUNT partition(s)" @@ -221,6 +449,8 @@ get_partition_count() { # Get partition configurations get_partition_configs() { PARTITION_CONFIGS=() + local total_allocated=0 + local available_space=$((DISK_SIZE_MB - 2)) # Reserve 2MB for partition table for i in $(seq 1 $PARTITION_COUNT); do echo "" @@ -228,106 +458,244 @@ get_partition_configs() { echo " Partition $i Configuration" echo "==========================================" + if [ $i -gt 1 ]; then + print_info "Available space: ${available_space}MB" + fi + # Get filesystem echo "" echo "Filesystem Type:" - echo " 1) NTFS (Windows default)" - echo " 2) FAT32 (Universal compatibility)" - echo " 3) exFAT (Modern, large file support)" - echo " 4) ext4 (Linux default)" - echo " 5) ext3 (Older Linux)" - echo " 6) ext2 (Legacy Linux, no journaling)" - echo " 7) XFS (High-performance Linux)" - echo " 8) swap (Linux swap space)" + echo " 1) FAT12 (Very small, <16MB, legacy)" + echo " 2) FAT16 (Small, 16MB-2GB, legacy)" + echo " 3) FAT32 (Universal, 32MB+, good compatibility)" + echo " 4) exFAT (Modern, large files, cross-platform)" + echo " 5) NTFS (Windows default, journaling)" + echo " 6) ext2 (Linux legacy, no journaling)" + echo " 7) ext3 (Linux, journaling)" + echo " 8) ext4 (Linux default, modern)" + echo " 9) XFS (High-performance Linux)" + echo " 10) HFS+ (macOS legacy)" + echo " 11) APFS (macOS modern - limited Linux support)" + echo " 12) swap (Linux swap space)" + echo " 13) unallocated (Empty space - for forensic practice)" echo "" - read -p "Select filesystem for partition $i [1-8]: " FS_CHOICE + read -p "Select filesystem for partition $i [1-13]: " FS_CHOICE case $FS_CHOICE in 1) - PART_FS="ntfs" - if ! command -v mkfs.ntfs >/dev/null 2>&1; then - print_error "mkfs.ntfs not found. Install: sudo apt-get install ntfs-3g" - exit 1 + PART_FS="fat12" + if ! command -v mkfs.fat >/dev/null 2>&1; then + print_error "mkfs.fat not found. Install: sudo apt-get install dosfstools" + get_partition_configs + return fi + print_note "FAT12 is limited to <16MB partitions" ;; 2) + PART_FS="fat16" + if ! command -v mkfs.fat >/dev/null 2>&1; then + print_error "mkfs.fat not found. Install: sudo apt-get install dosfstools" + get_partition_configs + return + fi + print_note "FAT16 is limited to 16MB-2GB partitions" + ;; + 3) PART_FS="vfat" if ! command -v mkfs.vfat >/dev/null 2>&1; then print_error "mkfs.vfat not found. Install: sudo apt-get install dosfstools" - exit 1 - fi - ;; - 3) - PART_FS="exfat" - if ! command -v mkfs.exfat >/dev/null 2>&1; then - print_error "mkfs.exfat not found. Install: sudo apt-get install exfat-fuse exfat-utils" - exit 1 + get_partition_configs + return fi ;; 4) - PART_FS="ext4" - if ! command -v mkfs.ext4 >/dev/null 2>&1; then - print_error "mkfs.ext4 not found. Install: sudo apt-get install e2fsprogs" - exit 1 + PART_FS="exfat" + if ! command -v mkfs.exfat >/dev/null 2>&1; then + print_error "mkfs.exfat not found. Install: sudo apt-get install exfatprogs" + get_partition_configs + return fi ;; 5) - PART_FS="ext3" - if ! command -v mkfs.ext3 >/dev/null 2>&1; then - print_error "mkfs.ext3 not found. Install: sudo apt-get install e2fsprogs" - exit 1 + PART_FS="ntfs" + if ! command -v mkfs.ntfs >/dev/null 2>&1; then + print_error "mkfs.ntfs not found. Install: sudo apt-get install ntfs-3g" + get_partition_configs + return fi ;; 6) PART_FS="ext2" if ! command -v mkfs.ext2 >/dev/null 2>&1; then print_error "mkfs.ext2 not found. Install: sudo apt-get install e2fsprogs" - exit 1 + get_partition_configs + return fi + print_note "ext2 has no journaling - faster but less crash-resistant" ;; 7) + PART_FS="ext3" + if ! command -v mkfs.ext3 >/dev/null 2>&1; then + print_error "mkfs.ext3 not found. Install: sudo apt-get install e2fsprogs" + get_partition_configs + return + fi + ;; + 8) + PART_FS="ext4" + if ! command -v mkfs.ext4 >/dev/null 2>&1; then + print_error "mkfs.ext4 not found. Install: sudo apt-get install e2fsprogs" + get_partition_configs + return + fi + ;; + 9) PART_FS="xfs" if ! command -v mkfs.xfs >/dev/null 2>&1; then print_error "mkfs.xfs not found. Install: sudo apt-get install xfsprogs" - exit 1 + get_partition_configs + return + fi + print_note "XFS requires at least 16MB" + ;; + 10) + PART_FS="hfsplus" + if ! command -v mkfs.hfsplus >/dev/null 2>&1; then + print_error "mkfs.hfsplus not found. Install: sudo apt-get install hfsprogs" + get_partition_configs + return + fi + print_warning "HFS+ support on Linux is limited" + ;; + 11) + PART_FS="apfs" + print_warning "APFS has very limited Linux support and may not work properly" + read -p "Continue anyway? (y/n): " continue + if [ "$continue" != "y" ]; then + get_partition_configs + return fi ;; - 8) + 12) PART_FS="swap" if ! command -v mkswap >/dev/null 2>&1; then print_error "mkswap not found. Install: sudo apt-get install util-linux" - exit 1 + get_partition_configs + return fi ;; + 13) + PART_FS="unallocated" + print_note "Unallocated space - useful for practicing partition recovery" + ;; *) print_error "Invalid choice" - exit 1 + get_partition_configs + return ;; esac # Get size if [ $i -lt $PARTITION_COUNT ]; then - read -p "Size for partition $i in MB: " PART_SIZE - if ! [[ "$PART_SIZE" =~ ^[0-9]+$ ]] || [ "$PART_SIZE" -lt 1 ]; then - print_error "Invalid size" - exit 1 + while true; do + if [ $i -eq $((PARTITION_COUNT - 1)) ]; then + # Second to last partition - show what will be left + print_tip "Press Enter to use remaining space (${available_space}MB) or specify size" + read -p "Size for partition $i in MB (default: remaining): " PART_SIZE + else + read -p "Size for partition $i in MB: " PART_SIZE + fi + + # Handle default to remaining space + if [ -z "$PART_SIZE" ] && [ $i -eq $((PARTITION_COUNT - 1)) ]; then + PART_SIZE="remaining" + print_info "Partition $i will use remaining space (~${available_space}MB)" + + # Validate remaining space for filesystem + if ! validate_fs_size "$PART_FS" "$available_space"; then + print_error "Remaining space (${available_space}MB) is not suitable for $PART_FS" + continue + fi + break + fi + + if ! [[ "$PART_SIZE" =~ ^[0-9]+$ ]] || [ "$PART_SIZE" -lt 1 ]; then + print_error "Invalid size. Enter a number or press Enter for remaining space" + continue + fi + + # Validate size for filesystem + if ! validate_fs_size "$PART_FS" "$PART_SIZE"; then + continue + fi + + # Check if size exceeds available space + if [ "$PART_SIZE" -ge "$available_space" ]; then + print_error "Not enough space. Available: ${available_space}MB" + continue + fi + + # Leave at least 10MB for the last partition + if [ $i -eq $((PARTITION_COUNT - 1)) ]; then + remaining=$((available_space - PART_SIZE)) + if [ "$remaining" -lt 10 ]; then + print_error "Not enough space left for last partition (need at least 10MB)" + print_tip "Press Enter to use remaining ${available_space}MB instead" + continue + fi + fi + + break + done + + if [ "$PART_SIZE" != "remaining" ]; then + total_allocated=$((total_allocated + PART_SIZE)) + available_space=$((available_space - PART_SIZE)) fi else PART_SIZE="remaining" - print_info "Partition $i will use remaining space" + print_info "Partition $i will use remaining space (~${available_space}MB)" + + # Validate remaining space for filesystem + if ! validate_fs_size "$PART_FS" "$available_space"; then + print_error "Remaining space (${available_space}MB) is not suitable for $PART_FS" + get_partition_configs + return + fi fi - # Get label (skip for swap) - if [ "$PART_FS" != "swap" ]; then - read -p "Volume label for partition $i (default: PART$i): " PART_LABEL - PART_LABEL=${PART_LABEL:-PART$i} + # Get label (skip for swap and unallocated) + if [ "$PART_FS" != "swap" ] && [ "$PART_FS" != "unallocated" ]; then + while true; do + read -p "Volume label for partition $i (default: PART$i): " PART_LABEL + PART_LABEL=${PART_LABEL:-PART$i} + + # Validate label + validated_label=$(validate_label "$PART_FS" "$PART_LABEL") + if [ $? -eq 0 ]; then + PART_LABEL="$validated_label" + break + fi + done else PART_LABEL="" fi PARTITION_CONFIGS+=("$PART_FS|$PART_SIZE|$PART_LABEL") - print_info "Partition $i: $PART_FS, ${PART_SIZE}MB, label='$PART_LABEL'" + + if [ "$PART_FS" = "swap" ]; then + print_info "Partition $i: $PART_FS, ${PART_SIZE}MB" + elif [ "$PART_FS" = "unallocated" ]; then + print_info "Partition $i: $PART_FS, ${PART_SIZE}MB (no filesystem)" + else + print_info "Partition $i: $PART_FS, ${PART_SIZE}MB, label='$PART_LABEL'" + fi done + + # Final sanity check + if [ "$total_allocated" -gt "$((DISK_SIZE_MB - 10))" ]; then + print_warning "Partitions use almost all disk space. This may cause issues." + fi } # Create the disk image @@ -340,15 +708,15 @@ create_disk_image() { fallocate -l ${DISK_SIZE_MB}M "$FILENAME" else print_warning "fallocate not available, falling back to /dev/zero" - dd if=/dev/zero of="$FILENAME" bs=1M count=$DISK_SIZE_MB status=progress + dd if=/dev/zero of="$FILENAME" bs=1M count=$DISK_SIZE_MB status=progress 2>&1 | grep -v "records" fi ;; zero) - dd if=/dev/zero of="$FILENAME" bs=1M count=$DISK_SIZE_MB status=progress + dd if=/dev/zero of="$FILENAME" bs=1M count=$DISK_SIZE_MB status=progress 2>&1 | grep -v "records" ;; random) print_warning "Using /dev/urandom - this will be SLOW!" - dd if=/dev/urandom of="$FILENAME" bs=1M count=$DISK_SIZE_MB status=progress + dd if=/dev/urandom of="$FILENAME" bs=1M count=$DISK_SIZE_MB status=progress 2>&1 | grep -v "records" ;; esac @@ -359,6 +727,13 @@ create_disk_image() { setup_loop_device() { print_info "Setting up loop device..." LOOP_DEVICE=$(losetup -f) + + if [ -z "$LOOP_DEVICE" ]; then + print_error "No free loop devices available" + print_info "Try: sudo modprobe loop max_loop=16" + exit 1 + fi + losetup "$LOOP_DEVICE" "$FILENAME" print_success "Loop device created: $LOOP_DEVICE" } @@ -378,18 +753,40 @@ create_partitions() { if [ "$size" = "remaining" ]; then end="100%" else - end="${start_mb}MiB + ${size}MiB" end=$(echo "$start_mb + $size" | bc) end="${end}MiB" fi print_info "Creating partition $part_num: ${start_mb}MiB -> $end" - if [ "$fs" = "swap" ]; then - parted -s "$LOOP_DEVICE" mkpart primary linux-swap "${start_mb}MiB" "$end" - else - parted -s "$LOOP_DEVICE" mkpart primary "${start_mb}MiB" "$end" - fi + # Set partition type based on filesystem + case $fs in + swap) + parted -s "$LOOP_DEVICE" mkpart primary linux-swap "${start_mb}MiB" "$end" + ;; + fat12|fat16|vfat) + parted -s "$LOOP_DEVICE" mkpart primary fat32 "${start_mb}MiB" "$end" + ;; + ntfs) + parted -s "$LOOP_DEVICE" mkpart primary ntfs "${start_mb}MiB" "$end" + ;; + ext2|ext3|ext4) + parted -s "$LOOP_DEVICE" mkpart primary ext4 "${start_mb}MiB" "$end" + ;; + xfs) + parted -s "$LOOP_DEVICE" mkpart primary xfs "${start_mb}MiB" "$end" + ;; + hfsplus) + parted -s "$LOOP_DEVICE" mkpart primary hfs+ "${start_mb}MiB" "$end" + ;; + unallocated) + # Don't create a partition for unallocated space - just leave it empty + print_info "Leaving space unallocated (no partition entry)" + ;; + *) + parted -s "$LOOP_DEVICE" mkpart primary "${start_mb}MiB" "$end" + ;; + esac if [ "$size" != "remaining" ]; then start_mb=$(echo "$start_mb + $size" | bc) @@ -399,7 +796,7 @@ create_partitions() { done # Inform kernel about partition table changes - partprobe "$LOOP_DEVICE" + partprobe "$LOOP_DEVICE" 2>/dev/null || true sleep 2 print_success "Partitions created" @@ -412,6 +809,12 @@ format_partitions() { for config in "${PARTITION_CONFIGS[@]}"; do IFS='|' read -r fs size label <<< "$config" + # Skip unallocated space - no partition to format + if [ "$fs" = "unallocated" ]; then + print_info "Skipping unallocated space (no partition to format)" + continue + fi + # Determine partition device name PARTITION="${LOOP_DEVICE}p${part_num}" if [ ! -e "$PARTITION" ]; then @@ -420,6 +823,7 @@ format_partitions() { if [ ! -e "$PARTITION" ]; then print_error "Cannot find partition device for partition $part_num" + print_info "Expected: ${LOOP_DEVICE}p${part_num} or ${LOOP_DEVICE}${part_num}" cleanup exit 1 fi @@ -427,23 +831,39 @@ format_partitions() { print_info "Formatting partition $part_num ($PARTITION) with $fs filesystem..." case $fs in - ntfs) - mkfs.ntfs -f -L "$label" "$PARTITION" + fat12) + # FAT12 requires specific cluster size + mkfs.fat -F 12 -n "$label" "$PARTITION" 2>&1 | grep -v "^mkfs.fat" + ;; + fat16) + # FAT16 + mkfs.fat -F 16 -n "$label" "$PARTITION" 2>&1 | grep -v "^mkfs.fat" ;; vfat) - mkfs.vfat -n "$label" "$PARTITION" + # FAT32 + mkfs.vfat -F 32 -n "$label" "$PARTITION" 2>&1 | grep -v "^mkfs.fat" + ;; + ntfs) + mkfs.ntfs -f -L "$label" "$PARTITION" 2>&1 | grep -E "^(Cluster|Creating|mkntfs completed)" ;; exfat) - mkfs.exfat -n "$label" "$PARTITION" + mkfs.exfat -n "$label" "$PARTITION" 2>&1 | grep -v "^exfatprogs" ;; ext2|ext3|ext4) - mkfs."$fs" -L "$label" "$PARTITION" + mkfs."$fs" -L "$label" "$PARTITION" 2>&1 | grep -E "^(Creating|Writing|mke2fs)" ;; xfs) - mkfs.xfs -f -L "$label" "$PARTITION" + mkfs.xfs -f -L "$label" "$PARTITION" 2>&1 | grep -v "^meta-data" + ;; + hfsplus) + mkfs.hfsplus -v "$label" "$PARTITION" 2>&1 + ;; + apfs) + print_warning "APFS formatting on Linux is not well supported" + print_info "Skipping format for APFS partition" ;; swap) - mkswap -L "SWAP$part_num" "$PARTITION" + mkswap -L "SWAP$part_num" "$PARTITION" 2>&1 | grep "^Setting up" ;; esac @@ -472,9 +892,9 @@ mount_filesystems() { for config in "${PARTITION_CONFIGS[@]}"; do IFS='|' read -r fs size label <<< "$config" - # Skip swap partitions - if [ "$fs" = "swap" ]; then - print_info "Skipping mount for swap partition $part_num" + # Skip swap, apfs, and unallocated filesystems + if [ "$fs" = "swap" ] || [ "$fs" = "apfs" ] || [ "$fs" = "unallocated" ]; then + print_info "Skipping mount for $fs partition $part_num" part_num=$((part_num + 1)) continue fi @@ -488,10 +908,28 @@ mount_filesystems() { mkdir -p "$MOUNT_POINT" print_info "Mounting partition $part_num to $MOUNT_POINT..." - mount "$PARTITION" "$MOUNT_POINT" - print_success "Partition $part_num mounted at: $MOUNT_POINT" - MOUNT_POINTS+=("$MOUNT_POINT") + # Use appropriate mount options + case $fs in + ntfs) + mount -t ntfs-3g "$PARTITION" "$MOUNT_POINT" 2>&1 || \ + mount "$PARTITION" "$MOUNT_POINT" 2>&1 + ;; + hfsplus) + mount -t hfsplus "$PARTITION" "$MOUNT_POINT" 2>&1 + ;; + *) + mount "$PARTITION" "$MOUNT_POINT" 2>&1 + ;; + esac + + if [ $? -eq 0 ]; then + print_success "Partition $part_num mounted at: $MOUNT_POINT" + MOUNT_POINTS+=("$MOUNT_POINT") + else + print_warning "Failed to mount partition $part_num" + rmdir "$MOUNT_POINT" 2>/dev/null + fi part_num=$((part_num + 1)) done @@ -506,7 +944,7 @@ show_summary() { echo "==========================================" echo "" echo "Image File: $(realpath $FILENAME)" - echo "Size: ${DISK_SIZE_MB} MB" + echo "Size: ${DISK_SIZE_MB} MB ($(echo "scale=2; $DISK_SIZE_MB/1024" | bc) GB)" echo "Init Method: $INIT_METHOD" echo "Partition Scheme: $PARTITION_SCHEME" echo "Loop Device: $LOOP_DEVICE" @@ -514,21 +952,27 @@ show_summary() { echo "Partitions:" local part_num=1 + local config_num=1 for config in "${PARTITION_CONFIGS[@]}"; do IFS='|' read -r fs size label <<< "$config" - PARTITION="${LOOP_DEVICE}p${part_num}" - if [ ! -e "$PARTITION" ]; then - PARTITION="${LOOP_DEVICE}${part_num}" - fi - - if [ "$fs" = "swap" ]; then - echo " [$part_num] $PARTITION - $fs (${size}MB)" + if [ "$fs" = "unallocated" ]; then + echo " [$config_num] unallocated space (${size}MB) - not partitioned" else - echo " [$part_num] $PARTITION - $fs (${size}MB) - '$label'" + PARTITION="${LOOP_DEVICE}p${part_num}" + if [ ! -e "$PARTITION" ]; then + PARTITION="${LOOP_DEVICE}${part_num}" + fi + + if [ "$fs" = "swap" ]; then + echo " [$config_num] $PARTITION - $fs (${size}MB)" + else + echo " [$config_num] $PARTITION - $fs (${size}MB) - '$label'" + fi + part_num=$((part_num + 1)) fi - part_num=$((part_num + 1)) + config_num=$((config_num + 1)) done if [ ${#MOUNT_POINTS[@]} -gt 0 ]; then @@ -553,7 +997,13 @@ show_summary() { echo " xxd $FILENAME | less" echo "" echo "Analyze with forensic tools:" - echo " mmls $FILENAME" + echo " mmls $FILENAME # View partitions" + echo " fsstat -o 2048 $FILENAME # Filesystem details" + echo " fls -o 2048 -r $FILENAME # List files" + echo "" + echo "View specific structures:" + echo " xxd -l 512 $FILENAME # Boot sector" + echo " xxd -s 0x1BE -l 64 $FILENAME # MBR partition table" echo "" echo "Clean up (when done):" if [ ${#MOUNT_POINTS[@]} -gt 0 ]; then @@ -563,6 +1013,8 @@ show_summary() { fi echo " sudo losetup -d $LOOP_DEVICE" echo "" + + print_tip "Remember to unmount filesystems and detach loop device when finished!" } # Trap to ensure cleanup on exit @@ -582,12 +1034,13 @@ main() { get_partition_count get_partition_configs + # Show final summary and confirm echo "" echo "==========================================" - echo " Summary" + echo " Configuration Summary" echo "==========================================" echo "Filename: $FILENAME" - echo "Size: ${DISK_SIZE_MB} MB" + echo "Size: ${DISK_SIZE_MB} MB ($(echo "scale=2; $DISK_SIZE_MB/1024" | bc) GB)" echo "Init Method: $INIT_METHOD" echo "Partition Scheme: $PARTITION_SCHEME" echo "Partitions: $PARTITION_COUNT" @@ -597,6 +1050,8 @@ main() { IFS='|' read -r fs size label <<< "$config" if [ "$fs" = "swap" ]; then echo " [$i] $fs (${size}MB)" + elif [ "$fs" = "unallocated" ]; then + echo " [$i] $fs (${size}MB) - no partition" else echo " [$i] $fs (${size}MB) - '$label'" fi @@ -606,10 +1061,11 @@ main() { read -p "Proceed with creation? (y/n): " CONFIRM if [ "$CONFIRM" != "y" ]; then - print_info "Cancelled" + print_info "Operation cancelled" exit 0 fi + echo "" create_disk_image setup_loop_device create_partitions