Download raw body.
less pipes in gotwebd
Hello, I'd like to simplify the way we're processing the output in gotwebd now. Explanations on the commit message, but the main reason I'm doing this is to avoid a race in the current code where we can send a GOTWEBD_IMSG_REQ_DONE while there's still data in the pipe between the process and... well, we send a truncated, corrupted page. commit 5c6ad43490b5b32f4c7c1c195a76073cbdf503fa (main) from: Omar Polo <op@omarpolo.com> date: Sun Aug 3 15:03:20 2025 UTC gotwebd: avoid the pipe; forward the socket directly Instead of keeping a pipe between the sockets and the gotwebd process, just forward the socket directly. This needs a little bit of care because once we enter process_request() the socket (possibly) no longer there, so on the socket side use the client_status as a flag to signal the caller that we're done. I hope to improve this in follow-ups. The motivation behind this is to avoid a (possible, but quite easily triggerable) race in the current code where the other side might process GOTWEBD_IMSG_REQ_DONE before draining the pipe, truncating the output page. diff 98958d75e928ddd95151c462562d1b91b172f7e9 5c6ad43490b5b32f4c7c1c195a76073cbdf503fa commit - 98958d75e928ddd95151c462562d1b91b172f7e9 commit + 5c6ad43490b5b32f4c7c1c195a76073cbdf503fa blob - 98b144aa064f722540e70cb344a42e3e59abf96e blob + 53d51f463017b20b82843d793916c4830ff32b4b --- gotwebd/fcgi.c +++ gotwebd/fcgi.c @@ -99,6 +99,17 @@ fcgi_request(int fd, short events, void *arg) */ do { parsed = fcgi_parse_record(c->buf + c->buf_pos, c->buf_len, c); + + /* + * When we start to actually process the entry, we + * send the request to the gotweb process, so we're + * done. + */ + if (c->client_status == CLIENT_DISCONNECT) { + fcgi_cleanup_request(c); + return; + } + if (parsed != 0) { c->buf_pos += parsed; c->buf_len -= parsed; @@ -184,46 +195,12 @@ fcgi_parse_begin_request(uint8_t *buf, uint16_t n, } static void -fcgi_forward_response(int fd, short event, void *arg) -{ - const struct got_error *err = NULL; - struct request *c = arg; - uint8_t outbuf[GOTWEBD_CACHESIZE]; - ssize_t r; - - if ((event & EV_READ) == 0) - return; - - r = read(fd, outbuf, sizeof(outbuf)); - if (r == 0) - return; - - if (r == -1) { - log_warn("read response"); - return; - } else { - err = got_poll_write_full_timeout(c->fd, outbuf, r, 1); - if (err) - log_warnx("forward response: %s", err->msg); - } - - event_add(c->resp_event, NULL); -} - -static void process_request(struct request *c) { struct gotwebd *env = gotwebd_env; - int ret, i, pipe[2]; + int ret, i; struct request ic; - struct event *resp_event = NULL; - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - PF_UNSPEC, pipe) == -1) { - log_warn("socketpair"); - return; - } - memcpy(&ic, c, sizeof(ic)); /* Don't leak pointers from our address space to another process. */ @@ -238,32 +215,16 @@ process_request(struct request *c) for (i = 0; i < nitems(c->priv_fd); i++) ic.priv_fd[i] = -1; ic.fd = -1; - ic.resp_fd = -1; - resp_event = calloc(1, sizeof(*resp_event)); - if (resp_event == NULL) { - log_warn("calloc"); - close(pipe[0]); - close(pipe[1]); - return; - } - ret = imsg_compose_event(env->iev_gotweb, GOTWEBD_IMSG_REQ_PROCESS, - GOTWEBD_PROC_SERVER, getpid(), pipe[0], &ic, sizeof(ic)); + GOTWEBD_PROC_SERVER, -1, c->fd, &ic, sizeof(ic)); if (ret == -1) { log_warn("imsg_compose_event"); - close(pipe[0]); - close(pipe[1]); - free(resp_event); - return; + close(c->fd); } + c->fd = -1; - event_set(resp_event, pipe[1], EV_READ, fcgi_forward_response, c); - event_add(resp_event, NULL); - - c->resp_fd = pipe[1]; - c->resp_event = resp_event; - c->client_status = CLIENT_REQUEST; + c->client_status = CLIENT_DISCONNECT; } void @@ -494,8 +455,6 @@ fcgi_cleanup_request(struct request *c) { cgi_inflight--; - sockets_del_request(c); - if (evtimer_initialized(&c->tmo)) evtimer_del(&c->tmo); if (event_initialized(&c->ev)) @@ -503,8 +462,6 @@ fcgi_cleanup_request(struct request *c) if (c->fd != -1) close(c->fd); - if (c->resp_fd != -1) - close(c->resp_fd); if (c->tp != NULL) template_free(c->tp); if (c->t != NULL) blob - 86caae0a2edf5c2a511c9da7f3b02a08a8586dff blob + 79d541d9296b56ef5b9c7f62d543296a73e5dfbc --- gotwebd/gotweb.c +++ gotwebd/gotweb.c @@ -143,20 +143,6 @@ gotweb_reply_file(struct request *c, const char *ctype return gotweb_reply(c, 200, ctype, NULL); } -static void -free_request(struct request *c) -{ - if (c->fd != -1) - close(c->fd); - if (c->tp != NULL) - template_free(c->tp); - if (c->t != NULL) - gotweb_free_transport(c->t); - free(c->buf); - free(c->outbuf); - free(c); -} - static struct socket * gotweb_get_socket(int sock_id) { @@ -225,14 +211,14 @@ recv_request(struct imsg *imsg) c->tp = template(c, fcgi_write, c->outbuf, GOTWEBD_CACHESIZE); if (c->tp == NULL) { log_warn("gotweb init template"); - free_request(c); + fcgi_cleanup_request(c); return NULL; } c->sock = gotweb_get_socket(c->sock_id); if (c->sock == NULL) { log_warn("socket id '%d' not found", c->sock_id); - free_request(c); + fcgi_cleanup_request(c); return NULL; } @@ -240,7 +226,7 @@ recv_request(struct imsg *imsg) error = gotweb_init_transport(&c->t); if (error) { log_warnx("gotweb init transport: %s", error->msg); - free_request(c); + fcgi_cleanup_request(c); return NULL; } @@ -248,7 +234,7 @@ recv_request(struct imsg *imsg) srv = gotweb_get_server(c->server_name); if (srv == NULL) { log_warnx("server '%s' not found", c->server_name); - free_request(c); + fcgi_cleanup_request(c); return NULL; } c->srv = srv; @@ -1396,8 +1382,6 @@ gotweb_shutdown(void) free(gotwebd_env->iev_server); } - sockets_purge(gotwebd_env); - while (!TAILQ_EMPTY(&gotwebd_env->servers)) { struct server *srv; @@ -1465,17 +1449,6 @@ gotweb_launch(struct gotwebd *env) } static void -send_request_done(struct imsgev *iev, int request_id) -{ - struct gotwebd *env = gotwebd_env; - - if (imsg_compose_event(env->iev_server, GOTWEBD_IMSG_REQ_DONE, - GOTWEBD_PROC_GOTWEB, getpid(), -1, - &request_id, sizeof(request_id)) == -1) - log_warn("imsg_compose_event"); -} - -static void gotweb_dispatch_server(int fd, short event, void *arg) { struct imsgev *iev = arg; @@ -1518,8 +1491,10 @@ gotweb_dispatch_server(int fd, short event, void *arg) request_id); } } - free_request(c); - send_request_done(iev, request_id); + + if (c->client_status == CLIENT_REQUEST) + fcgi_create_end_record(c); + fcgi_cleanup_request(c); } break; default: blob - b3a5e3687066935bb40b1fc0ced2997768dfa1ed blob + 01affdd22c9241eabef71fd1fc28c0ec9c226800 --- gotwebd/gotwebd.c +++ gotwebd/gotwebd.c @@ -690,7 +690,6 @@ gotwebd_shutdown(void) TAILQ_REMOVE(&gotwebd_env->servers, srv, entry); free(srv); } - sockets_purge(gotwebd_env); free(gotwebd_env); log_warnx("gotwebd terminating"); blob - 1973199772dbd6e269f7b884a5607f7e7d7f0414 blob + 888f71c5b60cbc3697f03efa568436627738f7dc --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -135,7 +135,6 @@ enum imsg_type { GOTWEBD_IMSG_CTL_PIPE, GOTWEBD_IMSG_CTL_START, GOTWEBD_IMSG_REQ_PROCESS, - GOTWEBD_IMSG_REQ_DONE, }; struct imsgev { @@ -253,7 +252,6 @@ struct request { uint16_t id; int fd; int priv_fd[PRIV_FDS__MAX]; - int resp_fd; struct event *resp_event; int sock_id; uint32_t request_id; @@ -463,9 +461,7 @@ void sockets(struct gotwebd *, int); void sockets_parse_sockets(struct gotwebd *); void sockets_socket_accept(int, short, void *); int sockets_privinit(struct gotwebd *, struct socket *, uid_t, gid_t); -void sockets_purge(struct gotwebd *); void sockets_rlimit(int); -void sockets_del_request(struct request *); /* gotweb.c */ void gotweb_index_navs(struct request *, struct gotweb_url *, int *, blob - 75d96baa9f14d17f1efe37982699d03c6a4d1ce3 blob + 0ddf1e382eb72e1f455b15c2ed1acf5408748f83 --- gotwebd/sockets.c +++ gotwebd/sockets.c @@ -79,94 +79,12 @@ static struct socket *sockets_conf_new_socket(struct g int cgi_inflight = 0; -/* Request hash table needs some spare room to avoid collisions. */ -struct requestlist requests[GOTWEBD_MAXCLIENTS * 4]; -static SIPHASH_KEY requests_hash_key; - -static void -requests_init(void) -{ - int i; - - arc4random_buf(&requests_hash_key, sizeof(requests_hash_key)); - - for (i = 0; i < nitems(requests); i++) - TAILQ_INIT(&requests[i]); -} - -static uint64_t -request_hash(uint32_t request_id) -{ - return SipHash24(&requests_hash_key, &request_id, sizeof(request_id)); -} - -static void -add_request(struct request *c) -{ - uint64_t slot = request_hash(c->request_id) % nitems(requests); - TAILQ_INSERT_HEAD(&requests[slot], c, entry); - client_cnt++; -} - void -sockets_del_request(struct request *c) -{ - uint64_t slot = request_hash(c->request_id) % nitems(requests); - TAILQ_REMOVE(&requests[slot], c, entry); - client_cnt--; -} - -static struct request * -find_request(uint32_t request_id) -{ - uint64_t slot; - struct request *c; - - slot = request_hash(request_id) % nitems(requests); - TAILQ_FOREACH(c, &requests[slot], entry) { - if (c->request_id == request_id) - return c; - } - - return NULL; -} - -static void -requests_purge(void) -{ - uint64_t slot; - struct request *c; - - for (slot = 0; slot < nitems(requests); slot++) { - while (!TAILQ_EMPTY(&requests[slot])) { - c = TAILQ_FIRST(&requests[slot]); - fcgi_cleanup_request(c); - } - } -} - -static uint32_t -get_request_id(void) -{ - int duplicate = 0; - uint32_t id; - - do { - id = arc4random(); - duplicate = (find_request(id) != NULL); - } while (duplicate || id == 0); - - return id; -} - -void sockets(struct gotwebd *env, int fd) { struct event sighup, sigint, sigusr1, sigchld, sigterm; struct event_base *evb; - requests_init(); - evb = event_init(); sockets_rlimit(-1); @@ -298,53 +216,7 @@ sockets_launch(struct gotwebd *env) } -void -sockets_purge(struct gotwebd *env) -{ - struct socket *sock, *tsock; - - /* shutdown and remove sockets */ - TAILQ_FOREACH_SAFE(sock, &env->sockets, entry, tsock) { - if (event_initialized(&sock->ev)) - event_del(&sock->ev); - if (evtimer_initialized(&sock->evt)) - evtimer_del(&sock->evt); - if (evtimer_initialized(&sock->pause)) - evtimer_del(&sock->pause); - if (sock->fd != -1) - close(sock->fd); - TAILQ_REMOVE(&env->sockets, sock, entry); - free(sock); - } -} - static void -request_done(struct imsg *imsg) -{ - struct request *c; - uint32_t request_id; - size_t datalen = imsg->hdr.len - IMSG_HEADER_SIZE; - - if (datalen != sizeof(request_id)) { - log_warn("IMSG_REQ_DONE with bad data length"); - return; - } - - memcpy(&request_id, imsg->data, sizeof(request_id)); - - c = find_request(request_id); - if (c == NULL) { - log_warnx("no request to clean up found for ID %u", - request_id); - return; - } - - if (c->client_status == CLIENT_REQUEST) - fcgi_create_end_record(c); - fcgi_cleanup_request(c); -} - -static void server_dispatch_gotweb(int fd, short event, void *arg) { struct imsgev *iev = arg; @@ -373,9 +245,6 @@ server_dispatch_gotweb(int fd, short event, void *arg) break; switch (imsg.hdr.type) { - case GOTWEBD_IMSG_REQ_DONE: - request_done(&imsg); - break; default: fatalx("%s: unknown imsg type %d", __func__, imsg.hdr.type); @@ -514,8 +383,6 @@ sockets_sighdlr(int sig, short event, void *arg) static void sockets_shutdown(void) { - sockets_purge(gotwebd_env); - /* clean servers */ while (!TAILQ_EMPTY(&gotwebd_env->servers)) { struct server *srv; @@ -533,8 +400,6 @@ sockets_shutdown(void) free(h); } - requests_purge(); - imsgbuf_clear(&gotwebd_env->iev_parent->ibuf); free(gotwebd_env->iev_parent); imsgbuf_clear(&gotwebd_env->iev_gotweb->ibuf); @@ -760,7 +625,6 @@ sockets_socket_accept(int fd, short event, void *arg) c->buf = buf; c->fd = s; - c->resp_fd = -1; c->sock = sock; memcpy(c->priv_fd, gotwebd_env->priv_fd, sizeof(c->priv_fd)); c->sock_id = sock->conf.id; @@ -768,7 +632,6 @@ sockets_socket_accept(int fd, short event, void *arg) c->buf_len = 0; c->request_started = 0; c->client_status = CLIENT_CONNECT; - c->request_id = get_request_id(); event_set(&c->ev, s, EV_READ, fcgi_request, c); event_add(&c->ev, NULL); @@ -776,7 +639,6 @@ sockets_socket_accept(int fd, short event, void *arg) evtimer_set(&c->tmo, fcgi_timeout, c); evtimer_add(&c->tmo, &timeout); - add_request(c); return; err: cgi_inflight--;
less pipes in gotwebd