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);