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"