serve

serve.git
git clone git://git.lenczewski.org/serve.git
Log | Files | Refs

main.c (6802B)


      1 #include "website.h"
      2 
      3 struct opts opts = {
      4 	.verbose = 0,
      5 	.host = "localhost",
      6 	.port = "8080",
      7 };
      8 
      9 #define OPTSTR "hva:p:"
     10 static void
     11 usage(char *prog)
     12 {
     13 	fprintf(stderr, "Usage: %s [-hv] [-a <addr>] [-p <port>] <webroot>\n", prog);
     14 	fprintf(stderr, "\t-h : display this help information\n");
     15 	fprintf(stderr, "\t-v : enable verbose logging\n");
     16 	fprintf(stderr, "\t-a : set the server socket bind address (default: localhost)\n");
     17 	fprintf(stderr, "\t-p : set the server socket bind port (default: 8080)\n");
     18 	fprintf(stderr, "\twebroot : directory out of which to serve files\n");
     19 }
     20 
     21 static int
     22 parse_opts(int argc, char **argv)
     23 {
     24 	int opt;
     25 	while ((opt = getopt(argc, argv, OPTSTR)) > 0) {
     26 		switch (opt) {
     27 		case 'v':
     28 			opts.verbose = 1;
     29 			break;
     30 
     31 		case 'a':
     32 			opts.host = optarg;
     33 			break;
     34 
     35 		case 'p':
     36 			opts.port = optarg;
     37 			break;
     38 
     39 		default:
     40 			return -1;
     41 		}
     42 	}
     43 
     44 	if (optind >= argc) {
     45 		fprintf(stderr, "Missing webroot\n");
     46 		return -1;
     47 	}
     48 
     49 	opts.webroot = argv[optind];
     50 
     51 	return 0;
     52 }
     53 
     54 sig_atomic_t quit;
     55 
     56 static void
     57 close_handler(int sig, siginfo_t *info, void *uctx)
     58 {
     59 	(void) info;
     60 	(void) uctx;
     61 
     62 	fprintf(stderr, "Signal %d caught. Quitting\n", sig);
     63 
     64 	quit = 1;
     65 }
     66 
     67 sig_atomic_t reload_tree;
     68 
     69 static void
     70 reload_handler(int sig, siginfo_t *info, void *uctx)
     71 {
     72 	(void) info;
     73 	(void) uctx;
     74 
     75 	fprintf(stderr, "Signal %d caught. Reloading file tree\n", sig);
     76 
     77 	reload_tree = 1;
     78 }
     79 
     80 int
     81 serve(struct io_uring *ioring, int webroot, int sock, struct connection_pool *pool)
     82 {
     83 	listen(sock, SERVER_LISTEN_BACKLOG);
     84 
     85 	struct sockaddr_storage client_addr;
     86 	socklen_t client_addrlen = sizeof client_addr;
     87 
     88 	struct ioreq accept_req = {
     89 		.type = IOREQ_ACCEPT,
     90 		.fd = sock,
     91 		.accept.addr = (struct sockaddr *) &client_addr,
     92 		.accept.addrlen = &client_addrlen,
     93 		.accept.flags = 0,
     94 	};
     95 
     96 	enqueue_ioreq(ioring, &accept_req);
     97 	submit_ioreqs(ioring);
     98 
     99 	quit = 0;
    100 	while (!quit) {
    101 		struct io_uring_cqe *cqe;
    102 		if (io_uring_wait_cqe(ioring, &cqe) < 0) {
    103 			fprintf(stderr, "io_uring_wait_cqe() failed\n");
    104 			return EXIT_FAILURE;
    105 		}
    106 
    107 		unsigned head, seen = 0;
    108 		io_uring_for_each_cqe(ioring, head, cqe) {
    109 			struct ioreq *ioreq = io_uring_cqe_get_data(cqe);
    110 			if (!ioreq) { /* overloaded client error messages */
    111 				// TODO: do something?
    112 			}
    113 
    114 			int res = cqe->res;
    115 			switch (ioreq->type) {
    116 			case IOREQ_ACCEPT: {
    117 				if (res < 0) { /* multishot failed, restart? */
    118 					fprintf(stderr, "multishot accept failed\n");
    119 					return EXIT_FAILURE;
    120 				}
    121 
    122 				struct connection *conn = connection_pool_alloc(pool);
    123 				if (!conn) { /* overloaded server */
    124 					close(res);
    125 					goto next_cqe;
    126 				}
    127 
    128 				if (opts.verbose) {
    129 				char host[NI_MAXHOST];
    130 				char port[NI_MAXSERV];
    131 				getnameinfo((struct sockaddr *) &client_addr,
    132 					    client_addrlen,
    133 					    host, sizeof host,
    134 					    port, sizeof port,
    135 					    NI_NUMERICHOST | NI_NUMERICSERV);
    136 
    137 				fprintf(stderr, "[%d] Client connection from %s:%s\n",
    138 						res, host, port);
    139 				}
    140 
    141 				handle_accept(ioring, conn, res);
    142 			} break;
    143 
    144 			case IOREQ_RECV: {
    145 				struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
    146 				assert(conn);
    147 
    148 				if (res <= 0) {
    149 					if (opts.verbose)
    150 					fprintf(stderr, "recv(): %d (%s), closing connection\n", res, strerror(-res));
    151 					close_connection(ioring, conn);
    152 					break;
    153 				}
    154 
    155 				if (opts.verbose)
    156 				fprintf(stderr, "[%d] Client recv: %d bytes\n",
    157 						conn->socket, res);
    158 
    159 				handle_request_chunk(ioring, conn, webroot, res);
    160 			} break;
    161 
    162 			case IOREQ_SEND: {
    163 				struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
    164 				assert(conn);
    165 
    166 				if (res <= 0) {
    167 					if (opts.verbose)
    168 					fprintf(stderr, "send(): %d (%s), closing connection\n", res, strerror(-res));
    169 					close_connection(ioring, conn);
    170 					break;
    171 				}
    172 
    173 				if (opts.verbose)
    174 				fprintf(stderr, "[%d] Client send(): %d bytes\n",
    175 						conn->socket, res);
    176 
    177 				handle_response_chunk(ioring, conn, res);
    178 			} break;
    179 
    180 			case IOREQ_READ: {
    181 				struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
    182 				assert(conn);
    183 
    184 				if (res <= 0) {
    185 					if (opts.verbose)
    186 					fprintf(stderr, "read(): %d (%s), closing connection\n", res, strerror(-res));
    187 					close_connection(ioring, conn);
    188 					break;
    189 				}
    190 
    191 				if (opts.verbose)
    192 				fprintf(stderr, "[%d] Client read: %d bytes\n",
    193 						conn->socket, res);
    194 
    195 				handle_fileread_chunk(ioring, conn, res);
    196 			} break;
    197 
    198 			case IOREQ_CLOSE: {
    199 				struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
    200 				assert(conn);
    201 
    202 				if (opts.verbose)
    203 				fprintf(stderr, "[%d] Client closed connection\n",
    204 						conn->socket);
    205 
    206 				connection_pool_free(pool, conn);
    207 			} break;
    208 			}
    209 
    210 next_cqe:
    211 			seen++;
    212 		}
    213 
    214 		io_uring_cq_advance(ioring, seen);
    215 
    216 		submit_ioreqs(ioring);
    217 	}
    218 
    219 	return EXIT_SUCCESS;
    220 }
    221 
    222 int
    223 main(int argc, char **argv)
    224 {
    225 	if (parse_opts(argc, argv) < 0) {
    226 		usage(argv[0]);
    227 		exit(EXIT_FAILURE);
    228 	}
    229 
    230 	struct sigaction on_close = {
    231 		.sa_sigaction = close_handler, .sa_flags = SA_SIGINFO,
    232 	};
    233 
    234 	sigaction(SIGINT, &on_close, NULL);
    235 	sigaction(SIGKILL, &on_close, NULL);
    236 
    237 	/* we want to cache the webroot tree in memory, and we need to know
    238 	 * when tree has changed. we could use inotify for this, but that has
    239 	 * the possibility of failure (overflow) and is more complicated than
    240 	 * the program updating our tree sending us a signal to reload
    241 	 */
    242 	struct sigaction on_reload = {
    243 		.sa_sigaction = reload_handler, .sa_flags = SA_SIGINFO,
    244 	};
    245 
    246 	sigaction(SIGHUP, &on_reload, NULL);
    247 
    248 	struct connection_pool pool;
    249 	if (connection_pool_init(&pool, CONNECTION_POOL_CAPACITY) < 0) {
    250 		fprintf(stderr, "Failed to initialise connection pool\n");
    251 		exit(EXIT_FAILURE);
    252 	}
    253 
    254 	struct io_uring ioring;
    255 	if (ioring_init(&ioring)) {
    256 		fprintf(stderr, "Failed to initialise io_uring\n");
    257 		exit(EXIT_FAILURE);
    258 	}
    259 
    260 	int webroot = open(opts.webroot, O_DIRECTORY | O_PATH | O_CLOEXEC);
    261 	if (webroot < 0) {
    262 		fprintf(stderr, "Failed to open webroot %s\n", opts.webroot);
    263 		exit(EXIT_FAILURE);
    264 	}
    265 
    266 	int sock = create_server_socket(opts.host, opts.port);
    267 	if (sock < 0) {
    268 		fprintf(stderr, "Failed to create and bind server socket to %s:%s\n",
    269 				opts.host, opts.port);
    270 		exit(EXIT_FAILURE);
    271 	}
    272 
    273 	struct sockaddr_storage addr;
    274 	socklen_t addrlen = sizeof addr;
    275 	if (getsockname(sock, (struct sockaddr *) &addr, &addrlen) < 0) {
    276 		fprintf(stderr, "Failed to get bound socket addr\n");
    277 		exit(EXIT_FAILURE);
    278 	}
    279 
    280 	char bound_host[NI_MAXHOST], bound_port[NI_MAXSERV];
    281 	getnameinfo((struct sockaddr *) &addr, addrlen,
    282 		    bound_host, sizeof bound_host,
    283 		    bound_port, sizeof bound_port,
    284 		    NI_NUMERICHOST | NI_NUMERICSERV);
    285 
    286 	fprintf(stderr, "Server socket bound to %s:%s\n", bound_host, bound_port);
    287 
    288 	exit(serve(&ioring, webroot, sock, &pool));
    289 }
    290 
    291 #include "utils.c"
    292 #include "net.c"
    293 #include "uri.c"
    294 #include "connection.c"