static int cgi_child()

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;
}