├── README.md ├── ermount.sh └── er2.sh /README.md: -------------------------------------------------------------------------------- 1 | # EverReady Disk Mount 2 | 3 | ## `er2.sh`: The All-in-One Mounting Script 4 | 5 | `er2.sh` is a complete rewrite of the original `ermount.sh`, designed for speed, automation, and advanced use cases. It is a more compact and utilitarian tool, providing support for LVM volumes and most virtual and forensic image formats. 6 | 7 | ### `er2.sh` Features 8 | 9 | | Feature | Description | 10 | |:--------|:------------| 11 | | **Universal Format Support** | Natively handles VDI, VMDK, VHD/VHDX, QCOW2, E01, AFF, split RAW (`.001`), ISOs, and more. | 12 | | **Safe Read-Only by Default** | All images are mounted read-only by default to prevent accidental modifications. | 13 | | **Explicit LVM Support** | Use the `-l` flag to enable LVM support when needed. LVM activation requires write access and will modify the image. | 14 | | **Case-Insensitive Extensions** | Works with any case combination (`.VHDX`, `.VhDx`, `.vhdx` all work). | 15 | | **Flat VMDK Support** | Automatically detects and handles flat VMDK files (descriptor + `-flat.vmdk` data file). | 16 | | **Forensically Sound Mounting** | Uses `noload` for ext4 and `norecover` for NTFS to prevent journal replay that would modify the image. | 17 | | **Comprehensive LVM Cleanup** | Properly deactivates LVM volumes and cleans up `/dev/mapper` devices on unmount or error. | 18 | | **Unpartitioned Disk Handling** | Intelligently mounts disks that are formatted as a single filesystem without a partition table. | 19 | | **Smart Forensic Mounting** | Uses the correct FUSE tools (`ewfmount`, `affuse`) with proper permissions (`allow_root`) for forensic images. | 20 | | **Robust Unmounting** | The `-u` flag properly deactivates LVM, detaches NBD devices (with `rmmod` fallback), and unmounts FUSE filesystems. | 21 | | **Intelligent Error Handling** | Provides detailed, actionable diagnostics on mount failures to help you troubleshoot quickly. | 22 | | **Pre-flight Checks** | Prevents errors by checking if mount points or NBD devices are already in use before starting. | 23 | 24 | ### `er2.sh` Usage 25 | 26 | ```shell 27 | Usage: er2.sh -i [-m mount/point] [-f filesystem] [-l] [-o offset] [-r ro|rw] [-s] [-u] 28 | 29 | Required: 30 | -i Disk image file or ISO 31 | 32 | Optional: 33 | -m Mount point directory (default: /mnt/image_mount) 34 | -f Filesystem type: ntfs, ext4, vfat, exfat, hfsplus 35 | -l Enable LVM support (allows image modification) 36 | -o Manual byte offset for partition mounting 37 | -r Mount mode: ro (read-only, default) or rw (read-write) 38 | -s Status - Check mount status only 39 | -u Unmount - Unmount image and cleanup 40 | -h, --help Show this help message 41 | 42 | Supported Formats: 43 | Virtual Disks: VDI, VMDK, VHD, VHDX, QCOW, QCOW2 44 | Forensic Images: E01, AFF, Split RAW (.001, .002, ...) 45 | Raw Images: .raw, .dd, .img, .iso 46 | 47 | Examples: 48 | # Mount a virtual disk (read-only) 49 | er2.sh -i disk.vmdk 50 | 51 | # Mount a forensic image 52 | er2.sh -i evidence.E01 -m /mnt/case1 53 | 54 | # Mount an image with LVM volumes 55 | er2.sh -i lvm_disk.dd -m /mnt/lvm -l 56 | 57 | # Mount a split RAW image 58 | er2.sh -i image.001 -f ntfs 59 | 60 | # Mount an ISO 61 | er2.sh -i ubuntu.iso 62 | 63 | # Unmount 64 | er2.sh -u -m /mnt/image_mount 65 | ``` 66 | 67 | ### Important Notes 68 | 69 | #### LVM Support 70 | 71 | **By default, all images are mounted read-only for safety.** This protects the vast majority of images (non-LVM) from accidental modification. 72 | 73 | If your image contains LVM volumes, you must use the `-l` flag: 74 | 75 | ```shell 76 | sudo er2.sh -i disk.dd -m /mnt/disk -l 77 | ``` 78 | 79 | **Why?** LVM activation requires writing metadata to the disk image, which will: 80 | - Modify the image file 81 | - Change its cryptographic hash (MD5/SHA-1/SHA-256) 82 | - Affect chain of custody for forensic evidence 83 | 84 | The script will detect LVM volumes and guide you if you forget the `-l` flag. 85 | 86 | #### Forensic Mounting 87 | 88 | The script uses forensically sound mount options: 89 | - **ext2/ext3/ext4**: Mounted with `noload` to prevent journal replay 90 | - **NTFS**: Mounted with `norecover` to prevent log file replay 91 | - **E01/AFF**: Always read-only (FUSE limitation), LVM not supported 92 | 93 | These options prevent the hash changes documented in [Maxim Suhanov's research](https://dfir.ru/2018/12/02/the-ro-option-is-not-a-solution/). 94 | 95 | ### Dependencies 96 | 97 | To use all features, you may need to install the following tools: 98 | 99 | ```shell 100 | sudo apt update 101 | sudo apt install -y qemu-utils lvm2 ewf-tools afflib-tools hfsprogs exfatprogs dosfstools ntfs-3g parted 102 | ``` 103 | 104 | --- 105 | 106 | ## `ermount.sh`: The Original Interactive Mounter 107 | 108 | `ermount.sh` remains as an interactive, step-by-step approach for mounting images, making it ideal for learning the mount process. It also retains support for legacy formats not included in `er2.sh`, such as VSS (Volume Shadow Copies) and BitLocker-encrypted volumes. 109 | 110 | ### `ermount.sh` Usage 111 | 112 | ```shell 113 | USAGE: ermount.sh [-h -s -u -b -rw] -i Image_file_or_Disk -m Mount_Point -t File_System_Type -o offset 114 | 115 | OPTIONAL: 116 | -i Image file or disk source to mount 117 | -m Mount point (Default /mnt/image_mount) 118 | -t File system type (Default NTFS) 119 | -o Image offset 120 | -h This help text 121 | -s ermount status 122 | -u umount all disks from $0 mount points 123 | -b mount bitlocker encrypted volume 124 | -rw mount image read write 125 | ``` 126 | 127 | --- 128 | 129 | ## Which Script Should I Use? 130 | 131 | | Scenario | Recommended Script | 132 | |:---------|:-------------------| 133 | | Fast, flexible, no-frills mounting of modern formats, LVM, or complex images. | **`er2.sh`** | 134 | | New to the mounting process and want to learn or go through each step interactively. | `ermount.sh` | 135 | | Need to mount VSS (Volume Shadow Copies) or BitLocker-encrypted volumes. | `ermount.sh` | 136 | 137 | --- 138 | 139 | ## License 140 | 141 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 142 | -------------------------------------------------------------------------------- /ermount.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # EverReady disk mount 3 | # John Brown forensic.studygroup@gmail.com 4 | # Mounts disks, disk images (E01,vmdk, vdi, Raw and bitlocker) in a linux evnironment using ewf_mount,qemu-ndb, affuse and bdemount 5 | # WARNING: Forcefully attempts to disconnect and remounts images and network block devices! 6 | # When in doubt, reboot 7 | # Creates directories in /mnt/ for different disk and amge types 8 | # Images are mounted based on extension: E01, VDI, VHD, VHDX, QCOw2, AFF 9 | # Otherwise mount is attempted as raw 10 | # Supports segmented disks images using aff 11 | # 12 | # Requires ewf_tools, affuse, bdemount and qemu-utils 13 | 14 | #Produce Red Text Color 15 | function makered() { 16 | COLOR='\033[01;31m' # bold red 17 | RESET='\033[00;00m' # normal white 18 | MESSAGE=${@:-"${RESET}Error: No message passed"} 19 | echo -e "${COLOR}${MESSAGE}${RESET}" 20 | } 21 | 22 | #Produce Green Text Color 23 | function makegreen() { 24 | COLOR='\033[0;32m' # Green 25 | RESET='\033[00;00m' # normal white 26 | MESSAGE=${@:-"${RESET}Error: No message passed"} 27 | echo -e "${COLOR}${MESSAGE}${RESET}" 28 | } 29 | 30 | #Function ask yes or no 31 | function yes-no(){ 32 | read -p "(Y/N)?" 33 | [ "$(echo $REPLY | tr [:upper:] [:lower:])" == "y" ] && YES_NO="yes"; 34 | } 35 | 36 | ######### MOUNT STATUS ###################### 37 | # Report mount status 38 | mount_status(){ 39 | mount_stat=$(echo " /mnt/image_mount/" && [ "$(ls -A /mnt/image_mount/ 2>/dev/null)" ] && makered " Mounted" || makegreen " Not Mounted" ) 40 | raw_stat=$(echo " /mnt/raw/........" && [ "$(ls -A /mnt/raw/ 2>/dev/null)" ] && makered " Mounted" || makegreen " Not Mounted") 41 | nbd_stat=$(echo " /dev/nbd1/......." && [ "$(ls /dev/nbd1 2>/dev/null)" ] && makered " Active" || makegreen " Inactive") 42 | vss_stat=$(echo " /mnt/vss/........" && [ "$(ls /mnt/vss 2>/dev/null)" ] && makered " Active" || makegreen " Inactive") 43 | vsc_stat=$(echo " /mnt/shadow/....." && [ "$(ls /mnt/shadow 2>/dev/null)" ] && makered " Active" || makegreen " Inactive") 44 | bde_stat=$(echo " /mnt/bde/........" && [ "$(ls /mnt/bde 2>/dev/null)" ] && makered " Active" || makegreen " Inactive") 45 | makered "Disk Status" 46 | lsblk -o NAME,SIZE,FSTYPE,FSAVAIL,FSUSE%,MOUNTPOINT 2>/dev/null || lsblk 47 | echo "" 48 | makered "ermount Volume Mount Points" 49 | echo $mount_stat && echo $raw_stat && echo $nbd_stat && echo $vss_stat && echo $vsc_stat && echo $bde_stat 50 | } 51 | 52 | 53 | ######### MOUNT PREFS ###################### 54 | # User supplies input path of the image file or disk 55 | function image_source(){ 56 | [ ! -f "${ipath}" ] && read -e -p "Enter Image File or Device Path: " -i "" ipath 57 | image_type=$(echo "$ipath"|awk -F . '{print toupper ($NF)}') 58 | [ ! -f "${ipath}" ] && [ ! -b "${ipath}" ] && makered "File or Device does not exist.." && sleep 2 && clear && exit 59 | image_name=$(echo $ipath|sed 's/\(.*\)\..*/\1\./') 60 | [ $image_type == "ISO" ] && return 1 61 | multi=$image_name"002" 62 | # Set source image and destination mount point for E01 && umount /mnt/raw 2>/dev/null 63 | printf "Image type " 64 | makegreen $image_type || makegreen "RAW" 65 | source_info=$(file "$ipath") 66 | echo "Source Information" 67 | makegreen $source_info 68 | 69 | } 70 | 71 | # User input to set mount directory (Default /mnt/image_mount) 72 | function mount_point(){ 73 | # Set Data Source or mount point" 74 | echo "" 75 | makegreen "Set Mount Point" 76 | echo "Set Path or Enter to Accept Default:" 77 | read -e -p "" -i "/mnt/image_mount" mount_dir 78 | mkdir -p $mount_dir 79 | [ "$(ls -A $mount_dir)" ] && umount $mount_dir -f -A 80 | [ "$(ls -A $mount_dir)" ] && echo "$mount_dir busy, try different mount point or reboot" && sleep 2 && exit 81 | echo "" 82 | } 83 | 84 | ######### IMAGE OFFSET ##################### 85 | # Set partition offset for disk images 86 | function set_image_offset(){ 87 | makegreen "Set Partition Offset" && \ 88 | fdisk -l "$image_src" && echo "" && \ 89 | read -e -p "Enter the starting block: " -i "" starting_block 90 | # Next line has been commented. Use default block size of 512 91 | # read -e -p "Set disk block size: " -i "512" block_size && \ 92 | partition_offset=$(echo $(($starting_block * 512))) && \ 93 | makegreen "Offset: $starting_block * 512 = $partition_offset" && \ 94 | offset="offset=$partition_offset" 95 | } 96 | 97 | ######### IMAGE MOUNTING ################### 98 | # Mount images in expert witness format as raw image to /mnt/raw 99 | function mount_e01(){ 100 | [ 'which ewfmount' == "" ] && makered "ewf-tools not installed" && sleep 1 && exit 101 | image_src="/mnt/raw/ewf1" 102 | [ "$(ls -A /mnt/raw/)" ] && echo "Attempting to remount /mnt/raw/ " && umount /mnt/raw/ -f -A && makegreen "Sucessfully umounted previous E01" 103 | makegreen "Executing ewfmount command: ewfmount "${ipath}" /mnt/raw" 104 | ewfmount "${ipath}" /mnt/raw && makegreen "Success!" && ipath="/mnt/raw/ewf1" || exit 105 | } 106 | 107 | #Mount vmdk, vdi and qcow2 Image types as a network block device 108 | function mount_nbd(){ 109 | [ 'which qemu-nbd' == "" ] && makered "qemu-utils not installed" && sleep 1 && exit 110 | echo $nbd_stat |grep -q Active && makered "/dev/nbd1 is already in use!!....\nTry $0 -u if umount fails" 111 | [ -d "/dev/nbd1" ] && qemu-nbd -d /dev/nbd1 2>/dev/null && \ 112 | rmmod nbd 2>/dev/null && makegreen "umounted of nbd suceeded!" 113 | [ -d "/dev/nbd1" ] && makered "Could not delete existing network block device! try -> $0 -u" && exit 114 | modprobe nbd && echo "modprobe nbd" 115 | makegreen " Excecuting: qemu-nbd -r -c /dev/nbd1 ${ipath}" && \ 116 | qemu-nbd -r -c /dev/nbd1 "${ipath}" || exit 117 | ls /dev/nbd1 && makegreen "nbd mount successful!" 118 | image_src="/dev/nbd1" 119 | sfdisk -V $image_src |grep "No errors" || makegreen "Waiting for remount..." && sleep 4 120 | } 121 | 122 | 123 | #Mount raw split images using affuse 124 | function mount_aff(){ 125 | [ 'which affuse' == "" ] && makered "afflib-tools not installed" && sleep 1 && exit 126 | [ "$(ls -A /mnt/raw/)" ] && fusermount -uz /mnt/raw/ 127 | [ "$(ls -A /mnt/raw/)" ] && echo "raw mount point in use, try manual unmount or reboot" && exit 128 | makegreen "Executing Affuse command: affuse "${ipath}" /mnt/raw" 129 | affuse "${ipath}" /mnt/raw && image_src=$(find /mnt/raw/ -type f) || mount_nbd 130 | } 131 | 132 | # Decrypt bitlocker disks and mount partitions 133 | function bit_locker_mount(){ 134 | [ 'which bdemount' == "" ] && makered "bdemount is not installed" && sleep 1 && exit 135 | [ "${partition_offset}" != "" ] && offset="-o $partition_offset " 136 | [ "$(ls -A /mnt/raw/)" ] && \ 137 | echo "" && makered "Bitlocker Encryption!!!" && makered "Enter decryption password or key" 138 | echo "-p " 139 | echo "-r " 140 | echo "" 141 | read -e -p "" bl_auth 142 | makegreen "Mounting with bdemount!! " 143 | makegreen "bdemount $bl_auth $offset $ipath /mnt/bde" 144 | bdemount $bl_auth $offset $ipath /mnt/bde 145 | ls /mnt/bde/bde1 && makegreen "Unlocked!!" && offset="" && image_src="/mnt/bde/bde1" 146 | mount_image 147 | exit 148 | } 149 | 150 | # Issue Mount command based on image type and prefs 151 | function mount_image(){ 152 | echo " 153 | USAGE: $0 [-h -s -u -b -rw] -i -m -t -o 154 | " 155 | makegreen "Executing Mount Command....." 156 | echo "Defaults file system type is ntfs, see mount man pages for a complete list" 157 | echo "Other common filesystem types: vfat, ext3, ext4, hfsplus, iso9660, udf" 158 | [ "${fstype}" ] || read -e -p "File System Type: " -i "ntfs" fstype 159 | [ $fstype == "ntfs" ] && ntfs_support="show_sys_files,streams_interface=windows," && \ 160 | umount_vss 161 | # Mount image to $mount_dir 162 | echo $image_src | grep -qiv "/dev/sd" && loop="loop," 163 | mount_options="-t $fstype -o $ro_rw," 164 | [ $image_type == "ISO" ] && mount_options="" 165 | [ "${block_device}" != "" ] && mount_options="-o $ro_rw," 166 | mount=$(echo "mount $mount_options$loop$ntfs_support$offset "$image_src" $mount_dir"|sed 's/, / /') 167 | makegreen $mount 168 | $mount 169 | echo "" 170 | [ "$(ls -A $mount_dir)" ] && \ 171 | echo "$ipath Mounted at: $mount_dir" 172 | echo "" 173 | ls $mount_dir 174 | echo "" 175 | [ "$(ls -A $mount_dir)" ] && \ 176 | makegreen "Success!" || makered "Mount Failed! Try $0 -u or reboot" 177 | echo "" 178 | [ "$(ls -A $mount_dir)" ] && [ "$fstype" == "ntfs" ] && mount_vss 179 | exit 180 | } 181 | 182 | #Identify and choose whether to mount any vss volumes 183 | function mount_vss(){ 184 | [ 'which vshadowinfo' == "" ] && makered "libvshadow-utils not installed" && sleep 1 && exit 185 | vss_dir="/mnt/vss" 186 | vss_info=$(vshadowinfo $image_src 2>/dev/null |grep "Number of stores:"|grep -v "0$") 187 | vshadowinfo $image_src 2>/dev/null 188 | [ "${vss_info}" != "" ] && echo "VSCs found! "$vss_info && \ 189 | echo "Mount Volume Shadow Copies?" && yes-no && vsc="yes" 190 | [ "${offset}" == "yes" ] && offset="-o $offset " 191 | [ "${vsc}" == "yes" ] && vshadowmount $image_src $offset$vss_dir && \ 192 | ls $vss_dir | while read vsc; 193 | do 194 | mkdir -p /mnt/shadow/$vsc 195 | mount -t ntfs -o ro,loop,show_sys_files,streams_interface=windows /mnt/vss/$vsc /mnt/shadow/$vsc 196 | done || exit 197 | ls /mnt/shadow/ && makegreen "Success! VSCs mounted on /mnt/shadow" || echo "No Volume Shadow Copies mounted" 198 | } 199 | 200 | ######### UNMOUNT IMAGES ################### 201 | 202 | function umount_all(){ 203 | echo "Umount commands sent to all $0 drives mounts" && echo "" 204 | umount_vss 205 | [ "$(ls -A /mnt/bde 2>/dev/null)" ] && umount /mnt/bde -f -A || fusermount -uz /mnt/bde 2>/dev/null 206 | [ "$(ls -A /mnt/image_mount 2>/dev/null)" ] && umount /mnt/image_mount -f -A || fusermount -uz /mnt/image_mount 2>/dev/null 207 | [ "$(ls -A /mnt/raw/ 2>/dev/null)" ] && umount /mnt/raw -f -A || fusermount -uz /mnt/raw/ 2>/dev/null 208 | ls /dev/nbd1p1 2>/dev/null && qemu-nbd -d /dev/nbd1 2>/dev/null 209 | lsmod |grep -i ^nbd && rmmod nbd 2>/dev/null && echo "Warning: unloading Network Block Device" 210 | mount_status 211 | exit 212 | } 213 | 214 | #Identify and umount any previously mounted vss volumes 215 | function umount_vss(){ 216 | vss_dir="/mnt/vss" 217 | #umount any existing mounts 218 | fusermount -uz $vss_dir 2>/dev/null || return 1 219 | ls /mnt/shadow/ 2>/dev/null|while read vsc; 220 | do 221 | umount /mnt/shadow/$vsc 2>/dev/null 222 | rmdir /mnt/shadow/$vsc 2>/dev/null 223 | echo "/mnt/shadow/$vsc umounted" 224 | done 225 | } 226 | 227 | ######### Help message ################### 228 | get_help(){ 229 | makegreen "EverReady Disk Mount" 230 | makegreen "Mount/umounts disk and disk images (E01, vmdk, vhd(x), vdi, raw, iso, hfs+, qcow2 and vss)" 231 | echo " 232 | USAGE: $0 [-h -s -u -b -rw] -i -m -t -o 233 | 234 | OPTIONAL: 235 | -i Image file or disk source to mount 236 | -m Mount point (Default /mnt/image_mount) 237 | -t File System Type (Default NTFS) 238 | -o File offset 239 | -h This help text 240 | -s ermount status 241 | -u umount all disks from $0 mount points 242 | -b mount bitlocker encrypted volume 243 | -rw mount image read write 244 | 245 | Default mount point: /mnt/image_mount 246 | Minimum requirements: libewf-tools libbde-tools libvshadow-tools afflib-tools, qemu-utils, libfuse2 247 | Works best with updated drivers from the gift repository (add-apt-repository ppa:gift/stable) 248 | Warning: forcefully disconnects mounted drives and Network Block Devices 249 | When in doubt reboot 250 | " 251 | } 252 | 253 | ###### END OF FUNTIONS ############## 254 | 255 | ###### COMMAND EXECUTION ############# 256 | clear 257 | #check root requirements 258 | [ `whoami` != 'root' ] && makered "Requires Root Access!" && sleep 1 && exit 259 | 260 | # Setup mount directories and display physical devices 261 | mkdir -p /mnt/raw 2>/dev/null 262 | mkdir -p /mnt/vss 2>/dev/null 263 | mkdir -p /mnt/bde 2>/dev/null 264 | 265 | while getopts "husri:m:t:o:" option; 266 | do 267 | case $option in 268 | h) get_help 269 | exit;; 270 | u) umount_all 271 | exit;; 272 | s) mount_status 273 | exit;; 274 | r) ro_rw="rw";; 275 | i) ipath=${OPTARG};; 276 | m) mount_dir=${OPTARG};; 277 | t) fstype=${OPTARG};; 278 | o) off_set=${OPTARG};; 279 | esac 280 | done 281 | 282 | [ "${ro_rw}" != "rw" ] && ro_rw="ro" 283 | 284 | # start mounting process and select source image and mount point 285 | makegreen "Use ERMount to mount a disk, disk image" 286 | image_source 287 | [ ! -d "${mount_dir}" ] && mount_point 288 | 289 | # Send to mounting function based on image type 290 | echo $image_type | grep -qie "E01$\|S01" && mount_e01 291 | echo $image_type | grep -qie "AFF$" && mount_aff 292 | [ -f "$image_name"002"" ] && mount_aff 293 | echo $image_type | grep -ie "VHD$\|VHDX$\|VMDK$\|VDI$\|QCOW2$\VMDK$\|VDI$" && mount_nbd 294 | 295 | # If no image type detected, process as raw 296 | [ "$image_src" == "" ] && image_src="${ipath}" 297 | is_device=$(echo "$image_src" | grep -i "/dev/" |grep -vi "nbd1") 298 | [ "${is_device}" != "" ] && [ "${1}" != "-b" ] && fdisk -l |grep $image_src && mount_image 299 | [ "${is_device}" != "" ] && [ "${1}" == "-b" ] && bit_locker_mount 300 | 301 | # Set image offset if needed 302 | [ ! "${off_set}" ] && partx -s "$image_src" 2>/dev/null | grep ^" 1" && set_image_offset 303 | [ ! "${off_set}" ] || offset="offset=$off_set" 304 | # Decrypt bitlocker if "-b" is specified 305 | [ "${1}" == "-b" ] && bit_locker_mount 306 | # mount image and detect any volume shadow copies 307 | mount_image 308 | -------------------------------------------------------------------------------- /er2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check for root privileges 4 | if [ "$EUID" -ne 0 ]; then 5 | echo -e "\033[31mError: This script must be run as root or with sudo.\033[0m" 6 | echo "Usage: sudo $0 [options]" 7 | exit 1 8 | fi 9 | 10 | # Colors for output 11 | RED='\033[31m' 12 | GREEN='\033[32m' 13 | NC='\033[0m' 14 | # Function to print in color 15 | print_error() { echo -e "${RED}$1${NC}"; } 16 | print_success() { echo -e "${GREEN}$1${NC}"; } 17 | # LVM cleanup function for er3.sh 18 | 19 | # Function to cleanup LVM volumes and /dev/mapper devices 20 | cleanup_lvm() { 21 | local nbd_device="${1:-/dev/nbd1}" 22 | local verbose="${2:-true}" 23 | 24 | if [ "$verbose" = "true" ]; then 25 | echo "Checking for active LVM volumes to deactivate..." 26 | fi 27 | 28 | # Check if LVM tools are available 29 | if ! command -v vgchange >/dev/null 2>&1; then 30 | return 0 31 | fi 32 | 33 | # Step 1: Check for /dev/mapper devices related to this NBD device 34 | local mapper_devices=$(ls -1 /dev/mapper/ 2>/dev/null | grep -v '^control$' || true) 35 | 36 | if [ -n "$mapper_devices" ]; then 37 | if [ "$verbose" = "true" ]; then 38 | echo "Found /dev/mapper devices:" 39 | echo "$mapper_devices" | sed 's/^/ - \/dev\/mapper\//' 40 | fi 41 | 42 | # Step 2: For each mapper device, check if it's associated with our NBD device 43 | for mapper_dev in $mapper_devices; do 44 | local mapper_path="/dev/mapper/$mapper_dev" 45 | 46 | # Check if this is an LVM device 47 | if lvs "$mapper_path" >/dev/null 2>&1; then 48 | # Get the volume group name 49 | local vg_name=$(lvs --noheadings -o vg_name "$mapper_path" 2>/dev/null | tr -d ' ') 50 | 51 | if [ -n "$vg_name" ]; then 52 | # Check if this VG is on our NBD device 53 | local pv_devices=$(pvs --noheadings -o pv_name -S vg_name="$vg_name" 2>/dev/null | tr -d ' ') 54 | 55 | # Check if any PV is on the NBD device 56 | if echo "$pv_devices" | grep -q "^${nbd_device}"; then 57 | if [ "$verbose" = "true" ]; then 58 | echo "Deactivating LVM device: $mapper_path (VG: $vg_name)" 59 | fi 60 | 61 | # Deactivate this specific logical volume 62 | lvchange -an "$mapper_path" 2>/dev/null || true 63 | fi 64 | fi 65 | fi 66 | done 67 | fi 68 | 69 | # Step 3: Deactivate all volume groups on this NBD device 70 | local vgs_on_nbd=$(pvs --noheadings -o vg_name -S "pv_name=~^${nbd_device}" 2>/dev/null | tr -d ' ' | sort -u || true) 71 | 72 | if [ -n "$vgs_on_nbd" ]; then 73 | for vg in $vgs_on_nbd; do 74 | if [ "$verbose" = "true" ]; then 75 | echo "Deactivating volume group: $vg" 76 | fi 77 | 78 | if vgchange -an "$vg" 2>/dev/null; then 79 | if [ "$verbose" = "true" ]; then 80 | print_success "Deactivated volume group: $vg" 81 | fi 82 | else 83 | if [ "$verbose" = "true" ]; then 84 | echo "Warning: Could not deactivate volume group: $vg" 85 | fi 86 | fi 87 | done 88 | 89 | # Give LVM time to clean up 90 | sleep 1 91 | fi 92 | 93 | # Step 4: Final check - verify no mapper devices remain for this NBD 94 | local remaining_mappers=$(ls -1 /dev/mapper/ 2>/dev/null | grep -v '^control$' || true) 95 | 96 | if [ -n "$remaining_mappers" ]; then 97 | for mapper_dev in $remaining_mappers; do 98 | local mapper_path="/dev/mapper/$mapper_dev" 99 | 100 | # Check if still associated with our NBD device 101 | if lvs "$mapper_path" >/dev/null 2>&1; then 102 | local pv_devices=$(pvs --noheadings -o pv_name -S "lv_path=$mapper_path" 2>/dev/null | tr -d ' ') 103 | 104 | if echo "$pv_devices" | grep -q "^${nbd_device}"; then 105 | if [ "$verbose" = "true" ]; then 106 | echo "Warning: Mapper device still active: $mapper_path" 107 | echo "Attempting force deactivation..." 108 | fi 109 | 110 | # Try force deactivation 111 | dmsetup remove "$mapper_dev" 2>/dev/null || true 112 | fi 113 | fi 114 | done 115 | fi 116 | 117 | # Step 5: Remove any stale PV cache entries 118 | if command -v pvs >/dev/null 2>&1; then 119 | pvs --cache 2>/dev/null || true 120 | fi 121 | 122 | if [ "$verbose" = "true" ]; then 123 | echo "LVM cleanup complete." 124 | fi 125 | 126 | return 0 127 | } 128 | 129 | # Function to check if LVM cleanup is needed 130 | check_lvm_active() { 131 | local nbd_device="${1:-/dev/nbd1}" 132 | 133 | # Quick check: any mapper devices exist? 134 | local mapper_count=$(ls -1 /dev/mapper/ 2>/dev/null | grep -v '^control$' | wc -l) 135 | 136 | if [ "$mapper_count" -gt 0 ]; then 137 | # Check if any are on our NBD device 138 | if command -v pvs >/dev/null 2>&1; then 139 | local vgs_on_nbd=$(pvs --noheadings -o vg_name -S "pv_name=~^${nbd_device}" 2>/dev/null | tr -d ' ' | sort -u || true) 140 | 141 | if [ -n "$vgs_on_nbd" ]; then 142 | return 0 # LVM cleanup needed 143 | fi 144 | fi 145 | fi 146 | 147 | return 1 # No LVM cleanup needed 148 | } 149 | 150 | # Smart cleanup wrapper function 151 | 152 | # Function to perform full cleanup with automatic LVM detection 153 | cleanup_and_exit() { 154 | local nbd_device="${1:-/dev/nbd1}" 155 | local temp_dir="$2" 156 | local ewf_mount="$3" 157 | local aff_mount="$4" 158 | local splitraw_mount="$5" 159 | local verbose="${6:-false}" 160 | 161 | # Check if LVM cleanup is needed (only if LVM tools are available) 162 | if command -v vgchange >/dev/null 2>&1; then 163 | if check_lvm_active "$nbd_device"; then 164 | if [ "$verbose" = "true" ]; then 165 | echo "Active LVM detected, cleaning up..." 166 | fi 167 | cleanup_lvm "$nbd_device" "$verbose" 168 | fi 169 | fi 170 | 171 | # Detach NBD device 172 | qemu-nbd -d "$nbd_device" >/dev/null 2>&1 173 | 174 | # Clean up temporary directories 175 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 176 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; } 177 | [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; } 178 | [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 179 | } 180 | # Function to check mount status 181 | check_mount_status() { 182 | local mount_point="$1" 183 | if mountpoint -q "$mount_point"; then 184 | print_success "$mount_point is mounted." 185 | df -h "$mount_point" || print_error "Warning: Unable to retrieve disk usage for $mount_point." 186 | return 0 187 | else 188 | print_error "$mount_point is not mounted." 189 | return 1 190 | fi 191 | } 192 | # Function to unmount and detach 193 | unmount_image() { 194 | local mount_point="$1" 195 | local nbd_device="/dev/nbd1" 196 | local temp_dir="$2" 197 | local ewf_mount="$3" 198 | local aff_mount="$4" 199 | # Step 1: Unmount the filesystem 200 | if mountpoint -q "$mount_point"; then 201 | if umount "$mount_point"; then 202 | print_success "Successfully unmounted $mount_point." 203 | else 204 | print_error "Failed to unmount $mount_point. Check processes with 'lsof $mount_point' or force with 'sudo umount -l $mount_point'." 205 | return 1 206 | fi 207 | else 208 | echo "$mount_point is not mounted." 209 | fi 210 | 211 | # Step 2: Deactivate LVM volumes BEFORE detaching NBD 212 | if command -v vgchange >/dev/null 2>&1; then 213 | # Check for any active LVM volumes on this NBD device 214 | local lvm_on_nbd=$(lsblk -ln -o NAME,TYPE "$nbd_device" 2>/dev/null | awk '$2 == "lvm" {print $1}' || true) 215 | if [ -n "$lvm_on_nbd" ]; then 216 | echo "Found active LVM volumes, deactivating..." 217 | # Get all active volume groups 218 | local active_vgs=$(vgs --noheadings -o vg_name 2>/dev/null | tr -d ' ') 219 | if [ -n "$active_vgs" ]; then 220 | for vg in $active_vgs; do 221 | echo "Deactivating volume group: $vg" 222 | if vgchange -an "$vg" 2>/dev/null; then 223 | print_success "Deactivated volume group: $vg" 224 | else 225 | print_warning "Could not deactivate volume group: $vg (may not be on this device)" 226 | fi 227 | done 228 | # Give LVM time to clean up 229 | sleep 1 230 | fi 231 | fi 232 | fi 233 | 234 | # Step 3: Detach the NBD device with retry logic 235 | # Check if NBD device is actually attached (has non-zero size) 236 | local nbd_size=$(cat /sys/block/nbd1/size 2>/dev/null || echo "0") 237 | if [ "$nbd_size" != "0" ]; then 238 | echo "Detaching NBD device..." 239 | local detach_success=false 240 | 241 | # Try qemu-nbd -d up to 3 times 242 | for attempt in 1 2 3; do 243 | if qemu-nbd -d "$nbd_device" >/dev/null 2>&1; then 244 | # Verify device is actually detached by checking size 245 | sleep 0.5 246 | local verify_size=$(cat /sys/block/nbd1/size 2>/dev/null || echo "0") 247 | if [ "$verify_size" = "0" ]; then 248 | print_success "Successfully detached $nbd_device." 249 | detach_success=true 250 | break 251 | else 252 | echo "Device still shows size $verify_size, not fully detached" 253 | fi 254 | else 255 | if [ $attempt -lt 3 ]; then 256 | echo "Detach attempt $attempt failed, retrying..." 257 | sleep 1 258 | fi 259 | fi 260 | done 261 | 262 | # If qemu-nbd -d failed, try aggressive rmmod approach 263 | if [ "$detach_success" = false ]; then 264 | print_error "qemu-nbd -d failed after 3 attempts. Trying aggressive cleanup..." 265 | echo "Attempting to unload NBD kernel module (rmmod nbd)..." 266 | 267 | if rmmod nbd 2>/dev/null; then 268 | print_success "Successfully unloaded NBD module." 269 | # Verify module is unloaded 270 | if ! lsmod | grep -q "^nbd "; then 271 | print_success "NBD module fully unloaded." 272 | # Reload the module for future use 273 | sleep 1 274 | modprobe nbd max_part=8 2>/dev/null || true 275 | detach_success=true 276 | else 277 | print_error "NBD module still loaded after rmmod." 278 | fi 279 | else 280 | print_error "Failed to detach $nbd_device even with rmmod." 281 | print_error "NBD device may still be in use by another process." 282 | echo "Checking what's still active:" 283 | lsblk "$nbd_device" 2>/dev/null || true 284 | echo "Try manually: sudo rmmod nbd && sudo modprobe nbd" 285 | return 1 286 | fi 287 | fi 288 | else 289 | echo "$nbd_device is not attached." 290 | fi 291 | # Unmount /mnt/raw/ewf1 if it exists 292 | if [ -f "/mnt/raw/ewf1" ]; then 293 | if umount "/mnt/raw/ewf1" 2>/dev/null; then 294 | print_success "Successfully unmounted /mnt/raw/ewf1." 295 | else 296 | echo "/mnt/raw/ewf1 is not mounted or already unmounted." 297 | fi 298 | fi 299 | if [ -n "$ewf_mount" ] && mountpoint -q "$ewf_mount"; then 300 | if umount "$ewf_mount"; then 301 | print_success "Successfully unmounted EWF mount $ewf_mount." 302 | else 303 | print_error "Failed to unmount EWF mount $ewf_mount." 304 | return 1 305 | fi 306 | fi 307 | # Unmount AFF FUSE mount if present 308 | if [ -n "$aff_mount" ] && mountpoint -q "$aff_mount"; then 309 | if fusermount -u "$aff_mount" 2>/dev/null; then 310 | print_success "Successfully unmounted AFF mount $aff_mount." 311 | else 312 | print_error "Failed to unmount AFF mount $aff_mount." 313 | return 1 314 | fi 315 | fi 316 | # Also check /mnt/aff explicitly 317 | if mountpoint -q "/mnt/aff" 2>/dev/null; then 318 | if fusermount -u "/mnt/aff" 2>/dev/null; then 319 | print_success "Successfully unmounted /mnt/aff." 320 | fi 321 | fi 322 | # Unmount split RAW FUSE mount if present (uses /mnt/raw) 323 | # Note: This is checked after EWF unmount since they share /mnt/raw 324 | if [ -n "$splitraw_mount" ] && mountpoint -q "$splitraw_mount" 2>/dev/null; then 325 | if fusermount -u "$splitraw_mount" 2>/dev/null; then 326 | print_success "Successfully unmounted split RAW at $splitraw_mount." 327 | fi 328 | fi 329 | # Clean up temporary directories 330 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 331 | [ -n "$ewf_mount" ] && rm -rf "$ewf_mount" 2>/dev/null 332 | [ -n "$aff_mount" ] && rm -rf "$aff_mount" 2>/dev/null 333 | [ -n "$splitraw_mount" ] && rm -rf "$splitraw_mount" 2>/dev/null 334 | return 0 335 | } 336 | # Function to mount disk image 337 | mount_image() { 338 | local image_path="$1" 339 | local mount_point="$2" 340 | local lvm_mode="$3" 341 | local filesystem="$4" 342 | local check_status_only="$5" 343 | local unmount_only="$6" 344 | local mount_mode="$7" 345 | local manual_offset="$8" 346 | local retries=5 347 | local sleep_time=2 348 | local nbd_device="/dev/nbd1" 349 | local temp_dir="" 350 | local ewf_mount="" 351 | local aff_mount="" 352 | local splitraw_mount="" 353 | if ! modprobe nbd; then 354 | print_error "Failed to load NBD module. Verify with 'modinfo nbd'." 355 | return 1 356 | fi 357 | if [ "$check_status_only" = "true" ]; then 358 | check_mount_status "$mount_point" 359 | return $? 360 | fi 361 | if [ "$unmount_only" = "true" ]; then 362 | unmount_image "$mount_point" "$temp_dir" "/mnt/raw" "/mnt/aff" 363 | return $? 364 | fi 365 | if [ -z "$image_path" ]; then 366 | echo "" 367 | echo "Error: Image path required (-i)" 368 | echo "" 369 | SCRIPT_NAME=$(basename "$0") 370 | echo "$SCRIPT_NAME mounts multiple image types" 371 | echo "Usage: $SCRIPT_NAME -i [-m mount/point] [-f filesystem] [-l] [-o offset] [-r ro|rw] [-s] [-u]" 372 | echo "" 373 | echo "Required:" 374 | echo " -i Disk image file or ISO" 375 | echo "" 376 | echo "Optional:" 377 | echo " -f Filesystem type: ntfs, ext4, vfat, exfat, hfsplus" 378 | echo " -l Enable LVM support (allows image modification)" 379 | echo " -s Status - Check mount status only" 380 | echo " -u Unmount - Unmount image and cleanup" 381 | echo " -r Mount mode: ro (read-only) or rw (read-write)" 382 | echo "" 383 | echo "For full help: $SCRIPT_NAME -h" 384 | echo "" 385 | return 1 386 | fi 387 | # Check if image file exists 388 | if [ ! -f "$image_path" ]; then 389 | echo "" 390 | echo "Error: Image file $image_path does not exist" 391 | echo "Verify with: ls -l $image_path" 392 | echo "" 393 | return 1 394 | fi 395 | if [ -n "$filesystem" ] && [[ "$filesystem" != "ntfs" && "$filesystem" != "ext4" && "$filesystem" != "vfat" && "$filesystem" != "exfat" && "$filesystem" != "hfsplus" ]]; then 396 | print_error "Invalid filesystem '$filesystem'. Use 'ntfs', 'ext4', 'vfat', 'exfat', or 'hfsplus'." 397 | return 1 398 | fi 399 | if [ -n "$mount_mode" ] && [[ "$mount_mode" != "ro" && "$mount_mode" != "rw" ]]; then 400 | print_error "Invalid mount mode '$mount_mode'. Use 'ro' or 'rw'." 401 | return 1 402 | fi 403 | [ -z "$filesystem" ] && filesystem="ntfs" 404 | [ -z "$mount_mode" ] && mount_mode="ro" 405 | 406 | # Pre-flight checks: Detect if mount points or NBD device are already in use 407 | echo "Performing pre-flight checks..." 408 | 409 | # Check if /mnt/raw is already mounted 410 | if mountpoint -q /mnt/raw 2>/dev/null; then 411 | print_error "/mnt/raw is already mounted (from previous operation)." 412 | print_error "Unmount first: sudo $0 -u -m $mount_point" 413 | echo "Or check what's mounted: mount | grep /mnt/raw" 414 | return 1 415 | fi 416 | 417 | # Check if /mnt/aff is already mounted 418 | if mountpoint -q /mnt/aff 2>/dev/null; then 419 | print_error "/mnt/aff is already mounted (from previous operation)." 420 | print_error "Unmount first: sudo $0 -u -m $mount_point" 421 | echo "Or check what's mounted: mount | grep /mnt/aff" 422 | return 1 423 | fi 424 | 425 | # Check if NBD device is already in use 426 | local nbd_check_size=$(cat /sys/block/nbd1/size 2>/dev/null || echo "0") 427 | if [ "$nbd_check_size" != "0" ]; then 428 | print_error "NBD device /dev/nbd1 is already in use (attached to another image)." 429 | print_error "Unmount first: sudo $0 -u -m $mount_point" 430 | echo "Or check status: lsblk | grep nbd1" 431 | return 1 432 | fi 433 | 434 | print_success "Pre-flight checks passed." 435 | 436 | # Convert to lowercase for case-insensitive extension matching throughout 437 | image_path_lower=$(echo "$image_path" | tr '[:upper:]' '[:lower:]') 438 | 439 | if [[ "$image_path_lower" =~ \.ova$ ]]; then 440 | temp_dir="/tmp/ova_extracted_$$" 441 | mkdir -p "$temp_dir" 442 | tar -xvf "$image_path" -C "$temp_dir" >/dev/null 2>&1 || { 443 | print_error "Failed to extract $image_path. Verify with 'tar -tvf $image_path'." 444 | rm -rf "$temp_dir" 2>/dev/null 445 | return 1 446 | } 447 | image_path=$(find "$temp_dir" -type f -name "*.vmdk" -o -name "*.vhd" -o -name "*.vhdx" -o -name "*.qcow" -o -name "*.qcow2" | head -n 1) 448 | if [ -z "$image_path" ]; then 449 | print_error "No disk image found in $image_path. Check contents with 'tar -tvf $image_path'." 450 | rm -rf "$temp_dir" 2>/dev/null 451 | return 1 452 | fi 453 | fi 454 | if [[ "$image_path_lower" =~ \.ovf$ ]]; then 455 | ovf_dir=$(dirname "$image_path") 456 | image_path=$(find "$ovf_dir" -type f -name "*.vmdk" -o -name "*.vhd" -o -name "*.vhdx" -o -name "*.qcow" -o -name "*.qcow2" | head -n 1) 457 | if [ -z "$image_path" ]; then 458 | print_error "No disk image found for $image_path. Check directory with 'ls -l $ovf_dir'." 459 | return 1 460 | fi 461 | fi 462 | # Support E01/EWF images 463 | if [[ "$image_path_lower" =~ \.e01$ ]]; then 464 | # Ensure FUSE allows root access (required for qemu-nbd) 465 | if [ ! -f /etc/fuse.conf ] || ! grep -q "user_allow_other" /etc/fuse.conf 2>/dev/null; then 466 | echo "user_allow_other" | sudo tee /etc/fuse.conf >/dev/null 2>&1 467 | fi 468 | ewf_mount="/mnt/raw" 469 | mkdir -p "$ewf_mount" 470 | if ! ewfmount -X allow_root "$image_path" "$ewf_mount" >/dev/null 2>&1; then 471 | print_error "Failed to mount $image_path with ewfmount. Verify with 'ewfmount --help'." 472 | rm -rf "$ewf_mount" 2>/dev/null 473 | return 1 474 | fi 475 | image_path="$ewf_mount/ewf1" 476 | if [ ! -f "$image_path" ]; then 477 | print_error "EWF raw image $image_path not found. Check ewfmount setup." 478 | umount "$ewf_mount" 2>/dev/null 479 | rm -rf "$ewf_mount" 2>/dev/null 480 | return 1 481 | fi 482 | fi 483 | # Support split RAW images (.001, .002, .003) created by FTK Imager 484 | # Note: AFF files can also use .001 extension, so we need to verify the format 485 | if [[ "$image_path_lower" =~ \.001$ ]]; then 486 | # Check if this is actually an AFF file by examining magic bytes 487 | local file_type=$(file -b "$image_path" 2>/dev/null || echo "unknown") 488 | 489 | if [[ "$file_type" =~ AFF|"Advanced Forensic Format" ]]; then 490 | # This is an AFF file, not a split RAW - skip this section 491 | # It will be handled by the .aff detection later (after renaming) 492 | echo "Detected AFF format file with .001 extension (not split RAW)" 493 | # AFF files with .001 extension need to be handled specially 494 | # Treat it like a .aff file 495 | if [ ! -f /etc/fuse.conf ] || ! grep -q "user_allow_other" /etc/fuse.conf 2>/dev/null; then 496 | echo "user_allow_other" | sudo tee /etc/fuse.conf >/dev/null 2>&1 497 | fi 498 | aff_mount="/mnt/aff" 499 | mkdir -p "$aff_mount" 500 | echo "Mounting AFF image with affuse..." 501 | if ! affuse -o allow_root "$image_path" "$aff_mount" >/dev/null 2>&1; then 502 | print_error "Failed to mount AFF $image_path with affuse. Verify with 'affuse --help'." 503 | rm -rf "$aff_mount" 2>/dev/null 504 | return 1 505 | fi 506 | local original_aff_basename=$(basename "$image_path") 507 | if [ -f "$aff_mount/${original_aff_basename}.raw" ]; then 508 | image_path="$aff_mount/${original_aff_basename}.raw" 509 | elif [ -f "$aff_mount/${original_aff_basename%.*}.raw" ]; then 510 | image_path="$aff_mount/${original_aff_basename%.*}.raw" 511 | elif [ -f "$aff_mount/$original_aff_basename" ]; then 512 | image_path="$aff_mount/$original_aff_basename" 513 | else 514 | echo "Available files in $aff_mount:" 515 | ls -la "$aff_mount/" 2>/dev/null || true 516 | print_error "AFF raw image not found in $aff_mount. Check affuse setup." 517 | fusermount -u "$aff_mount" 2>/dev/null 518 | rm -rf "$aff_mount" 2>/dev/null 519 | return 1 520 | fi 521 | echo "AFF mounted at: $image_path" 522 | else 523 | # This is a split RAW file (FTK Imager format) 524 | echo "Detected split RAW image (FTK Imager format)" 525 | # Ensure FUSE allows root access (required for qemu-nbd) 526 | if [ ! -f /etc/fuse.conf ] || ! grep -q "user_allow_other" /etc/fuse.conf 2>/dev/null; then 527 | echo "user_allow_other" | sudo tee /etc/fuse.conf >/dev/null 2>&1 528 | fi 529 | splitraw_mount="/mnt/raw" 530 | mkdir -p "$splitraw_mount" 531 | echo "Mounting split RAW image with affuse..." 532 | if ! affuse -o allow_root "$image_path" "$splitraw_mount" >/dev/null 2>&1; then 533 | print_error "Failed to mount split RAW $image_path with affuse. Verify with 'affuse --help'." 534 | rm -rf "$splitraw_mount" 2>/dev/null 535 | return 1 536 | fi 537 | # affuse creates a raw file - it keeps the original basename with .raw appended 538 | # e.g., roberto.001 becomes roberto.001.raw 539 | local original_basename=$(basename "$image_path") 540 | # Try different possible names that affuse might create 541 | if [ -f "$splitraw_mount/${original_basename}.raw" ]; then 542 | # Most common: basename.001.raw 543 | image_path="$splitraw_mount/${original_basename}.raw" 544 | elif [ -f "$splitraw_mount/${original_basename%.*}.raw" ]; then 545 | # Alternative: basename.raw (without .001) 546 | image_path="$splitraw_mount/${original_basename%.*}.raw" 547 | elif [ -f "$splitraw_mount/$original_basename" ]; then 548 | # No .raw extension 549 | image_path="$splitraw_mount/$original_basename" 550 | else 551 | # List what affuse actually created 552 | echo "Available files in $splitraw_mount:" 553 | ls -la "$splitraw_mount/" 2>/dev/null || true 554 | print_error "Split RAW image not found in $splitraw_mount. Check affuse setup." 555 | fusermount -u "$splitraw_mount" 2>/dev/null 556 | rm -rf "$splitraw_mount" 2>/dev/null 557 | return 1 558 | fi 559 | echo "Split RAW mounted at: $image_path" 560 | fi 561 | fi 562 | # Support ISO images 563 | if [[ "$image_path_lower" =~ \.iso$ ]]; then 564 | echo "Detected ISO image: $image_path" 565 | echo "Mounting ISO directly with loop device..." 566 | # ISOs can be mounted directly - no NBD needed 567 | mkdir -p "$mount_point" 568 | if mount -o loop,ro "$image_path" "$mount_point" 2>/dev/null; then 569 | print_success "Successfully mounted ISO to $mount_point" 570 | echo "" 571 | echo "Contents:" 572 | ls "$mount_point" 573 | echo "" 574 | print_success "Success!!" 575 | return 0 576 | else 577 | print_error "Failed to mount ISO $image_path" 578 | echo "Verify ISO file: file $image_path" 579 | return 1 580 | fi 581 | fi 582 | # Support AFF (Advanced Forensic Format) images 583 | if [[ "$image_path_lower" =~ \.aff$ ]]; then 584 | # Ensure FUSE allows root access (required for qemu-nbd) 585 | if [ ! -f /etc/fuse.conf ] || ! grep -q "user_allow_other" /etc/fuse.conf 2>/dev/null; then 586 | echo "user_allow_other" | sudo tee /etc/fuse.conf >/dev/null 2>&1 587 | fi 588 | aff_mount="/mnt/aff" 589 | mkdir -p "$aff_mount" 590 | if ! affuse -o allow_root "$image_path" "$aff_mount" >/dev/null 2>&1; then 591 | print_error "Failed to mount $image_path with affuse. Verify with 'affuse --help'." 592 | rm -rf "$aff_mount" 2>/dev/null 593 | return 1 594 | fi 595 | # AFF creates a raw file with .raw extension 596 | # affuse keeps the original filename and appends .raw 597 | # e.g., 16GB-aff.aff becomes 16GB-aff.aff.raw 598 | local original_aff_basename=$(basename "$image_path") 599 | # Try different possible names that affuse might create 600 | if [ -f "$aff_mount/${original_aff_basename}.raw" ]; then 601 | # Most common: keeps .aff and adds .raw (e.g., file.aff.raw) 602 | image_path="$aff_mount/${original_aff_basename}.raw" 603 | elif [ -f "$aff_mount/${original_aff_basename%.*}.raw" ]; then 604 | # Alternative: strips .aff and adds .raw (e.g., file.raw) 605 | image_path="$aff_mount/${original_aff_basename%.*}.raw" 606 | elif [ -f "$aff_mount/$original_aff_basename" ]; then 607 | # No .raw extension 608 | image_path="$aff_mount/$original_aff_basename" 609 | else 610 | # List what affuse actually created 611 | echo "Available files in $aff_mount:" 612 | ls -la "$aff_mount/" 2>/dev/null || true 613 | print_error "AFF raw image not found in $aff_mount. Check affuse setup." 614 | fusermount -u "$aff_mount" 2>/dev/null 615 | rm -rf "$aff_mount" 2>/dev/null 616 | return 1 617 | fi 618 | echo "AFF mounted at: $image_path" 619 | fi 620 | # Validate file extension 621 | if ! [[ "$image_path_lower" =~ \.(vhd|vhdx|vdi|qcow|qcow2|vmdk|dd|img|raw|iso)$ || "$image_path" =~ /ewf1$ || "$image_path" =~ /mnt/aff/ || "$image_path" =~ /mnt/raw/ ]]; then 622 | echo "" 623 | echo "Error: Invalid disk image $image_path" 624 | echo "Must be: .vhd, .vhdx, .vdi, .qcow, .qcow2, .vmdk, .dd, .img, .raw, .iso" 625 | echo "Or: E01-mounted ewf1, AFF-mounted, or split RAW image" 626 | echo "" 627 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 628 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 629 | return 1 630 | fi 631 | if [ -n "$log_csv" ] && [ ! -e "$log_csv" ]; then 632 | echo "MountPoint,StartingSector,ByteOffset,Filesystem,MountCommand,PartitionSize,Success" > "$log_csv" 633 | fi 634 | if mountpoint -q "$mount_point"; then 635 | print_error "Mount point $mount_point is already in use." 636 | print_error "Try unmounting first: sudo $0 -u -m $mount_point" 637 | echo "Or check what's mounted: mount | grep $mount_point" 638 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 639 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 640 | return 1 641 | fi 642 | if [ -d "$mount_point" ]; then 643 | if ! rm -rf "$mount_point"/*; then 644 | print_error "Failed to clear $mount_point: directory is in use or mounted. Run 'mountpoint $mount_point', 'lsof $mount_point', or 'sudo umount $mount_point'." 645 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 646 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 647 | return 1 648 | fi 649 | fi 650 | if ! mkdir -p "$mount_point"; then 651 | print_error "Failed to create $mount_point: check permissions or disk space with 'df -h'." 652 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 653 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 654 | return 1 655 | fi 656 | if ! command -v fdisk >/dev/null 2>&1; then 657 | print_error "fdisk not found. Install with 'sudo apt install fdisk' for partition detection." 658 | fi 659 | if ! command -v partprobe >/dev/null 2>&1; then 660 | print_error "partprobe not found. Install with 'sudo apt install parted' for partition detection." 661 | fi 662 | if ! command -v qemu-nbd >/dev/null 2>&1; then 663 | print_error "qemu-nbd not found. Install with 'sudo apt install qemu-utils'." 664 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 665 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 666 | return 1 667 | fi 668 | if [ "$filesystem" = "ntfs" ] && ! command -v ntfs-3g >/dev/null 2>&1; then 669 | print_error "ntfs-3g not found. Install with 'sudo apt install ntfs-3g' for NTFS support." 670 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 671 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 672 | return 1 673 | fi 674 | if [ "$filesystem" = "vfat" ] && ! command -v mount.vfat >/dev/null 2>&1; then 675 | print_error "vfat support not found. Install with 'sudo apt install dosfstools'." 676 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 677 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 678 | return 1 679 | fi 680 | if [ "$filesystem" = "exfat" ] && ! command -v mount.exfat >/dev/null 2>&1; then 681 | print_error "exfat support not found. Install with 'sudo apt install exfatprogs'." 682 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 683 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 684 | return 1 685 | fi 686 | if [ "$filesystem" = "hfsplus" ] && ! command -v mount.hfsplus >/dev/null 2>&1; then 687 | print_error "hfsplus support not found. Install with 'sudo apt install hfsprogs'." 688 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 689 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 690 | return 1 691 | fi 692 | if [[ "$image_path_lower" =~ \.ova$ ]] && ! command -v tar >/dev/null 2>&1; then 693 | print_error "tar not found. Install with 'sudo apt install tar' for OVA support." 694 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 695 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 696 | return 1 697 | fi 698 | if [[ "$image_path_lower" =~ \.e01$ ]] && ! command -v ewfmount >/dev/null 2>&1; then 699 | print_error "ewfmount not found. Install with 'sudo apt install libewf' for E01 support." 700 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 701 | return 1 702 | fi 703 | if lsblk | grep -q "nbd1"; then 704 | echo "NBD device $nbd_device is already in use. Attempting to detach..." 705 | if ! qemu-nbd -d "$nbd_device" >/dev/null 2>&1; then 706 | print_error "Failed to detach $nbd_device - device is in use." 707 | print_error "Try unmounting first: sudo $0 -u -m /mnt/image_mount" 708 | echo "Or manually detach: sudo qemu-nbd -d $nbd_device" 709 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 710 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 711 | return 1 712 | fi 713 | fi 714 | # Determine if we need -r flag for qemu-nbd 715 | # NBD mounting strategy: 716 | # - Forensic images (E01, AFF, Split RAW): Always read-only (-r flag) 717 | # - Other images: Read-only by default (-r flag) for safety 718 | # - LVM mode (-l flag): Writable (no -r flag) to allow LVM activation 719 | local qemu_nbd_opts="" 720 | local is_raw_image=false 721 | local is_virtual_disk=false 722 | 723 | if [ -n "$ewf_mount" ] || [ -n "$splitraw_mount" ] || [ -n "$aff_mount" ]; then 724 | # Forensic images: always read-only (FUSE limitation) 725 | qemu_nbd_opts="-r -f raw" 726 | echo "Using read-only mode for forensic image..." 727 | elif [[ "$image_path_lower" =~ \.(dd|raw|img)$ ]]; then 728 | # Raw images 729 | is_raw_image=true 730 | if [ "$lvm_mode" = true ]; then 731 | # LVM mode: writable (user explicitly requested) 732 | qemu_nbd_opts="-f raw" 733 | echo "Detected raw disk image format (LVM mode enabled)..." 734 | else 735 | # Default: read-only for safety 736 | qemu_nbd_opts="-r -f raw" 737 | echo "Detected raw disk image format (read-only)..." 738 | fi 739 | else 740 | # Virtual disks (VMDK, VDI, QCOW2, etc.) 741 | is_virtual_disk=true 742 | if [ "$lvm_mode" = true ]; then 743 | # LVM mode: writable (user explicitly requested) 744 | qemu_nbd_opts="" 745 | echo "Detected virtual disk image format (LVM mode enabled)..." 746 | else 747 | # Default: read-only for safety 748 | qemu_nbd_opts="-r" 749 | echo "Detected virtual disk image format (read-only)..." 750 | fi 751 | fi 752 | 753 | # Check for flat VMDK files and adjust options 754 | if [[ "$image_path_lower" =~ \.vmdk$ ]]; then 755 | # Check if it's a flat VMDK by examining file size and content 756 | local file_size=$(stat -c%s "$image_path" 2>/dev/null || stat -f%z "$image_path" 2>/dev/null) 757 | 758 | # Flat VMDK descriptors are typically small (< 10KB) 759 | if [ "$file_size" -lt 10240 ]; then 760 | if grep -q -i "createType.*=.*\".*flat\|createType.*=.*\".*vmfs" "$image_path" 2>/dev/null; then 761 | echo "Detected flat VMDK descriptor file..." 762 | # For flat VMDKs, we need to use the -flat.vmdk file with -f raw 763 | local flat_file="${image_path%.vmdk}-flat.vmdk" 764 | if [ -f "$flat_file" ]; then 765 | echo "Using flat VMDK data file: $flat_file" 766 | image_path="$flat_file" 767 | # For flat VMDK, always use -f raw 768 | # Preserve -r flag only if it was already set (forensic images) 769 | if [[ " $qemu_nbd_opts " =~ " -r " ]]; then 770 | qemu_nbd_opts="-r -f raw" 771 | else 772 | qemu_nbd_opts="-f raw" 773 | fi 774 | else 775 | print_error "Flat VMDK descriptor found but data file not found: $flat_file" 776 | cleanup_and_exit "$nbd_device" "$temp_dir" "$ewf_mount" "$aff_mount" "$splitraw_mount" false 777 | return 1 778 | fi 779 | fi 780 | # If filename contains "-flat", it's already the data file 781 | elif [[ "$image_path_lower" =~ -flat\.vmdk$ ]]; then 782 | echo "Detected flat VMDK data file..." 783 | # Preserve -r flag if it was set 784 | if [[ " $qemu_nbd_opts " =~ " -r " ]]; then 785 | qemu_nbd_opts="-r -f raw" 786 | else 787 | qemu_nbd_opts="-f raw" 788 | fi 789 | fi 790 | fi 791 | 792 | # Attach image to NBD device 793 | if ! qemu-nbd $qemu_nbd_opts -c "$nbd_device" "$image_path"; then 794 | print_error "Failed to attach image to NBD device." 795 | print_error "Possible causes:" 796 | echo " 1. NBD device $nbd_device is already in use - try: sudo $0 -u -m $mount_point" 797 | echo " 2. File permissions issue - check: ls -l $image_path" 798 | echo " 3. Invalid or corrupted image file" 799 | echo " 4. NBD kernel module not loaded - try: sudo modprobe nbd" 800 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 801 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 802 | return 1 803 | fi 804 | if command -v partprobe >/dev/null 2>&1; then 805 | partprobe "$nbd_device" >/dev/null 2>&1 || print_error "partprobe failed on $nbd_device. Partition table may be invalid." 806 | fi 807 | # Give kernel time to recognize partitions 808 | sleep 1 809 | 810 | # Diagnostic: Show what partitions exist 811 | echo "Detected partitions:" 812 | lsblk "$nbd_device" 2>/dev/null || echo "lsblk failed" 813 | echo "" 814 | echo "Partition types:" 815 | blkid "$nbd_device"* 2>/dev/null || echo "blkid found no partitions" 816 | echo "" 817 | 818 | # Scan for LVM physical volumes and activate volume groups 819 | if command -v pvscan >/dev/null 2>&1; then 820 | # First, scan the NBD device and its partitions for PVs (silently) 821 | pvscan --cache "$nbd_device"* >/dev/null 2>&1 || true 822 | sleep 0.5 823 | 824 | # Scan for volume groups (silently) 825 | vgscan --mknodes >/dev/null 2>&1 || true 826 | sleep 0.5 827 | 828 | # Check if any volume groups were found 829 | local vgs_found=$(vgs --noheadings -o vg_name 2>/dev/null | tr -d ' ') 830 | if [ -n "$vgs_found" ]; then 831 | # Only show messages if LVM is actually detected 832 | echo "Scanning for LVM physical volumes..." 833 | print_success "LVM volume groups detected: $vgs_found" 834 | 835 | # Check if user specified -l flag for LVM support 836 | if [ "$lvm_mode" = false ]; then 837 | # LVM detected but -l flag not specified 838 | echo "" 839 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 840 | echo "⚠️ LVM Volume Groups Detected" 841 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 842 | echo "" 843 | echo "This image contains LVM volume groups: $vgs_found" 844 | echo "" 845 | echo "LVM activation requires write access and will modify the image." 846 | echo "The image is currently mounted read-only for safety." 847 | echo "" 848 | echo "To mount this image with LVM support, use:" 849 | echo " sudo $(basename "$0") -i $image_path -m $mount_point -l" 850 | echo "" 851 | if [ -n "$filesystem" ]; then 852 | echo " (with filesystem: sudo $(basename "$0") -i $image_path -m $mount_point -l -f $filesystem)" 853 | echo "" 854 | fi 855 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 856 | echo "" 857 | print_error "Cannot proceed without -l flag for LVM support." 858 | echo "Cleaning up and exiting..." 859 | cleanup_and_exit "$nbd_device" "$temp_dir" "$ewf_mount" "$aff_mount" "$splitraw_mount" false 860 | return 1 861 | fi 862 | 863 | # LVM mode enabled, proceed with warnings 864 | # Warn user about metadata writes for raw images and virtual disks 865 | if [ "$is_raw_image" = true ]; then 866 | echo "" 867 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 868 | echo "⚠️ WARNING: LVM Metadata Modification Required" 869 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 870 | echo "" 871 | echo "Activating LVM volume groups requires writing metadata to the disk image." 872 | echo "This will ALTER the image file and change its cryptographic hash." 873 | echo "" 874 | echo "FORENSIC IMPACT:" 875 | echo " • The image file's hash (MD5/SHA-1/SHA-256) will change" 876 | echo " • Chain of custody may be affected" 877 | echo " • Original evidence integrity cannot be verified after activation" 878 | echo "" 879 | echo "RECOMMENDED PRACTICE:" 880 | echo " • Only proceed if this is a verified working copy" 881 | echo " • Never use this on original evidence" 882 | echo " • Document this action in your forensic notes" 883 | echo "" 884 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 885 | echo "" 886 | read -r -p "Do you want to proceed with LVM activation? (yes/no): " lvm_proceed 887 | 888 | if [[ ! "$lvm_proceed" =~ ^[Yy]([Ee][Ss])?$ ]]; then 889 | echo "" 890 | print_error "LVM activation cancelled by user." 891 | echo "Cleaning up and exiting..." 892 | cleanup_lvm "$nbd_device" true 893 | qemu-nbd -d "$nbd_device" >/dev/null 2>&1 894 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 895 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; } 896 | [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; } 897 | [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 898 | return 1 899 | fi 900 | echo "" 901 | print_success "User confirmed. Proceeding with LVM activation..." 902 | echo "" 903 | # Warn user for virtual disks with LVM (less severe than raw images) 904 | elif [ -z "$ewf_mount" ] && [ -z "$aff_mount" ] && [ -z "$splitraw_mount" ]; then 905 | echo "" 906 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 907 | echo "ℹ️ INFO: LVM Metadata Modification" 908 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 909 | echo "" 910 | echo "Activating LVM volume groups will write metadata to the virtual disk." 911 | echo "This is normal for virtual disk images (VMDK, VHDX, VDI, QCOW2)." 912 | echo "" 913 | echo "NOTE:" 914 | echo " • Virtual disk metadata will be updated" 915 | echo " • This is expected behavior for working copies" 916 | echo " • The virtual disk file will be modified" 917 | echo "" 918 | echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" 919 | echo "" 920 | read -r -p "Do you want to proceed with LVM activation? (yes/no): " lvm_proceed 921 | 922 | if [[ ! "$lvm_proceed" =~ ^[Yy]([Ee][Ss])?$ ]]; then 923 | echo "" 924 | print_error "LVM activation cancelled by user." 925 | echo "Cleaning up and exiting..." 926 | cleanup_lvm "$nbd_device" true 927 | qemu-nbd -d "$nbd_device" >/dev/null 2>&1 928 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 929 | return 1 930 | fi 931 | echo "" 932 | print_success "User confirmed. Proceeding with LVM activation..." 933 | echo "" 934 | fi 935 | 936 | # Now activate the volume groups 937 | for vg in $vgs_found; do 938 | echo "Activating volume group: $vg" 939 | if vgchange -ay "$vg" 2>/dev/null; then 940 | print_success "Activated volume group: $vg" 941 | else 942 | print_warning "Could not activate volume group: $vg" 943 | fi 944 | done 945 | # Give LVM time to create device nodes 946 | sleep 1 947 | 948 | # Verify logical volumes are available 949 | echo "Checking for logical volumes..." 950 | lvs 2>/dev/null || true 951 | fi 952 | # If no LVM found, don't print anything (silent) 953 | fi 954 | local partitions 955 | # Get all partitions, but filter out LVM2_member types (they can't be mounted directly) 956 | local all_parts=$(lsblk -ln -o NAME | grep "^nbd1p" || true) 957 | partitions="" 958 | for part in $all_parts; do 959 | local part_type=$(blkid -o value -s TYPE "/dev/$part" 2>/dev/null || echo "") 960 | # Skip LVM2_member partitions - only their logical volumes can be mounted 961 | if [ "$part_type" != "LVM2_member" ]; then 962 | if [ -z "$partitions" ]; then 963 | partitions="$part" 964 | else 965 | partitions="$partitions"$'\n'"$part" 966 | fi 967 | fi 968 | done 969 | 970 | # Check if the device itself is a filesystem (unpartitioned disk) 971 | if [ -z "$partitions" ]; then 972 | echo "No partitions found, checking if device is directly formatted..." 973 | local device_fs=$(blkid -o value -s TYPE "$nbd_device" 2>/dev/null || true) 974 | if [ -n "$device_fs" ]; then 975 | echo "Device $nbd_device is directly formatted as: $device_fs" 976 | # Add the device itself as the "partition" to mount 977 | partitions="nbd1" 978 | fi 979 | fi 980 | 981 | # Also check for LVM logical volumes using lvs 982 | local lvm_volumes=$(lvs --noheadings -o lv_path 2>/dev/null | tr -d ' ' || true) 983 | 984 | # Fallback: If lvs fails, parse lsblk output for LVM devices 985 | if [ -z "$lvm_volumes" ]; then 986 | # Look for lines with TYPE=lvm in lsblk output (silently) 987 | local lvm_from_lsblk=$(lsblk -ln -o NAME,TYPE "$nbd_device" | awk '$2 == "lvm" {print $1}' || true) 988 | if [ -n "$lvm_from_lsblk" ]; then 989 | echo "Checking for LVM volumes via lsblk..." 990 | echo "Found LVM volumes in lsblk output:" 991 | for lv_name in $lvm_from_lsblk; do 992 | # Convert name to device path 993 | local lv_path="/dev/mapper/$lv_name" 994 | if [ -b "$lv_path" ]; then 995 | echo " - $lv_path" 996 | lvm_volumes="$lvm_volumes"$'\n'"$lv_path" 997 | fi 998 | done 999 | fi 1000 | fi 1001 | 1002 | if [ -n "$lvm_volumes" ]; then 1003 | echo "LVM logical volumes detected:" 1004 | for lv in $lvm_volumes; do 1005 | echo " - $lv" 1006 | # Add LVM volumes to partitions list (use full path) 1007 | partitions="$partitions"$'\n'"$lv" 1008 | done 1009 | fi 1010 | if [ -n "$partitions" ]; then 1011 | local part_count 1012 | part_count=$(echo "$partitions" | wc -l) 1013 | if [ "$part_count" -gt 1 ]; then 1014 | echo "Multiple partitions found:" 1015 | local i=1 1016 | local part_array=() 1017 | for part in $partitions; do 1018 | # Handle both regular partitions and LVM volumes 1019 | if [[ "$part" =~ ^/ ]]; then 1020 | # Already a full path (LVM volume) 1021 | local part_device="$part" 1022 | else 1023 | # Regular partition 1024 | local part_device="/dev/$part" 1025 | fi 1026 | local size="Unknown" 1027 | local fstype="Unknown" 1028 | if command -v lsblk >/dev/null 2>&1; then 1029 | size=$(lsblk -ln -o SIZE -b "$part_device" 2>/dev/null | numfmt --to=iec-i --suffix=B --format="%.2f" || echo "Unknown") 1030 | fi 1031 | if command -v blkid >/dev/null 2>&1; then 1032 | fstype=$(blkid -o value -s TYPE "$part_device" 2>/dev/null || echo "Unknown") 1033 | fi 1034 | # For LVM, also show volume group info 1035 | if [[ "$part_device" =~ ^/dev/mapper/ ]] || [[ "$part_device" =~ ^/dev/.*/.* ]]; then 1036 | local vg_info=$(lvs --noheadings -o vg_name "$part_device" 2>/dev/null | tr -d ' ' || echo "") 1037 | if [ -n "$vg_info" ]; then 1038 | echo "$i) $part_device (VG: $vg_info, Size: $size, Filesystem: $fstype)" 1039 | else 1040 | echo "$i) $part_device (Size: $size, Filesystem: $fstype)" 1041 | fi 1042 | else 1043 | echo "$i) $part_device (Size: $size, Filesystem: $fstype)" 1044 | fi 1045 | part_array[$i]="$part_device" 1046 | ((i++)) 1047 | done 1048 | echo "Enter partition number (1-$part_count) to mount:" 1049 | read -r choice 1050 | if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "$part_count" ]; then 1051 | print_error "Invalid choice. Please select a number between 1 and $part_count." 1052 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 1053 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 1054 | return 1 1055 | fi 1056 | local selected_part="${part_array[$choice]}" 1057 | echo "Selected device: $selected_part" 1058 | 1059 | # Try to detect filesystem type 1060 | local mount_fstype="$filesystem" 1061 | local blkid_fstype="" 1062 | if command -v blkid >/dev/null 2>&1; then 1063 | blkid_fstype=$(blkid -o value -s TYPE "$selected_part" 2>/dev/null || echo "") 1064 | if [ -n "$blkid_fstype" ]; then 1065 | echo "Detected filesystem: $blkid_fstype" 1066 | case "$blkid_fstype" in 1067 | vfat|fat12|fat16|fat32) mount_fstype="vfat" ;; 1068 | exfat) mount_fstype="exfat" ;; 1069 | ntfs) mount_fstype="ntfs" ;; 1070 | ext4|ext3|ext2) mount_fstype="ext4" ;; 1071 | hfsplus) mount_fstype="hfsplus" ;; 1072 | *) mount_fstype="$blkid_fstype" ;; 1073 | esac 1074 | else 1075 | echo "blkid could not detect filesystem type" 1076 | if [ -n "$filesystem" ]; then 1077 | echo "Using user-specified filesystem: $filesystem" 1078 | mount_fstype="$filesystem" 1079 | else 1080 | echo "Defaulting to ext4" 1081 | mount_fstype="ext4" 1082 | fi 1083 | fi 1084 | fi 1085 | echo "Will attempt to mount as: $mount_fstype" 1086 | 1087 | # Check if device is already mounted (e.g., by automount) 1088 | local existing_mount=$(mount | grep "^$selected_part " | awk '{print $3}') 1089 | if [ -n "$existing_mount" ]; then 1090 | echo "" 1091 | echo "Notice: $selected_part is already mounted at: $existing_mount" 1092 | echo "" 1093 | echo "Options:" 1094 | echo " 1) Use existing mount location: $existing_mount" 1095 | echo " 2) Unmount and remount to: $mount_point" 1096 | echo " 3) Cancel" 1097 | echo "" 1098 | read -r -p "Enter choice (1-3): " mount_choice 1099 | 1100 | case "$mount_choice" in 1101 | 1) 1102 | echo "" 1103 | print_success "Using existing mount at: $existing_mount" 1104 | echo "" 1105 | return 0 1106 | ;; 1107 | 2) 1108 | echo "Unmounting $selected_part from $existing_mount..." 1109 | if umount "$selected_part" 2>/dev/null; then 1110 | print_success "Successfully unmounted." 1111 | else 1112 | echo "" 1113 | echo "Error: Failed to unmount $selected_part" 1114 | echo "It may be in use. Try: lsof | grep $existing_mount" 1115 | echo "" 1116 | return 1 1117 | fi 1118 | ;; 1119 | 3) 1120 | echo "Cancelled." 1121 | return 1 1122 | ;; 1123 | *) 1124 | echo "Invalid choice. Cancelled." 1125 | return 1 1126 | ;; 1127 | esac 1128 | fi 1129 | 1130 | local mount_cmd 1131 | if [ "$mount_fstype" = "ntfs" ]; then 1132 | mount_cmd="mount -t ntfs-3g -o $mount_mode,norecover,streams_interface=windows,uid=$(id -u),gid=$(id -g),show_sys_files $selected_part $mount_point" 1133 | elif [ "$mount_fstype" = "vfat" ]; then 1134 | mount_cmd="mount -t vfat -o $mount_mode,uid=$(id -u),gid=$(id -g) $selected_part $mount_point" 1135 | elif [ "$mount_fstype" = "exfat" ]; then 1136 | mount_cmd="mount -t exfat -o $mount_mode,uid=$(id -u),gid=$(id -g) $selected_part $mount_point" 1137 | elif [ "$mount_fstype" = "hfsplus" ]; then 1138 | mount_cmd="mount -t hfsplus -o $mount_mode,uid=$(id -u),gid=$(id -g) $selected_part $mount_point" 1139 | else 1140 | # For ext2/ext3/ext4, add noload to prevent journal replay 1141 | if [[ "$mount_fstype" =~ ^ext[234]$ ]]; then 1142 | mount_cmd="mount -t $mount_fstype -o $mount_mode,noload $selected_part $mount_point" 1143 | else 1144 | mount_cmd="mount -t $mount_fstype -o $mount_mode $selected_part $mount_point" 1145 | fi 1146 | fi 1147 | set +e 1148 | eval "$mount_cmd" >/dev/null 2>&1 1149 | local mount_status=$? 1150 | set -e 1151 | if [ $mount_status -eq 0 ] && mountpoint -q "$mount_point"; then 1152 | local dir_check 1153 | case "$mount_fstype" in 1154 | ntfs) dir_check=$(ls "$mount_point" | grep -E "^Windows$|^Users$|^Program Files$" || true) ;; 1155 | ext4) dir_check=$(ls "$mount_point" | grep "^etc$" || true) ;; 1156 | vfat|exfat) dir_check=$(ls "$mount_point" | grep -E "^EFI$|^Boot$|^bootmgr$" || true) ;; 1157 | hfsplus) dir_check=$(ls "$mount_point" | grep -E "^System$|^Users$|^\.HFS\+ Private Directory Data" || true) ;; 1158 | esac 1159 | if [ -n "$dir_check" ] || [ -n "$(ls "$mount_point")" ]; then 1160 | echo 1161 | echo "Mount command:" 1162 | echo " $mount_cmd" 1163 | echo 1164 | echo "ls $mount_point" 1165 | ls "$mount_point" || { 1166 | print_error "Failed to list contents of $mount_point: possible I/O error. Check with 'ls $mount_point' or 'dmesg'." 1167 | umount "$mount_point" 2>/dev/null 1168 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 1169 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 1170 | return 1 1171 | } 1172 | echo 1173 | print_success "Success!!" 1174 | echo 1175 | if [ -n "$log_csv" ]; then 1176 | local partition_size="Unknown" 1177 | if command -v fdisk >/dev/null 2>&1; then 1178 | partition_size=$(fdisk -l "$nbd_device" 2>/dev/null | grep "^$selected_part" | awk '{print $5}' | numfmt --to=iec-i --suffix=B --format="%.2f") 1179 | fi 1180 | echo "$mount_point,partition,$selected_part,$mount_fstype,\"$mount_cmd\",${partition_size:-Unknown},Success" >> "$log_csv" 1181 | fi 1182 | return 0 1183 | fi 1184 | umount "$mount_point" 2>/dev/null 1185 | fi 1186 | echo "Mount failed. Running diagnostics..." 1187 | echo "Device: $selected_part" 1188 | echo "Filesystem type attempted: $mount_fstype" 1189 | echo "Mount command: $mount_cmd" 1190 | echo "" 1191 | echo "Checking device accessibility:" 1192 | ls -l "$selected_part" 2>&1 || echo "Device file not accessible" 1193 | echo "" 1194 | echo "Trying blkid:" 1195 | blkid "$selected_part" 2>&1 || echo "blkid failed" 1196 | echo "" 1197 | echo "Checking dmesg for errors:" 1198 | dmesg | tail -10 1199 | echo "" 1200 | print_error "Failed to mount $selected_part. Check diagnostics above." 1201 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 1202 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 1203 | return 1 1204 | else 1205 | local part_device="/dev/$partitions" 1206 | local mount_fstype="$filesystem" 1207 | if command -v blkid >/dev/null 2>&1; then 1208 | local blkid_fstype 1209 | blkid_fstype=$(blkid -o value -s TYPE "$part_device" 2>/dev/null) 1210 | if [ -n "$blkid_fstype" ]; then 1211 | case "$blkid_fstype" in 1212 | vfat|fat12|fat16|fat32) mount_fstype="vfat" ;; 1213 | exfat) mount_fstype="exfat" ;; 1214 | ntfs) mount_fstype="ntfs" ;; 1215 | ext4|ext3|ext2) mount_fstype="ext4" ;; 1216 | hfsplus) mount_fstype="hfsplus" ;; 1217 | esac 1218 | fi 1219 | fi 1220 | local mount_cmd 1221 | if [ "$mount_fstype" = "ntfs" ]; then 1222 | mount_cmd="mount -t ntfs-3g -o $mount_mode,norecover,streams_interface=windows,uid=$(id -u),gid=$(id -g),show_sys_files $part_device $mount_point" 1223 | elif [ "$mount_fstype" = "vfat" ]; then 1224 | mount_cmd="mount -t vfat -o $mount_mode,uid=$(id -u),gid=$(id -g) $part_device $mount_point" 1225 | elif [ "$mount_fstype" = "exfat" ]; then 1226 | mount_cmd="mount -t exfat -o $mount_mode,uid=$(id -u),gid=$(id -g) $part_device $mount_point" 1227 | elif [ "$mount_fstype" = "hfsplus" ]; then 1228 | mount_cmd="mount -t hfsplus -o $mount_mode,uid=$(id -u),gid=$(id -g) $part_device $mount_point" 1229 | else 1230 | # For ext2/ext3/ext4, add noload to prevent journal replay 1231 | if [[ "$mount_fstype" =~ ^ext[234]$ ]]; then 1232 | mount_cmd="mount -t $mount_fstype -o $mount_mode,noload $part_device $mount_point" 1233 | else 1234 | mount_cmd="mount -t $mount_fstype -o $mount_mode $part_device $mount_point" 1235 | fi 1236 | fi 1237 | set +e 1238 | eval "$mount_cmd" >/dev/null 2>&1 1239 | local mount_status=$? 1240 | set -e 1241 | if [ $mount_status -eq 0 ] && mountpoint -q "$mount_point"; then 1242 | local dir_check 1243 | case "$mount_fstype" in 1244 | ntfs) dir_check=$(ls "$mount_point" | grep -E "^Windows$|^Users$|^Program Files$" || true) ;; 1245 | ext4) dir_check=$(ls "$mount_point" | grep "^etc$" || true) ;; 1246 | vfat|exfat) dir_check=$(ls "$mount_point" | grep -E "^EFI$|^Boot$|^bootmgr$" || true) ;; 1247 | hfsplus) dir_check=$(ls "$mount_point" | grep -E "^System$|^Users$|^\.HFS\+ Private Directory Data" || true) ;; 1248 | esac 1249 | if [ -n "$dir_check" ] || [ -n "$(ls "$mount_point")" ]; then 1250 | echo 1251 | echo "Mount command:" 1252 | echo " $mount_cmd" 1253 | echo 1254 | echo "ls $mount_point" 1255 | ls "$mount_point" || { 1256 | print_error "Failed to list contents of $mount_point: possible I/O error. Check with 'ls $mount_point' or 'dmesg'." 1257 | umount "$mount_point" 2>/dev/null 1258 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 1259 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 1260 | return 1 1261 | } 1262 | echo 1263 | print_success "Success!!" 1264 | echo 1265 | if [ -n "$log_csv" ]; then 1266 | local partition_size="Unknown" 1267 | if command -v fdisk >/dev/null 2>&1; then 1268 | partition_size=$(fdisk -l "$nbd_device" 2>/dev/null | grep "^$part_device" | awk '{print $5}' | numfmt --to=iec-i --suffix=B --format="%.2f") 1269 | fi 1270 | echo "$mount_point,partition,$part_device,$mount_fstype,\"$mount_cmd\",${partition_size:-Unknown},Success" >> "$log_csv" 1271 | fi 1272 | return 0 1273 | fi 1274 | umount "$mount_point" 2>/dev/null 1275 | fi 1276 | print_error "Failed to mount $part_device." 1277 | print_error "Possible causes:" 1278 | echo " 1. Unsupported or corrupted filesystem" 1279 | echo " 2. Device is already mounted elsewhere" 1280 | echo " 3. Insufficient permissions" 1281 | echo "Check filesystem type: blkid $part_device" 1282 | echo "Check if mounted: mount | grep $part_device" 1283 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 1284 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 1285 | return 1 1286 | fi 1287 | fi 1288 | for ((i=1; i<=retries; i++)); do 1289 | umount "$mount_point" 2>/dev/null 1290 | if [ $? -eq 0 ] || ! mountpoint -q "$mount_point"; then 1291 | print_success "Existing mounts cleared for $mount_point." 1292 | break 1293 | fi 1294 | if [ "$i" -eq "$retries" ]; then 1295 | print_error "Failed to clear existing mounts for $mount_point after $retries attempts. Check with 'mountpoint $mount_point' or 'lsof $mount_point'." 1296 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 1297 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 1298 | return 1 1299 | fi 1300 | sleep $sleep_time 1301 | done 1302 | local starting_sectors=(0 1 2 17 31 63 2048 4096 34) 1303 | for offset_sectors in "${starting_sectors[@]}"; do 1304 | local byte_offset=$((offset_sectors * 512)) 1305 | local mount_cmd 1306 | if [ "$filesystem" = "ntfs" ]; then 1307 | mount_cmd="mount -t ntfs-3g -o $mount_mode,norecover,streams_interface=windows,uid=$(id -u),gid=$(id -g),show_sys_files,offset=$byte_offset $nbd_device $mount_point" 1308 | elif [ "$filesystem" = "vfat" ]; then 1309 | mount_cmd="mount -t vfat -o $mount_mode,uid=$(id -u),gid=$(id -g),offset=$byte_offset $nbd_device $mount_point" 1310 | elif [ "$filesystem" = "exfat" ]; then 1311 | mount_cmd="mount -t exfat -o $mount_mode,uid=$(id -u),gid=$(id -g),offset=$byte_offset $nbd_device $mount_point" 1312 | elif [ "$filesystem" = "hfsplus" ]; then 1313 | mount_cmd="mount -t hfsplus -o $mount_mode,uid=$(id -u),gid=$(id -g),offset=$byte_offset $nbd_device $mount_point" 1314 | else 1315 | # For ext2/ext3/ext4, add noload to prevent journal replay 1316 | if [[ "$filesystem" =~ ^ext[234]$ ]]; then 1317 | mount_cmd="mount -t $filesystem -o $mount_mode,noload,offset=$byte_offset $nbd_device $mount_point" 1318 | else 1319 | mount_cmd="mount -t $filesystem -o $mount_mode,offset=$byte_offset $nbd_device $mount_point" 1320 | fi 1321 | fi 1322 | set +e 1323 | eval "$mount_cmd" >/dev/null 2>&1 1324 | local mount_status=$? 1325 | set -e 1326 | if [ $mount_status -eq 0 ] && mountpoint -q "$mount_point"; then 1327 | local dir_check 1328 | case "$filesystem" in 1329 | ntfs) dir_check=$(ls "$mount_point" | grep -E "^Windows$|^Users$|^Program Files$" || true) ;; 1330 | ext4) dir_check=$(ls "$mount_point" | grep "^etc$" || true) ;; 1331 | vfat|exfat) dir_check=$(ls "$mount_point" | grep -E "^EFI$|^Boot$|^bootmgr$" || true) ;; 1332 | hfsplus) dir_check=$(ls "$mount_point" | grep -E "^System$|^Users$|^\.HFS\+ Private Directory Data" || true) ;; 1333 | esac 1334 | if [ -n "$dir_check" ] || [ -n "$(ls "$mount_point")" ]; then 1335 | echo 1336 | echo "Mount command:" 1337 | echo " $mount_cmd" 1338 | echo 1339 | echo "ls $mount_point" 1340 | ls "$mount_point" || { 1341 | print_error "Failed to list contents of $mount_point: possible I/O error. Check with 'ls $mount_point' or 'dmesg'." 1342 | umount "$mount_point" 2>/dev/null 1343 | [ -n "$temp_dir" ] && rm -rf "$temp_dir" 2>/dev/null 1344 | [ -n "$ewf_mount" ] && { umount "$ewf_mount" 2>/dev/null; rm -rf "$ewf_mount" 2>/dev/null; }; [ -n "$aff_mount" ] && { fusermount -u "$aff_mount" 2>/dev/null; rm -rf "$aff_mount" 2>/dev/null; }; [ -n "$splitraw_mount" ] && { fusermount -u "$splitraw_mount" 2>/dev/null; rm -rf "$splitraw_mount" 2>/dev/null; } 1345 | return 1 1346 | } 1347 | echo 1348 | print_success "Success!!" 1349 | echo 1350 | if [ -n "$output_csv" ]; then 1351 | local partition_size="Unknown" 1352 | if command -v fdisk >/dev/null 2>&1; then 1353 | partition_size=$(fdisk -l "$nbd_device" 2>/dev/null | grep "^$nbd_device" | head -n 1 | awk '{print $5}' | numfmt --to=iec-i --suffix=B --format="%.2f") 1354 | fi 1355 | echo "$mount_point,$offset_sectors,$byte_offset,$filesystem,\"$mount_cmd\",${partition_size:-Unknown},Success" >> "$log_csv" 1356 | fi 1357 | return 0 1358 | fi 1359 | fi 1360 | umount "$mount_point" 2>/dev/null 1361 | done 1362 | print_error "Failed to mount $image_path. Verify filesystem or image format with 'file $image_path' or 'fdisk -l $image_path'." 1363 | cleanup_and_exit "$nbd_device" "$temp_dir" "$ewf_mount" "$aff_mount" "$splitraw_mount" false 1364 | return 1 1365 | } 1366 | # Check for help flag first 1367 | if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then 1368 | SCRIPT_NAME=$(basename "$0") 1369 | echo "" 1370 | echo "$SCRIPT_NAME - Mounts multiple disk image types" 1371 | echo "" 1372 | echo "Usage: $SCRIPT_NAME -i [-m mount/point] [-f filesystem] [-l] [-o offset] [-r ro|rw] [-s] [-u]" 1373 | echo "" 1374 | echo "Required:" 1375 | echo " -i Disk image file or ISO" 1376 | echo "" 1377 | echo "Optional:" 1378 | echo " -m Mount point directory (default: /mnt/image_mount)" 1379 | echo " -f Filesystem type: ntfs, ext4, vfat, exfat, hfsplus" 1380 | echo " -l Enable LVM support (allows image modification)" 1381 | echo " -o Manual byte offset for partition mounting" 1382 | echo " -r Mount mode: ro (read-only, default) or rw (read-write)" 1383 | echo " -s Status - Check mount status only" 1384 | echo " -u Unmount - Unmount image and cleanup" 1385 | echo " -h, --help Show this help message" 1386 | echo "" 1387 | echo "Supported Formats:" 1388 | echo " Virtual Disks: VDI, VMDK, VHD, VHDX, QCOW, QCOW2" 1389 | echo " Forensic Images: E01, AFF, Split RAW (.001, .002, ...)" 1390 | echo " Raw Images: .raw, .dd, .img, .iso" 1391 | echo "" 1392 | echo "Examples:" 1393 | echo " $SCRIPT_NAME -i disk.vmdk" 1394 | echo " $SCRIPT_NAME -i evidence.E01 -m /mnt/case1" 1395 | echo " $SCRIPT_NAME -i lvm_disk.dd -m /mnt/lvm -l" 1396 | echo " $SCRIPT_NAME -i image.001 -f ntfs" 1397 | echo " $SCRIPT_NAME -i ubuntu.iso" 1398 | echo " $SCRIPT_NAME -u -m /mnt/image_mount" 1399 | echo "" 1400 | exit 0 1401 | fi 1402 | 1403 | check_status=false 1404 | unmount=false 1405 | lvm_mode=false 1406 | manual_offset="" 1407 | while getopts "i:m:f:lo:r:su" opt; do 1408 | case $opt in 1409 | i) image_path="$OPTARG" ;; 1410 | m) mount_point="$OPTARG" ;; 1411 | f) filesystem="$OPTARG" ;; 1412 | l) lvm_mode=true ;; 1413 | o) manual_offset="$OPTARG" ;; 1414 | r) mount_mode="$OPTARG" ;; 1415 | s) check_status=true ;; 1416 | u) unmount=true ;; 1417 | \?) 1418 | echo "" 1419 | echo "Error: Invalid option" 1420 | echo "" 1421 | SCRIPT_NAME=$(basename "$0") 1422 | echo "Usage: $SCRIPT_NAME -i [-m mount/point] [-f filesystem] [-l] [-o offset] [-r ro|rw] [-s] [-u]" 1423 | echo "For full help: $SCRIPT_NAME -h" 1424 | echo "" 1425 | exit 1 1426 | ;; 1427 | esac 1428 | done 1429 | [ -z "$mount_point" ] && mount_point="/mnt/image_mount" 1430 | mount_image "$image_path" "$mount_point" "$lvm_mode" "$filesystem" "$check_status" "$unmount" "$mount_mode" "$manual_offset" 1431 | exit $? --------------------------------------------------------------------------------