PR 54211 and 60692 track a design problem in mod_cgid: the stderr of spawned CGI scripts is a copy of the main server's stderr. This is a significant regression from mod_cgi (you lose logging prefixes, per-vhost config, non-file/pipe-logging provider support e.g. syslog)
I can think of two main approaches to fix this: 1) enhance the client/server protocol which cgid uses to be a bit more like FastCGI with headers, record types, etc and multiplex both stderr and stdout over the Unix socket. We'd need a new thread or process for each new spawned script child to translate CGI stdout/stderr into this protocol, or to completely redesign the cgid_server main loop to be event-driven. Plus a new bucket type similar to the CGI bucket which handles the protocol client side. 2) Create a new pipe for stderr in the client and pass this to the server using Unix fd passing magic for the CGI script to use as stderr. Factor out the CGI bucket from mod_cgi and use this as-is in mod_cgid. I think (1) might be a cleaner design in the long run but (2) is going to be vastly simpler to implement. (2) might take some effort from platform maintainers to work across all Unixes. I'm going to run with (2) in two steps unless there are major objections. a) make fd passing work (opt-in via configure switch) if ErrorLog is an fd, by passing that fd directly to the server (fixing PR 60692 only) - PoC attached to show this is relatively simple, +100 lines b) then make that work via a new pipe with the CGI bucket abstracted out and used across both mod_cgi/cgid (properly fixing PR 54211) Regards, Joe
Index: modules/generators/config5.m4 =================================================================== --- modules/generators/config5.m4 (revision 1862592) +++ modules/generators/config5.m4 (working copy) @@ -78,4 +78,10 @@ APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current]) +AC_ARG_ENABLE(cgid-fdpassing, [APACHE_HELP_STRING(--enable-cgid-fdpassing,Enable experimental mod_cgid support for fd passing)], + [if test "$enableval" = "yes"; then + AC_DEFINE([HAVE_CGID_FDPASSING], 1, [Enable FD passing support in mod_cgid]) + fi +]) + APACHE_MODPATH_FINISH Index: modules/generators/mod_cgid.c =================================================================== --- modules/generators/mod_cgid.c (revision 1862592) +++ modules/generators/mod_cgid.c (working copy) @@ -342,15 +342,19 @@ return close(fd); } -/* deal with incomplete reads and signals - * assume you really have to read buf_size bytes - */ -static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size) +/* Read from the socket dealing with incomplete messages and signals. + * Returns 0 on success or errno on failure. Stderr fd passed as + * auxiliary data from other end is written to *errfd, or else stderr + * fileno if not present. */ +static apr_status_t sock_readhdr(int fd, int *errfd, void *vbuf, size_t buf_size) { + int rc; +#ifndef HAVE_CGID_FDPASSING char *buf = vbuf; - int rc; size_t bytes_read = 0; + if (errfd) *errfd = 0; + do { do { rc = read(fd, buf + bytes_read, buf_size - bytes_read); @@ -365,9 +369,52 @@ } } while (bytes_read < buf_size); + +#else /* with FD passing */ + struct msghdr msg = {0}; + struct iovec vec = {vbuf, buf_size}; + struct cmsghdr *cmsg; + union { /* union to ensure alignment */ + struct cmsghdr cm; + char buf[CMSG_SPACE(sizeof(int))]; + } u; + + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + if (errfd) *errfd = 0; + + /* use MSG_WAITALL to skip loop on truncated reads */ + do { + rc = recvmsg(fd, &msg, MSG_WAITALL); + } while (rc < 0 && errno == EINTR); + + if (rc == 0) { + return ECONNRESET; + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (errfd + && cmsg + && cmsg->cmsg_len == CMSG_LEN(sizeof(*errfd)) + && cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + *errfd = *((int *) CMSG_DATA(cmsg)); + } +#endif + return APR_SUCCESS; } +/* As sock_readhdr but without auxiliary fd passing. */ +static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size) +{ + return sock_readhdr(fd, NULL, vbuf, buf_size); +} + /* deal with signals */ static apr_status_t sock_write(int fd, const void *buf, size_t buf_size) @@ -384,7 +431,7 @@ return APR_SUCCESS; } -static apr_status_t sock_writev(int fd, request_rec *r, int count, ...) +static apr_status_t sock_writev(int fd, int auxfd, request_rec *r, int count, ...) { va_list ap; int rc; @@ -399,9 +446,39 @@ } va_end(ap); +#ifndef HAVE_CGID_FDPASSING do { rc = writev(fd, vec, count); } while (rc < 0 && errno == EINTR); +#else + { + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + union { /* union for alignment */ + char buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + + msg.msg_iov = vec; + msg.msg_iovlen = count; + + if (auxfd) { + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + *((int *) CMSG_DATA(cmsg)) = auxfd; + } + + do { + rc = sendmsg(fd, &msg, 0); + } while (rc < 0 && errno == EINTR); + } +#endif + if (rc < 0) { return errno; } @@ -410,7 +487,7 @@ } static apr_status_t get_req(int fd, request_rec *r, char **argv0, char ***env, - cgid_req_t *req) + int *errfd, cgid_req_t *req) { int i; char **environ; @@ -421,7 +498,7 @@ r->server = apr_pcalloc(r->pool, sizeof(server_rec)); /* read the request header */ - stat = sock_read(fd, req, sizeof(*req)); + stat = sock_readhdr(fd, errfd, req, sizeof(*req)); if (stat != APR_SUCCESS) { return stat; } @@ -487,6 +564,7 @@ apr_status_t stat; ap_unix_identity_t * ugid = ap_run_get_suexec_identity(r); core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config); + int errfd; if (ugid == NULL) { @@ -507,9 +585,14 @@ req.args_len = r->args ? strlen(r->args) : 0; req.loglevel = r->server->log.level; + if (r->server->error_log) + apr_os_file_get(&errfd, r->server->error_log); + else + errfd = 0; + /* Write the request header */ if (req.args_len) { - stat = sock_writev(fd, r, 5, + stat = sock_writev(fd, errfd, r, 5, &req, sizeof(req), r->filename, req.filename_len, argv0, req.argv0_len, @@ -516,7 +599,7 @@ r->uri, req.uri_len, r->args, req.args_len); } else { - stat = sock_writev(fd, r, 4, + stat = sock_writev(fd, errfd, r, 4, &req, sizeof(req), r->filename, req.filename_len, argv0, req.argv0_len, @@ -531,7 +614,7 @@ for (i = 0; i < req.env_count; i++) { apr_size_t curlen = strlen(env[i]); - if ((stat = sock_writev(fd, r, 2, &curlen, sizeof(curlen), + if ((stat = sock_writev(fd, 0, r, 2, &curlen, sizeof(curlen), env[i], curlen)) != APR_SUCCESS) { return stat; } @@ -669,7 +752,7 @@ } while (!daemon_should_exit) { - int errfileno = STDERR_FILENO; + int errfileno; char *argv0 = NULL; char **env = NULL; const char * const *argv; @@ -709,7 +792,7 @@ r = apr_pcalloc(ptrans, sizeof(request_rec)); procnew = apr_pcalloc(ptrans, sizeof(*procnew)); r->pool = ptrans; - stat = get_req(sd2, r, &argv0, &env, &cgid_req); + stat = get_req(sd2, r, &argv0, &env, &errfileno, &cgid_req); if (stat != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, stat, main_server, APLOGNO(01248) @@ -741,6 +824,16 @@ continue; } + if (errfileno == 0) { + errfileno = STDERR_FILENO; + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, main_server, + "using passed fd %d as stderr", errfileno); + /* Limit the received fd lifetime to pool lifetime */ + apr_pool_cleanup_register(ptrans, (void *)((long)errfileno), + close_unix_socket, close_unix_socket); + } apr_os_file_put(&r->server->error_log, &errfileno, 0, r->pool); apr_os_file_put(&inout, &sd2, 0, r->pool);