From 5c03ed950f3425f3869aaeec4b16423e60b78470 Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Thu, 13 Oct 2022 17:00:08 +0100 Subject: [PATCH] Send srvroot listing for empty selector --- main.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 128 insertions(+), 10 deletions(-) diff --git a/main.c b/main.c index 0b4b774..ff84858 100644 --- a/main.c +++ b/main.c @@ -16,17 +16,33 @@ * . */ +#include #include #include #include #include -#include #include +#include +#include #include +#include #include +#define HOST "::1" +#define PORT 7070 + +#define PBUF_SIZE 1024 +#define RBUF_SIZE 1024 #define SBUF_SIZE 1024 +/* + * Enumeration of directory entry types. + * + * The numeric values are significant here (hence them being set + * explicitly): they match those defined in RFC 1436. + */ +enum etype { ETYPE_FILE = 0, ETYPE_DIR = 1 }; + static bool exit_requested = false; void handle_exit_signal(int signum) @@ -35,12 +51,40 @@ void handle_exit_signal(int signum) exit_requested = true; } +static int retrying_write(int fd, const char *buf, size_t len) +{ + int n; + do { + errno = 0; + n = write(fd, buf, len); + } while (errno == EINTR); + return n; +} + int main(int argc, char *argv[]) { (void)argc; (void)argv; int res = EXIT_SUCCESS; + static char pbuf[PBUF_SIZE], rbuf[RBUF_SIZE], sbuf[SBUF_SIZE]; + + /* + * Get srvroot path from arguments and copy into pbuf. + * + * The srvroot being at the start of pbuf should be maintained + * through the whole application. + */ + if (argc < 2) { + fprintf(stderr, "Usage: %s srvroot\n", argv[0]); + return EXIT_FAILURE; + } + const size_t srvroot_len = strlen(argv[1]); + if (srvroot_len > PBUF_SIZE) { + fprintf(stderr, "srvroot path is too long\n"); + return EXIT_FAILURE; + } + memcpy(pbuf, argv[1], srvroot_len); /* * Register signal handler for SIGTERM and SIGINT. @@ -72,7 +116,7 @@ int main(int argc, char *argv[]) } const struct sockaddr_in6 haddr = { .sin6_family = AF_INET6, - .sin6_port = htons(7070), + .sin6_port = htons(PORT), .sin6_addr = IN6ADDR_LOOPBACK_INIT, }; if (bind(sfd, (const struct sockaddr *)&haddr, sizeof(haddr)) == -1) { @@ -89,8 +133,8 @@ int main(int argc, char *argv[]) struct sockaddr_in6 paddr; socklen_t paddr_size = sizeof(paddr); int cfd; - char sbuf[SBUF_SIZE]; ssize_t n, slen; + DIR *rdir; while (!exit_requested) { /* * Accept incoming connection. @@ -143,16 +187,90 @@ int main(int argc, char *argv[]) } /* - * Write an empty response to the client. + * Open the requested resource. + * + * For now, this is just opening srvroot. */ - do { - errno = 0; - n = write(cfd, ".\r\n", 3); - } while (errno == EINTR); - if (n == -1) { - fprintf(stderr, "Error sending response to client\n"); + if (srvroot_len + 1 > PBUF_SIZE) { + fprintf(stderr, "Path buffer is too small\n"); goto close_client_socket; } + pbuf[srvroot_len] = '\0'; + do { + errno = 0; + rdir = opendir(pbuf); + } while (errno == EINTR); + if (rdir == NULL) { + fprintf(stderr, "Failed to open %s\n", pbuf); + goto close_client_socket; + } + + /* + * Write a line for each entry in the directory to the client. + */ + struct dirent *ent; + struct stat rstat; + unsigned namelen; + while ((ent = readdir(rdir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + + /* + * Construct full path to entry in pbuf. + * + * This is needed in order to pass it to stat() later. The + * path must be null-terminated. + */ + namelen = strlen(ent->d_name); + if (srvroot_len + namelen + 2 > PBUF_SIZE) { + fprintf(stderr, "Path buffer is too small\n"); + goto close_client_socket; + } + pbuf[srvroot_len] = '/'; + memcpy(&pbuf[srvroot_len + 1], ent->d_name, namelen); + pbuf[srvroot_len + 1 + namelen] = '\0'; + + /* + * Identify entry type from file inode information. + */ + if (stat(pbuf, &rstat) == -1) { + fprintf(stderr, + "Failed to stat() path \"%s\", skipping entry\n", pbuf); + continue; + } + enum etype type; + if (S_ISREG(rstat.st_mode)) + type = ETYPE_FILE; + else if (S_ISDIR(rstat.st_mode)) + type = ETYPE_DIR; + else + continue; + + /* + * Format and send response line for current entry. + */ + n = snprintf(rbuf, RBUF_SIZE, "%1u%s\t%s\t%s\t%u\r\n", type, + ent->d_name, &pbuf[srvroot_len], HOST, PORT); + if (n >= RBUF_SIZE) { + fprintf(stderr, + "Response buffer was too small, skipping entry\n"); + continue; + } + if (retrying_write(cfd, rbuf, n) != n) { + fprintf(stderr, + "Error sending respose line to client\n"); + continue; + } + } + if (retrying_write(cfd, ".\r\n", 3) != 3) { + fprintf(stderr, "Error sending response terminator to client\n"); + goto close_client_socket; + } + + /* + * Close the resource. + */ + closedir(rdir); close_client_socket: close(cfd);