in netutils/thttpd/thttpd_cgi.c [715:1017]
static int cgi_child(int argc, char **argv)
{
FAR httpd_conn *hc = (FAR httpd_conn *)strtoul(argv[1], NULL, 16);
#if CONFIG_THTTPD_CGI_TIMELIMIT > 0
clientdata client_data;
#endif
FAR char **argp;
FAR struct cgi_conn_s *cc;
FAR struct fdwatch_s *fw;
FAR char *directory;
FAR char *dupname;
bool indone;
bool outdone;
int child;
int pipefd[2];
int nbytes;
int fd;
int ret;
int errcode = 1;
/* Use low-level debug out (because the low-level output may survive
* closing all file descriptors
*/
ninfo("Started: %s\n", argv[1]);
/* Allocate memory and initialize memory for interposing */
cc = (FAR struct cgi_conn_s *)httpd_malloc(sizeof(struct cgi_conn_s));
if (!cc)
{
nerr("ERROR: cgi_conn allocation failed\n");
close(hc->conn_fd);
goto errout;
}
cc->connfd = hc->conn_fd;
cc->wrfd = -1;
cc->rdfd = -1;
memset(&cc->outbuf, 0, sizeof(struct cgi_outbuffer_s));
/* Update all of the environment variable settings, these will be inherited
* by the CGI task.
*/
create_environment(hc);
/* Make the argument vector. */
argp = make_argp(hc);
/* Close all file descriptors EXCEPT for stdin, stdout, stderr and
* hc->conn_fd. We'll keep stderr open for error reporting; stdin and
* stdout will be closed later by dup2(). Keeping stdin and stdout open
* now prevents re-use of fd=0 and 1 by pipe().
*/
ninfo("Closing descriptors\n");
for (fd = 3; fd < CONFIG_THTTPD_NFILE_DESCRIPTORS; fd++)
{
/* Keep hc->conn_fd open for obvious reasons */
if (fd != hc->conn_fd)
{
close(fd);
}
}
/* Create pipes that will be interposed between the CGI task's stdin or
* stdout and the socket.
*
* Setup up the STDIN pipe - a pipe to transfer data received on the
* socket to the CGI program.
*/
ninfo("Create STDIN pipe\n");
ret = pipe(pipefd);
if (ret < 0)
{
nerr("ERROR: STDIN pipe: %d\n", errno);
goto errout_with_cgiconn;
}
else
{
/* Then map the receiving end the pipe to stdin, save the sending end,
* and closing the original receiving end
*/
ret = dup2(pipefd[0], 0);
cc->wrfd = pipefd[1];
close(pipefd[0]);
if (ret < 0)
{
nerr("ERROR: STDIN dup2: %d\n", errno);
goto errout_with_descriptors;
}
}
/* Set up the STDOUT pipe - a pipe to transfer data received from the CGI
* program to the client.
*/
if (ret == 0)
{
ninfo("Create STDOUT pipe\n");
ret = pipe(pipefd);
if (ret < 0)
{
nerr("ERROR: STDOUT pipe: %d\n", errno);
goto errout_with_descriptors;
}
else
{
/* Then map the sending end the pipe to stdout, save the
* receiving end, and closing the original sending end
*/
ret = dup2(pipefd[1], 1);
cc->rdfd = pipefd[0];
close(pipefd[1]);
if (ret < 0)
{
nerr("ERROR: STDOUT dup2: %d\n", errno);
goto errout_with_descriptors;
}
}
}
/* chdir to the directory containing the binary. This isn't in the CGI 1.1
* spec, but it's what other HTTP servers do.
*/
dupname = httpd_strdup(hc->expnfilename);
if (dupname)
{
directory = dirname(dupname);
if (directory)
{
chdir(directory); /* ignore errors */
}
httpd_free(dupname);
}
/* Allocate memory for output buffering */
httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size,
CONFIG_THTTPD_CGIOUTBUFFERSIZE);
if (!cc->outbuf.buffer)
{
nerr("ERROR: hdr allocation failed\n");
goto errout_with_descriptors;
}
/* Create fdwatch structures */
fw = fdwatch_initialize(2);
if (!fw)
{
nerr("ERROR: fdwatch allocation failed\n");
goto errout_with_outbuffer;
}
/* Run the CGI program. */
ninfo("Starting CGI: %s\n", hc->expnfilename);
#ifdef CONFIG_THTTPD_NXFLAT
child = exec(hc->expnfilename, argp, NULL,
g_thttpdsymtab, g_thttpdnsymbols);
#else
child = exec(hc->expnfilename, argp, NULL, NULL, 0);
#endif
if (child < 0)
{
/* Something went wrong. */
nerr("ERROR: execve %s: %d\n", hc->expnfilename, errno);
goto errout_with_watch;
}
/* Schedule a kill for the child task in case it runs too long. */
#if CONFIG_THTTPD_CGI_TIMELIMIT > 0
client_data.i = child;
if (tmr_create(NULL, cgi_kill, client_data,
CONFIG_THTTPD_CGI_TIMELIMIT * 1000L, 0) == NULL)
{
nerr("ERROR: tmr_create(cgi_kill child) failed\n");
goto errout_with_watch;
}
#endif
/* Add the read descriptors to the watch */
fdwatch_add_fd(fw, cc->connfd, NULL);
fdwatch_add_fd(fw, cc->rdfd, NULL);
/* Send any data that is already buffer to the CGI task */
nbytes = hc->read_idx - hc->checked_idx;
ninfo("nbytes: %d contentlength: %d\n", nbytes, hc->contentlength);
if (nbytes > 0)
{
if (httpd_write(cc->wrfd, &(hc->read_buf[hc->checked_idx]), nbytes)
!= nbytes)
{
nerr("ERROR: httpd_write failed\n");
return 1;
}
}
cc->inbuf.contentlength = hc->contentlength;
cc->inbuf.nbytes = nbytes;
/* Then perform the interposition */
indone = false;
outdone = false;
ninfo("Interposing\n");
cgi_semgive(); /* Not safe to reference hc after this point */
do
{
fdwatch(fw, 1000);
/* Check for incoming data from the remote client to the CGI task */
if (!indone && fdwatch_check_fd(fw, cc->connfd))
{
/* Transfer data from the client to the CGI program (POST) */
ninfo("Interpose input\n");
indone = cgi_interpose_input(cc);
if (indone)
{
fdwatch_del_fd(fw, cc->connfd);
}
}
/* Check for outgoing data from the CGI task to the remote client */
if (fdwatch_check_fd(fw, cc->rdfd))
{
/* Handle receipt of headers and CGI program response (GET) */
ninfo("Interpose output\n");
outdone = cgi_interpose_output(cc);
}
/* No outgoing data... is the child task still running? Use kill()
* kill() with signal number == 0 does not actually send a signal, but
* can be used to check if the target task exists. If the task exists
* but is hung, then you might enable CONFIG_THTTPD_CGI_TIMELIMIT to
* kill the task. However, killing the task could cause other problems
* (consider resetting the microprocessor instead).
*/
else if (kill(child, 0) != 0)
{
ninfo("CGI no longer running: %d\n", errno);
outdone = true;
}
}
while (!outdone);
errcode = 0;
/* Get rid of watch structures */
errout_with_watch:
fdwatch_uninitialize(fw);
/* Free output buffer memory */
errout_with_outbuffer:
httpd_free(cc->outbuf.buffer);
/* Close all descriptors */
errout_with_descriptors:
close(cc->wrfd);
close(cc->rdfd);
errout_with_cgiconn:
close(cc->connfd);
httpd_free(cc);
errout:
ninfo("Return %d\n", errcode);
if (errcode != 0)
{
INTERNALERROR("errout");
httpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl);
httpd_write_response(hc);
cgi_semgive();
}
return errcode;
}