/* * usbhsfs_mount.c * * Copyright (c) 2020-2023, DarkMatterCore . * Copyright (c) 2020-2021, XorTroll. * Copyright (c) 2020-2021, Rhys Koedijk. * * This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs). */ #include "usbhsfs_utils.h" #include "usbhsfs_manager.h" #include "usbhsfs_mount.h" #include "usbhsfs_scsi.h" #include "fatfs/ff_dev.h" #ifdef GPL_BUILD #include "ntfs-3g/ntfs_dev.h" #include "lwext4/ext_dev.h" #endif #define MOUNT_NAME_PREFIX "ums" #define BOOT_SIGNATURE 0xAA55 #define MBR_PARTITION_COUNT 4 #define DEVOPTAB_INVALID_ID UINT32_MAX #ifdef DEBUG #define FS_TYPE_STR(x) ((x) == UsbHsFsDriveLogicalUnitFileSystemType_FAT ? "FAT" : ((x) == UsbHsFsDriveLogicalUnitFileSystemType_NTFS ? "NTFS" : "EXT")) #endif /* Type definitions. */ /// DOS 2.0 BIOS Parameter Block. Used for FAT12 (13 bytes). #pragma pack(push, 1) typedef struct { u16 sector_size; ///< Logical sector size in bytes. Belongs to the DOS 2.0 BPB area. u8 sectors_per_cluster; ///< Logical sectors per cluster. Belongs to the DOS 2.0 BPB area. u16 reserved_sectors; ///< Reserved sectors. Belongs to the DOS 2.0 BPB area. u8 num_fats; ///< Number of FATs. Belongs to the DOS 2.0 BPB area. u16 root_dir_entries; ///< Root directory entries. Belongs to the DOS 2.0 BPB area. u16 total_sectors; ///< Total logical sectors. Belongs to the DOS 2.0 BPB area. u8 media_desc; ///< Media descriptor. Belongs to the DOS 2.0 BPB area. u16 sectors_per_fat; ///< Logical sectors per FAT. Belongs to the DOS 2.0 BPB area. } DOS_2_0_BPB; #pragma pack(pop) /// DOS 3.31 BIOS Parameter Block. Used for FAT12, FAT16 and FAT16B (25 bytes). #pragma pack(push, 1) typedef struct { DOS_2_0_BPB dos_2_0_bpb; ///< DOS 2.0 BIOS Parameter Block. u16 sectors_per_track; ///< Physical sectors per track. u16 num_heads; ///< Number of heads. u32 hidden_sectors; ///< Hidden sectors. u32 total_sectors; ///< Large total logical sectors. } DOS_3_31_BPB; #pragma pack(pop) /// DOS 7.1 Extended BIOS Parameter Block (full variant). Used for FAT32 (79 bytes). #pragma pack(push, 1) typedef struct { DOS_3_31_BPB dos_3_31_bpb; ///< DOS 3.31 BIOS Parameter Block. u32 sectors_per_fat; ///< Logical sectors per FAT. u16 mirroring_flags; ///< Mirroring flags. u16 version; ///< Version. u32 root_dir_cluster; ///< Root directory cluster. u16 fsinfo_sector; ///< Location of FS Information Sector. u16 backup_sector; ///< Location of Backup Sector. u8 boot_filename[0xC]; ///< Boot filename. u8 pdrv; ///< Physical drive number. u8 flags; ///< Flags. u8 ext_boot_sig; ///< Extended boot signature (0x29). u32 vol_serial_num; ///< Volume serial number. u8 vol_label[0xB]; ///< Volume label. u8 fs_type[0x8]; ///< Filesystem type. Padded with spaces (0x20). Set to "FAT32 " if this is an FAT32 VBR. } DOS_7_1_EBPB; #pragma pack(pop) /// Volume Boot Record (VBR). Represents the first sector from every FAT and NTFS filesystem. If a drive is formatted using Super Floppy Drive (SFD) configuration, this is located at LBA 0. typedef struct { u8 jmp_boot[0x3]; ///< Jump boot code. First byte must match 0xEB (short jump), 0xE9 (near jump) or 0xE8 (near call). Set to "\xEB\x76\x90" is this is an exFAT VBR. char oem_name[0x8]; ///< OEM name. Padded with spaces (0x20). Set to "EXFAT " if this is an exFAT VBR. Set to "NTFS " if this is an NTFS VBR. DOS_7_1_EBPB dos_7_1_ebpb; ///< DOS 7.1 Extended BIOS Parameter Block (full variant). u8 boot_code[0x1A3]; ///< File system and operating system specific boot code. u8 pdrv; ///< Physical drive number. u16 boot_sig; ///< Matches BOOT_SIGNATURE for FAT32, exFAT and NTFS. Serves a different purpose under other FAT filesystems. } VolumeBootRecord; /// Master Boot Record (MBR) partition types. All these types support logical block addresses. CHS addressing only and hidden types have been excluded. typedef enum { MasterBootRecordPartitionType_Empty = 0x00, MasterBootRecordPartitionType_FAT12 = 0x01, MasterBootRecordPartitionType_FAT16 = 0x04, MasterBootRecordPartitionType_ExtendedBootRecord_CHS = 0x05, MasterBootRecordPartitionType_FAT16B = 0x06, MasterBootRecordPartitionType_NTFS_exFAT = 0x07, MasterBootRecordPartitionType_FAT32_CHS = 0x0B, MasterBootRecordPartitionType_FAT32_LBA = 0x0C, MasterBootRecordPartitionType_FAT16B_LBA = 0x0E, MasterBootRecordPartitionType_ExtendedBootRecord_LBA = 0x0F, MasterBootRecordPartitionType_LinuxFileSystem = 0x83, MasterBootRecordPartitionType_ExtendedBootRecord_Linux = 0x85, ///< Corresponds to MasterBootRecordPartitionType_ExtendedBootRecord_CHS. MasterBootRecordPartitionType_GPT_Protective_MBR = 0xEE } MasterBootRecordPartitionType; /// Master Boot Record (MBR) partition entry. typedef struct { u8 status; ///< Partition status. We won't use this. u8 chs_start[0x3]; ///< Cylinder-head-sector address to the first block in the partition. Unused nowadays. u8 type; ///< MasterBootRecordPartitionType. u8 chs_end[0x3]; ///< Cylinder-head-sector address to the last block in the partition. Unused nowadays. u32 lba; ///< Logical block address to the first block in the partition. u32 block_count; ///< Logical block count in the partition. } MasterBootRecordPartitionEntry; /// Master Boot Record (MBR). Always located at LBA 0, as long as SFD configuration isn't used (VBR at LBA 0). #pragma pack(push, 1) typedef struct { u8 code_area[0x1BE]; ///< Bootstrap code area. We won't use this. MasterBootRecordPartitionEntry partitions[MBR_PARTITION_COUNT]; ///< Primary partition entries. u16 boot_sig; ///< Boot signature. Must match BOOT_SIGNATURE. } MasterBootRecord; #pragma pack(pop) /// Extended Boot Record (EBR). Represents a way to store more than 4 partitions in a MBR-formatted logical unit using linked lists. typedef struct { u8 code_area[0x1BE]; ///< Bootstrap code area. Normally empty. MasterBootRecordPartitionEntry partition; ///< Primary partition entry. MasterBootRecordPartitionEntry next_ebr; ///< Next EBR in the chain. u8 reserved[0x20]; ///< Normally empty. u16 boot_sig; ///< Boot signature. Must match BOOT_SIGNATURE. } ExtendedBootRecord; /// Globally Unique ID Partition Table (GPT) entry. These usually start at LBA 2. typedef struct { u8 type_guid[0x10]; ///< Partition type GUID. u8 unique_guid[0x10]; ///< Unique partition GUID. u64 lba_start; ///< First LBA. u64 lba_end; ///< Last LBA (inclusive). u64 flags; ///< Attribute flags. u16 name[0x24]; ///< Partition name (36 UTF-16LE code units). } GuidPartitionTableEntry; /// Globally Unique ID Partition Table (GPT) header. If available, it's always located at LBA 1. typedef struct { u64 signature; ///< Must match "EFI PART". u32 revision; ///< GUID Partition Table revision. u32 header_size; ///< Header size. Must match 0x5C. u32 header_crc32; ///< Little-endian CRC32 checksum calculated over this header, with this field zeroed during calculation. u8 reserved_1[0x4]; ///< Reserved. u64 cur_header_lba; ///< LBA from this GPT header. u64 backup_header_lba; ///< LBA from the backup GPT header. u64 partition_lba_start; ///< First usable LBA for partitions (primary partition table last LBA + 1). u64 partition_lba_end; ///< Last usable LBA (secondary partition table first LBA - 1). u8 disk_guid[0x10]; ///< Disk GUID. u64 partition_array_lba; ///< Starting LBA of array of partition entries (always 2 in primary copy). u32 partition_array_count; ///< Number of partition entries in array. u32 partition_array_entry_size; ///< Size of a single partition entry (usually 0x80). u32 partition_array_crc32; ///< Little-endian CRC32 checksum calculated over the partition array. u8 reserved_2[0x1A4]; ///< Reserved; must be zeroes for the rest of the block. } GuidPartitionTableHeader; static_assert(sizeof(DOS_2_0_BPB) == 0xD, "Bad DOS_2_0_BPB size! Expected 0xD."); static_assert(sizeof(DOS_3_31_BPB) == 0x19, "Bad DOS_3_31_BPB size! Expected 0x19."); static_assert(sizeof(DOS_7_1_EBPB) == 0x4F, "Bad DOS_7_1_EBPB size! Expected 0x4F."); static_assert(sizeof(VolumeBootRecord) == 0x200, "Bad VolumeBootRecord size! Expected 0x200."); static_assert(sizeof(MasterBootRecord) == 0x200, "Bad MasterBootRecord size! Expected 0x200."); static_assert(sizeof(MasterBootRecordPartitionEntry) == 0x10, "Bad MasterBootRecordPartitionEntry size! Expected 0x10."); static_assert(sizeof(GuidPartitionTableEntry) == 0x80, "Bad GuidPartitionTableEntry size! Expected 0x80."); static_assert(sizeof(GuidPartitionTableHeader) == 0x200, "Bad GuidPartitionTableHeader size! Expected 0x200."); /* Global variables. */ static u32 g_devoptabDeviceCount = 0; static u32 *g_devoptabDeviceIds = NULL; static u32 g_devoptabDefaultDeviceId = DEVOPTAB_INVALID_ID; static Mutex g_devoptabDefaultDeviceMutex = 0; static bool g_fatFsVolumeTable[FF_VOLUMES] = { false }; static const u8 g_microsoftBasicDataPartitionGuid[0x10] = { 0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44, 0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7 }; /* EBD0A0A2-B9E5-4433-87C0-68B6B72699C7. */ static const u8 g_linuxFilesystemDataGuid[0x10] = { 0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4 }; /* 0FC63DAF-8483-4772-8E79-3D69D8477DE4. */ static u32 g_fileSystemMountFlags = (UsbHsFsMountFlags_UpdateAccessTimes | UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ReplayJournal); __thread char __usbhsfs_dev_path_buf[MAX_PATH_LENGTH] = {0}; /* Function prototypes. */ static bool usbHsFsMountParseMasterBootRecord(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block); static void usbHsFsMountParseMasterBootRecordPartitionEntry(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u8 type, u64 lba, u64 size, bool parse_ebr_gpt); static u8 usbHsFsMountInspectVolumeBootRecord(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 block_addr); #ifdef GPL_BUILD static u8 usbHsFsMountInspectExtSuperBlock(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 block_addr); #endif static void usbHsFsMountParseExtendedBootRecord(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 ebr_lba); static void usbHsFsMountParseGuidPartitionTable(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 gpt_lba); static bool usbHsFsMountRegisterVolume(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 block_addr, u64 block_count, u8 fs_type); static bool usbHsFsMountRegisterFatVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, u8 *block, u64 block_addr); static void usbHsFsMountUnregisterFatVolume(char *name, UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx); #ifdef GPL_BUILD static bool usbHsFsMountRegisterNtfsVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, u8 *block, u64 block_addr); static void usbHsFsMountUnregisterNtfsVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx); static bool usbHsFsMountRegisterExtVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, u64 block_addr, u64 block_count); static void usbHsFsMountUnregisterExtVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx); #endif static bool usbHsFsMountRegisterDevoptabDevice(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx); static u32 usbHsFsMountGetAvailableDevoptabDeviceId(void); static void usbHsFsMountUnsetDefaultDevoptabDevice(u32 device_id); bool usbHsFsMountInitializeLogicalUnitFileSystemContexts(UsbHsFsDriveLogicalUnitContext *lun_ctx) { if (!usbHsFsDriveIsValidLogicalUnitContext(lun_ctx)) { USBHSFS_LOG_MSG("Invalid parameters!"); return false; } u8 *block = NULL; u8 fs_type = 0; bool ret = false; /* Allocate memory to hold data from a single logical block. */ block = malloc(lun_ctx->block_length); if (!block) { USBHSFS_LOG_MSG("Failed to allocate memory to hold logical block data! (interface %d, LUN %u).", lun_ctx->usb_if_id, lun_ctx->lun); goto end; } /* Check if we're dealing with a SFD-formatted logical unit with a Microsoft VBR at LBA 0. */ fs_type = usbHsFsMountInspectVolumeBootRecord(lun_ctx, block, 0); if (fs_type > UsbHsFsDriveLogicalUnitFileSystemType_Unsupported) { /* Mount volume at LBA 0 right away. */ ret = usbHsFsMountRegisterVolume(lun_ctx, block, 0, lun_ctx->block_count, fs_type); } else if (fs_type == UsbHsFsDriveLogicalUnitFileSystemType_Unsupported) { /* Parse MBR. */ ret = usbHsFsMountParseMasterBootRecord(lun_ctx, block); } else { #ifdef GPL_BUILD /* We may be dealing with an EXT volume at LBA 0. */ fs_type = usbHsFsMountInspectExtSuperBlock(lun_ctx, block, 0); if (fs_type == UsbHsFsDriveLogicalUnitFileSystemType_EXT) { /* Mount EXT volume at LBA 0. */ ret = usbHsFsMountRegisterVolume(lun_ctx, block, 0, lun_ctx->block_count, fs_type); } else { USBHSFS_LOG_MSG("Unable to locate a valid boot sector! (interface %d, LUN %u).", lun_ctx->usb_if_id, lun_ctx->lun); } #else USBHSFS_LOG_MSG("Unable to locate a valid boot sector! (interface %d, LUN %u).", lun_ctx->usb_if_id, lun_ctx->lun); #endif } end: if (block) free(block); return ret; } void usbHsFsMountDestroyLogicalUnitFileSystemContext(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx) { if (!usbHsFsDriveIsValidLogicalUnitFileSystemContext(fs_ctx)) return; char name[MOUNT_NAME_LENGTH] = {0}; u32 *tmp_device_ids = NULL; /* Unset default devoptab device. */ usbHsFsMountUnsetDefaultDevoptabDevice(fs_ctx->device_id); /* Unregister devoptab interface. */ sprintf(name, "%s:", fs_ctx->name); RemoveDevice(name); /* Free devoptab virtual device interface. */ free(fs_ctx->device); fs_ctx->device = NULL; /* Free current working directory. */ free(fs_ctx->cwd); fs_ctx->cwd = NULL; /* Free mount name. */ free(fs_ctx->name); fs_ctx->name = NULL; /* Locate device ID in devoptab device ID buffer and remove it. */ for(u32 i = 0; i < g_devoptabDeviceCount; i++) { if (g_devoptabDeviceIds[i] != fs_ctx->device_id) continue; USBHSFS_LOG_MSG("Found device ID %u at index %u.", fs_ctx->device_id, i); if (g_devoptabDeviceCount > 1) { /* Move data within the device ID buffer, if needed. */ if (i < (g_devoptabDeviceCount - 1)) memmove(&(g_devoptabDeviceIds[i]), &(g_devoptabDeviceIds[i + 1]), (g_devoptabDeviceCount - (i + 1)) * sizeof(u32)); /* Reallocate devoptab device IDs buffer. */ tmp_device_ids = realloc(g_devoptabDeviceIds, (g_devoptabDeviceCount - 1) * sizeof(u32)); if (tmp_device_ids) { g_devoptabDeviceIds = tmp_device_ids; tmp_device_ids = NULL; } } else { /* Free devoptab device ID buffer. */ free(g_devoptabDeviceIds); g_devoptabDeviceIds = NULL; } /* Decrease devoptab virtual device count. */ g_devoptabDeviceCount--; break; } /* Unmount filesystem. */ switch(fs_ctx->fs_type) { case UsbHsFsDriveLogicalUnitFileSystemType_FAT: /* FAT12/FAT16/FAT32/exFAT. */ usbHsFsMountUnregisterFatVolume(name, fs_ctx); break; #ifdef GPL_BUILD case UsbHsFsDriveLogicalUnitFileSystemType_NTFS: /* NTFS. */ usbHsFsMountUnregisterNtfsVolume(fs_ctx); break; case UsbHsFsDriveLogicalUnitFileSystemType_EXT: /* EXT2/3/4. */ usbHsFsMountUnregisterExtVolume(fs_ctx); break; #endif /* TODO: populate this after adding support for additional filesystems. */ default: break; } } u32 usbHsFsMountGetDevoptabDeviceCount(void) { return g_devoptabDeviceCount; } bool usbHsFsMountSetDefaultDevoptabDevice(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx) { bool ret = false; SCOPED_LOCK(&g_devoptabDefaultDeviceMutex) { if (!g_devoptabDeviceCount || !g_devoptabDeviceIds || !usbHsFsDriveIsValidLogicalUnitFileSystemContext(fs_ctx)) { USBHSFS_LOG_MSG("Invalid parameters!"); break; } const devoptab_t *cur_default_devoptab = NULL; int new_default_device = -1; char name[MOUNT_NAME_LENGTH] = {0}; /* Get current default devoptab device index. */ cur_default_devoptab = GetDeviceOpTab(""); if (cur_default_devoptab && cur_default_devoptab->deviceData == fs_ctx) { /* Device already set as default. */ USBHSFS_LOG_MSG("Device \"%s\" already set as default.", fs_ctx->name); ret = true; break; } /* Get devoptab device index for our filesystem. */ sprintf(name, "%s:", fs_ctx->name); new_default_device = FindDevice(name); if (new_default_device < 0) { USBHSFS_LOG_MSG("Failed to retrieve devoptab device index for \"%s\"!", fs_ctx->name); break; } /* Set default devoptab device. */ setDefaultDevice(new_default_device); cur_default_devoptab = GetDeviceOpTab(""); if (!cur_default_devoptab || cur_default_devoptab->deviceData != fs_ctx) { USBHSFS_LOG_MSG("Failed to set default devoptab device to index %d! (device \"%s\").", new_default_device, fs_ctx->name); break; } USBHSFS_LOG_MSG("Successfully set default devoptab device to index %d! (device \"%s\").", new_default_device, fs_ctx->name); /* Update default device ID. */ g_devoptabDefaultDeviceId = fs_ctx->device_id; /* Update return value. */ ret = true; } return ret; } u32 usbHsFsMountGetFileSystemMountFlags(void) { return g_fileSystemMountFlags; } void usbHsFsMountSetFileSystemMountFlags(u32 flags) { g_fileSystemMountFlags = flags; } static bool usbHsFsMountParseMasterBootRecord(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block) { MasterBootRecord mbr = {0}; bool ret = false; memcpy(&mbr, block, sizeof(MasterBootRecord)); /* Parse MBR partition entries. */ for(u8 i = 0; i < MBR_PARTITION_COUNT; i++) { MasterBootRecordPartitionEntry *partition = &(mbr.partitions[i]); usbHsFsMountParseMasterBootRecordPartitionEntry(lun_ctx, block, partition->type, partition->lba, partition->block_count, true); } /* Update return value. */ ret = (lun_ctx->fs_count > 0); return ret; } static void usbHsFsMountParseMasterBootRecordPartitionEntry(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u8 type, u64 lba, u64 size, bool parse_ebr_gpt) { u8 fs_type = UsbHsFsDriveLogicalUnitFileSystemType_Invalid; switch(type) { case MasterBootRecordPartitionType_Empty: USBHSFS_LOG_MSG("Found empty partition entry (interface %d, LUN %u). Skipping.", lun_ctx->usb_if_id, lun_ctx->lun); break; case MasterBootRecordPartitionType_FAT12: case MasterBootRecordPartitionType_FAT16: case MasterBootRecordPartitionType_FAT16B: case MasterBootRecordPartitionType_NTFS_exFAT: case MasterBootRecordPartitionType_FAT32_CHS: case MasterBootRecordPartitionType_FAT32_LBA: case MasterBootRecordPartitionType_FAT16B_LBA: USBHSFS_LOG_MSG("Found FAT/NTFS partition entry with type 0x%02X at LBA 0x%lX (interface %d, LUN %u).", type, lba, lun_ctx->usb_if_id, lun_ctx->lun); /* Inspect VBR. */ fs_type = usbHsFsMountInspectVolumeBootRecord(lun_ctx, block, lba); break; case MasterBootRecordPartitionType_LinuxFileSystem: USBHSFS_LOG_MSG("Found Linux partition entry with type 0x%02X at LBA 0x%lX (interface %d, LUN %u).", type, lba, lun_ctx->usb_if_id, lun_ctx->lun); #ifdef GPL_BUILD /* Inspect EXT superblock. */ fs_type = usbHsFsMountInspectExtSuperBlock(lun_ctx, block, lba); #endif break; case MasterBootRecordPartitionType_ExtendedBootRecord_CHS: case MasterBootRecordPartitionType_ExtendedBootRecord_LBA: case MasterBootRecordPartitionType_ExtendedBootRecord_Linux: USBHSFS_LOG_MSG("Found EBR partition entry with type 0x%02X at LBA 0x%lX (interface %d, LUN %u).", type, lba, lun_ctx->usb_if_id, lun_ctx->lun); /* Parse EBR. */ if (parse_ebr_gpt) usbHsFsMountParseExtendedBootRecord(lun_ctx, block, lba); break; case MasterBootRecordPartitionType_GPT_Protective_MBR: USBHSFS_LOG_MSG("Found GPT partition entry at LBA 0x%lX (interface %d, LUN %u).", lba, lun_ctx->usb_if_id, lun_ctx->lun); /* Parse GPT. */ if (parse_ebr_gpt) usbHsFsMountParseGuidPartitionTable(lun_ctx, block, lba); break; default: USBHSFS_LOG_MSG("Found unsupported partition entry with type 0x%02X (interface %d, LUN %u). Skipping.", type, lun_ctx->usb_if_id, lun_ctx->lun); break; } /* Register detected volume. */ if (fs_type > UsbHsFsDriveLogicalUnitFileSystemType_Unsupported && usbHsFsMountRegisterVolume(lun_ctx, block, lba, size, fs_type)) { USBHSFS_LOG_MSG("Successfully registered %s volume at LBA 0x%lX (interface %d, LUN %u).", FS_TYPE_STR(fs_type), lba, lun_ctx->usb_if_id, lun_ctx->lun); } } static u8 usbHsFsMountInspectVolumeBootRecord(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 block_addr) { u32 block_length = lun_ctx->block_length; u8 ret = UsbHsFsDriveLogicalUnitFileSystemType_Invalid; /* Read block at the provided address from this LUN. */ if (!usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, block, block_addr, 1)) { USBHSFS_LOG_MSG("Failed to read block at LBA 0x%lX! (interface %d, LUN %u).", block_addr, lun_ctx->usb_if_id, lun_ctx->lun); goto end; } VolumeBootRecord *vbr = (VolumeBootRecord*)block; u8 jmp_code = vbr->jmp_boot[0]; u16 boot_sig = vbr->boot_sig; DOS_3_31_BPB *dos_3_31_bpb = &(vbr->dos_7_1_ebpb.dos_3_31_bpb); DOS_2_0_BPB *dos_2_0_bpb = &(dos_3_31_bpb->dos_2_0_bpb); u8 sectors_per_cluster = dos_2_0_bpb->sectors_per_cluster, num_fats = dos_2_0_bpb->num_fats; u16 sector_size = dos_2_0_bpb->sector_size, reserved_sectors = dos_2_0_bpb->reserved_sectors, root_dir_entries = dos_2_0_bpb->root_dir_entries, total_sectors_16 = dos_2_0_bpb->total_sectors; u16 sectors_per_fat = dos_2_0_bpb->sectors_per_fat; u32 total_sectors_32 = dos_3_31_bpb->total_sectors; /* Check if we have a valid boot sector signature. */ if (boot_sig == BOOT_SIGNATURE) { /* Check if this is an exFAT VBR. */ if (!memcmp(vbr->jmp_boot, "\xEB\x76\x90" "EXFAT ", 11)) { ret = UsbHsFsDriveLogicalUnitFileSystemType_FAT; goto end; } /* Check if this is an NTFS VBR. */ if (!memcmp(vbr->oem_name, "NTFS ", 8)) { ret = UsbHsFsDriveLogicalUnitFileSystemType_NTFS; goto end; } } /* Check if we have a valid jump boot code. */ if (jmp_code == 0xEB || jmp_code == 0xE9 || jmp_code == 0xE8) { /* Check if this is a FAT32 VBR. */ if (boot_sig == BOOT_SIGNATURE && !memcmp(vbr->dos_7_1_ebpb.fs_type, "FAT32 ", 8)) { ret = UsbHsFsDriveLogicalUnitFileSystemType_FAT; goto end; } /* FAT volumes formatted with old tools lack a boot sector signature and a filesystem type string, so we'll try to identify the FAT VBR without them. */ if ((sector_size & (sector_size - 1)) == 0 && sector_size <= (u16)block_length && sectors_per_cluster != 0 && (sectors_per_cluster & (sectors_per_cluster - 1)) == 0 && \ reserved_sectors != 0 && (num_fats - 1) <= 1 && root_dir_entries != 0 && (total_sectors_16 >= 128 || total_sectors_32 >= 0x10000) && sectors_per_fat != 0) \ ret = UsbHsFsDriveLogicalUnitFileSystemType_FAT; } /* Change return value if we couldn't identify a potential VBR but there's valid boot signature. */ /* We may be dealing with a MBR/EBR. */ if (ret == UsbHsFsDriveLogicalUnitFileSystemType_Invalid && boot_sig == BOOT_SIGNATURE) ret = UsbHsFsDriveLogicalUnitFileSystemType_Unsupported; end: if (ret > UsbHsFsDriveLogicalUnitFileSystemType_Unsupported) USBHSFS_LOG_MSG("Found %s VBR at LBA 0x%lX (interface %d, LUN %u).", FS_TYPE_STR(ret), block_addr, lun_ctx->usb_if_id, lun_ctx->lun); return ret; } #ifdef GPL_BUILD static u8 usbHsFsMountInspectExtSuperBlock(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 block_addr) { u32 block_length = lun_ctx->block_length; u32 block_read_addr = (block_addr + (EXT4_SUPERBLOCK_OFFSET / block_length)); u32 block_read_count = (block_length >= EXT4_SUPERBLOCK_SIZE ? 1 : (EXT4_SUPERBLOCK_SIZE / block_length)); struct ext4_sblock superblock = {0}; u8 ret = UsbHsFsDriveLogicalUnitFileSystemType_Invalid; if (block_read_count == 1) { /* Read entire EXT superblock. */ if (!usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, block, block_read_addr, 1)) { USBHSFS_LOG_MSG("Failed to read block at LBA 0x%X! (interface %d, LUN %u).", block_read_addr, lun_ctx->usb_if_id, lun_ctx->lun); goto end; } /* Copy EXT superblock data. */ memcpy(&superblock, block + (block_read_addr == block_addr ? EXT4_SUPERBLOCK_OFFSET : 0), sizeof(struct ext4_sblock)); } else { /* Read entire EXT superblock. */ if (!usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, (u8*)&superblock, block_read_addr, block_read_count)) { USBHSFS_LOG_MSG("Failed to read %u blocks at LBA 0x%X! (interface %d, LUN %u).", block_read_count, block_read_addr, lun_ctx->usb_if_id, lun_ctx->lun); goto end; } } /* Check if this is a valid EXT superblock. */ if (ext4_sb_check(&superblock)) ret = UsbHsFsDriveLogicalUnitFileSystemType_EXT; end: if (ret == UsbHsFsDriveLogicalUnitFileSystemType_EXT) USBHSFS_LOG_MSG("Found EXT superblock at LBA 0x%X (interface %d, LUN %u).", block_read_addr, lun_ctx->usb_if_id, lun_ctx->lun); return ret; } #endif /* GPL_BUILD */ static void usbHsFsMountParseExtendedBootRecord(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 ebr_lba) { ExtendedBootRecord ebr = {0}; u64 next_ebr_lba = 0, part_lba = 0; do { /* Read current EBR sector. */ if (!usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, block, ebr_lba + next_ebr_lba, 1)) { USBHSFS_LOG_MSG("Failed to read EBR at LBA 0x%lX! (interface %d, LUN %u).", ebr_lba, lun_ctx->usb_if_id, lun_ctx->lun); break; } /* Copy EBR data to struct. */ memcpy(&ebr, block, sizeof(ExtendedBootRecord)); /* Check boot signature. */ if (ebr.boot_sig == BOOT_SIGNATURE) { /* Calculate LBAs for the current partition and the next EBR in the chain. */ part_lba = (ebr_lba + next_ebr_lba + ebr.partition.lba); next_ebr_lba = ebr.next_ebr.lba; /* Parse partition entry. */ usbHsFsMountParseMasterBootRecordPartitionEntry(lun_ctx, block, ebr.partition.type, part_lba, ebr.partition.block_count, false); } else { /* Reset LBA from next EBR. */ next_ebr_lba = 0; } } while(next_ebr_lba); } static void usbHsFsMountParseGuidPartitionTable(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 gpt_lba) { GuidPartitionTableHeader gpt_header = {0}; u32 header_crc32 = 0, header_crc32_calc = 0, part_count = 0, part_per_block = 0, part_array_block_count = 0; u64 part_lba = 0; /* Read block where the GPT header is located. */ if (!usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, block, gpt_lba, 1)) { USBHSFS_LOG_MSG("Failed to read GPT header from LBA 0x%lX! (interface %d, LUN %u).", gpt_lba, lun_ctx->usb_if_id, lun_ctx->lun); return; } /* Copy GPT header data. */ memcpy(&gpt_header, block, sizeof(GuidPartitionTableHeader)); /* Verify GPT header signature, revision and header size fields. */ if (memcmp(&(gpt_header.signature), "EFI PART" "\x00\x00\x01\x00" "\x5C\x00\x00\x00", 16) != 0) { USBHSFS_LOG_MSG("Invalid GPT header at LBA 0x%lX! (interface %d, LUN %u).", gpt_lba, lun_ctx->usb_if_id, lun_ctx->lun); return; } /* Verify GPT header CRC32 checksum. */ header_crc32 = gpt_header.header_crc32; gpt_header.header_crc32 = 0; header_crc32_calc = crc32Calculate(&gpt_header, gpt_header.header_size); gpt_header.header_crc32 = header_crc32; if (header_crc32_calc != header_crc32) { USBHSFS_LOG_MSG("Invalid CRC32 checksum for GPT header at LBA 0x%lX! (%08X != %08X) (interface %d, LUN %u).", gpt_lba, header_crc32_calc, header_crc32, lun_ctx->usb_if_id, lun_ctx->lun); /* Check if the LBA for the backup GPT header points to a valid location. */ gpt_lba = gpt_header.backup_header_lba; if (!gpt_lba || gpt_lba == gpt_header.cur_header_lba || gpt_lba >= lun_ctx->block_count) return; /* Read block where the backup GPT header is located. */ if (!usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, block, gpt_lba, 1)) { USBHSFS_LOG_MSG("Failed to read backup GPT header from LBA 0x%lX! (interface %d, LUN %u).", gpt_lba, lun_ctx->usb_if_id, lun_ctx->lun); return; } /* Copy backup GPT header data. */ memcpy(&gpt_header, block, sizeof(GuidPartitionTableHeader)); /* Verify backup GPT header CRC32 checksum. */ header_crc32 = gpt_header.header_crc32; gpt_header.header_crc32 = 0; header_crc32_calc = crc32Calculate(&gpt_header, gpt_header.header_size); gpt_header.header_crc32 = header_crc32; if (header_crc32_calc != header_crc32) { USBHSFS_LOG_MSG("Invalid CRC32 checksum for backup GPT header at LBA 0x%lX! (%08X != %08X) (interface %d, LUN %u).", gpt_lba, header_crc32_calc, header_crc32, lun_ctx->usb_if_id, \ lun_ctx->lun); return; } USBHSFS_LOG_MSG("Backup GPT header located at LBA 0x%lX (interface %d, LUN %u).", gpt_lba, lun_ctx->usb_if_id, lun_ctx->lun); } /* Verify GPT partition entry size. */ if (gpt_header.partition_array_entry_size != sizeof(GuidPartitionTableEntry)) { USBHSFS_LOG_MSG("Invalid GPT partition entry size in GPT header at LBA 0x%lX! (0x%X != 0x%lX) (interface %d, LUN %u).", gpt_lba, gpt_header.partition_array_entry_size, \ sizeof(GuidPartitionTableEntry), lun_ctx->usb_if_id, lun_ctx->lun); return; } /* Get GPT partition entry count. Only process the first 128 entries if there's more than that. */ part_count = gpt_header.partition_array_count; if (part_count > 128) part_count = 128; /* Calculate number of partition entries per block and the total block count for the whole partition array. */ part_lba = gpt_header.partition_array_lba; part_per_block = (lun_ctx->block_length / (u32)sizeof(GuidPartitionTableEntry)); part_array_block_count = (part_count / part_per_block); /* Parse GPT partition entries. */ for(u32 i = 0; i < part_array_block_count; i++) { /* Read current partition array block. */ if (!usbHsFsScsiReadLogicalUnitBlocks(lun_ctx, block, part_lba + i, 1)) { USBHSFS_LOG_MSG("Failed to read GPT partition array block #%u from LBA 0x%lX! (interface %d, LUN %u).", i, part_lba + i, lun_ctx->usb_if_id, lun_ctx->lun); break; } for(u32 j = 0; j < part_per_block; j++) { GuidPartitionTableEntry *gpt_entry = (GuidPartitionTableEntry*)(block + (j * sizeof(GuidPartitionTableEntry))); u64 entry_lba = gpt_entry->lba_start; u64 entry_size = ((gpt_entry->lba_end + 1) - gpt_entry->lba_start); u8 fs_type = UsbHsFsDriveLogicalUnitFileSystemType_Invalid; if (!memcmp(gpt_entry->type_guid, g_microsoftBasicDataPartitionGuid, sizeof(g_microsoftBasicDataPartitionGuid))) { /* We're dealing with a Microsoft Basic Data Partition entry. */ USBHSFS_LOG_MSG("Found Microsoft Basic Data Partition entry at LBA 0x%lX (interface %d, LUN %u).", entry_lba, lun_ctx->usb_if_id, lun_ctx->lun); /* Inspect Microsoft VBR. Register the volume if we detect a supported VBR. */ fs_type = usbHsFsMountInspectVolumeBootRecord(lun_ctx, block, entry_lba); #ifdef GPL_BUILD if (fs_type == UsbHsFsDriveLogicalUnitFileSystemType_Invalid) { /* We may be dealing with a EXT volume. Check if we can find a valid EXT superblock. */ /* Certain tools set the type GUID from EXT volumes to the one from Microsoft. */ fs_type = usbHsFsMountInspectExtSuperBlock(lun_ctx, block, entry_lba); } #endif } else if (!memcmp(gpt_entry->type_guid, g_linuxFilesystemDataGuid, sizeof(g_linuxFilesystemDataGuid))) { /* We're dealing with a Linux Filesystem Data entry. */ USBHSFS_LOG_MSG("Found Linux Filesystem Data entry at LBA 0x%lX (interface %d, LUN %u).", entry_lba, lun_ctx->usb_if_id, lun_ctx->lun); #ifdef GPL_BUILD /* Check if this LBA points to a valid EXT superblock. Register the EXT volume if so. */ fs_type = usbHsFsMountInspectExtSuperBlock(lun_ctx, block, entry_lba); #endif } /* Register volume. */ if (fs_type > UsbHsFsDriveLogicalUnitFileSystemType_Unsupported && usbHsFsMountRegisterVolume(lun_ctx, block, entry_lba, entry_size, fs_type)) { USBHSFS_LOG_MSG("Successfully registered %s volume at LBA 0x%lX (interface %d, LUN %u).", FS_TYPE_STR(fs_type), entry_lba, lun_ctx->usb_if_id, lun_ctx->lun); } } } } static bool usbHsFsMountRegisterVolume(UsbHsFsDriveLogicalUnitContext *lun_ctx, u8 *block, u64 block_addr, u64 block_count, u8 fs_type) { #ifndef GPL_BUILD (void)block_count; #endif UsbHsFsDriveLogicalUnitFileSystemContext **tmp_fs_ctx = NULL, *fs_ctx = NULL; bool ret = false, free_entry = false; /* Reallocate filesystem context pointer array. */ tmp_fs_ctx = realloc(lun_ctx->fs_ctx, (lun_ctx->fs_count + 1) * sizeof(UsbHsFsDriveLogicalUnitFileSystemContext*)); if (!tmp_fs_ctx) { USBHSFS_LOG_MSG("Failed to reallocate filesystem context pointer array! (interface %d, LUN %u).", lun_ctx->usb_if_id, lun_ctx->lun); goto end; } lun_ctx->fs_ctx = tmp_fs_ctx; tmp_fs_ctx = NULL; free_entry = true; /* Allocate memory for a new filesystem context. */ fs_ctx = calloc(1, sizeof(UsbHsFsDriveLogicalUnitFileSystemContext)); if (!fs_ctx) { USBHSFS_LOG_MSG("Failed to allocate memory for filesystem context entry #%u! (interface %d, LUN %u).", lun_ctx->fs_count, lun_ctx->usb_if_id, lun_ctx->lun); goto end; } /* Set filesystem context properties. */ fs_ctx->lun_ctx = lun_ctx; fs_ctx->fs_idx = lun_ctx->fs_count; fs_ctx->fs_type = fs_type; fs_ctx->flags = g_fileSystemMountFlags; /* Set filesystem context entry pointer and update filesystem context count. */ lun_ctx->fs_ctx[(lun_ctx->fs_count)++] = fs_ctx; /* Mount and register filesystem. */ switch(fs_type) { case UsbHsFsDriveLogicalUnitFileSystemType_FAT: /* FAT12/FAT16/FAT32/exFAT. */ ret = usbHsFsMountRegisterFatVolume(fs_ctx, block, block_addr); break; #ifdef GPL_BUILD case UsbHsFsDriveLogicalUnitFileSystemType_NTFS: /* NTFS. */ ret = usbHsFsMountRegisterNtfsVolume(fs_ctx, block, block_addr); break; case UsbHsFsDriveLogicalUnitFileSystemType_EXT: /* EXT2/3/4. */ ret = usbHsFsMountRegisterExtVolume(fs_ctx, block_addr, block_count); break; #endif /* TODO: populate this after adding support for additional filesystems. */ default: USBHSFS_LOG_MSG("Invalid FS type provided! (0x%02X) (interface %d, LUN %u, FS %u).", fs_type, lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); break; } end: if (!ret && free_entry) { /* Free filesystem context. */ if (fs_ctx) { free(fs_ctx); /* Update filesystem context count and clear filesystem context entry pointer. */ lun_ctx->fs_ctx[--(lun_ctx->fs_count)] = fs_ctx = NULL; } if (lun_ctx->fs_count) { /* Reallocate filesystem context buffer. */ tmp_fs_ctx = realloc(lun_ctx->fs_ctx, lun_ctx->fs_count * sizeof(UsbHsFsDriveLogicalUnitFileSystemContext*)); if (tmp_fs_ctx) { lun_ctx->fs_ctx = tmp_fs_ctx; tmp_fs_ctx = NULL; } } else { /* Free filesystem context buffer. */ free(lun_ctx->fs_ctx); lun_ctx->fs_ctx = NULL; } } return ret; } static bool usbHsFsMountRegisterFatVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, u8 *block, u64 block_addr) { UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx; u8 pdrv = 0; char name[MOUNT_NAME_LENGTH] = {0}; FRESULT ff_res = FR_DISK_ERR; bool ret = false; /* Check if there's a free FatFs volume slot. */ for(pdrv = 0; pdrv < FF_VOLUMES; pdrv++) { if (!g_fatFsVolumeTable[pdrv]) { /* Jackpot. Prepare mount name. */ sprintf(name, "%u:", pdrv); break; } } if (pdrv == FF_VOLUMES) { USBHSFS_LOG_MSG("Failed to locate a free FatFs volume slot! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } USBHSFS_LOG_MSG("Located free FatFs volume slot: %u (interface %d, LUN %u, FS %u).", pdrv, lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); /* Allocate memory for the FatFs object. */ fs_ctx->fatfs = calloc(1, sizeof(FATFS)); if (!fs_ctx->fatfs) { USBHSFS_LOG_MSG("Failed to allocate memory for FATFS object! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Set read-only flag. */ fs_ctx->fatfs->ro_flag = ((fs_ctx->flags & UsbHsFsMountFlags_ReadOnly) || lun_ctx->write_protect); /* Copy VBR data. */ fs_ctx->fatfs->winsect = (LBA_t)block_addr; memcpy(fs_ctx->fatfs->win, block, sizeof(VolumeBootRecord)); /* Try to mount FAT volume. */ ff_res = ff_mount(fs_ctx->fatfs, name, 1); if (ff_res != FR_OK) { USBHSFS_LOG_MSG("Failed to mount FAT volume! (%u) (interface %d, LUN %u, FS %u).", ff_res, lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Register devoptab device. */ if (!usbHsFsMountRegisterDevoptabDevice(fs_ctx)) goto end; /* Update FatFs volume slot. */ g_fatFsVolumeTable[pdrv] = true; /* Update return value. */ ret = true; end: /* Free stuff if something went wrong. */ if (!ret && fs_ctx->fatfs) { if (ff_res == FR_OK) ff_unmount(name); free(fs_ctx->fatfs); fs_ctx->fatfs = NULL; } return ret; } static void usbHsFsMountUnregisterFatVolume(char *name, UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx) { /* Update FatFs volume slot. */ g_fatFsVolumeTable[fs_ctx->fatfs->pdrv] = false; /* Prepare mount name. */ sprintf(name, "%u:", fs_ctx->fatfs->pdrv); /* Unmount FAT volume. */ ff_unmount(name); /* Free FATFS object. */ free(fs_ctx->fatfs); fs_ctx->fatfs = NULL; } #ifdef GPL_BUILD static bool usbHsFsMountRegisterNtfsVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, u8 *block, u64 block_addr) { UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx; char name[MOUNT_NAME_LENGTH] = {0}; u32 flags = fs_ctx->flags; bool ret = false; /* Allocate memory for the NTFS volume descriptor. */ fs_ctx->ntfs = calloc(1, sizeof(ntfs_vd)); if (!fs_ctx->ntfs) { USBHSFS_LOG_MSG("Failed to allocate memory for NTFS volume descriptor! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Allocate memory for the NTFS device descriptor. */ fs_ctx->ntfs->dd = calloc(1, sizeof(ntfs_dd)); if (!fs_ctx->ntfs->dd) { USBHSFS_LOG_MSG("Failed to allocate memory for NTFS device descriptor! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Get available devoptab device ID. */ fs_ctx->device_id = usbHsFsMountGetAvailableDevoptabDeviceId(); sprintf(name, MOUNT_NAME_PREFIX "%u", fs_ctx->device_id); /* Allocate memory for the NTFS device handle. */ fs_ctx->ntfs->dev = ntfs_device_alloc(name, 0, ntfs_disk_io_get_dops(), fs_ctx->ntfs->dd); if (!fs_ctx->ntfs->dev) { USBHSFS_LOG_MSG("Failed to allocate memory for NTFS device object! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Copy VBR data. */ memcpy(&(fs_ctx->ntfs->dd->vbr), block, sizeof(NTFS_BOOT_SECTOR)); /* Setup NTFS device descriptor. */ fs_ctx->ntfs->dd->lun_ctx = lun_ctx; fs_ctx->ntfs->dd->sector_start = block_addr; /* Setup NTFS volume descriptor. */ fs_ctx->ntfs->id = fs_ctx->device_id; fs_ctx->ntfs->update_access_times = (flags & UsbHsFsMountFlags_UpdateAccessTimes); fs_ctx->ntfs->ignore_read_only_attr = (flags & UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute); if ((flags & UsbHsFsMountFlags_ReadOnly) || lun_ctx->write_protect) fs_ctx->ntfs->flags |= NTFS_MNT_RDONLY; if (flags & UsbHsFsMountFlags_ReplayJournal) fs_ctx->ntfs->flags |= NTFS_MNT_RECOVER; if (flags & UsbHsFsMountFlags_IgnoreHibernation) fs_ctx->ntfs->flags |= NTFS_MNT_IGNORE_HIBERFILE; /* Try to mount NTFS volume. */ fs_ctx->ntfs->vol = ntfs_device_mount(fs_ctx->ntfs->dev, fs_ctx->ntfs->flags); if (!fs_ctx->ntfs->vol) { USBHSFS_LOG_MSG("Failed to mount NTFS volume! (%d) (interface %d, LUN %u, FS %u).", ntfs_volume_error(errno), lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Create all LRU caches. */ /* No errors returned -- if this fails internally, LRU caches simply won't be available. */ ntfs_create_lru_caches(fs_ctx->ntfs->vol); /* Setup volume case sensitivity. */ if (flags & UsbHsFsMountFlags_IgnoreCaseSensitivity) ntfs_set_ignore_case(fs_ctx->ntfs->vol); /* Set appropriate flags for showing system/hidden files on the NTFS volume. */ ntfs_set_shown_files(fs_ctx->ntfs->vol, (flags & UsbHsFsMountFlags_ShowSystemFiles) != 0, (flags & UsbHsFsMountFlags_ShowHiddenFiles) != 0, false); /* Get NTFS volume free space. */ /* This will speed up subsequent calls to stavfs(). */ if (ntfs_volume_get_free_space(fs_ctx->ntfs->vol) < 0) { USBHSFS_LOG_MSG("Failed to retrieve free space from NTFS volume! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Register devoptab device. */ if (!usbHsFsMountRegisterDevoptabDevice(fs_ctx)) goto end; /* Update return value. */ ret = true; end: /* Free stuff if something went wrong. */ if (!ret && fs_ctx->ntfs) { if (fs_ctx->ntfs->vol) { /* ntfs_umount() takes care of calling both ntfs_create_lru_caches() and ntfs_device_free() for us. */ ntfs_umount(fs_ctx->ntfs->vol, true); fs_ctx->ntfs->vol = NULL; fs_ctx->ntfs->dev = NULL; } if (fs_ctx->ntfs->dev) { ntfs_device_free(fs_ctx->ntfs->dev); fs_ctx->ntfs->dev = NULL; } if (fs_ctx->ntfs->dd) { free(fs_ctx->ntfs->dd); fs_ctx->ntfs->dd = NULL; } free(fs_ctx->ntfs); fs_ctx->ntfs = NULL; } return ret; } static void usbHsFsMountUnregisterNtfsVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx) { /* Unmount NTFS volume. */ /* We don't need to manually free the NTFS device handle nor the LRU caches - ntfs_umount() does that for us. */ ntfs_umount(fs_ctx->ntfs->vol, true); fs_ctx->ntfs->vol = NULL; fs_ctx->ntfs->dev = NULL; /* Free NTFS device descriptor. */ free(fs_ctx->ntfs->dd); fs_ctx->ntfs->dd = NULL; /* Free NTFS volume descriptor. */ free(fs_ctx->ntfs); fs_ctx->ntfs = NULL; } static bool usbHsFsMountRegisterExtVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, u64 block_addr, u64 block_count) { UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx; bool ret = false, vol_mounted = false; /* Allocate memory for the EXT volume descriptor. */ fs_ctx->ext = calloc(1, sizeof(ext_vd)); if (!fs_ctx->ext) { USBHSFS_LOG_MSG("Failed to allocate memory for EXT volume descriptor! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Setup EXT block device handle. */ fs_ctx->ext->bdev = ext_disk_io_alloc_blockdev(lun_ctx, block_addr, block_count); if (!fs_ctx->ext->bdev) { USBHSFS_LOG_MSG("Failed to setup EXT block device handle! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Get available devoptab device ID. */ fs_ctx->device_id = usbHsFsMountGetAvailableDevoptabDeviceId(); /* Setup EXT volume descriptor. */ sprintf(fs_ctx->ext->dev_name, MOUNT_NAME_PREFIX "%u", fs_ctx->device_id); fs_ctx->ext->flags = fs_ctx->flags; fs_ctx->ext->id = fs_ctx->device_id; /* Try to mount EXT volume. */ if (!ext_mount(fs_ctx->ext)) { USBHSFS_LOG_MSG("Failed to mount EXT volume! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } vol_mounted = true; /* Register devoptab device. */ if (!usbHsFsMountRegisterDevoptabDevice(fs_ctx)) goto end; /* Update return value. */ ret = true; end: /* Free stuff if something went wrong. */ if (!ret && fs_ctx->ext) { if (vol_mounted) ext_umount(fs_ctx->ext); if (fs_ctx->ext->bdev) { ext_disk_io_free_blockdev(fs_ctx->ext->bdev); fs_ctx->ext->bdev = NULL; } free(fs_ctx->ext); fs_ctx->ext = NULL; } return ret; } static void usbHsFsMountUnregisterExtVolume(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx) { /* Unmount EXT volume. */ ext_umount(fs_ctx->ext); /* Free EXT block device handle. */ ext_disk_io_free_blockdev(fs_ctx->ext->bdev); fs_ctx->ext->bdev = NULL; /* Free EXT volume descriptor. */ free(fs_ctx->ext); fs_ctx->ext = NULL; } #endif /* GPL_BUILD */ static bool usbHsFsMountRegisterDevoptabDevice(UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx) { #ifdef DEBUG UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx; #endif char name[MOUNT_NAME_LENGTH] = {0}; const devoptab_t *fs_device = NULL; int ad_res = -1; u32 *tmp_device_ids = NULL; bool ret = false; /* Generate devoptab mount name. */ fs_ctx->name = calloc(MOUNT_NAME_LENGTH, sizeof(char)); if (!fs_ctx->name) { USBHSFS_LOG_MSG("Failed to allocate memory for the mount name! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } fs_ctx->device_id = usbHsFsMountGetAvailableDevoptabDeviceId(); USBHSFS_LOG_MSG("Available device ID: %u (interface %d, LUN %u, FS %u).", fs_ctx->device_id, lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); sprintf(fs_ctx->name, MOUNT_NAME_PREFIX "%u", fs_ctx->device_id); sprintf(name, "%s:", fs_ctx->name); /* Will be used if something goes wrong and we end up having to remove the devoptab device. */ /* Allocate memory for the current working directory. */ fs_ctx->cwd = calloc(MAX_PATH_LENGTH, sizeof(char)); if (!fs_ctx->cwd) { USBHSFS_LOG_MSG("Failed to allocate memory for the current working directory! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } fs_ctx->cwd[0] = '/'; /* Always start at the root directory. */ /* Allocate memory for our devoptab virtual device interface. */ fs_ctx->device = calloc(1, sizeof(devoptab_t)); if (!fs_ctx->device) { USBHSFS_LOG_MSG("Failed to allocate memory for devoptab virtual device interface! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Retrieve pointer to the devoptab interface from our filesystem type. */ switch(fs_ctx->fs_type) { case UsbHsFsDriveLogicalUnitFileSystemType_FAT: /* FAT12/FAT16/FAT32/exFAT. */ fs_device = ffdev_get_devoptab(); break; #ifdef GPL_BUILD case UsbHsFsDriveLogicalUnitFileSystemType_NTFS: /* NTFS. */ fs_device = ntfsdev_get_devoptab(); break; case UsbHsFsDriveLogicalUnitFileSystemType_EXT: /* EXT2/3/4. */ fs_device = extdev_get_devoptab(); break; #endif default: USBHSFS_LOG_MSG("Invalid FS type provided! (0x%02X) (interface %d, LUN %u, FS %u).", fs_ctx->fs_type, lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); break; } if (!fs_device) { USBHSFS_LOG_MSG("Failed to get pointer to devoptab interface! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Copy devoptab interface data and set mount name and device data. */ memcpy(fs_ctx->device, fs_device, sizeof(devoptab_t)); fs_ctx->device->name = fs_ctx->name; fs_ctx->device->deviceData = fs_ctx; /* Add devoptab device. */ ad_res = AddDevice(fs_ctx->device); if (ad_res < 0) { USBHSFS_LOG_MSG("AddDevice failed! (%d) (interface %d, LUN %u, FS %u).", ad_res, lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } /* Reallocate devoptab device IDs buffer. */ tmp_device_ids = realloc(g_devoptabDeviceIds, (g_devoptabDeviceCount + 1) * sizeof(u32)); if (!tmp_device_ids) { USBHSFS_LOG_MSG("Failed to reallocate devoptab device IDs buffer! (interface %d, LUN %u, FS %u).", lun_ctx->usb_if_id, lun_ctx->lun, fs_ctx->fs_idx); goto end; } g_devoptabDeviceIds = tmp_device_ids; tmp_device_ids = NULL; /* Store devoptab device ID and increase devoptab virtual device count. */ g_devoptabDeviceIds[g_devoptabDeviceCount++] = fs_ctx->device_id; /* Update return value. */ ret = true; end: /* Free stuff if something went wrong. */ if (!ret) { if (ad_res >= 0) RemoveDevice(name); if (fs_ctx->device) { free(fs_ctx->device); fs_ctx->device = NULL; } if (fs_ctx->cwd) { free(fs_ctx->cwd); fs_ctx->cwd = NULL; } if (fs_ctx->name) { free(fs_ctx->name); fs_ctx->name = NULL; } } return ret; } static u32 usbHsFsMountGetAvailableDevoptabDeviceId(void) { if (!g_devoptabDeviceCount || !g_devoptabDeviceIds) return 0; u32 i = 0, ret = 0; while(true) { if (i >= g_devoptabDeviceCount) break; if (ret == g_devoptabDeviceIds[i]) { ret++; i = 0; } else { i++; } } return ret; } static void usbHsFsMountUnsetDefaultDevoptabDevice(u32 device_id) { SCOPED_LOCK(&g_devoptabDefaultDeviceMutex) { /* Check if the provided device ID matches the current default devoptab device ID. */ if (g_devoptabDefaultDeviceId == DEVOPTAB_INVALID_ID || g_devoptabDefaultDeviceId != device_id) break; USBHSFS_LOG_MSG("Current default devoptab device matches provided device ID! (%u).", device_id); u32 cur_device_id = 0; const devoptab_t *cur_default_devoptab = GetDeviceOpTab(""); /* Check if the current default devoptab device is the one we previously set. */ /* If so, set the SD card as the new default devoptab device. */ if (cur_default_devoptab && cur_default_devoptab->name && strlen(cur_default_devoptab->name) >= 4 && sscanf(cur_default_devoptab->name, MOUNT_NAME_PREFIX "%u", &cur_device_id) == 1 && \ cur_device_id == device_id) { USBHSFS_LOG_MSG("Setting SD card as the default devoptab device."); setDefaultDevice(FindDevice("sdmc:")); } /* Update default device ID. */ g_devoptabDefaultDeviceId = DEVOPTAB_INVALID_ID; } }