mirror of
https://github.com/bdnugget/kcgi.git
synced 2026-11-07 05:47:27 +00:00
755 lines
17 KiB
C
755 lines
17 KiB
C
/* $Id$ */
|
|
/*
|
|
* Copyright (c) 2015--2016, 2018 Kristaps Dzonsons <kristaps@bsd.lv>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
#include "config.h"
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h> /* BUFSIZ */
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "kcgi.h"
|
|
#include "extern.h"
|
|
|
|
struct kfcgi {
|
|
const struct kvalid *keys;
|
|
size_t keysz;
|
|
const char *const *mimes;
|
|
size_t mimesz;
|
|
size_t defmime;
|
|
unsigned int debugging;
|
|
const char *const *pages;
|
|
size_t pagesz;
|
|
size_t defpage;
|
|
const struct kmimemap *mimemap;
|
|
pid_t work_pid;
|
|
pid_t sock_pid;
|
|
int work_dat;
|
|
int sock_ctl;
|
|
struct kopts opts;
|
|
void *arg;
|
|
};
|
|
|
|
static volatile sig_atomic_t sig = 0;
|
|
|
|
static void
|
|
dosignal(int arg)
|
|
{
|
|
|
|
sig = 1;
|
|
}
|
|
|
|
/*
|
|
* This is our control process.
|
|
* It listens for FastCGI connections on the manager connection in
|
|
* traditional mode ("fdaccept") xor extended mode ("fdfiled").
|
|
* When it has one, it reads and passes to the worker (sibling) process,
|
|
* which will be parsing the data.
|
|
* When the worker has finished, it passes back the request identifier,
|
|
* which this passes to the main application for output.
|
|
* If the current FastCGI connection closes, abandon it and wait for the
|
|
* next.
|
|
* This exits with the manager connection closes.
|
|
* On exit, it will close the fdaccept or fdfiled descriptor.
|
|
*/
|
|
static int
|
|
kfcgi_control(int work, int ctrl,
|
|
int fdaccept, int fdfiled, pid_t worker)
|
|
{
|
|
struct sockaddr_storage ss;
|
|
socklen_t sslen;
|
|
int fd = -1, rc, ourfd, erc = EXIT_FAILURE;
|
|
uint64_t magic;
|
|
uint32_t cookie, test;
|
|
struct pollfd pfd[2];
|
|
char buf[BUFSIZ];
|
|
ssize_t ssz;
|
|
enum kcgi_err kerr;
|
|
uint16_t rid, rtest;
|
|
|
|
ourfd = fdaccept == -1 ? fdfiled : fdaccept;
|
|
assert(ourfd != -1);
|
|
|
|
if (kxsocketprep(ourfd) != KCGI_OK)
|
|
goto out;
|
|
|
|
for (;;) {
|
|
pfd[0].fd = ourfd;
|
|
pfd[0].events = POLLIN;
|
|
pfd[0].revents = 0;
|
|
pfd[1].fd = ctrl;
|
|
pfd[1].events = POLLIN;
|
|
pfd[1].revents = 0;
|
|
|
|
/*
|
|
* If either the worker or manager disconnect, then exit
|
|
* cleanly.
|
|
* The calling application will check the worker's exit
|
|
* code, which will say whether it did something bad, so
|
|
* we don't really care.
|
|
*/
|
|
|
|
if ((rc = poll(pfd, 2, INFTIM)) < 0) {
|
|
kutil_warn(NULL, NULL, "poll");
|
|
goto out;
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "poll: timeout!?");
|
|
continue;
|
|
}
|
|
|
|
if ((pfd[1].revents & POLLHUP) ||
|
|
!(pfd[0].revents & POLLIN))
|
|
break;
|
|
|
|
/*
|
|
* Accept a new connection.
|
|
* In the old way, a blocking accept from FastCGI
|
|
* socket.
|
|
* This will be round-robined by the kernel so that
|
|
* other control processes are fairly notified.
|
|
* In the new one, accepting a descriptor from the
|
|
* caller.
|
|
*/
|
|
|
|
if (fdaccept != -1) {
|
|
assert(fdfiled == -1);
|
|
sslen = sizeof(ss);
|
|
fd = accept(fdaccept,
|
|
(struct sockaddr *)&ss, &sslen);
|
|
if (fd < 0) {
|
|
if (errno == EAGAIN ||
|
|
errno == EWOULDBLOCK)
|
|
continue;
|
|
kutil_warn(NULL, NULL, "accept");
|
|
goto out;
|
|
}
|
|
} else {
|
|
assert(fdfiled != -1);
|
|
rc = fullreadfd(fdfiled,
|
|
&fd, &magic, sizeof(uint64_t));
|
|
if (rc < 0)
|
|
goto out;
|
|
else if (rc == 0)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We then set that the FastCGI socket is non-blocking,
|
|
* making it consistent with the behaviour of the CGI
|
|
* socket, which is also set as such.
|
|
*/
|
|
|
|
if (kxsocketprep(fd) != KCGI_OK)
|
|
goto out;
|
|
|
|
/* This doesn't need to be crypto quality. */
|
|
|
|
#if HAVE_ARC4RANDOM
|
|
cookie = arc4random();
|
|
#else
|
|
cookie = random();
|
|
#endif
|
|
|
|
/* Write a header cookie to the work. */
|
|
|
|
fullwrite(work, &cookie, sizeof(uint32_t));
|
|
|
|
/*
|
|
* Keep pushing data into the worker til it has read it
|
|
* all, at which point it will write to us.
|
|
* If at any point we have errors (e.g., the connection
|
|
* closes), then write a zero-length frame.
|
|
* Write a zero-length frame at the end anyway.
|
|
*/
|
|
|
|
pfd[0].fd = fd;
|
|
pfd[0].events = POLLIN;
|
|
pfd[1].fd = work;
|
|
pfd[1].events = POLLIN;
|
|
|
|
for (;;) {
|
|
if ((rc = poll(pfd, 2, INFTIM)) < 0) {
|
|
kutil_warn(NULL, NULL, "poll");
|
|
goto out;
|
|
} else if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "poll: timeout!?");
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* If the child responds, that means that the
|
|
* full request has been read and processed.
|
|
* If not, we probably still have data to write
|
|
* to it from the connection.
|
|
*/
|
|
|
|
if (pfd[1].revents & POLLIN) {
|
|
ssz = 0;
|
|
kerr = fullwritenoerr
|
|
(pfd[1].fd, &ssz, sizeof(size_t));
|
|
if (kerr != KCGI_OK)
|
|
goto out;
|
|
break;
|
|
}
|
|
|
|
if (!(pfd[0].revents & POLLIN)) {
|
|
kutil_warnx(NULL, NULL, "poll: no input");
|
|
goto out;
|
|
} else if ((ssz = read(fd, buf, BUFSIZ)) < 0) {
|
|
kutil_warn(NULL, NULL, "read");
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Send the child the amount of data we've read.
|
|
* This will let the child see if the connection
|
|
* abruptly closes, at which point we'll have a
|
|
* read size of zero, and error out.
|
|
*/
|
|
|
|
kerr = fullwritenoerr
|
|
(pfd[1].fd, &ssz, sizeof(size_t));
|
|
if (KCGI_OK != kerr)
|
|
goto out;
|
|
|
|
kerr = fullwritenoerr(pfd[1].fd, buf, ssz);
|
|
if (KCGI_OK != kerr)
|
|
goto out;
|
|
|
|
/*
|
|
* If we wrote a zero-sized buffer, it means
|
|
* that the connection has unexpectedly closed.
|
|
* The child will stop all processing for the
|
|
* request and will not return to the parsing
|
|
* routine for the given session.
|
|
*/
|
|
|
|
if (ssz == 0) {
|
|
kutil_warnx(NULL, NULL, "read: "
|
|
"connection closed");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Now verify that the worker is sane. */
|
|
|
|
if (fullread(pfd[1].fd, &rc,
|
|
sizeof(int), 0, &kerr) < 0)
|
|
goto out;
|
|
|
|
if (rc == 0) {
|
|
kutil_warnx(NULL, NULL, "FastCGI: bad code");
|
|
goto recover;
|
|
}
|
|
|
|
/*
|
|
* We have a non-zero return code.
|
|
* Check our cookie and responseId values.
|
|
*/
|
|
|
|
if (fullread(pfd[1].fd, &test,
|
|
sizeof(uint32_t), 0, &kerr) < 0)
|
|
goto out;
|
|
|
|
if (cookie != test) {
|
|
kutil_warnx(NULL, NULL, "FastCGI: bad cookie");
|
|
goto out;
|
|
}
|
|
|
|
if (fullread(pfd[1].fd, &rid,
|
|
sizeof(uint16_t), 0, &kerr) < 0)
|
|
goto out;
|
|
|
|
/*
|
|
* Pass the file descriptor, which has had its data
|
|
* sucked dry, to the main application.
|
|
* It will do output, so it also needs the FastCGI
|
|
* socket request identifier.
|
|
*/
|
|
|
|
if (!fullwritefd(ctrl, fd, &rid, sizeof(uint16_t)))
|
|
goto out;
|
|
|
|
/*
|
|
* This will wait til the application is finished.
|
|
* It will then double-check the requestId.
|
|
*/
|
|
|
|
if (fullread(ctrl, &rtest,
|
|
sizeof(uint16_t), 0, &kerr) < 0)
|
|
goto out;
|
|
|
|
if (rid != rtest) {
|
|
kutil_warnx(NULL, NULL, "FastCGI: bad cookie");
|
|
goto out;
|
|
}
|
|
|
|
recover:
|
|
/*
|
|
* If we are being passed descriptors (instead of
|
|
* waiting on the accept()), then notify the manager
|
|
* that we've finished processing this request.
|
|
* We also jump to here if the connection fails in any
|
|
* way whilst being transcribed to the worker.
|
|
*/
|
|
|
|
if (fdfiled != -1) {
|
|
kerr = fullwritenoerr(fdfiled,
|
|
&magic, sizeof(uint64_t));
|
|
if (KCGI_OK != kerr)
|
|
goto out;
|
|
}
|
|
|
|
close(fd);
|
|
fd = -1;
|
|
}
|
|
|
|
erc = EXIT_SUCCESS;
|
|
out:
|
|
if (fd != -1)
|
|
close(fd);
|
|
close(ourfd);
|
|
return erc;
|
|
}
|
|
|
|
void
|
|
khttp_fcgi_child_free(struct kfcgi *fcgi)
|
|
{
|
|
|
|
close(fcgi->sock_ctl);
|
|
close(fcgi->work_dat);
|
|
free(fcgi);
|
|
}
|
|
|
|
enum kcgi_err
|
|
khttp_fcgi_free(struct kfcgi *fcgi)
|
|
{
|
|
|
|
if (fcgi == NULL)
|
|
return KCGI_OK;
|
|
|
|
close(fcgi->sock_ctl);
|
|
close(fcgi->work_dat);
|
|
kxwaitpid(fcgi->work_pid);
|
|
kxwaitpid(fcgi->sock_pid);
|
|
free(fcgi);
|
|
return KCGI_OK;
|
|
}
|
|
|
|
enum kcgi_err
|
|
khttp_fcgi_initx(struct kfcgi **fcgip,
|
|
const char *const *mimes, size_t mimesz,
|
|
const struct kvalid *keys, size_t keysz,
|
|
const struct kmimemap *mimemap, size_t defmime,
|
|
const char *const *pages, size_t pagesz,
|
|
size_t defpage, void *arg, void (*argfree)(void *),
|
|
unsigned int debugging, const struct kopts *opts)
|
|
{
|
|
struct kfcgi *fcgi;
|
|
int er, fdaccept, fdfiled;
|
|
int work_ctl[2], work_dat[2], sock_ctl[2];
|
|
pid_t work_pid, sock_pid;
|
|
const char *cp, *ercp;
|
|
sigset_t mask;
|
|
enum sandtype st;
|
|
|
|
/*
|
|
* Determine whether we're supposed to accept() on a socket or,
|
|
* rather, we're supposed to receive file descriptors from a
|
|
* kfcgi-like manager.
|
|
*/
|
|
|
|
st = SAND_CONTROL_OLD;
|
|
fdaccept = fdfiled = -1;
|
|
|
|
if ((cp = getenv("FCGI_LISTENSOCK_DESCRIPTORS")) != NULL) {
|
|
fdfiled = strtonum(cp, 0, INT_MAX, &ercp);
|
|
if (ercp != NULL) {
|
|
fdaccept = STDIN_FILENO;
|
|
fdfiled = -1;
|
|
} else
|
|
st = SAND_CONTROL_NEW;
|
|
} else
|
|
fdaccept = STDIN_FILENO;
|
|
|
|
/*
|
|
* Block this signal unless we're right at the fullreadfd
|
|
* function, at which point unblock and let it interrupt us.
|
|
* We don't save the signal mask because we're allowed free
|
|
* reign on the SIGTERM value.
|
|
*/
|
|
|
|
if (signal(SIGTERM, dosignal) == SIG_ERR) {
|
|
kutil_warn(NULL, NULL, "signal");
|
|
return KCGI_SYSTEM;
|
|
}
|
|
|
|
sigemptyset(&mask);
|
|
sigaddset(&mask, SIGTERM);
|
|
sigprocmask(SIG_BLOCK, &mask, NULL);
|
|
sig = 0;
|
|
|
|
if (kxsocketpair(work_ctl) != KCGI_OK)
|
|
return KCGI_SYSTEM;
|
|
|
|
if (kxsocketpair(work_dat) != KCGI_OK) {
|
|
close(work_ctl[KWORKER_PARENT]);
|
|
close(work_ctl[KWORKER_CHILD]);
|
|
return KCGI_SYSTEM;
|
|
}
|
|
|
|
if ((work_pid = fork()) == -1) {
|
|
er = errno;
|
|
kutil_warn(NULL, NULL, "fork");
|
|
close(work_ctl[KWORKER_PARENT]);
|
|
close(work_ctl[KWORKER_CHILD]);
|
|
close(work_dat[KWORKER_PARENT]);
|
|
close(work_dat[KWORKER_CHILD]);
|
|
return (er == EAGAIN) ? KCGI_EAGAIN : KCGI_ENOMEM;
|
|
} else if (work_pid == 0) {
|
|
if (signal(SIGTERM, SIG_IGN) == SIG_ERR) {
|
|
kutil_warn(NULL, NULL, "signal");
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (argfree != NULL)
|
|
argfree(arg);
|
|
|
|
/*
|
|
* STDIN_FILENO isn't really stdin, it's the control
|
|
* socket used to pass input sockets to us.
|
|
* Thus, close the parent's control socket.
|
|
*/
|
|
|
|
if (fdaccept != -1)
|
|
close(fdaccept);
|
|
if (fdfiled != -1)
|
|
close(fdfiled);
|
|
|
|
close(STDOUT_FILENO);
|
|
close(work_dat[KWORKER_PARENT]);
|
|
close(work_ctl[KWORKER_PARENT]);
|
|
|
|
/*
|
|
* Prep child sandbox and run worker process.
|
|
* The worker code will exit on failure, so no need to
|
|
* check any return codes on it.
|
|
*/
|
|
|
|
er = EXIT_SUCCESS;
|
|
if (!ksandbox_init_child(SAND_WORKER,
|
|
work_dat[KWORKER_CHILD],
|
|
work_ctl[KWORKER_CHILD], -1, -1))
|
|
er = EXIT_FAILURE;
|
|
else
|
|
kworker_fcgi_child
|
|
(work_dat[KWORKER_CHILD],
|
|
work_ctl[KWORKER_CHILD],
|
|
keys, keysz, mimes, mimesz,
|
|
debugging);
|
|
|
|
close(work_dat[KWORKER_CHILD]);
|
|
close(work_ctl[KWORKER_CHILD]);
|
|
_exit(er);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
close(work_dat[KWORKER_CHILD]);
|
|
close(work_ctl[KWORKER_CHILD]);
|
|
|
|
if (kxsocketpair(sock_ctl) != KCGI_OK) {
|
|
close(work_dat[KWORKER_PARENT]);
|
|
close(work_ctl[KWORKER_PARENT]);
|
|
kxwaitpid(work_pid);
|
|
return KCGI_SYSTEM;
|
|
}
|
|
|
|
if ((sock_pid = fork()) == -1) {
|
|
er = errno;
|
|
kutil_warn(NULL, NULL, "fork");
|
|
close(work_dat[KWORKER_PARENT]);
|
|
close(work_ctl[KWORKER_PARENT]);
|
|
close(sock_ctl[KWORKER_CHILD]);
|
|
close(sock_ctl[KWORKER_PARENT]);
|
|
kxwaitpid(work_pid);
|
|
return (er == EAGAIN) ? KCGI_EAGAIN : KCGI_ENOMEM;
|
|
} else if (sock_pid == 0) {
|
|
if (signal(SIGTERM, SIG_IGN) == SIG_ERR) {
|
|
kutil_warn(NULL, NULL, "signal");
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (argfree != NULL)
|
|
argfree(arg);
|
|
|
|
close(STDOUT_FILENO);
|
|
close(work_dat[KWORKER_PARENT]);
|
|
close(sock_ctl[KWORKER_PARENT]);
|
|
|
|
if (!ksandbox_init_child(st,
|
|
sock_ctl[KWORKER_CHILD], -1, fdfiled, fdaccept))
|
|
er = EXIT_FAILURE;
|
|
else
|
|
er = kfcgi_control
|
|
(work_ctl[KWORKER_PARENT],
|
|
sock_ctl[KWORKER_CHILD],
|
|
fdaccept, fdfiled, work_pid);
|
|
|
|
close(work_ctl[KWORKER_PARENT]);
|
|
close(sock_ctl[KWORKER_CHILD]);
|
|
_exit(er);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
close(sock_ctl[KWORKER_CHILD]);
|
|
close(work_ctl[KWORKER_PARENT]);
|
|
|
|
if (fdaccept != -1)
|
|
close(fdaccept);
|
|
if (fdfiled != -1)
|
|
close(fdfiled);
|
|
|
|
/* Now allocate our device. */
|
|
|
|
*fcgip = fcgi = kxcalloc(1, sizeof(struct kfcgi));
|
|
if (fcgi == NULL) {
|
|
close(sock_ctl[KWORKER_PARENT]);
|
|
close(work_dat[KWORKER_PARENT]);
|
|
kxwaitpid(work_pid);
|
|
kxwaitpid(sock_pid);
|
|
return KCGI_ENOMEM;
|
|
}
|
|
|
|
if (opts == NULL)
|
|
fcgi->opts.sndbufsz = -1;
|
|
else
|
|
memcpy(&fcgi->opts, opts, sizeof(struct kopts));
|
|
|
|
if (fcgi->opts.sndbufsz < 0)
|
|
fcgi->opts.sndbufsz = UINT16_MAX;
|
|
|
|
fcgi->work_pid = work_pid;
|
|
fcgi->work_dat = work_dat[KWORKER_PARENT];
|
|
fcgi->sock_pid = sock_pid;
|
|
fcgi->sock_ctl = sock_ctl[KWORKER_PARENT];
|
|
fcgi->arg = arg;
|
|
fcgi->mimes = mimes;
|
|
fcgi->mimesz = mimesz;
|
|
fcgi->defmime = defmime;
|
|
fcgi->keys = keys;
|
|
fcgi->keysz = keysz;
|
|
fcgi->mimemap = mimemap;
|
|
fcgi->pages = pages;
|
|
fcgi->pagesz = pagesz;
|
|
fcgi->defpage = defpage;
|
|
fcgi->debugging = debugging;
|
|
return KCGI_OK;
|
|
}
|
|
|
|
enum kcgi_err
|
|
khttp_fcgi_init(struct kfcgi **fcgi,
|
|
const struct kvalid *keys, size_t keysz,
|
|
const char *const *pages, size_t pagesz,
|
|
size_t defpage)
|
|
{
|
|
|
|
return khttp_fcgi_initx(fcgi, kmimetypes,
|
|
KMIME__MAX, keys, keysz, ksuffixmap,
|
|
KMIME_TEXT_HTML, pages, pagesz, defpage,
|
|
NULL, NULL, 0, NULL);
|
|
}
|
|
|
|
/*
|
|
* Here we wait for the next FastCGI connection in such a way that, if
|
|
* we're notified that we must exit via a SIGTERM, we'll properly close
|
|
* down without spurious warnings.
|
|
*/
|
|
static enum kcgi_err
|
|
fcgi_waitread(int fd)
|
|
{
|
|
int rc;
|
|
struct pollfd pfd;
|
|
sigset_t mask;
|
|
|
|
again:
|
|
pfd.fd = fd;
|
|
pfd.events = POLLIN;
|
|
|
|
/*
|
|
* Unblock SIGTERM around the poll().
|
|
* XXX: this could be drastically simplified with ppoll(), but
|
|
* it's not available on many systems.
|
|
* TODO: provide a shim in wrappers.c.
|
|
*/
|
|
|
|
sigemptyset(&mask);
|
|
sigaddset(&mask, SIGTERM);
|
|
sigprocmask(SIG_UNBLOCK, &mask, NULL);
|
|
rc = poll(&pfd, 1, 1000);
|
|
sigprocmask(SIG_BLOCK, &mask, NULL);
|
|
|
|
/* Exit signal has been set. */
|
|
|
|
if (sig)
|
|
return KCGI_EXIT;
|
|
|
|
/* Problems? Exit. Timeout? Retry. */
|
|
|
|
if (rc < 0) {
|
|
kutil_warn(NULL, NULL, "poll");
|
|
return KCGI_SYSTEM;
|
|
} else if (rc == 0)
|
|
goto again;
|
|
|
|
/* Only POLLIN is a "good" exit from this. */
|
|
|
|
if (pfd.revents & POLLIN)
|
|
return KCGI_OK;
|
|
else if (pfd.revents & POLLHUP)
|
|
return KCGI_EXIT;
|
|
|
|
kutil_warnx(NULL, NULL, "poll: error");
|
|
return KCGI_SYSTEM;
|
|
}
|
|
|
|
enum kcgi_err
|
|
khttp_fcgi_parse(struct kfcgi *fcgi, struct kreq *req)
|
|
{
|
|
enum kcgi_err kerr;
|
|
const struct kmimemap *mm;
|
|
int c, fd = -1;
|
|
uint16_t rid;
|
|
|
|
memset(req, 0, sizeof(struct kreq));
|
|
|
|
/*
|
|
* Blocking wait until our control process sends us the file
|
|
* descriptor and requestId of the current sequence.
|
|
* It may also decide to exit, which we note by seeing that
|
|
* "sig" has been set (inheriting the SIGTERM) or the channel
|
|
* closed gracefully.
|
|
*/
|
|
|
|
if ((kerr = fcgi_waitread(fcgi->sock_ctl)) != KCGI_OK)
|
|
return kerr;
|
|
|
|
c = fullreadfd(fcgi->sock_ctl, &fd, &rid, sizeof(uint16_t));
|
|
if (c < 0)
|
|
return KCGI_SYSTEM;
|
|
else if (c == 0)
|
|
return KCGI_EXIT;
|
|
|
|
/* Now get ready to receive data from the child. */
|
|
|
|
req->arg = fcgi->arg;
|
|
req->keys = fcgi->keys;
|
|
req->keysz = fcgi->keysz;
|
|
req->kdata = kdata_alloc(fcgi->sock_ctl,
|
|
fd, rid, fcgi->debugging, &fcgi->opts);
|
|
|
|
if (req->kdata == NULL) {
|
|
close(fd);
|
|
goto err;
|
|
}
|
|
|
|
if (fcgi->keysz) {
|
|
req->cookiemap = kxcalloc
|
|
(fcgi->keysz, sizeof(struct kpair *));
|
|
if (req->cookiemap == NULL)
|
|
goto err;
|
|
req->cookienmap = kxcalloc
|
|
(fcgi->keysz, sizeof(struct kpair *));
|
|
if (req->cookienmap == NULL)
|
|
goto err;
|
|
req->fieldmap = kxcalloc
|
|
(fcgi->keysz, sizeof(struct kpair *));
|
|
if (req->fieldmap == NULL)
|
|
goto err;
|
|
req->fieldnmap = kxcalloc
|
|
(fcgi->keysz, sizeof(struct kpair *));
|
|
if (req->fieldnmap == NULL)
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Read the request itself from the worker child.
|
|
* We'll wait perpetually on data until the channel closes or
|
|
* until we're interrupted during a read by the parent.
|
|
*/
|
|
|
|
kerr = kworker_parent(fcgi->work_dat, req, 0, fcgi->mimesz);
|
|
if (KCGI_OK != kerr)
|
|
goto err;
|
|
|
|
/* Look up page type from component. */
|
|
|
|
req->page = fcgi->defpage;
|
|
if (*req->pagename != '\0')
|
|
for (req->page = 0; req->page < fcgi->pagesz; req->page++)
|
|
if (strcasecmp(fcgi->pages[req->page], req->pagename) == 0)
|
|
break;
|
|
|
|
/* Start with the default. */
|
|
|
|
req->mime = fcgi->defmime;
|
|
if (*req->suffix != '\0') {
|
|
for (mm = fcgi->mimemap; NULL != mm->name; mm++)
|
|
if (strcasecmp(mm->name, req->suffix) == 0) {
|
|
req->mime = mm->mime;
|
|
break;
|
|
}
|
|
/* Could not find this mime type! */
|
|
if (mm->name == NULL)
|
|
req->mime = fcgi->mimesz;
|
|
}
|
|
|
|
return kerr;
|
|
err:
|
|
kdata_free(req->kdata, 0);
|
|
req->kdata = NULL;
|
|
kreq_free(req);
|
|
return kerr;
|
|
}
|
|
|
|
int
|
|
khttp_fcgi_test(void)
|
|
{
|
|
socklen_t len = 0;
|
|
const char *cp, *ercp = NULL;
|
|
|
|
if ((cp = getenv("FCGI_LISTENSOCK_DESCRIPTORS")) != NULL) {
|
|
strtonum(cp, 0, INT_MAX, &ercp);
|
|
if (ercp == NULL)
|
|
return 1;
|
|
}
|
|
|
|
if (getpeername(STDIN_FILENO, NULL, &len) != -1)
|
|
return 0;
|
|
return errno == ENOTCONN;
|
|
}
|