Send srvroot listing for empty selector

This commit is contained in:
Camden Dixie O'Brien 2022-10-13 17:00:08 +01:00
parent fa0dbed44c
commit 5c03ed950f

138
main.c
View File

@ -16,17 +16,33 @@
* <https://www.gnu.org/licenses/>.
*/
#include <dirent.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#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);