--- a/src/server/safs/cgi.c Sat Nov 12 17:34:58 2022 +0100 +++ b/src/server/safs/cgi.c Sat Nov 12 20:50:45 2022 +0100 @@ -42,6 +42,7 @@ #include "../util/pblock.h" #include "../daemon/netsite.h" #include "../util/io.h" +#include "../daemon/event.h" #include "cgiutils.h" @@ -80,13 +81,23 @@ const char *args = pblock_findval("query", rq->reqpb); char **argv = cgi_create_argv(path, NULL, args); + if(!argv) { + return REQ_ABORTED; + } char **env = http_hdrs2env(rq->headers); env = cgi_common_vars(sn, rq, env); env = cgi_specific_vars(sn, rq, args, env, 1); - CGIProcess cgip; - int ret = cgi_start(&cgip, path, argv, env); + // event handler object for non-blocking io event handler + CGIHandler *handler = pool_malloc(sn->pool, sizeof(CGIHandler)); + if(!handler) { + return REQ_ABORTED; + } + ZERO(handler, sizeof(CGIHandler)); + handler->path = path; + + int ret = cgi_start(&handler->process, path, argv, env); if(ret != REQ_PROCEED) { util_env_free(env); cgi_free_argv(argv); @@ -95,7 +106,7 @@ util_env_free(env); cgi_free_argv(argv); - + char buf[4096]; // I/O buffer ssize_t r; @@ -109,101 +120,173 @@ LOG_FAILURE, "send-cgi: script: %s: cannot read request body", path); - kill(cgip.pid, SIGTERM); - cgi_close(&cgip); + kill(handler->process.pid, SIGTERM); + cgi_close(&handler->process); return REQ_ABORTED; } - ssize_t w = write(cgip.in[1], buf, r); + ssize_t w = write(handler->process.in[1], buf, r); if(w <= 0) { // TODO: handle error log_ereport( LOG_FAILURE, "send-cgi: script: %s: cannot send request body to cgi process", path); - kill(cgip.pid, SIGKILL); - cgi_close(&cgip); + kill(handler->process.pid, SIGKILL); + cgi_close(&handler->process); return REQ_ABORTED; } n += r; } } - system_close(cgip.in[1]); - cgip.in[1] = -1; + system_close(handler->process.in[1]); + handler->process.in[1] = -1; + + handler->parser = cgi_parser_new(sn, rq); + + Event *readev = pool_malloc(sn->pool, sizeof(Event)); + ZERO(readev, sizeof(Event)); + readev->cookie = handler; + readev->fn = cgi_stdout_readevent; + readev->finish = cgi_event_finish; + + Event *stderr_readev = pool_malloc(sn->pool, sizeof(Event)); + ZERO(stderr_readev, sizeof(Event)); + stderr_readev->cookie = handler; + stderr_readev->fn = cgi_stderr_readevent; + stderr_readev->finish = NULL; + + Event *writeev = pool_malloc(sn->pool, sizeof(Event)); + ZERO(writeev, sizeof(Event)); + writeev->cookie = handler; + // TODO: fn + + handler->writeev = writeev; - // read from child - CGIResponseParser *parser = cgi_parser_new(sn, rq); - WSBool cgiheader = TRUE; + // add poll events for cgi stdout/stderr + int error = 0; + if(ev_pollin(sn->ev, handler->process.err[0], stderr_readev)) { + log_ereport(LOG_FAILURE, "send-cgi: stderr ev_pollin failed"); + error = 1; + } + if(ev_pollin(sn->ev, handler->process.out[0], readev)) { + log_ereport(LOG_FAILURE, "send-cgi: stdout ev_pollin failed"); + error = 1; + } + + if(error) { + log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", path); + kill(handler->process.pid, SIGKILL); + cgi_parser_free(handler->parser); + return REQ_ABORTED; + } + + return REQ_PROCESSING; +} + +int cgi_stdout_readevent(EventHandler *ev, Event *event) { + CGIHandler *handler = event->cookie; + CGIResponseParser *parser = handler->parser; + Session *sn = parser->sn; + Request *rq = parser->rq; + + char buf[4096]; // I/O buffer + ssize_t r; ssize_t wr = 0; - int result = REQ_PROCEED; - size_t response_length = 0; - while((r = read(cgip.out[0], buf, 4096)) > 0) { - if(cgiheader) { + + handler->result = REQ_PROCEED; + while((r = read(handler->process.out[0], buf, 4096)) > 0) { + if(parser->cgiheader) { size_t pos; - ret = cgi_parse_response(parser, buf, r, &pos); + int ret = cgi_parse_response(parser, buf, r, &pos); if(ret == -1) { log_ereport( LOG_FAILURE, - "broken cgi script response: path: %s", path); + "broken cgi script response: path: %s", handler->path); protocol_status(sn, rq, 500, NULL); - result = REQ_ABORTED; + handler->result = REQ_ABORTED; break; } else if(ret == 1) { - cgiheader = FALSE; + parser->cgiheader = FALSE; if(parser->status > 0) { protocol_status(sn, rq, parser->status, parser->msg); } http_start_response(sn, rq); if(pos < r) { - response_length += r-pos; + parser->response_length += r-pos; wr = net_write(sn->csd, &buf[pos], r-pos); if(wr <= 0) { - result = REQ_ABORTED; + handler->result = REQ_ABORTED; break; } } } } else { - response_length += r; + parser->response_length += r; wr = net_write(sn->csd, buf, r); if(wr <= 0) { - result = REQ_ABORTED; + handler->result = REQ_ABORTED; break; } } } + if(r < 0 && errno == EWOULDBLOCK) { + event->events = EVENT_POLLIN; + return 1; + } char *ctlen_header = pblock_findkeyval(pb_key_content_length, rq->srvhdrs); if(ctlen_header) { int64_t ctlenhdr; if(util_strtoint(ctlen_header, &ctlenhdr)) { - if(ctlenhdr != response_length) { + if(ctlenhdr != parser->response_length) { log_ereport( LOG_FAILURE, "cgi-send: script: %s: content length mismatch", - path); + handler->path); rq->rq_attr.keep_alive = 0; - result = REQ_ABORTED; + handler->result = REQ_ABORTED; } } } - if(result == REQ_ABORTED) { - log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", path); - kill(cgip.pid, SIGKILL); + return 0; +} + +int cgi_stderr_readevent(EventHandler *ev, Event *event) { + CGIHandler *handler = event->cookie; + + char buf[4096]; + ssize_t r = read(handler->process.err[0], buf, 4096); + log_ereport(LOG_INFORM, "cgi pid %d %s stderr: %.*s", (int)handler->process.pid, handler->path, (int)r, buf); + + return 0; +} + +int cgi_event_finish(EventHandler *ev, Event *event) { + CGIHandler *handler = event->cookie; + CGIResponseParser *parser = handler->parser; + Session *sn = parser->sn; + Request *rq = parser->rq; + + if(handler->result == REQ_ABORTED) { + log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", handler->path); + kill(handler->process.pid, SIGKILL); } - int exit_code = cgi_close(&cgip); + int exit_code = cgi_close(&handler->process); if(exit_code != 0) { - log_ereport(LOG_FAILURE, "send-cgi: script: %s exited with code %d", path, exit_code); - ret = REQ_ABORTED; + log_ereport(LOG_FAILURE, "send-cgi: script: %s exited with code %d", handler->path, exit_code); + handler->result = REQ_ABORTED; } cgi_parser_free(parser); - return result; + // return to nsapi loop + nsapi_function_return(sn, rq, handler->result); + return 0; } int cgi_start(CGIProcess *p, char *path, char *const argv[], char *const envp[]) { - if(pipe(p->in) || pipe(p->out)) { + if(pipe(p->in) || pipe(p->out) || pipe(p->err)) { log_ereport( LOG_FAILURE, "send-cgi: cannot create pipe: %s", @@ -241,6 +324,10 @@ perror("cgi_start: dup2"); exit(EXIT_FAILURE); } + if(dup2(p->err[1], STDERR_FILENO) == -1) { + perror("cgi_start: dup2"); + exit(EXIT_FAILURE); + } // we need to close this unused pipe // otherwise stdin cannot reach EOF @@ -251,7 +338,9 @@ } else { // parent system_close(p->out[1]); + system_close(p->err[1]); p->out[1] = -1; + p->err[1] = -1; } return REQ_PROCEED; @@ -273,6 +362,12 @@ if(p->out[1] != -1) { system_close(p->out[1]); } + if(p->err[0] != -1) { + system_close(p->err[0]); + } + if(p->err[1] != -1) { + system_close(p->err[1]); + } return status; } @@ -283,6 +378,8 @@ parser->rq = rq; parser->status = 0; parser->msg = NULL; + parser->response_length = 0; + parser->cgiheader = TRUE; cxBufferInit(&parser->tmp, NULL, 64, pool_allocator(sn->pool), CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); return parser; }