--- a/src/server/safs/cgi.c Sat Jun 10 18:12:04 2023 +0200 +++ b/src/server/safs/cgi.c Sun Jun 11 15:53:55 2023 +0200 @@ -198,6 +198,7 @@ error = 1; } + handler->wait_read = TRUE; handler->events = 2; // 2 events (stdout, stderr) if(error) { @@ -222,28 +223,29 @@ > 0) { handler->writebuf_pos += wr; + handler->count_write += wr; } - if(wr < 0) { - if(errno != EWOULDBLOCK) { + if(handler->writebuf_size - handler->writebuf_pos > 0) { + if(net_errno(sn->csd) != EWOULDBLOCK) { handler->result = REQ_ABORTED; } - handler->wait_write = TRUE; + return 1; - } else { - handler->wait_write = FALSE; } return 0; } static int cgi_try_write(CGIHandler *handler, EventHandler *ev, Session *sn, char *buf, size_t size) { - handler->wait_write = FALSE; + size_t pos = 0; ssize_t wr = 0; while(size - pos > 0 && (wr = net_write(sn->csd, buf + pos, size - pos)) > 0) { pos += wr; + handler->count_write += wr; } - if(wr < 0) { - if(errno == EWOULDBLOCK) { + + if(pos < size) { + if(net_errno(sn->csd) == EWOULDBLOCK) { // copy remaining bytes to the write buffer // we assume there are no remaining bytes in writebuf size_t remaining = size-pos; @@ -268,10 +270,9 @@ handler->events++; handler->poll_out = TRUE; } - handler->wait_write = TRUE; } else { handler->result = REQ_ABORTED; - log_ereport(LOG_FAILURE, "cgi_try_write: %s", strerror(errno)); + log_ereport(LOG_FAILURE, "cgi_try_write: %s", strerror(net_errno(sn->csd))); } return 1; } @@ -282,14 +283,22 @@ int cgi_stdout_readevent(EventHandler *ev, Event *event) { CGIHandler *handler = event->cookie; - return cgi_read_output(handler, ev); + int ret = cgi_read_output(handler, ev); + if(ret == 0) { + handler->wait_read = FALSE; + } + return ret; } int cgi_writeevent(EventHandler *ev, Event *event) { CGIHandler *handler = event->cookie; // cgi_read_output will try to flush the buffer - return cgi_read_output(handler, ev); + int ret = cgi_read_output(handler, ev); + if(ret == 0) { + handler->poll_out = FALSE; + } + return ret; } @@ -303,7 +312,7 @@ // if writebuf is empty, this does nothing and returns 0 if(cgi_try_write_flush(handler, sn)) { if(handler->result == REQ_ABORTED) { - log_ereport(LOG_DEBUG, "cgi-send: req: %p write failed: %s: abort", strerror(errno), rq); + log_ereport(LOG_DEBUG, "cgi-send: req: %p write failed: %s: abort", handler->parser->rq, strerror(net_errno(sn->csd)), rq); return 0; } else { return 1; @@ -375,7 +384,7 @@ if(r < 0 && errno == EWOULDBLOCK) { return 1; } - + handler->cgi_eof = TRUE; return 0; } @@ -473,12 +482,32 @@ Session *sn = parser->sn; Request *rq = parser->rq; + char *event_fn = ""; + if(event->fn == cgi_stdout_readevent) { + event_fn = "stdout"; + } else if(event->fn == cgi_stderr_readevent) { + event_fn = "stderr"; + } else if(event->fn == cgi_writeevent) { + event_fn = "httpout"; + } + log_ereport(LOG_DEBUG, "cgi-send: req: %p finish: event: %d pollout: %d cgi_eof: %d fn: %s", rq, handler->events, handler->poll_out, handler->cgi_eof, event_fn); + if(--handler->events > 0) { - if(handler->events == 1 && handler->poll_out && !handler->wait_write) { - // write event registered, however it will not be activated anymore - // we can safely remove the event - if(event_removepoll(ev, sn->csd)) { - log_ereport(LOG_FAILURE, "cgi_event_finish: event_removepoll: %s", strerror(errno)); + if(handler->events == 1) { + if(handler->poll_out) { + // write event registered, however it will not be activated anymore + // we can safely remove the event + log_ereport(LOG_DEBUG, "cgi-send: req: %p finish: event: 1 remove-poll write", rq); + if(event_removepoll(ev, sn->csd)) { + log_ereport(LOG_FAILURE, "cgi_event_finish: event_removepoll: %s", strerror(errno)); + } + } else if(handler->cgi_eof && handler->wait_read) { + log_ereport(LOG_DEBUG, "cgi-send: req: %p finish: event: 1 remove-poll read", rq); + if(ev_remove_poll(ev, handler->process.out[0])) { + log_ereport(LOG_FAILURE, "cgi_event_finish: ev_remove_poll: %s", strerror(errno)); + } + } else { + return 0; } } else { return 0; @@ -490,7 +519,7 @@ killpg(handler->process.pid, SIGTERM); } - log_ereport(LOG_DEBUG, "cgi-send: req: %p cgi_close", rq); + log_ereport(LOG_DEBUG, "cgi-send: req: %p cgi_close", rq); int exit_code = cgi_close(&handler->process); if(exit_code != 0) { @@ -500,6 +529,7 @@ cgi_parser_free(parser); + WSBool response_length_error = FALSE; // check if content-length set by the cgi script matches the number // of writes, that were written to the stream // this ensures, that broken cgi scripts don't break the connection @@ -512,11 +542,25 @@ LOG_FAILURE, "cgi-send: script: %s: content length mismatch", handler->path); - rq->rq_attr.keep_alive = 0; - handler->result = REQ_ABORTED; + response_length_error = TRUE; } } } + // make sure we haven't lost any bytes + // should not happen unless the non-blocking IO code is buggy + if(handler->result != REQ_ABORTED && handler->parser->response_length != handler->count_write) { + log_ereport( + LOG_FAILURE, + "cgi-send: script: %s: IO error: cgi response length != http response length", + handler->path); + response_length_error = TRUE; + } + + // if the response length is broken, we must close the connection + if(response_length_error) { + rq->rq_attr.keep_alive = 0; + handler->result = REQ_ABORTED; + } net_setnonblock(sn->csd, 0);