diff --git a/src/mod_cgi.c b/src/mod_cgi.c index 9e834456..e107d4f4 100644 --- a/src/mod_cgi.c +++ b/src/mod_cgi.c @@ -72,7 +72,9 @@ typedef struct { typedef struct { pid_t pid; int fd; + int fdtocgi; int fde_ndx; /* index into the fd-event buffer */ + int fde_ndx_tocgi; /* index into the fd-event buffer */ connection *remote_conn; /* dumb pointer */ plugin_data *plugin_data; /* dumb pointer */ @@ -89,6 +91,7 @@ static handler_ctx * cgi_handler_ctx_init(void) { hctx->response = buffer_init(); hctx->response_header = buffer_init(); hctx->fd = -1; + hctx->fdtocgi = -1; return hctx; } @@ -519,6 +522,17 @@ static int cgi_demux_response(server *srv, handler_ctx *hctx) { return FDEVENT_HANDLED_NOT_FINISHED; } +static void cgi_connection_close_fdtocgi(server *srv, handler_ctx *hctx) { + /*(closes only hctx->fdtocgi)*/ + fdevent_event_del(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi); + fdevent_unregister(srv->ev, hctx->fdtocgi); + + if (close(hctx->fdtocgi)) { + log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", hctx->fdtocgi, strerror(errno)); + } + hctx->fdtocgi = -1; +} + static void cgi_connection_close(server *srv, handler_ctx *hctx) { int status; pid_t pid; @@ -541,16 +555,16 @@ static void cgi_connection_close(server *srv, handler_ctx *hctx) { if (close(hctx->fd)) { log_error_write(srv, __FILE__, __LINE__, "sds", "cgi close failed ", hctx->fd, strerror(errno)); } + } - hctx->fd = -1; - hctx->fde_ndx = -1; + if (hctx->fdtocgi != -1) { + cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } pid = hctx->pid; con->plugin_ctx[p->id] = NULL; - /* is this a good idea ? */ cgi_handler_ctx_free(hctx); /* if waitpid hasn't been called by response.c yet, do it here */ @@ -631,6 +645,43 @@ static handler_t cgi_connection_close_callback(server *srv, connection *con, voi } +static int cgi_write_request(server *srv, handler_ctx *hctx, int fd); + + +static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) { + handler_ctx *hctx = ctx; + connection *con = hctx->remote_conn; + + /*(joblist only actually necessary here in mod_cgi fdevent send if returning HANDLER_ERROR)*/ + joblist_append(srv, con); + + if (revents & FDEVENT_OUT) { + if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) { + cgi_connection_close(srv, hctx); + return HANDLER_ERROR; + } + /* more request body to be sent to CGI */ + } + + if (revents & FDEVENT_HUP) { + /* skip sending remaining data to CGI */ + chunkqueue *cq = con->request_content_queue; + chunkqueue_mark_written(cq, chunkqueue_length(cq)); + + cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ + } else if (revents & FDEVENT_ERR) { + /* kill all connections to the cgi process */ +#if 1 + log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR"); +#endif + cgi_connection_close(srv, hctx); + return HANDLER_ERROR; + } + + return HANDLER_FINISHED; +} + + static handler_t cgi_handle_fdevent(server *srv, void *ctx, int revents) { handler_ctx *hctx = ctx; connection *con = hctx->remote_conn; @@ -724,7 +775,7 @@ static int cgi_env_add(char_array *env, const char *key, size_t key_len, const c * * Also always use mmap; the files are "trusted", as we created them. */ -static int cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) { +static ssize_t cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) { chunk* const c = cq->first; off_t offset, toSend, file_end; ssize_t r; @@ -793,6 +844,89 @@ static int cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunk chunkqueue_mark_written(cq, r); } + return r; +} + +static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) { + connection *con = hctx->remote_conn; + chunkqueue *cq = con->request_content_queue; + chunk *c; + + /* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms. + * solution: if this is still a problem on windows, then substitute + * socketpair() for pipe() and closesocket() for close() on windows. + */ + + for (c = cq->first; c; c = cq->first) { + ssize_t r = -1; + + switch(c->type) { + case FILE_CHUNK: + r = cgi_write_file_chunk_mmap(srv, con, fd, cq); + break; + + case MEM_CHUNK: + if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) { + switch(errno) { + case EAGAIN: + case EINTR: + /* ignore and try again */ + r = 0; + break; + case EPIPE: + case ECONNRESET: + /* connection closed */ + r = -2; + break; + default: + /* fatal error */ + log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno)); + r = -1; + break; + } + } else if (r > 0) { + chunkqueue_mark_written(cq, r); + } + break; + } + + if (0 == r) break; /*(might block)*/ + + switch (r) { + case -1: + /* fatal error */ + return -1; + case -2: + /* connection reset */ + log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI"); + /* skip all remaining data */ + chunkqueue_mark_written(cq, chunkqueue_length(cq)); + break; + default: + break; + } + } + + if (chunkqueue_is_empty(cq)) { + /* sent all request body input */ + /* close connection to the cgi-script */ + if (-1 == hctx->fdtocgi) { /*(entire request body sent in initial send to pipe buffer)*/ + if (close(fd)) { + log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno)); + } + } else { + cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ + } + } else { + /* more request body remains to be sent to CGI so register for fdevents */ + if (-1 == hctx->fdtocgi) { /*(not registered yet)*/ + hctx->fdtocgi = fd; + hctx->fde_ndx_tocgi = -1; + fdevent_register(srv->ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx); + fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT); + } + } + return 0; } @@ -1099,82 +1233,30 @@ static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_ close(from_cgi_fds[1]); close(to_cgi_fds[0]); - if (con->request.content_length) { - chunkqueue *cq = con->request_content_queue; - chunk *c; + /* register PID and wait for them asynchronously */ - assert(chunkqueue_length(cq) == (off_t)con->request.content_length); - - /* NOTE: yes, this is synchronous sending of CGI post data; - * if you need something asynchronous (recommended with large - * request bodies), use mod_fastcgi + fcgi-cgi. - * - * Also: windows doesn't support select() on pipes - wouldn't be - * easy to fix for all platforms. - */ + hctx->pid = pid; + hctx->fd = from_cgi_fds[0]; + hctx->fde_ndx = -1; + if (0 == con->request.content_length) { + close(to_cgi_fds[1]); + } else { /* there is content to send */ - for (c = cq->first; c; c = cq->first) { - int r = -1; - - switch(c->type) { - case FILE_CHUNK: - r = cgi_write_file_chunk_mmap(srv, con, to_cgi_fds[1], cq); - break; - - case MEM_CHUNK: - if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) { - switch(errno) { - case EAGAIN: - case EINTR: - /* ignore and try again */ - r = 0; - break; - case EPIPE: - case ECONNRESET: - /* connection closed */ - r = -2; - break; - default: - /* fatal error */ - log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno)); - r = -1; - break; - } - } else if (r > 0) { - chunkqueue_mark_written(cq, r); - } - break; - } + if (-1 == fdevent_fcntl_set(srv->ev, to_cgi_fds[1])) { + log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); + close(to_cgi_fds[1]); + cgi_connection_close(srv, hctx); + return -1; + } - switch (r) { - case -1: - /* fatal error */ - close(from_cgi_fds[0]); - close(to_cgi_fds[1]); - kill(pid, SIGTERM); - cgi_pid_add(srv, p, pid); - return -1; - case -2: - /* connection reset */ - log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI"); - /* skip all remaining data */ - chunkqueue_mark_written(cq, chunkqueue_length(cq)); - break; - default: - break; - } + if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) { + close(to_cgi_fds[1]); + cgi_connection_close(srv, hctx); + return -1; } } - close(to_cgi_fds[1]); - - /* register PID and wait for them asyncronously */ - - hctx->pid = pid; - hctx->fd = from_cgi_fds[0]; - hctx->fde_ndx = -1; - fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx); fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);