TinWoo/include/libusbhsfs/source/usbhsfs_manager.c

1036 lines
36 KiB
C
Raw Normal View History

2023-09-05 02:09:05 +01:00
/*
* usbhsfs_manager.c
*
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
* 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 "sxos/usbfs_dev.h"
#if defined(DEBUG) && defined(GPL_BUILD)
#include "ntfs-3g/ntfs.h"
#include "lwext4/ext.h"
#endif
#define MAX_USB_INTERFACES 0x20
/* Global variables. */
static Mutex g_managerMutex = 0;
static bool g_usbHsFsInitialized = false;
static bool g_isSXOS = false, g_isSXOSDeviceAvailable = false;
static UsbHsFsDevice g_sxOSDevice = {0};
static UsbHsInterfaceFilter g_usbInterfaceFilter = {0};
static Event g_usbInterfaceAvailableEvent = {0}, *g_usbInterfaceStateChangeEvent = NULL;
static u8 g_usbInterfaceAvailableEventIndex = 0;
static UsbHsInterface *g_usbInterfaces = NULL;
static const size_t g_usbInterfacesMaxSize = (MAX_USB_INTERFACES * sizeof(UsbHsInterface));
static Thread g_usbDriveManagerThread = {0};
static UEvent g_usbDriveManagerThreadExitEvent = {0};
static UsbHsFsDriveContext **g_driveContexts = NULL;
static u32 g_driveCount = 0;
static UEvent g_usbStatusChangeEvent = {0};
/* Function prototypes. */
static Result usbHsFsCreateDriveManagerThread(void);
static Result usbHsFsCloseDriveManagerThread(void);
static void usbHsFsDriveManagerThreadFuncSXOS(void *arg);
static void usbHsFsDriveManagerThreadFuncAtmosphere(void *arg);
static void usbHsFsResetDrives(void);
static bool usbHsFsUpdateDriveContexts(bool remove);
static void usbHsFsRemoveDriveContextFromListByIndex(u32 drive_ctx_idx, bool stop_lun);
static bool usbHsFsAddDriveContextToList(UsbHsInterface *usb_if);
static void usbHsFsFillDeviceElement(UsbHsFsDriveContext *drive_ctx, UsbHsFsDriveLogicalUnitContext *lun_ctx, UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, UsbHsFsDevice *device);
Result usbHsFsInitialize(u8 event_idx)
{
Result rc = 0;
SCOPED_LOCK(&g_managerMutex)
{
/* Check if the interface has already been initialized. */
if (g_usbHsFsInitialized) break;
bool usbhs_init = false, usb_event_created = false, usbfs_init = false;
#ifdef DEBUG
/* Start new log session. */
usbHsFsLogWriteStringToLogFile("________________________________________________________________\r\n");
//USBHSFS_LOG_MSG(LIB_TITLE " v%u.%u.%u starting. Built on " BUILD_TIMESTAMP ".", LIBUSBHSFS_VERSION_MAJOR, LIBUSBHSFS_VERSION_MINOR, LIBUSBHSFS_VERSION_MICRO);
USBHSFS_LOG_MSG(LIB_TITLE " v%u.%u.%u starting. Built on 30/8/23", LIBUSBHSFS_VERSION_MAJOR, LIBUSBHSFS_VERSION_MINOR, LIBUSBHSFS_VERSION_MICRO);
/* Log Horizon OS version. */
u32 hos_version = hosversionGet();
USBHSFS_LOG_MSG("Horizon OS version: %u.%u.%u.", HOSVER_MAJOR(hos_version), HOSVER_MINOR(hos_version), HOSVER_MICRO(hos_version));
#ifdef GPL_BUILD
USBHSFS_LOG_MSG("Build type: GPL.");
/* Setup NTFS-3G logging. */
ntfs_log_set_handler(ntfs_log_handler_usbhsfs);
ntfs_log_set_levels(NTFS_LOG_LEVEL_DEBUG | NTFS_LOG_LEVEL_TRACE | NTFS_LOG_LEVEL_QUIET | NTFS_LOG_LEVEL_INFO | NTFS_LOG_LEVEL_VERBOSE | NTFS_LOG_LEVEL_PROGRESS | NTFS_LOG_LEVEL_WARNING | \
NTFS_LOG_LEVEL_ERROR | NTFS_LOG_LEVEL_PERROR | NTFS_LOG_LEVEL_CRITICAL | NTFS_LOG_LEVEL_ENTER | NTFS_LOG_LEVEL_LEAVE);
/* Setup lwext4 logging. */
ext4_dmask_set(DEBUG_ALL & ~DEBUG_NOPREFIX);
#else /* GPL_BUILD */
USBHSFS_LOG_MSG("Build type: ISC.");
#endif /* GPL_BUILD */
#else /* DEBUG */
#ifdef GPL_BUILD
/* Disable NTFS-3G logging. */
ntfs_log_set_handler(ntfs_log_handler_null);
ntfs_log_set_levels(0);
/* Disable lwext4 logging. */
ext4_dmask_set(0);
#endif /* GPL_BUILD */
#endif /* DEBUG */
/* Check if the deprecated fsp-usb service is running. */
/* This custom mitm service offers system-wide UMS support - we definitely don't want to run alongside it to avoid undesired results. */
if (usbHsFsUtilsIsFspUsbRunning())
{
USBHSFS_LOG_MSG("Error: fsp-usb is running!");
rc = MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
goto end;
}
/* Check if we're running under SX OS. */
/* If true, this completely changes the way the library works. */
g_isSXOS = usbHsFsUtilsSXOSCustomFirmwareCheck();
USBHSFS_LOG_MSG("Running under SX OS: %s.", g_isSXOS ? "yes" : "no");
if (!g_isSXOS)
{
/* Check if the provided event index value is valid. */
if (event_idx > 2)
{
USBHSFS_LOG_MSG("Invalid event index value provided! (%u).", event_idx);
rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
goto end;
}
/* Allocate memory for the USB interfaces. */
g_usbInterfaces = malloc(g_usbInterfacesMaxSize);
if (!g_usbInterfaces)
{
USBHSFS_LOG_MSG("Failed to allocate memory for USB interfaces!");
rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
goto end;
}
/* Initialize usb:hs service. */
rc = usbHsInitialize();
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("usbHsInitialize failed! (0x%08X).", rc);
goto end;
}
usbhs_init = true;
/* Fill USB interface filter. */
g_usbInterfaceFilter.Flags = (UsbHsInterfaceFilterFlags_bInterfaceClass | UsbHsInterfaceFilterFlags_bInterfaceSubClass);
g_usbInterfaceFilter.bInterfaceClass = USB_CLASS_MASS_STORAGE;
g_usbInterfaceFilter.bInterfaceSubClass = USB_SUBCLASS_SCSI_TRANSPARENT_CMD_SET;
/* Create USB interface available event for our filter. */
/* This will be signaled each time a USB device with a descriptor that matches our filter is connected to the console. */
rc = usbHsCreateInterfaceAvailableEvent(&g_usbInterfaceAvailableEvent, false, event_idx, &g_usbInterfaceFilter);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("usbHsCreateInterfaceAvailableEvent failed! (0x%08X).", rc);
goto end;
}
usb_event_created = true;
/* Update USB interface available event index. */
g_usbInterfaceAvailableEventIndex = event_idx;
/* Retrieve the interface change event. */
/* This will be signaled each time a device is removed from the console. */
g_usbInterfaceStateChangeEvent = usbHsGetInterfaceStateChangeEvent();
} else {
/* Initialize usbfs service. */
rc = usbFsInitialize();
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("usbFsInitialize failed! (0x%08X).", rc);
goto end;
}
usbfs_init = true;
/* Prepare SX OS device. */
memset(&g_sxOSDevice, 0, sizeof(UsbHsFsDevice));
sprintf(g_sxOSDevice.manufacturer, "TX");
sprintf(g_sxOSDevice.product_name, "USBHDD");
sprintf(g_sxOSDevice.name, USBFS_MOUNT_NAME ":");
}
/* Create user-mode drive manager thread exit event. */
ueventCreate(&g_usbDriveManagerThreadExitEvent, true);
/* Create user-mode USB status change event. */
ueventCreate(&g_usbStatusChangeEvent, true);
/* Create and start drive manager background thread. */
rc = usbHsFsCreateDriveManagerThread();
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("Failed to create drive manager background thread!");
goto end;
}
/* Update flag. */
g_usbHsFsInitialized = true;
end:
/* Close usb:hs service if initialization failed. */
if (R_FAILED(rc))
{
if (usbfs_init) usbFsExit();
if (usb_event_created) usbHsDestroyInterfaceAvailableEvent(&g_usbInterfaceAvailableEvent, event_idx);
if (usbhs_init) usbHsExit();
if (g_usbInterfaces)
{
free(g_usbInterfaces);
g_usbInterfaces = NULL;
}
#ifdef DEBUG
usbHsFsLogCloseLogFile();
#endif
}
}
return rc;
}
void usbHsFsExit(void)
{
SCOPED_LOCK(&g_managerMutex)
{
/* Check if the interface has already been initialized. */
if (!g_usbHsFsInitialized) break;
/* Stop and close drive manager background thread. */
usbHsFsCloseDriveManagerThread();
if (!g_isSXOS)
{
/* Destroy the USB interface available event we previously created for our filter. */
usbHsDestroyInterfaceAvailableEvent(&g_usbInterfaceAvailableEvent, g_usbInterfaceAvailableEventIndex);
g_usbInterfaceAvailableEventIndex = 0;
/* Close usb:hs service. */
usbHsExit();
/* Free USB interfaces. */
free(g_usbInterfaces);
g_usbInterfaces = NULL;
} else {
/* Close usbfs service. */
usbFsExit();
}
#ifdef DEBUG
/* Close logfile. */
usbHsFsLogCloseLogFile();
#endif
/* Update flag. */
g_usbHsFsInitialized = false;
}
}
UEvent *usbHsFsGetStatusChangeUserEvent(void)
{
UEvent *event = NULL;
SCOPED_LOCK(&g_managerMutex) event = (g_usbHsFsInitialized ? &g_usbStatusChangeEvent : NULL);
return event;
}
u32 usbHsFsGetPhysicalDeviceCount(void)
{
u32 ret = 0;
SCOPED_LOCK(&g_managerMutex) ret = (g_usbHsFsInitialized ? (!g_isSXOS ? g_driveCount : (g_isSXOSDeviceAvailable ? 1 : 0)) : 0);
return ret;
}
u32 usbHsFsGetMountedDeviceCount(void)
{
u32 ret = 0;
SCOPED_LOCK(&g_managerMutex) ret = (g_usbHsFsInitialized ? (!g_isSXOS ? usbHsFsMountGetDevoptabDeviceCount() : (g_isSXOSDeviceAvailable ? 1 : 0)) : 0);
return ret;
}
u32 usbHsFsListMountedDevices(UsbHsFsDevice *out, u32 max_count)
{
u32 ret = 0;
SCOPED_LOCK(&g_managerMutex)
{
u32 device_count = (g_usbHsFsInitialized ? (!g_isSXOS ? usbHsFsMountGetDevoptabDeviceCount() : (g_isSXOSDeviceAvailable ? 1 : 0)) : 0);
if ((!g_isSXOS && (!g_driveCount || !g_driveContexts)) || !device_count || !out || !max_count)
{
USBHSFS_LOG_MSG("Invalid parameters!");
break;
}
if (g_isSXOS)
{
/* Copy device data, update return value and jump to the end. */
memcpy(out, &g_sxOSDevice, sizeof(UsbHsFsDevice));
ret = device_count;
break;
}
for(u32 i = 0; i < g_driveCount; i++)
{
UsbHsFsDriveContext *drive_ctx = g_driveContexts[i];
if (!drive_ctx) continue;
for(u8 j = 0; j < drive_ctx->lun_count; j++)
{
UsbHsFsDriveLogicalUnitContext *lun_ctx = drive_ctx->lun_ctx[j];
for(u32 k = 0; k < lun_ctx->fs_count; k++)
{
UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = lun_ctx->fs_ctx[k];
/* Fill device element. */
UsbHsFsDevice *device = &(out[ret++]); /* Increase return value. */
usbHsFsFillDeviceElement(drive_ctx, lun_ctx, fs_ctx, device);
/* Jump out of the loops if we have reached a limit */
if (ret >= max_count || ret >= device_count) goto end;
}
}
}
}
end:
#ifdef DEBUG
/* Flush logfile. */
usbHsFsLogFlushLogFile();
#endif
return ret;
}
bool usbHsFsUnmountDevice(UsbHsFsDevice *device, bool signal_status_event)
{
bool ret = false;
SCOPED_LOCK(&g_managerMutex)
{
u32 drive_ctx_idx = 0;
if (!g_usbHsFsInitialized || g_isSXOS || (!g_isSXOS && (!g_driveCount || !g_driveContexts)) || !device)
{
USBHSFS_LOG_MSG("Invalid parameters!");
break;
}
/* Locate drive context. */
for(drive_ctx_idx = 0; drive_ctx_idx < g_driveCount; drive_ctx_idx++)
{
UsbHsFsDriveContext *drive_ctx = g_driveContexts[drive_ctx_idx];
if (!drive_ctx) continue;
if (drive_ctx->usb_if_id == device->usb_if_id) break;
}
if (drive_ctx_idx >= g_driveCount)
{
USBHSFS_LOG_MSG("Unable to find a matching drive context with USB interface ID %d.", device->usb_if_id);
break;
}
/* Destroy drive context and remove it from our pointer array. */
usbHsFsRemoveDriveContextFromListByIndex(drive_ctx_idx, true);
USBHSFS_LOG_MSG("Successfully unmounted UMS device with ID %d.", device->usb_if_id);
if (signal_status_event)
{
/* Signal user-mode event. */
USBHSFS_LOG_MSG("Signaling status change event.");
ueventSignal(&g_usbStatusChangeEvent);
}
/* Update return value. */
ret = true;
}
#ifdef DEBUG
/* Flush logfile. */
usbHsFsLogFlushLogFile();
#endif
return ret;
}
u32 usbHsFsGetFileSystemMountFlags(void)
{
u32 flags = 0;
SCOPED_LOCK(&g_managerMutex) flags = usbHsFsMountGetFileSystemMountFlags();
return flags;
}
void usbHsFsSetFileSystemMountFlags(u32 flags)
{
SCOPED_LOCK(&g_managerMutex) usbHsFsMountSetFileSystemMountFlags(flags & (UsbHsFsMountFlags_IgnoreCaseSensitivity | UsbHsFsMountFlags_UpdateAccessTimes | \
UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ShowSystemFiles | \
UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute | UsbHsFsMountFlags_ReadOnly | \
UsbHsFsMountFlags_ReplayJournal | UsbHsFsMountFlags_IgnoreHibernation));
}
/* Non-static function not meant to be disclosed to users. */
bool usbHsFsManagerIsDriveContextPointerValid(UsbHsFsDriveContext *drive_ctx)
{
bool ret = false;
SCOPED_LOCK(&g_managerMutex)
{
/* Try to find a drive context in our pointer array that matches the provided drive context. */
for(u32 i = 0; i < g_driveCount; i++)
{
UsbHsFsDriveContext *cur_drive_ctx = g_driveContexts[i];
if (!cur_drive_ctx) continue;
if (cur_drive_ctx == drive_ctx)
{
ret = true;
break;
}
}
/* Lock drive context mutex if we found a match. */
if (ret) mutexLock(&(drive_ctx->mutex));
}
return ret;
}
/* Non-static function not meant to be disclosed to users. */
UsbHsFsDriveLogicalUnitContext *usbHsFsManagerGetLogicalUnitContextForFatFsDriveNumber(u8 pdrv)
{
UsbHsFsDriveLogicalUnitContext *ret = NULL;
SCOPED_LOCK(&g_managerMutex)
{
if (!g_driveCount || !g_driveContexts || pdrv >= FF_VOLUMES)
{
USBHSFS_LOG_MSG("Invalid parameters!");
break;
}
for(u32 i = 0; i < g_driveCount; i++)
{
UsbHsFsDriveContext *drive_ctx = g_driveContexts[i];
if (!drive_ctx) continue;
for(u8 j = 0; j < drive_ctx->lun_count; j++)
{
UsbHsFsDriveLogicalUnitContext *lun_ctx = drive_ctx->lun_ctx[j];
for(u32 k = 0; k < lun_ctx->fs_count; k++)
{
UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = lun_ctx->fs_ctx[k];
if (fs_ctx->fs_type == UsbHsFsDriveLogicalUnitFileSystemType_FAT && fs_ctx->fatfs && fs_ctx->fatfs->pdrv == pdrv)
{
ret = lun_ctx;
goto end;
}
}
}
}
end:
if (!ret) USBHSFS_LOG_MSG("Unable to find a matching LUN context for filesystem context with FatFs drive number %u!", pdrv);
}
return ret;
}
/* Used to create and start a new thread with preemptive multithreading enabled without using libnx's newlib wrappers. */
/* This lets us manage threads using libnx types. */
static Result usbHsFsCreateDriveManagerThread(void)
{
Result rc = 0;
u64 core_mask = 0;
size_t stack_size = 0x20000; /* Same value as libnx's newlib. */
/* Clear thread. */
memset(&g_usbDriveManagerThread, 0, sizeof(Thread));
/* Get process core mask. */
rc = svcGetInfo(&core_mask, InfoType_CoreMask, CUR_PROCESS_HANDLE, 0);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("svcGetInfo failed! (0x%08X).", rc);
goto end;
}
/* Create thread. */
/* Enable preemptive multithreading by using priority 0x3B. */
rc = threadCreate(&g_usbDriveManagerThread, g_isSXOS ? usbHsFsDriveManagerThreadFuncSXOS : usbHsFsDriveManagerThreadFuncAtmosphere, NULL, NULL, stack_size, 0x3B, -2);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("threadCreate failed! (0x%08X).", rc);
goto end;
}
/* Set thread core mask. */
rc = svcSetThreadCoreMask(g_usbDriveManagerThread.handle, -1, core_mask);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("svcSetThreadCoreMask failed! (0x%08X).", rc);
goto end;
}
/* Start thread. */
rc = threadStart(&g_usbDriveManagerThread);
if (R_FAILED(rc)) USBHSFS_LOG_MSG("threadStart failed! (0x%08X).", rc);
end:
/* Close thread if something went wrong. */
if (R_FAILED(rc) && g_usbDriveManagerThread.handle != INVALID_HANDLE) threadClose(&g_usbDriveManagerThread);
return rc;
}
static Result usbHsFsCloseDriveManagerThread(void)
{
Result rc = 0;
USBHSFS_LOG_MSG("Signaling drive manager thread exit event...");
/* Signal user-mode drive manager thread exit event. */
ueventSignal(&g_usbDriveManagerThreadExitEvent);
/* Wait for the drive manager thread to exit. */
rc = threadWaitForExit(&g_usbDriveManagerThread);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("threadWaitForExit failed! (0x%08X).", rc);
goto end;
}
/* Close drive manager thread. */
threadClose(&g_usbDriveManagerThread);
USBHSFS_LOG_MSG("Thread successfully closed.");
end:
return rc;
}
static void usbHsFsDriveManagerThreadFuncSXOS(void *arg)
{
(void)arg;
Result rc = 0;
u64 prev_status = USBFS_UNMOUNTED, cur_status = prev_status;
Waiter thread_exit_waiter = waiterForUEvent(&g_usbDriveManagerThreadExitEvent);
while(true)
{
/* Check if the thread exit event has been triggered (1s timeout). */
rc = waitSingle(thread_exit_waiter, (u64)1000000000);
/* Exit event triggered. */
if (R_SUCCEEDED(rc)) break;
SCOPED_LOCK(&g_managerMutex)
{
/* Get UMS mount status. */
rc = usbFsGetMountStatus(&cur_status);
if (R_SUCCEEDED(rc))
{
/* Check if the mount status has changed. */
if (cur_status != prev_status)
{
USBHSFS_LOG_MSG("New status received: %lu.", cur_status);
/* Check if the filesystem from the UMS device is truly mounted and if we can register a devoptab interface for it. */
g_isSXOSDeviceAvailable = (cur_status == USBFS_MOUNTED && usbfsdev_register());
/* Unregister devoptab device, if needed. */
if (!g_isSXOSDeviceAvailable) usbfsdev_unregister();
/* Update previous status. */
prev_status = cur_status;
/* Signal user-mode event. */
USBHSFS_LOG_MSG("Signaling status change event.");
ueventSignal(&g_usbStatusChangeEvent);
}
} else {
USBHSFS_LOG_MSG("usbFsGetMountStatus failed! (0x%08X).", rc);
}
}
#ifdef DEBUG
/* Flush logfile. */
usbHsFsLogFlushLogFile();
#endif
}
/* Unregister devoptab device. */
if (g_isSXOSDeviceAvailable) usbfsdev_unregister();
/* Update device available flag. */
g_isSXOSDeviceAvailable = false;
/* Exit thread. */
threadExit();
}
static void usbHsFsDriveManagerThreadFuncAtmosphere(void *arg)
{
(void)arg;
Result rc = 0;
int idx = 0;
Waiter usb_if_available_waiter = waiterForEvent(&g_usbInterfaceAvailableEvent);
Waiter usb_if_state_change_waiter = waiterForEvent(g_usbInterfaceStateChangeEvent);
Waiter thread_exit_waiter = waiterForUEvent(&g_usbDriveManagerThreadExitEvent);
/* Check if any UMS devices are already connected to the console (no timeout). */
rc = waitSingle(usb_if_available_waiter, 0);
if (R_SUCCEEDED(rc))
{
USBHSFS_LOG_MSG("Interface available event triggered at startup (UMS devices already available).");
/* Reset each UMS device so we can safely issue Start Unit commands later on (if needed). */
/* A Stop Unit command could have been issued before for each UMS device (e.g. if an app linked against this library was previously launched, but the UMS devices weren't disconnected). */
/* Performing a bus reset on each one makes it possible to re-use them. */
SCOPED_LOCK(&g_managerMutex) usbHsFsResetDrives();
#ifdef DEBUG
/* Flush logfile. */
usbHsFsLogFlushLogFile();
#endif
}
while(true)
{
/* Wait until an event is triggered. */
rc = waitMulti(&idx, -1, usb_if_available_waiter, usb_if_state_change_waiter, thread_exit_waiter);
if (R_FAILED(rc)) continue;
#ifdef DEBUG
switch(idx)
{
case 0:
USBHSFS_LOG_MSG("Interface available event triggered.");
break;
case 1:
USBHSFS_LOG_MSG("Interface state change event triggered.");
break;
case 2:
USBHSFS_LOG_MSG("Exit event triggered.");
break;
default:
break;
}
#endif
/* Exit event triggered. */
if (idx == 2) break;
SCOPED_LOCK(&g_managerMutex)
{
/* Update drive contexts. */
bool ctx_updated = usbHsFsUpdateDriveContexts(idx == 1);
if (idx == 0)
{
/* Clear the interface available event if it was triggered (not an autoclear event). */
eventClear(&g_usbInterfaceAvailableEvent);
} else
if (idx == 1)
{
/* Clear the interface change event if it was triggered (not an autoclear event). */
eventClear(g_usbInterfaceStateChangeEvent);
}
/* Signal user-mode event if contexts were updated. */
if (ctx_updated)
{
USBHSFS_LOG_MSG("Signaling status change event.");
ueventSignal(&g_usbStatusChangeEvent);
}
}
#ifdef DEBUG
/* Flush logfile. */
usbHsFsLogFlushLogFile();
#endif
}
if (g_driveContexts)
{
/* Destroy drive contexts, one by one. */
for(u32 i = 0; i < g_driveCount; i++)
{
UsbHsFsDriveContext *drive_ctx = g_driveContexts[i];
if (!drive_ctx) continue;
SCOPED_LOCK(&(drive_ctx->mutex)) usbHsFsDriveDestroyContext(drive_ctx, true);
free(drive_ctx);
drive_ctx = NULL;
}
/* Free drive context pointer array. */
free(g_driveContexts);
g_driveContexts = NULL;
}
/* Reset drive count. */
g_driveCount = 0;
/* Exit thread. */
threadExit();
}
static void usbHsFsResetDrives(void)
{
Result rc = 0;
s32 usb_if_count = 0;
UsbHsClientIfSession usb_if_session = {0};
/* Clear USB interfaces buffer. */
memset(g_usbInterfaces, 0, g_usbInterfacesMaxSize);
/* Retrieve available USB interfaces. */
rc = usbHsQueryAvailableInterfaces(&g_usbInterfaceFilter, g_usbInterfaces, g_usbInterfacesMaxSize, &usb_if_count);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("usbHsQueryAvailableInterfaces failed! (0x%08X).", rc);
return;
}
USBHSFS_LOG_MSG("usbHsQueryAvailableInterfaces returned %d interface(s) matching our filter.", usb_if_count);
/* Loop through the available USB interfaces. */
for(s32 i = 0; i < usb_if_count; i++)
{
UsbHsInterface *usb_if = &(g_usbInterfaces[i]);
memset(&usb_if_session, 0, sizeof(UsbHsClientIfSession));
/* Filter interface protocol. */
//if (usb_if->inf.interface_desc.bInterfaceProtocol != USB_PROTOCOL_BULK_ONLY_TRANSPORT && usb_if->inf.interface_desc.bInterfaceProtocol != USB_PROTOCOL_USB_ATTACHED_SCSI)
if (usb_if->inf.interface_desc.bInterfaceProtocol != USB_PROTOCOL_BULK_ONLY_TRANSPORT)
{
USBHSFS_LOG_MSG("Interface #%d (%d) discarded.", i, usb_if->inf.ID);
continue;
}
USBHSFS_LOG_MSG("Resetting USB Mass Storage device with interface %d.", usb_if->inf.ID);
/* Open current interface. */
rc = usbHsAcquireUsbIf(&usb_if_session, usb_if);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("usbHsAcquireUsbIf failed! (0x%08X) (interface %d).", rc, usb_if->inf.ID);
continue;
}
/* Perform a bus reset on this UMS device. */
rc = usbHsIfResetDevice(&usb_if_session);
if (R_FAILED(rc)) USBHSFS_LOG_MSG("usbHsIfResetDevice failed! (0x%08X) (interface %d).", rc, usb_if->inf.ID);
/* Close interface. */
usbHsIfClose(&usb_if_session);
}
/* Clear both interface events (not autoclear). */
eventClear(&g_usbInterfaceAvailableEvent);
eventClear(g_usbInterfaceStateChangeEvent);
}
static bool usbHsFsUpdateDriveContexts(bool remove)
{
Result rc = 0;
bool ret = false;
s32 usb_if_count = 0;
u32 ctx_count = 0;
/* Clear USB interfaces buffer. */
memset(g_usbInterfaces, 0, g_usbInterfacesMaxSize);
USBHSFS_LOG_MSG("Current drive count: %u.", g_driveCount);
if (remove)
{
/* Safety check: don't proceed if we haven't acquired any drives. */
if (!g_driveCount || !g_driveContexts) goto end;
/* We're dealing with at least one removed drive. Check which ones were removed and close their USB sessions. */
USBHSFS_LOG_MSG("Checking interfaces from previously acquired drives.");
rc = usbHsQueryAcquiredInterfaces(g_usbInterfaces, g_usbInterfacesMaxSize, &usb_if_count);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("usbHsQueryAcquiredInterfaces failed! (0x%08X).", rc);
goto end;
}
USBHSFS_LOG_MSG("usbHsQueryAcquiredInterfaces returned %d previously acquired interface(s).", usb_if_count);
/* Find out which drives were removed. */
for(u32 i = 0; i < g_driveCount; i++)
{
UsbHsFsDriveContext *cur_drive_ctx = g_driveContexts[i];
if (!cur_drive_ctx) continue;
bool found = false;
for(s32 j = 0; j < usb_if_count; j++)
{
UsbHsInterface *usb_if = &(g_usbInterfaces[j]);
if (usb_if->inf.ID == cur_drive_ctx->usb_if_session.ID)
{
found = true;
break;
}
}
if (!found)
{
/* Remove drive context from list and update drive index. */
USBHSFS_LOG_MSG("Removing drive context with ID %d.", cur_drive_ctx->usb_if_session.ID);
usbHsFsRemoveDriveContextFromListByIndex(i--, false);
ctx_count++;
}
}
} else {
/* Check if we have reached our limit. */
if (g_driveCount >= MAX_USB_INTERFACES) goto end;
/* Retrieve available USB interfaces. */
rc = usbHsQueryAvailableInterfaces(&g_usbInterfaceFilter, g_usbInterfaces, g_usbInterfacesMaxSize, &usb_if_count);
if (R_FAILED(rc))
{
USBHSFS_LOG_MSG("usbHsQueryAvailableInterfaces failed! (0x%08X).", rc);
goto end;
}
USBHSFS_LOG_MSG("usbHsQueryAvailableInterfaces returned %d interface(s) matching our filter.", usb_if_count);
/* Loop through the available USB interfaces. */
for(s32 i = 0; i < usb_if_count; i++)
{
UsbHsInterface *usb_if = &(g_usbInterfaces[i]);
USBHSFS_LOG_DATA(usb_if, sizeof(UsbHsInterface), "Interface #%d (%d) data:", i, usb_if->inf.ID);
/* Filter interface protocol. */
//if (usb_if->inf.interface_desc.bInterfaceProtocol != USB_PROTOCOL_BULK_ONLY_TRANSPORT && usb_if->inf.interface_desc.bInterfaceProtocol != USB_PROTOCOL_USB_ATTACHED_SCSI)
if (usb_if->inf.interface_desc.bInterfaceProtocol != USB_PROTOCOL_BULK_ONLY_TRANSPORT)
{
USBHSFS_LOG_MSG("Interface #%d (%d) discarded.", i, usb_if->inf.ID);
continue;
}
/* Add current interface to the drive context list. */
if (usbHsFsAddDriveContextToList(usb_if))
{
USBHSFS_LOG_MSG("Successfully added drive with ID %d to drive context list.", usb_if->inf.ID);
ctx_count++;
} else {
USBHSFS_LOG_MSG("Failed to add drive with ID %d to drive context list.", usb_if->inf.ID);
}
}
}
USBHSFS_LOG_MSG("%s %u drive context(s).", remove ? "Removed" : "Added", ctx_count);
/* Update return value. */
ret = (ctx_count > 0);
end:
return ret;
}
static void usbHsFsRemoveDriveContextFromListByIndex(u32 drive_ctx_idx, bool stop_lun)
{
UsbHsFsDriveContext *drive_ctx = NULL, **tmp_drive_ctx = NULL;
if (!g_driveContexts || !g_driveCount || drive_ctx_idx >= g_driveCount || !(drive_ctx = g_driveContexts[drive_ctx_idx])) return;
SCOPED_LOCK(&(drive_ctx->mutex)) usbHsFsDriveDestroyContext(drive_ctx, stop_lun);
free(drive_ctx);
drive_ctx = NULL;
USBHSFS_LOG_MSG("Destroyed drive context with index %u.", drive_ctx_idx);
if (g_driveCount > 1)
{
/* Move pointers within the drive context pointer array, if needed. */
if (drive_ctx_idx < (g_driveCount - 1))
{
u32 move_count = (g_driveCount - (drive_ctx_idx + 1));
memmove(&(g_driveContexts[drive_ctx_idx]), &(g_driveContexts[drive_ctx_idx + 1]), move_count * sizeof(UsbHsFsDriveContext*));
USBHSFS_LOG_MSG("Moved %u drive context pointer(s) within drive context pointer array.", move_count);
}
/* Reallocate drive context pointer array. */
tmp_drive_ctx = realloc(g_driveContexts, (g_driveCount - 1) * sizeof(UsbHsFsDriveContext*));
if (tmp_drive_ctx)
{
g_driveContexts = tmp_drive_ctx;
tmp_drive_ctx = NULL;
USBHSFS_LOG_MSG("Successfully reallocated drive context pointer array.");
}
} else {
/* Free drive context pointer array. */
free(g_driveContexts);
g_driveContexts = NULL;
USBHSFS_LOG_MSG("Freed drive context pointer array.");
}
/* Decrease drive count. */
g_driveCount--;
}
static bool usbHsFsAddDriveContextToList(UsbHsInterface *usb_if)
{
if (!usb_if)
{
USBHSFS_LOG_MSG("Invalid parameters!");
return false;
}
UsbHsFsDriveContext *drive_ctx = NULL, **tmp_drive_ctx = NULL;
bool ret = false;
USBHSFS_LOG_MSG("Adding drive context for interface %d.", usb_if->inf.ID);
/* Reallocate drive context pointer array. */
USBHSFS_LOG_MSG("Reallocating drive context pointer array from %u to %u (interface %d).", g_driveCount, g_driveCount + 1, usb_if->inf.ID);
tmp_drive_ctx = realloc(g_driveContexts, (g_driveCount + 1) * sizeof(UsbHsFsDriveContext*));
if (!tmp_drive_ctx)
{
USBHSFS_LOG_MSG("Failed to reallocate drive context pointer array! (interface %d).", usb_if->inf.ID);
goto end;
}
g_driveContexts = tmp_drive_ctx;
tmp_drive_ctx = NULL;
/* Allocate memory for new drive context. */
g_driveContexts[g_driveCount] = calloc(1, sizeof(UsbHsFsDriveContext));
if (!g_driveContexts[g_driveCount])
{
USBHSFS_LOG_MSG("Failed to allocate memory for new drive context! (interface %d).", usb_if->inf.ID);
goto end;
}
drive_ctx = g_driveContexts[g_driveCount++]; /* Increase drive count. */
/* Initialize drive context. */
/* We don't need to lock its mutex - it's a new drive context the user knows nothing about. */
ret = usbHsFsDriveInitializeContext(drive_ctx, usb_if);
if (!ret)
{
free(drive_ctx);
drive_ctx = NULL;
if (g_driveCount > 1)
{
/* Reallocate drive context pointer array. */
tmp_drive_ctx = realloc(g_driveContexts, (g_driveCount - 1) * sizeof(UsbHsFsDriveContext*));
if (tmp_drive_ctx)
{
g_driveContexts = tmp_drive_ctx;
tmp_drive_ctx = NULL;
USBHSFS_LOG_MSG("Successfully reallocated drive context pointer array.");
}
} else {
/* Free drive context pointer array. */
free(g_driveContexts);
g_driveContexts = NULL;
USBHSFS_LOG_MSG("Freed drive context pointer array.");
}
/* Decrease drive count. */
g_driveCount--;
}
end:
return ret;
}
static void usbHsFsFillDeviceElement(UsbHsFsDriveContext *drive_ctx, UsbHsFsDriveLogicalUnitContext *lun_ctx, UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx, UsbHsFsDevice *device)
{
memset(device, 0, sizeof(UsbHsFsDevice));
device->usb_if_id = drive_ctx->usb_if_id;
device->lun = lun_ctx->lun;
device->fs_idx = fs_ctx->fs_idx;
device->write_protect = lun_ctx->write_protect;
device->vid = drive_ctx->vid;
device->pid = drive_ctx->pid;
snprintf(device->manufacturer, sizeof(device->manufacturer), "%s", (drive_ctx->manufacturer ? drive_ctx->manufacturer : lun_ctx->vendor_id));
snprintf(device->product_name, sizeof(device->product_name), "%s", (drive_ctx->product_name ? drive_ctx->product_name : lun_ctx->product_id));
if (drive_ctx->serial_number) snprintf(device->serial_number, sizeof(device->serial_number), "%s", drive_ctx->serial_number);
device->capacity = lun_ctx->capacity;
sprintf(device->name, "%s:", fs_ctx->name);
switch(fs_ctx->fs_type)
{
case UsbHsFsDriveLogicalUnitFileSystemType_FAT:
device->fs_type = fs_ctx->fatfs->fs_type; /* FatFs type values correlate with our UsbHsFsDeviceFileSystemType enum. */
break;
#ifdef GPL_BUILD
case UsbHsFsDriveLogicalUnitFileSystemType_NTFS:
device->fs_type = UsbHsFsDeviceFileSystemType_NTFS;
break;
case UsbHsFsDriveLogicalUnitFileSystemType_EXT:
device->fs_type = fs_ctx->ext->version;
break;
#endif
/* TODO: populate this after adding support for additional filesystems. */
default:
break;
}
device->flags = fs_ctx->flags;
}