diff --git a/Makefile b/Makefile index 0e8a08f..8574d1c 100644 --- a/Makefile +++ b/Makefile @@ -48,9 +48,10 @@ cp bin/ps2symbolize $(DESTDIR)$(PREFIX)/bin endif - check: bin/ps2client bin/smoke bin/fake_ps2link bin/crc32 bin/ps2symbolize bin/test_symbolize + check: bin/ps2client bin/fsclient bin/smoke bin/fake_ps2link bin/crc32 bin/ps2symbolize bin/test_symbolize bin/fsclient_smoke bin/smoke $(CURDIR)/bin/ps2client bin/test_symbolize + bin/fsclient_smoke $(CURDIR)/bin/fsclient #################### ## CLIENT MODULES ## @@ -107,3 +108,7 @@ bin/test_symbolize: tests/test_symbolize.c tools/ps2symbolize.c @mkdir -p bin $(CC) $(CFLAGS) $< -o $@ + + bin/fsclient_smoke: tests/fsclient_smoke.c + @mkdir -p bin + $(CC) $(CFLAGS) $< -o $@ -lpthread diff --git a/src/fsclient.c b/src/fsclient.c index 2048575..29ac615 100644 --- a/src/fsclient.c +++ b/src/fsclient.c @@ -24,7 +24,7 @@ for (loop0=0; env[loop0]; loop0++) { // A hostname has been specified... - if (strncmp(env[loop0], "PS2HOSTNAME", 11) == 0) { snprintf(hostname, sizeof(hostname), "%s", &env[loop0][12]); } + if (strncmp(env[loop0], "PS2HOSTNAME=", 12) == 0) { snprintf(hostname, sizeof(hostname), "%s", &env[loop0][12]); } } @@ -55,6 +55,10 @@ // Check to make sure a command was actually supplied. if (argc < 0) { printf("Error: No command was supplied.\n"); print_usage(); return -1; } + if ((strcmp(argv[-1], "copyfrom") == 0 || strcmp(argv[-1], "copyto") == 0) && argc < 2) { + fprintf(stderr, "Error: Copy commands need source and destination paths.\n"); + return -1; + } // Startup network if (network_startup() < 0) { printf("Error: Could not startup network.\n"); return 1; } @@ -89,112 +93,93 @@ // FSCLIENT FUNCTIONS // //////////////////////// - int fsclient_copyfrom(char *source, char *destination) { int result = 0; - int fd0, fd1, size, total = 0; char buffer[28000]; + int fsclient_copyfrom(char *source, char *destination) { + int fd0 = -1, fd1 = -1, size, total = 0, status = -1; + char buffer[28000]; - // Open the source file. fd0 = ps2netfs_command_open(source, OPEN_READ); - if (fd0 < 0) { printf("Error: Open source file failed. (%d)\n", fd0); return -1; } + if (fd0 < 0) { printf("Error: Open source file failed. (%d)\n", fd0); goto done; } - // Get the source file size. - size = ps2netfs_command_lseek(fd0, 0, LSEEK_END); ps2netfs_command_lseek(fd0, 0, LSEEK_SET); - if (size < 0) { printf("Error: Get source file size failed. (%d)\n", size); return -1; } + size = ps2netfs_command_lseek(fd0, 0, LSEEK_END); + if (size < 0 || ps2netfs_command_lseek(fd0, 0, LSEEK_SET) < 0) { + printf("Error: Get source file size failed.\n"); goto done; + } - // Open the destination file. #if defined (__CYGWIN__) || defined (__MINGW32__) - fd1 = open(destination, O_RDWR | O_CREAT | O_BINARY, 0644); + fd1 = open(destination, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); #else - fd1 = open(destination, O_RDWR | O_CREAT, 0644); + fd1 = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0644); #endif - if (fd1 < 1) { printf("Error: Open destination file failed. (%d)\n", fd1); return -1; } + if (fd1 < 0) { printf("Error: Open destination file failed. (%d)\n", fd1); goto done; } - // Output the display header. printf("\n [%s --> %s]\n\n Progress: ", source, destination); - - // Repeat until the copy is finished. - while (total < size) { printf("#"); - - // Read the source data. - result = ps2netfs_command_read(fd0, buffer, sizeof(buffer)); - if (result < 0) { printf("Error: Read source data failed. (%d)\n", result); return -1; } - - // Write the destination data. - result = write(fd1, buffer, result); - if (result < 0) { printf("Error: Write destination data failed. (%d)\n", result); return -1; } - - // Increment the counter. - total += result; - + while (total < size) { + int count = size - total < (int)sizeof(buffer) ? size - total : sizeof(buffer); + int bytes = ps2netfs_command_read(fd0, buffer, count); + int sent = 0; + if (bytes <= 0 || bytes > count) { + printf("Error: Unexpected source read result. (%d)\n", bytes); goto done; + } + while (sent < bytes) { + int written = write(fd1, buffer + sent, bytes - sent); + if (written <= 0) { printf("Error: Local file write failed.\n"); goto done; } + sent += written; + } + total += bytes; + printf("#"); } - // Output the display footer. - printf("\n\n [%d Bytes Copied]\n\n", total); - - // Close the source file. - result = ps2netfs_command_close(fd0); - if (result < 0) { printf("Error: Close source file failed. (%d)\n", result); return -1; } - - // Close the destination file. - result = close(fd1); - if (result < 0) { printf("Error: Close destination file failed. (%d)\n", result); return -1; } - - // End function. - return 0; - + status = 0; + done: + if (fd0 >= 0 && ps2netfs_command_close(fd0) < 0) { status = -1; } + if (fd1 >= 0 && close(fd1) < 0) { status = -1; } + if (status == 0) { printf("\n\n [%d Bytes Copied]\n\n", total); } + return status; } - int fsclient_copyto(char *source, char *destination) { int result = 0; - int fd0, fd1, size, total = 0; char buffer[28000]; + int fsclient_copyto(char *source, char *destination) { + int fd0 = -1, fd1 = -1, size, total = 0, status = -1; + char buffer[28000]; - // Open the source file. #if defined (__CYGWIN__) || defined (__MINGW32__) fd0 = open(source, O_RDONLY | O_BINARY); #else fd0 = open(source, O_RDONLY); #endif - if (fd0 < 0) { printf("Error: Open source file failed. (%d)\n", fd0); return -1; } + if (fd0 < 0) { printf("Error: Open source file failed. (%d)\n", fd0); goto done; } - // Get the source file size. - size = lseek(fd0, 0, SEEK_END); lseek(fd0, 0, SEEK_SET); - if (size < 0) { printf("Error: Get source file size failed. (%d)\n", size); return -1; } + size = lseek(fd0, 0, SEEK_END); + if (size < 0 || lseek(fd0, 0, SEEK_SET) < 0) { + printf("Error: Get source file size failed.\n"); goto done; + } - // Open the destination file. - fd1 = ps2netfs_command_open(destination, OPEN_WRITE | OPEN_CREATE); - if (fd0 < 1) { printf("Error: Open destination file failed. (%d)\n", fd1); return -1; } + fd1 = ps2netfs_command_open(destination, OPEN_WRITE | OPEN_CREATE | OPEN_TRUNCATE); + if (fd1 < 0) { printf("Error: Open destination file failed. (%d)\n", fd1); goto done; } - // Output the display header. printf("\n [%s --> %s]\n\n Progress: ", source, destination); - - // Repeat until the copy is finished. - while (total < size) { printf("#"); - - // Read the source data. - result = read(fd0, buffer, sizeof(buffer)); - if (result < 0) { printf("Error: Read source data failed. (%d)\n", result); return -1; } - - // Write the destination data. - result = ps2netfs_command_write(fd1, buffer, result); - if (result < 0) { printf("Error: Write destination data failed. (%d)\n", result); return -1; } - - // Increment the counter. - total += result; - + while (total < size) { + int count = size - total < (int)sizeof(buffer) ? size - total : sizeof(buffer); + // Count never exceeds buffer capacity; the destination handles partial writes. + int bytes = read(fd0, buffer, count); // Flawfinder: ignore + int sent = 0; + if (bytes <= 0) { printf("Error: Local file read failed. (%d)\n", bytes); goto done; } + while (sent < bytes) { + int written = ps2netfs_command_write(fd1, buffer + sent, bytes - sent); + if (written <= 0 || written > bytes - sent) { + printf("Error: Remote file write failed. (%d)\n", written); goto done; + } + sent += written; + } + total += bytes; + printf("#"); } - // Output the display footer. - printf("\n\n [%d Bytes Copied]\n\n", total); - - // Close the source file. - result = close(fd0); - if (result < 0) { printf("Error: Close source file failed. (%d)\n", result); return -1; } - - // Close the destination file. - result = ps2netfs_command_close(fd1); - if (result < 0) { printf("Error: Close destination file failed. (%d)\n", result); return -1; } - - // End function. - return 0; - + status = 0; + done: + if (fd0 >= 0 && close(fd0) < 0) { status = -1; } + if (fd1 >= 0 && ps2netfs_command_close(fd1) < 0) { status = -1; } + if (status == 0) { printf("\n\n [%d Bytes Copied]\n\n", total); } + return status; } int fsclient_delete(char *pathname) { int result = 0; diff --git a/src/ps2netfs.c b/src/ps2netfs.c index 30fba42..69d92fe 100644 --- a/src/ps2netfs.c +++ b/src/ps2netfs.c @@ -41,12 +41,16 @@ int ps2netfs_command_open(char *pathname, int flags) { struct { unsigned int number; unsigned short length; int flags; char pathname[256]; } PACKED command; struct { unsigned int number; unsigned short length; int result; } PACKED response; + int length; // Build the command packet. + if (pathname == NULL) { return -1; } + memset(&command, 0, sizeof(command)); command.number = htonl(PS2NETFS_COMMAND_OPEN); command.length = htons(sizeof(command)); command.flags = htonl(flags); - if (pathname) { strcpy(command.pathname, pathname); } + length = snprintf(command.pathname, sizeof(command.pathname), "%s", pathname); + if (length < 0 || length >= (int)sizeof(command.pathname)) { return -1; } // Send the command packet. if (network_send(ps2netfs_socket, &command, sizeof(command)) < 0) { return -1; } @@ -82,6 +86,9 @@ int ps2netfs_command_read(int fd, void *buffer, int size) { struct { unsigned int number; unsigned short length; int fd, size; } PACKED command; struct { unsigned int number; unsigned short length; int result, size; } PACKED response; + int received; + + if (size < 0) { return -1; } // Build the command packet. command.number = htonl(PS2NETFS_COMMAND_READ); @@ -96,10 +103,13 @@ if (network_receive_all(ps2netfs_socket, &response, sizeof(response)) < 0) { return -1; } // Receive the response data. - if (network_receive_all(ps2netfs_socket, buffer, ntohl(response.result)) < 0) { return -1; } + received = ntohl(response.result); + if (received < 0) { return received; } + if (received > size) { return -1; } + if (network_receive_all(ps2netfs_socket, buffer, received) < 0) { return -1; } // End function. - return ntohl(response.result); + return received; } diff --git a/tests/fsclient_smoke.c b/tests/fsclient_smoke.c new file mode 100644 index 0000000..a43fca4 --- /dev/null +++ b/tests/fsclient_smoke.c @@ -0,0 +1,217 @@ +/* Fake ps2netfs loopback tests for short transfers and malformed replies. */ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT 0x4713 +#define OPEN 0xBEEF8011u +#define CLOSE 0xBEEF8021u +#define READ 0xBEEF8031u +#define WRITE 0xBEEF8041u +#define LSEEK 0xBEEF8051u + +struct server { + int listener; + int mode; /* 1 short reads, 2 short writes, 3 malformed read */ + int errors; + size_t position, bytes_received; + char received[128]; +}; +static const char from_content[] = "partial-read-content"; +static const char to_content[] = "partial-write-content"; + +static uint32_t get32(const unsigned char *p) +{ + uint32_t v; + memcpy(&v, p, sizeof(v)); + return ntohl(v); +} +static void set32(unsigned char *p, uint32_t v) +{ + v = htonl(v); + memcpy(p, &v, sizeof(v)); +} +static int read_all(int s, void *buffer, size_t size) +{ + size_t done = 0; + while (done < size) { + ssize_t n = recv(s, (char *)buffer + done, size - done, 0); + if (n <= 0) return -1; + done += (size_t)n; + } + return 0; +} +static int send_all(int s, const void *buffer, size_t size) +{ + size_t done = 0; + while (done < size) { + ssize_t n = send(s, (const char *)buffer + done, size - done, 0); + if (n <= 0) return -1; + done += (size_t)n; + } + return 0; +} +static void *serve(void *context) +{ + struct server *state = context; + unsigned char hdr[6], packet[1024], reply[14]; + int client = accept(state->listener, NULL, NULL); + if (client < 0) { state->errors++; return NULL; } + for (;;) { + uint32_t command, result = 0; + uint16_t length; + const char *data = NULL; + size_t data_len = 0; + if (read_all(client, hdr, sizeof(hdr)) < 0) break; + command = get32(hdr); + memcpy(&length, hdr + 4, 2); length = ntohs(length); + if (length < 6 || length - 6 > sizeof(packet) || + read_all(client, packet, length - 6) < 0) { + state->errors++; break; + } + if (command == OPEN) { + if (state->mode == 2 && !(get32(packet) & 0x0400)) state->errors++; + result = 3; + } else if (command == CLOSE) { + result = 0; + } else if (command == LSEEK) { + uint32_t whence = get32(packet + 8); + state->position = whence == 2 ? (state->mode == 3 ? 1 : + sizeof(from_content) - 1) : + (size_t)get32(packet + 4); + result = (uint32_t)state->position; + } else if (command == READ) { + size_t requested = get32(packet + 4); + if (state->mode == 3) { + result = 4096; /* Deliberately inconsistent with request. */ + } else { + size_t remaining = (sizeof(from_content) - 1) - state->position; + data_len = remaining < requested ? remaining : requested; + if (data_len > 4) data_len = 4; + data = from_content + state->position; + state->position += data_len; + result = (uint32_t)data_len; + } + } else if (command == WRITE) { + unsigned char buffer[28000]; + size_t count = get32(packet + 4); + if (count > sizeof(buffer) || read_all(client, buffer, count) < 0) { + state->errors++; break; + } + result = count > 4 ? 4 : (uint32_t)count; + if (state->bytes_received + result > sizeof(state->received)) { + state->errors++; break; + } + memcpy(state->received + state->bytes_received, buffer, result); + state->bytes_received += result; + } else { state->errors++; break; } + set32(reply, command + 1); + { uint16_t rlen = htons(command == READ ? 14 : 10); + memcpy(reply + 4, &rlen, 2); } + set32(reply + 6, result); + if (command == READ) set32(reply + 10, result); + if (send_all(client, reply, command == READ ? 14 : 10) < 0 || + (data_len && send_all(client, data, data_len) < 0)) { + state->errors++; break; + } + } + close(client); + return NULL; +} +static int start_server(struct server *state, pthread_t *thread) +{ + struct sockaddr_in addr = {0}; + int reuse = 1; + state->listener = socket(AF_INET, SOCK_STREAM, 0); + if (state->listener < 0) return -1; + setsockopt(state->listener, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + addr.sin_family = AF_INET; + addr.sin_port = htons(PORT); + addr.sin_addr.s_addr = htonl(0x7f000001u); + if (bind(state->listener, (struct sockaddr *)&addr, sizeof(addr)) < 0 || + listen(state->listener, 1) < 0 || pthread_create(thread, NULL, serve, state)) { + close(state->listener); return -1; + } + return 0; +} +static int run_client(const char *executable, const char *command, + const char *source, const char *dest) +{ + int status; + pid_t pid = fork(); + if (pid < 0) return -1; + if (!pid) { + int devnull = open("/dev/null", O_WRONLY); + if (devnull >= 0) { + dup2(devnull, STDOUT_FILENO); + dup2(devnull, STDERR_FILENO); + close(devnull); + } + execl(executable, executable, "-h", "127.0.0.1", + command, source, dest, (char *)NULL); + _exit(127); + } + if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status)) return -1; + return WEXITSTATUS(status); +} +static int test_case(const char *exe, int mode) +{ + struct server state = {0}; + pthread_t worker; + char name[] = "/tmp/fsclient-test-XXXXXX"; + int fd, rc, failed = 0; + state.mode = mode; + fd = mkstemp(name); + if (fd < 0) return 1; + if (mode == 2) { + if (write(fd, to_content, sizeof(to_content) - 1) != + (ssize_t)(sizeof(to_content) - 1)) failed = 1; + } else { + write(fd, "old contents longer than result", 31); + } + close(fd); + if (start_server(&state, &worker)) { + perror("start fake ps2netfs"); unlink(name); return 1; + } + rc = mode == 2 ? run_client(exe, "copyto", name, "mass:/file") : + run_client(exe, "copyfrom", "mass:/file", name); + pthread_join(worker, NULL); + close(state.listener); + if (state.errors) failed = 1; + if (mode == 1) { + fd = open(name, O_RDONLY); + if (fd < 0) failed = 1; + else { + char result[128]; + ssize_t n = read(fd, result, sizeof(result)); /* Flawfinder: ignore */ + if (n != (ssize_t)(sizeof(from_content) - 1) || + memcmp(result, from_content, sizeof(from_content) - 1)) failed = 1; + close(fd); + } + if (rc != 0) failed = 1; + } else if (mode == 2) { + if (rc != 0 || state.bytes_received != sizeof(to_content) - 1 || + memcmp(state.received, to_content, sizeof(to_content) - 1)) failed = 1; + } else if (rc == 0) failed = 1; + unlink(name); + printf("%s %s\n", failed ? "FAIL" : "PASS", + mode == 1 ? "short remote reads" : mode == 2 ? + "short remote writes" : "oversized remote read rejection"); + return failed; +} +int main(int argc, char **argv) +{ + int failures = 0, mode; + if (argc != 2) { fprintf(stderr, "Usage: %s fsclient\n", argv[0]); return 2; } + for (mode = 1; mode <= 3; mode++) failures += test_case(argv[1], mode); + return failures ? 1 : 0; +}