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