From c231b891e4ac2ab945731fad2d80a453f1a85270 Mon Sep 17 00:00:00 2001 From: Francisco Javier Trujillo Mata Date: Mon, 26 Oct 2026 10:01:03 +0100 Subject: [PATCH] Fix HostFS directory enumeration, cleanup and DREAD replies Free scandir results after successful enumerations, preserve dangling symbolic links via lstat, clear released directory pointers, and return the correct HostFS DREAD reply command. Add a native C regression test for enumeration, replies and repeated open/close. Fixes #57. --- tests/Makefile | 9 +++ tests/hostfs_directory_test.c | 100 ++++++++++++++++++++++++++++++++++ usbhostfs_pc/main.c | 16 +++--- 3 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 tests/Makefile create mode 100644 tests/hostfs_directory_test.c diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..7640f67 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,9 @@ +CC ?= cc +HOST_CFLAGS = -g -O1 -DPC_SIDE -D_FILE_OFFSET_BITS=64 -I../usbhostfs -I../usbhostfs_pc $(shell pkg-config --cflags libusb-1.0) +HOST_LIBS = -lpthread $(shell pkg-config --libs libusb-1.0) + +.PHONY: test +test: + @bin=$$(mktemp /tmp/psplinkusb-hostfs-test.XXXXXX); \ + trap 'rm -f "$$bin"' EXIT; \ + $(CC) $(HOST_CFLAGS) -o "$$bin" hostfs_directory_test.c $(HOST_LIBS) && "$$bin" diff --git a/tests/hostfs_directory_test.c b/tests/hostfs_directory_test.c new file mode 100644 index 0000000..b67ede1 --- /dev/null +++ b/tests/hostfs_directory_test.c @@ -0,0 +1,100 @@ +/* + * HostFS directory regression test. Include the implementation so its + * otherwise-internal directory helpers can be exercised without USB hardware. + */ +#define main usbhostfs_pc_main +#include "../usbhostfs_pc/main.c" +#undef main + +#include + +/* Exercise the on-wire directory response without a physical USB device. */ +static int usb_write_count; +static struct HostFsDreadResp usb_dread_response; +static SceIoDirent usb_dread_entry; + +int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev, unsigned char endpoint, + unsigned char *data, int length, int *transferred, unsigned int timeout) +{ + (void) dev; + (void) timeout; + assert(endpoint == 0x02); + if(usb_write_count == 0) + { + assert(length == sizeof(usb_dread_response)); + memcpy(&usb_dread_response, data, length); + } + else if(usb_write_count == 1) + { + assert(length == sizeof(usb_dread_entry)); + memcpy(&usb_dread_entry, data, length); + } + else + { + assert(0 && "unexpected USB transfer"); + } + usb_write_count++; + *transferred = length; + return LIBUSB_SUCCESS; +} + +int main(void) +{ + char root[] = "/tmp/psplinkusb-dir-XXXXXX"; + char linkpath[PATH_MAX]; + const char *iterations = getenv("HOSTFS_TEST_ITERATIONS"); + int repetitions = iterations ? atoi(iterations) : 1000; + int did; + int found_link = 0; + int i; + + assert(repetitions > 0); + assert(mkdtemp(root) != NULL); + assert(snprintf(linkpath, sizeof(linkpath), "%s/broken-link", root) < (int)sizeof(linkpath)); + assert(symlink("missing-target", linkpath) == 0); + + init_hostfs(); + strcpy(g_drives[0].rootdir, root); + + did = dir_open(0, "/"); + assert(did >= 0); + for(i = 0; i < open_dirs[did].count; i++) + { + if(strcmp(open_dirs[did].pDir[i].name, "broken-link") == 0) + { + found_link = 1; + assert(LE32(open_dirs[did].pDir[i].stat.mode) & FIO_S_IFLNK); + } + } + assert(found_link); + { + struct HostFsDreadCmd cmd; + int ret; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd.magic = LE32(HOSTFS_MAGIC); + cmd.cmd.command = LE32(HOSTFS_CMD_DREAD); + cmd.did = LE32(did); + ret = handle_dread(NULL, &cmd, sizeof(cmd)); + assert(ret == sizeof(usb_dread_entry)); + assert(usb_write_count == 2); + assert(LE32(usb_dread_response.cmd.magic) == HOSTFS_MAGIC); + assert(LE32(usb_dread_response.cmd.command) == HOSTFS_CMD_DREAD); + assert(LE32(usb_dread_response.cmd.extralen) == sizeof(SceIoDirent)); + assert(LE32(usb_dread_response.res) > 0); + assert(usb_dread_entry.name[0] != '\0'); + } + assert(dir_close(did) == 0); + + /* Run this under a leak sanitizer to catch scandir allocations on success. */ + for(i = 0; i < repetitions; i++) + { + did = dir_open(0, "/"); + assert(did >= 0); + assert(dir_close(did) == 0); + } + + assert(unlink(linkpath) == 0); + assert(rmdir(root) == 0); + puts("HostFS directory test passed"); + return 0; +} diff --git a/usbhostfs_pc/main.c b/usbhostfs_pc/main.c index c37d942..5ed30ef 100644 --- a/usbhostfs_pc/main.c +++ b/usbhostfs_pc/main.c @@ -698,7 +698,8 @@ int fill_stat(const char *dirname, const char *name, SceIoStat *scestat) strcpy(path, name); } - if(stat(path, &st) < 0) + /* Preserve dangling symlinks as directory entries rather than failing the listing. */ + if(lstat(path, &st) < 0) { fprintf(stderr, "Couldn't stat file %s (%s)\n", path, strerror(errno)); return GETERROR(errno); @@ -804,6 +805,7 @@ int dir_open(int drive, const char *dirname) else { free(open_dirs[did].pDir); + open_dirs[did].pDir = NULL; } } else @@ -811,14 +813,12 @@ int dir_open(int drive, const char *dirname) fprintf(stderr, "Could not allocate memory for directories\n"); } - if(ret < 0) + /* scandir owns these allocations even when the listing succeeds. */ + for(i = 0; i < dirnum; i++) { - for(i = 0; i < dirnum; i++) - { - free(entries[i]); - } - free(entries); + free(entries[i]); } + free(entries); } while(0); @@ -1230,7 +1230,7 @@ int handle_dread(libusb_device_handle *dev, struct HostFsDreadCmd *cmd, int cmdl memset(&resp, 0, sizeof(resp)); resp.cmd.magic = LE32(HOSTFS_MAGIC); - resp.cmd.command = LE32(HOSTFS_CMD_READ); + resp.cmd.command = LE32(HOSTFS_CMD_DREAD); resp.res = LE32(-1); do