From: Fredrik Tolf Date: Wed, 9 Mar 2011 00:18:16 +0000 (+0100) Subject: Merge branch 'master' into sslres X-Git-Tag: 0.7~1 X-Git-Url: http://www.dolda2000.com/gitweb/?p=ashd.git;a=commitdiff_plain;h=4d2dc22a9a68395a5788ae66c84ae0ced2d0e733;hp=d8d4ed57d59c1606bfe868894a1391814f1a3db0 Merge branch 'master' into sslres --- diff --git a/Makefile.am b/Makefile.am index 775edba..4edc773 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,3 @@ SUBDIRS = lib src doc -EXTRA_DIST = etc +EXTRA_DIST = etc examples diff --git a/configure.in b/configure.in index 38327ad..d3d1e4f 100644 --- a/configure.in +++ b/configure.in @@ -39,6 +39,7 @@ fi if test "$HAS_EPOLL" = yes; then AC_DEFINE(HAVE_EPOLL) fi +AM_CONDITIONAL(USE_EPOLL, [test "$HAS_EPOLL" = yes ]) AH_TEMPLATE(HAVE_XATTR, [define to compile support for filesystem extended attributes]) AC_ARG_WITH(xattr, [ --with-xattr Enable XATTR support]) diff --git a/etc/ashd/dirplex.d/send.rc b/etc/ashd/dirplex.d/send.rc index 5e00547..1c4f7cf 100644 --- a/etc/ashd/dirplex.d/send.rc +++ b/etc/ashd/dirplex.d/send.rc @@ -4,6 +4,9 @@ match match filename *.css fork sendfile -c text/css +match + filename *.txt + fork sendfile -c text/plain # Image types match diff --git a/etc/debian/init.d-ashd b/etc/debian/init.d-ashd index c36bc11..9fd1e42 100755 --- a/etc/debian/init.d-ashd +++ b/etc/debian/init.d-ashd @@ -24,7 +24,6 @@ ROOTSPEC="dirplex /srv/www" start() { export LANG log_daemon_msg "Starting HTTP server" "ashd" - ulimit -c unlimited start-stop-daemon -S -p "$PIDFILE" -qx "$HTPARSER" -- -Sf -p "$PIDFILE" -u nobody -r /var/tmp $PORTSPEC -- $ROOTSPEC log_end_msg $? } diff --git a/examples/README b/examples/README new file mode 100644 index 0000000..3f7c9d6 --- /dev/null +++ b/examples/README @@ -0,0 +1,12 @@ +This directory contains some examples of more or less simple ashd +configurations. Each sub-directory contains a self-contained example +with a shell script called `run'. Simply run that script to start the +example, and examine it to see how it was made. All of them will start +an non-SSL HTTP server on port 8080. + +Note that ashd, including the default configuration files, must be +installed on the system to be able to run the examples. + +The `python' sub-directory contains examples for using the Python +module. To run them, the Python module has to be installed and +accessible as well. diff --git a/examples/python/dynhosts/127.0.0.1/index.txt b/examples/python/dynhosts/127.0.0.1/index.txt new file mode 100644 index 0000000..e874877 --- /dev/null +++ b/examples/python/dynhosts/127.0.0.1/index.txt @@ -0,0 +1 @@ +You're accessing via IP address. diff --git a/examples/python/dynhosts/README b/examples/python/dynhosts/README new file mode 100644 index 0000000..a7f806e --- /dev/null +++ b/examples/python/dynhosts/README @@ -0,0 +1,7 @@ +This example uses Python to dynamically dispatch requests for +different vhosts. The Python program dynhosts will look in a directory +for subdirectories named after the virtual host and start a dirplex +instance for each such host that is requested. + +To try this example, run it on your local machine and request + or . diff --git a/examples/python/dynhosts/dynhosts b/examples/python/dynhosts/dynhosts new file mode 100755 index 0000000..9a69a70 --- /dev/null +++ b/examples/python/dynhosts/dynhosts @@ -0,0 +1,25 @@ +#!/usr/bin/python + +import os, sys, signal +from ashd import util + +children = {} +root = sys.argv[1] + +# Automatically reap all children that die for any reason. +signal.signal(signal.SIGCHLD, signal.SIG_IGN) + +def serve(req): + if "Host" in req: + # Strip port specification + dname = req["Host"].split(':')[0] + dname = dname.lower() + path = os.path.join(root, dname) + if os.path.isdir(path): + if dname not in children: + children[dname] = util.pchild(["dirplex", path], autorespawn = True) + children[dname].passreq(req) + return + util.respond(req, "No such host in configured.\n", status = "404 Not Found", ctype = "text/plain") + +util.serveloop(serve) diff --git a/examples/python/dynhosts/localhost/index.txt b/examples/python/dynhosts/localhost/index.txt new file mode 100644 index 0000000..e6b86d8 --- /dev/null +++ b/examples/python/dynhosts/localhost/index.txt @@ -0,0 +1 @@ +You're accessing via hostname. diff --git a/examples/python/dynhosts/run b/examples/python/dynhosts/run new file mode 100755 index 0000000..d4203ea --- /dev/null +++ b/examples/python/dynhosts/run @@ -0,0 +1,12 @@ +#!/bin/sh + +# Change to the directory containing this script +set -e +cd "$(dirname "$0")" + +# Start htparser running this dynhosts script. The setsid command +# ensures that SIGINT is only received by htparser and not by +# dynhosts; it is not of grave importance, but makes shutdown slightly +# more clean, and hides the KeyboardInterrupt otherwise raised by +# Python. +htparser plain:port=8080 -- setsid ./dynhosts . diff --git a/examples/static-files/README b/examples/static-files/README new file mode 100644 index 0000000..d1987ca --- /dev/null +++ b/examples/static-files/README @@ -0,0 +1,2 @@ +To view this example, simply run it on your local machine and point +your web browser at . diff --git a/examples/static-files/index.html b/examples/static-files/index.html new file mode 100644 index 0000000..0492ddb --- /dev/null +++ b/examples/static-files/index.html @@ -0,0 +1,17 @@ + + + + +Test page + + + +

Test page

+

+Note how this page is accessible as +/, +/index and +/index.html. +

+ + diff --git a/examples/static-files/run b/examples/static-files/run new file mode 100755 index 0000000..382c6bb --- /dev/null +++ b/examples/static-files/run @@ -0,0 +1,8 @@ +#!/bin/sh + +# Change to the directory containing this script +set -e +cd "$(dirname "$0")" + +# Simply invoke htparser running dirplex in this directory +htparser plain:port=8080 -- dirplex . diff --git a/examples/static-files/test.css b/examples/static-files/test.css new file mode 100644 index 0000000..178f96e --- /dev/null +++ b/examples/static-files/test.css @@ -0,0 +1,11 @@ +body { + font-family: sans-serif; +} + +h1 { + font-size: 1.75em; +} + +p { + font-size: 1em; +} diff --git a/examples/vhosts/README b/examples/vhosts/README new file mode 100644 index 0000000..d1987ca --- /dev/null +++ b/examples/vhosts/README @@ -0,0 +1,2 @@ +To view this example, simply run it on your local machine and point +your web browser at . diff --git a/examples/vhosts/default-site/index.html b/examples/vhosts/default-site/index.html new file mode 100644 index 0000000..8f75829 --- /dev/null +++ b/examples/vhosts/default-site/index.html @@ -0,0 +1,19 @@ + + + + +Default site + + + +

Default site

+

+This site is called when the requested Host is not exactly +localhost, such as 127.0.0.1. +

+Try visiting +localhost +instead (and make sure the server is running on your local machine). +

+ + diff --git a/examples/vhosts/default-site/test.css b/examples/vhosts/default-site/test.css new file mode 100644 index 0000000..178f96e --- /dev/null +++ b/examples/vhosts/default-site/test.css @@ -0,0 +1,11 @@ +body { + font-family: sans-serif; +} + +h1 { + font-size: 1.75em; +} + +p { + font-size: 1em; +} diff --git a/examples/vhosts/localhost/index.html b/examples/vhosts/localhost/index.html new file mode 100644 index 0000000..26796c0 --- /dev/null +++ b/examples/vhosts/localhost/index.html @@ -0,0 +1,19 @@ + + + + +localhost site + + + +

localhost site

+

+This site is called when the requested Host is +exactly localhost (plus any port specification). +

+Try requesting the server via a different hostname, such as +127.0.0.1, +instead. +

+ + diff --git a/examples/vhosts/localhost/test.css b/examples/vhosts/localhost/test.css new file mode 100644 index 0000000..178f96e --- /dev/null +++ b/examples/vhosts/localhost/test.css @@ -0,0 +1,11 @@ +body { + font-family: sans-serif; +} + +h1 { + font-size: 1.75em; +} + +p { + font-size: 1em; +} diff --git a/examples/vhosts/patterns.conf b/examples/vhosts/patterns.conf new file mode 100644 index 0000000..e73838d --- /dev/null +++ b/examples/vhosts/patterns.conf @@ -0,0 +1,23 @@ +# Sample virtual host configuration. See the patplex(1) manual page +# for further details on the configuration format. + +match + # Match localhost specifically. Note that the Host header may + # include a port specification, which is optionally matched by the + # end of the regex. + header host ^localhost(:[0-9]+)?$ i + handler localhost + +child localhost + # The child specification called when the localhost pattern + # matches. Simply run dirplex(1) in the subdirectory `localhost'. + exec dirplex localhost + +match + # Catch-all pattern for all requests not specifically directed at + # localhost. + default + handler default + +child default + exec dirplex default-site diff --git a/examples/vhosts/run b/examples/vhosts/run new file mode 100755 index 0000000..3c21690 --- /dev/null +++ b/examples/vhosts/run @@ -0,0 +1,9 @@ +#!/bin/sh + +# Change to the directory containing this script +set -e +cd "$(dirname "$0")" + +# Start htparser running patplex; see the patterns.conf file for +# further details. +htparser plain:port=8080 -- patplex patterns.conf diff --git a/lib/Makefile.am b/lib/Makefile.am index a41cde0..d48cfb6 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,6 +1,12 @@ -noinst_LIBRARIES = libht.a +lib_LIBRARIES = libht.a -libht_a_SOURCES = utils.c mt.c log.c req.c proc.c mtio.c resp.c cf.c \ - utils.h mt.h log.h req.h proc.h mtio.h resp.h cf.h -libht_a_CFLAGS = -fPIC -libht_a_CPPFLAGS = -D_GNU_SOURCE +libht_a_SOURCES = utils.c mt.c log.c req.c proc.c mtio.c resp.c cf.c +libht_a_CFLAGS = -fPIC +libht_a_CPPFLAGS = -D_GNU_SOURCE +if USE_EPOLL +libht_a_SOURCES += mtio-epoll.c +else +libht_a_SOURCES += mtio-select.c +endif + +pkginclude_HEADERS = utils.h mt.h log.h req.h proc.h mtio.h resp.h cf.h diff --git a/lib/cf.h b/lib/cf.h index 35529c2..862df4e 100644 --- a/lib/cf.h +++ b/lib/cf.h @@ -1,7 +1,7 @@ #ifndef _ASHCONF_H #define _ASHCONF_H -#include +#include "req.h" struct cfstate { struct muth *pf; diff --git a/lib/mtio-epoll.c b/lib/mtio-epoll.c new file mode 100644 index 0000000..511af81 --- /dev/null +++ b/lib/mtio-epoll.c @@ -0,0 +1,222 @@ +/* + ashd - A Sane HTTP Daemon + Copyright (C) 2008 Fredrik Tolf + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include + +static struct blocker *blockers; + +struct blocker { + struct blocker *n, *p, *n2, *p2; + int fd, reg; + int ev; + time_t to; + struct muth *th; +}; + +static int epfd = -1, fdln = 0; +static struct blocker **fdlist; + +static int regfd(struct blocker *bl) +{ + struct blocker *o; + struct epoll_event evd; + + memset(&evd, 0, sizeof(evd)); + evd.events = 0; + if(bl->ev & EV_READ) + evd.events |= EPOLLIN; + if(bl->ev & EV_WRITE) + evd.events |= EPOLLOUT; + evd.data.fd = bl->fd; + if(bl->fd >= fdln) { + if(fdlist) { + fdlist = srealloc(fdlist, sizeof(*fdlist) * (bl->fd + 1)); + memset(fdlist + fdln, 0, sizeof(*fdlist) * (bl->fd + 1 - fdln)); + fdln = bl->fd + 1; + } else { + fdlist = szmalloc(sizeof(*fdlist) * (fdln = (bl->fd + 1))); + } + } + if(fdlist[bl->fd] == NULL) { + if(epoll_ctl(epfd, EPOLL_CTL_ADD, bl->fd, &evd)) { + /* XXX?! Whatever to do, really? */ + flog(LOG_ERR, "epoll_add on fd %i: %s", bl->fd, strerror(errno)); + return(-1); + } + } else { + for(o = fdlist[bl->fd]; o; o = o->n2) { + if(o->ev & EV_READ) + evd.events |= EPOLLIN; + if(o->ev & EV_WRITE) + evd.events |= EPOLLOUT; + } + if(epoll_ctl(epfd, EPOLL_CTL_MOD, bl->fd, &evd)) { + /* XXX?! Whatever to do, really? */ + flog(LOG_ERR, "epoll_mod on fd %i: %s", bl->fd, strerror(errno)); + return(-1); + } + } + bl->n2 = fdlist[bl->fd]; + bl->p2 = NULL; + if(fdlist[bl->fd] != NULL) + fdlist[bl->fd]->p2 = bl; + fdlist[bl->fd] = bl; + bl->reg = 1; + return(0); +} + +static void remfd(struct blocker *bl) +{ + struct blocker *o; + struct epoll_event evd; + + if(!bl->reg) + return; + if(bl->n2) + bl->n2->p2 = bl->p2; + if(bl->p2) + bl->p2->n2 = bl->n2; + if(bl == fdlist[bl->fd]) + fdlist[bl->fd] = bl->n2; + if(fdlist[bl->fd] == NULL) { + if(epoll_ctl(epfd, EPOLL_CTL_DEL, bl->fd, NULL)) + flog(LOG_ERR, "epoll_del on fd %i: %s", bl->fd, strerror(errno)); + } else { + memset(&evd, 0, sizeof(evd)); + evd.events = 0; + evd.data.fd = bl->fd; + for(o = fdlist[bl->fd]; o; o = o->n2) { + if(o->ev & EV_READ) + evd.events |= EPOLLIN; + if(o->ev & EV_WRITE) + evd.events |= EPOLLOUT; + } + if(epoll_ctl(epfd, EPOLL_CTL_MOD, bl->fd, &evd)) { + /* XXX?! Whatever to do, really? */ + flog(LOG_ERR, "epoll_mod on fd %i: %s", bl->fd, strerror(errno)); + } + } +} + +int block(int fd, int ev, time_t to) +{ + struct blocker *bl; + int rv; + + omalloc(bl); + bl->fd = fd; + bl->ev = ev; + if(to > 0) + bl->to = time(NULL) + to; + bl->th = current; + if((epfd >= 0) && regfd(bl)) { + free(bl); + return(-1); + } + bl->n = blockers; + if(blockers) + blockers->p = bl; + blockers = bl; + rv = yield(); + if(bl->n) + bl->n->p = bl->p; + if(bl->p) + bl->p->n = bl->n; + if(bl == blockers) + blockers = bl->n; + remfd(bl); + free(bl); + return(rv); +} + +void ioloop(void) +{ + struct blocker *bl, *nbl; + struct epoll_event evr[16]; + int i, fd, nev, ev, toval; + time_t now, timeout; + + epfd = epoll_create(128); + fcntl(epfd, F_SETFD, FD_CLOEXEC); + for(bl = blockers; bl; bl = nbl) { + nbl = bl->n; + if(regfd(bl)) + resume(bl->th, -1); + } + while(blockers != NULL) { + timeout = 0; + for(bl = blockers; bl; bl = bl->n) { + if((bl->to != 0) && ((timeout == 0) || (timeout > bl->to))) + timeout = bl->to; + } + now = time(NULL); + if(timeout == 0) + toval = -1; + else if(timeout > now) + toval = (timeout - now) * 1000; + else + toval = 1000; + nev = epoll_wait(epfd, evr, sizeof(evr) / sizeof(*evr), toval); + if(nev < 0) { + if(errno != EINTR) { + flog(LOG_CRIT, "ioloop: select errored out: %s", strerror(errno)); + /* To avoid CPU hogging in case it's bad, which it + * probably is. */ + sleep(1); + } + continue; + } + for(i = 0; i < nev; i++) { + fd = evr[i].data.fd; + ev = 0; + if(evr[i].events & EPOLLIN) + ev |= EV_READ; + if(evr[i].events & EPOLLOUT) + ev |= EV_WRITE; + if(evr[i].events & ~(EPOLLIN | EPOLLOUT)) + ev = -1; + for(bl = fdlist[fd]; bl; bl = nbl) { + nbl = bl->n2; + if((ev < 0) || (ev & bl->ev)) + resume(bl->th, ev); + } + } + now = time(NULL); + for(bl = blockers; bl; bl = nbl) { + nbl = bl->n; + if((bl->to != 0) && (bl->to <= now)) + resume(bl->th, 0); + } + } + close(epfd); + epfd = -1; +} diff --git a/lib/mtio-select.c b/lib/mtio-select.c new file mode 100644 index 0000000..eb84a45 --- /dev/null +++ b/lib/mtio-select.c @@ -0,0 +1,123 @@ +/* + ashd - A Sane HTTP Daemon + Copyright (C) 2008 Fredrik Tolf + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include + +static struct blocker *blockers; + +struct blocker { + struct blocker *n, *p; + int fd; + int ev; + time_t to; + struct muth *th; +}; + +int block(int fd, int ev, time_t to) +{ + struct blocker *bl; + int rv; + + omalloc(bl); + bl->fd = fd; + bl->ev = ev; + if(to > 0) + bl->to = time(NULL) + to; + bl->th = current; + bl->n = blockers; + if(blockers) + blockers->p = bl; + blockers = bl; + rv = yield(); + if(bl->n) + bl->n->p = bl->p; + if(bl->p) + bl->p->n = bl->n; + if(bl == blockers) + blockers = bl->n; + free(bl); + return(rv); +} + +void ioloop(void) +{ + int ret; + fd_set rfds, wfds, efds; + struct blocker *bl, *nbl; + struct timeval toval; + time_t now, timeout; + int maxfd; + int ev; + + while(blockers != NULL) { + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + maxfd = 0; + now = time(NULL); + timeout = 0; + for(bl = blockers; bl; bl = bl->n) { + if(bl->ev & EV_READ) + FD_SET(bl->fd, &rfds); + if(bl->ev & EV_WRITE) + FD_SET(bl->fd, &wfds); + FD_SET(bl->fd, &efds); + if(bl->fd > maxfd) + maxfd = bl->fd; + if((bl->to != 0) && ((timeout == 0) || (timeout > bl->to))) + timeout = bl->to; + } + toval.tv_sec = timeout - now; + toval.tv_usec = 0; + ret = select(maxfd + 1, &rfds, &wfds, &efds, timeout?(&toval):NULL); + if(ret < 0) { + if(errno != EINTR) { + flog(LOG_CRIT, "ioloop: select errored out: %s", strerror(errno)); + /* To avoid CPU hogging in case it's bad, which it + * probably is. */ + sleep(1); + } + } + now = time(NULL); + for(bl = blockers; bl; bl = nbl) { + nbl = bl->n; + ev = 0; + if(FD_ISSET(bl->fd, &rfds)) + ev |= EV_READ; + if(FD_ISSET(bl->fd, &wfds)) + ev |= EV_WRITE; + if(FD_ISSET(bl->fd, &efds)) + ev = -1; + if((ev < 0) || (ev & bl->ev)) + resume(bl->th, ev); + else if((bl->to != 0) && (bl->to <= now)) + resume(bl->th, 0); + } + } +} diff --git a/lib/mtio.c b/lib/mtio.c index b594f0d..3b728d0 100644 --- a/lib/mtio.c +++ b/lib/mtio.c @@ -18,12 +18,10 @@ #include #include -#include #include +#include #include -#include #include -#include #include #ifdef HAVE_CONFIG_H @@ -34,301 +32,6 @@ #include #include -static struct blocker *blockers; - -#ifdef HAVE_EPOLL - -/* - * Support for epoll. Optimally, different I/O loops should be split - * into different files for greater clarity, but I'll save that fun - * for another day. - * - * Scroll down to #else for the normal select loop. - */ - -#include - -struct blocker { - struct blocker *n, *p, *n2, *p2; - int fd, reg; - int ev; - time_t to; - struct muth *th; -}; - -static int epfd = -1, fdln = 0; -static struct blocker **fdlist; - -static int regfd(struct blocker *bl) -{ - struct blocker *o; - struct epoll_event evd; - - memset(&evd, 0, sizeof(evd)); - evd.events = 0; - if(bl->ev & EV_READ) - evd.events |= EPOLLIN; - if(bl->ev & EV_WRITE) - evd.events |= EPOLLOUT; - evd.data.fd = bl->fd; - if(bl->fd >= fdln) { - if(fdlist) { - fdlist = srealloc(fdlist, sizeof(*fdlist) * (bl->fd + 1)); - memset(fdlist + fdln, 0, sizeof(*fdlist) * (bl->fd + 1 - fdln)); - fdln = bl->fd + 1; - } else { - fdlist = szmalloc(sizeof(*fdlist) * (fdln = (bl->fd + 1))); - } - } - if(fdlist[bl->fd] == NULL) { - if(epoll_ctl(epfd, EPOLL_CTL_ADD, bl->fd, &evd)) { - /* XXX?! Whatever to do, really? */ - flog(LOG_ERR, "epoll_add on fd %i: %s", bl->fd, strerror(errno)); - return(-1); - } - } else { - for(o = fdlist[bl->fd]; o; o = o->n2) { - if(o->ev & EV_READ) - evd.events |= EPOLLIN; - if(o->ev & EV_WRITE) - evd.events |= EPOLLOUT; - } - if(epoll_ctl(epfd, EPOLL_CTL_MOD, bl->fd, &evd)) { - /* XXX?! Whatever to do, really? */ - flog(LOG_ERR, "epoll_mod on fd %i: %s", bl->fd, strerror(errno)); - return(-1); - } - } - bl->n2 = fdlist[bl->fd]; - bl->p2 = NULL; - if(fdlist[bl->fd] != NULL) - fdlist[bl->fd]->p2 = bl; - fdlist[bl->fd] = bl; - bl->reg = 1; - return(0); -} - -static void remfd(struct blocker *bl) -{ - struct blocker *o; - struct epoll_event evd; - - if(!bl->reg) - return; - if(bl->n2) - bl->n2->p2 = bl->p2; - if(bl->p2) - bl->p2->n2 = bl->n2; - if(bl == fdlist[bl->fd]) - fdlist[bl->fd] = bl->n2; - if(fdlist[bl->fd] == NULL) { - if(epoll_ctl(epfd, EPOLL_CTL_DEL, bl->fd, NULL)) - flog(LOG_ERR, "epoll_del on fd %i: %s", bl->fd, strerror(errno)); - } else { - memset(&evd, 0, sizeof(evd)); - evd.events = 0; - evd.data.fd = bl->fd; - for(o = fdlist[bl->fd]; o; o = o->n2) { - if(o->ev & EV_READ) - evd.events |= EPOLLIN; - if(o->ev & EV_WRITE) - evd.events |= EPOLLOUT; - } - if(epoll_ctl(epfd, EPOLL_CTL_MOD, bl->fd, &evd)) { - /* XXX?! Whatever to do, really? */ - flog(LOG_ERR, "epoll_mod on fd %i: %s", bl->fd, strerror(errno)); - } - } -} - -int block(int fd, int ev, time_t to) -{ - struct blocker *bl; - int rv; - - omalloc(bl); - bl->fd = fd; - bl->ev = ev; - if(to > 0) - bl->to = time(NULL) + to; - bl->th = current; - if((epfd >= 0) && regfd(bl)) { - free(bl); - return(-1); - } - bl->n = blockers; - if(blockers) - blockers->p = bl; - blockers = bl; - rv = yield(); - if(bl->n) - bl->n->p = bl->p; - if(bl->p) - bl->p->n = bl->n; - if(bl == blockers) - blockers = bl->n; - remfd(bl); - free(bl); - return(rv); -} - -void ioloop(void) -{ - struct blocker *bl, *nbl; - struct epoll_event evr[16]; - int i, fd, nev, ev, toval; - time_t now, timeout; - - epfd = epoll_create(128); - for(bl = blockers; bl; bl = nbl) { - nbl = bl->n; - if(regfd(bl)) - resume(bl->th, -1); - } - while(blockers != NULL) { - timeout = 0; - for(bl = blockers; bl; bl = bl->n) { - if((bl->to != 0) && ((timeout == 0) || (timeout > bl->to))) - timeout = bl->to; - } - now = time(NULL); - if(timeout == 0) - toval = -1; - else if(timeout > now) - toval = (timeout - now) * 1000; - else - toval = 1000; - nev = epoll_wait(epfd, evr, sizeof(evr) / sizeof(*evr), toval); - if(nev < 0) { - if(errno != EINTR) { - flog(LOG_CRIT, "ioloop: select errored out: %s", strerror(errno)); - /* To avoid CPU hogging in case it's bad, which it - * probably is. */ - sleep(1); - } - continue; - } - for(i = 0; i < nev; i++) { - fd = evr[i].data.fd; - ev = 0; - if(evr[i].events & EPOLLIN) - ev |= EV_READ; - if(evr[i].events & EPOLLOUT) - ev |= EV_WRITE; - if(evr[i].events & ~(EPOLLIN | EPOLLOUT)) - ev = -1; - for(bl = fdlist[fd]; bl; bl = nbl) { - nbl = bl->n2; - if((ev < 0) || (ev & bl->ev)) - resume(bl->th, ev); - } - } - now = time(NULL); - for(bl = blockers; bl; bl = nbl) { - nbl = bl->n; - if((bl->to != 0) && (bl->to <= now)) - resume(bl->th, 0); - } - } - close(epfd); - epfd = -1; -} - -#else - -struct blocker { - struct blocker *n, *p; - int fd; - int ev; - time_t to; - struct muth *th; -}; - -int block(int fd, int ev, time_t to) -{ - struct blocker *bl; - int rv; - - omalloc(bl); - bl->fd = fd; - bl->ev = ev; - if(to > 0) - bl->to = time(NULL) + to; - bl->th = current; - bl->n = blockers; - if(blockers) - blockers->p = bl; - blockers = bl; - rv = yield(); - if(bl->n) - bl->n->p = bl->p; - if(bl->p) - bl->p->n = bl->n; - if(bl == blockers) - blockers = bl->n; - free(bl); - return(rv); -} - -void ioloop(void) -{ - int ret; - fd_set rfds, wfds, efds; - struct blocker *bl, *nbl; - struct timeval toval; - time_t now, timeout; - int maxfd; - int ev; - - while(blockers != NULL) { - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - maxfd = 0; - now = time(NULL); - timeout = 0; - for(bl = blockers; bl; bl = bl->n) { - if(bl->ev & EV_READ) - FD_SET(bl->fd, &rfds); - if(bl->ev & EV_WRITE) - FD_SET(bl->fd, &wfds); - FD_SET(bl->fd, &efds); - if(bl->fd > maxfd) - maxfd = bl->fd; - if((bl->to != 0) && ((timeout == 0) || (timeout > bl->to))) - timeout = bl->to; - } - toval.tv_sec = timeout - now; - toval.tv_usec = 0; - ret = select(maxfd + 1, &rfds, &wfds, &efds, timeout?(&toval):NULL); - if(ret < 0) { - if(errno != EINTR) { - flog(LOG_CRIT, "ioloop: select errored out: %s", strerror(errno)); - /* To avoid CPU hogging in case it's bad, which it - * probably is. */ - sleep(1); - } - } - now = time(NULL); - for(bl = blockers; bl; bl = nbl) { - nbl = bl->n; - ev = 0; - if(FD_ISSET(bl->fd, &rfds)) - ev |= EV_READ; - if(FD_ISSET(bl->fd, &wfds)) - ev |= EV_WRITE; - if(FD_ISSET(bl->fd, &efds)) - ev = -1; - if((ev < 0) || (ev & bl->ev)) - resume(bl->th, ev); - else if((bl->to != 0) && (bl->to <= now)) - resume(bl->th, 0); - } - } -} - -#endif - struct stdiofd { int fd; int sock; diff --git a/lib/proc.c b/lib/proc.c index 8646434..2c05608 100644 --- a/lib/proc.c +++ b/lib/proc.c @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H #include @@ -33,7 +34,6 @@ int stdmkchild(char **argv, void (*chinit)(void *), void *idata) { - int i; pid_t pid; int fd[2]; @@ -44,17 +44,15 @@ int stdmkchild(char **argv, void (*chinit)(void *), void *idata) if(pid == 0) { if(chinit != NULL) chinit(idata); - for(i = 3; i < FD_SETSIZE; i++) { - if(i != fd[0]) - close(i); - } dup2(fd[0], 0); close(fd[0]); + close(fd[1]); execvp(argv[0], argv); flog(LOG_WARNING, "could not exec child program %s: %s", argv[0], strerror(errno)); exit(127); } close(fd[0]); + fcntl(fd[1], F_SETFD, FD_CLOEXEC); return(fd[1]); } @@ -141,8 +139,7 @@ pid_t stdforkserve(char **argv, struct hthead *req, int fd, void (*chinit)(void dup2(fd, 0); dup2(fd, 1); - for(i = 3; i < FD_SETSIZE; i++) - close(i); + close(fd); bufinit(args); for(i = 0; argv[i]; i++) diff --git a/lib/proc.h b/lib/proc.h index 72bff20..6d2ea92 100644 --- a/lib/proc.h +++ b/lib/proc.h @@ -1,7 +1,7 @@ #ifndef _LIB_PROC_H #define _LIB_PROC_H -#include +#include "req.h" int stdmkchild(char **argv, void (*chinit)(void *), void *idata); int sendfd(int sock, int fd, char *data, size_t datalen); diff --git a/lib/req.c b/lib/req.c index 64944bf..a3e7273 100644 --- a/lib/req.c +++ b/lib/req.c @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H #include @@ -254,6 +255,7 @@ int recvreq(int sock, struct hthead **reqp) if((fd = recvfd(sock, &buf.b, &buf.d)) < 0) { return(-1); } + fcntl(fd, F_SETFD, FD_CLOEXEC); buf.s = buf.d; p = buf.b; l = buf.d; diff --git a/lib/resp.h b/lib/resp.h index 8f18f87..6cb7ec3 100644 --- a/lib/resp.h +++ b/lib/resp.h @@ -1,7 +1,7 @@ #ifndef _LIB_HTRESP_H #define _LIB_HTRESP_H -#include +#include "req.h" char *urlquote(char *text); char *htmlquote(char *text); diff --git a/python/ashd/__init__.py b/python/ashd/__init__.py index e69de29..c918ad6 100644 --- a/python/ashd/__init__.py +++ b/python/ashd/__init__.py @@ -0,0 +1,5 @@ +"""Base module for ashd(7)-related fucntions. + +This module implements nothing. Please see the ashd.util or ashd.proto +modules. +""" diff --git a/python/ashd/proto.py b/python/ashd/proto.py index 90ba012..c1ae1b0 100644 --- a/python/ashd/proto.py +++ b/python/ashd/proto.py @@ -1,3 +1,12 @@ +"""Low-level protocol module for ashd(7) + +This module provides primitive functions that speak the raw ashd(7) +protocol. Primarily, it implements the `req' class that is used to +represent ashd requests. The functions it provides can also be used to +create ashd handlers, but unless you require very precise control, the +ashd.util module provides an easier-to-use interface. +""" + import os, socket import htlib @@ -5,6 +14,30 @@ class protoerr(Exception): pass class req(object): + """Represents a single ashd request. Normally, you would not + create instances of this class manually, but receive them from the + recvreq function. + + For the abstract structure of ashd requests, please see the + ashd(7) manual page. This class provides access to the HTTP + method, raw URL, HTTP version and rest string via the `method', + `url', `ver' and `rest' variables respectively. It also implements + a dict-like interface for case-independent access to the HTTP + headers. The raw headers are available as a list of (name, value) + tuples in the `headers' variable. + + For responding, the response socket is available as a standard + Python stream object in the `sk' variable. Again, see the ashd(7) + manpage for what to receive and transmit on the response socket. + + Note that instances of this class contain a reference to the live + socket used for responding to requests, which should be closed + when you are done with the request. The socket can be closed + manually by calling the close() method on this + object. Alternatively, this class implements the resource-manager + interface, so that it can be used in `with' statements. + """ + def __init__(self, method, url, ver, rest, headers, fd): self.method = method self.url = url @@ -15,9 +48,15 @@ class req(object): os.close(fd) def close(self): + "Close this request's response socket." self.sk.close() def __getitem__(self, header): + """Find a HTTP header case-insensitively. For example, + req["Content-Type"] returns the value of the content-type + header regardlessly of whether the client specified it as + "Content-Type", "content-type" or "Content-type". + """ header = header.lower() for key, val in self.headers: if key.lower() == header: @@ -25,6 +64,9 @@ class req(object): raise KeyError(header) def __contains__(self, header): + """Works analogously to the __getitem__ method for checking + header presence case-insensitively. + """ header = header.lower() for key, val in self.headers: if key.lower() == header: @@ -32,9 +74,25 @@ class req(object): return False def dup(self): + """Creates a duplicate of this request, referring to a + duplicate of the response socket. + """ return req(self.method, self.url, self.ver, self.rest, self.headers, os.dup(self.sk.fileno())) def match(self, match): + """If the `match' argument matches exactly the leading part of + the rest string, this method strips that part of the rest + string off and returns True. Otherwise, it returns False + without doing anything. + + This can be used for simple dispatching. For example: + if req.match("foo/"): + handle(req) + elif req.match("bar/"): + handle_otherwise(req) + else: + util.respond(req, "Not found", status = "404 Not Found", ctype = "text/plain") + """ if self.rest[:len(match)] == match: self.rest = self.rest[len(match):] return True @@ -51,6 +109,18 @@ class req(object): return False def recvreq(sock = 0): + """Receive a single ashd request on the specified socket file + descriptor (or standard input if unspecified). + + The returned value is an instance of the `req' class. As per its + description, care should be taken to close() the request when + done, to avoid leaking response sockets. If end-of-file is + received on the socket, None is returned. + + This function may either raise on OSError if an error occurs on + the socket, or a ashd.proto.protoerr if the incoming request is + invalidly encoded. + """ data, fd = htlib.recvfd(sock) if fd is None: return None @@ -72,6 +142,13 @@ def recvreq(sock = 0): os.close(fd) def sendreq(sock, req): + """Encode and send a single request to the specified socket file + descriptor using the ashd protocol. The request should be an + instance of the `req' class. + + This function may raise an OSError if an error occurs on the + socket. + """ data = "" data += req.method + '\0' data += req.url + '\0' diff --git a/python/ashd/util.py b/python/ashd/util.py index 15d61b0..2ac7dc0 100644 --- a/python/ashd/util.py +++ b/python/ashd/util.py @@ -1,7 +1,25 @@ +"""High-level utility module for ashd(7) + +This module implements a rather convenient interface for writing ashd +handlers, wrapping the low-level ashd.proto module. +""" + import os, socket import proto def stdfork(argv, chinit = None): + """Fork a persistent handler process using the `argv' argument + list, as per the standard ashd(7) calling convention. For an + easier-to-use interface, see the `pchild' class. + + If a callable object of no arguments is provided in the `chinit' + argument, it will be called in the child process before exec()'ing + the handler program, and can be used to set parameters for the new + process, such as working directory, nice level or ulimits. + + Returns the file descriptor of the socket for sending requests to + the new child. + """ csk, psk = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET) pid = os.fork() if pid == 0: @@ -22,7 +40,95 @@ def stdfork(argv, chinit = None): psk.close() return fd +class pchild(object): + """class pchild(argv, autorespawn=False, chinit=None) + + Represents a persistent child handler process, started as per the + standard ashd(7) calling convention. It will be called with the + `argv' argument lest, which should be a list (or other iterable) + of strings. + + If `autorespawn' is specified as True, the child process will be + automatically restarted if a request cannot be successfully sent + to it. + + For a description of the `chinit' argument, see `stdfork'. + + When this child handler should be disposed of, care should be + taken to call the close() method to release its socket and let it + exit. This class also implements the resource-manager interface, + so that it can be used in `with' statements. + """ + + def __init__(self, argv, autorespawn = False, chinit = None): + self.argv = argv + self.chinit = chinit + self.fd = -1 + self.respawn = autorespawn + self.spawn() + + def spawn(self): + """Start the child handler, or restart it if it is already + running. You should not have to call this method manually + unless you explicitly want to manage the process' lifecycle. + """ + self.close() + self.fd = stdfork(self.argv, self.chinit) + + def close(self): + """Close this child handler's socket. For normal child + handlers, this will make the program terminate normally. + """ + if self.fd >= 0: + os.close(self.fd) + self.fd = -1 + + def __del__(self): + self.close() + + def passreq(self, req): + """Pass the specified request (which should be an instance of + the ashd.proto.req class) to this child handler. If the child + handler fails for some reason, and `autorespawn' was specified + as True when creating this handler, one attempt will be made + to restart it. + + Note: You still need to close the request normally. + + This method may raise an OSError if the request fails and + autorespawning was either not requested, or if the + autorespawning fails. + """ + try: + proto.sendreq(self.fd, req) + except OSError: + if self.respawn: + self.spawn() + proto.sendreq(self.fd, req) + + def __enter__(self): + return self + + def __exit__(self, *excinfo): + self.close() + return False + def respond(req, body, status = ("200 OK"), ctype = "text/html"): + """Simple function for conveniently responding to a request. + + Sends the specified body text to the request's response socket, + prepending an HTTP header with the appropriate Content-Type and + Content-Length headers, and then closes the response socket. + + The `status' argument can be used to specify a non-200 response, + and the `ctype' argument can be used to specify a non-HTML + MIME-type. + + If `body' is a Unicode object, it will be encoded as UTF-8. + + For example: + respond(req, "Not found", status = "404 Not Found", ctype = "text/plain") + """ if type(body) == unicode: body = body.decode("utf-8") if ctype[:5] == "text/" and ctype.find(';') < 0: @@ -39,6 +145,15 @@ def respond(req, body, status = ("200 OK"), ctype = "text/html"): req.close() def serveloop(handler, sock = 0): + """Implements a simple loop for serving requests sequentially, by + receiving requests from standard input (or the specified socket), + passing them to the specified handler function, and finally making + sure to close them. Returns when end-of-file is received on the + incoming socket. + + The handler function should be a callable object of one argument, + and is called once for each received request. + """ while True: req = proto.recvreq(sock) if req is None: diff --git a/python/htp.c b/python/htp.c index e31528e..33c0361 100644 --- a/python/htp.c +++ b/python/htp.c @@ -19,8 +19,8 @@ #include #include -#include -#include +#include +#include static PyObject *p_recvfd(PyObject *self, PyObject *args) { diff --git a/python/setup.py b/python/setup.py index 117ab3f..8fbf028 100755 --- a/python/setup.py +++ b/python/setup.py @@ -3,9 +3,7 @@ from distutils.core import setup, Extension htlib = Extension("ashd.htlib", ["htp.c"], - libraries = ["ht"], - library_dirs = ["../lib/"], - include_dirs = ["../lib/"]) + libraries = ["ht"]) setup(name = "ashd-py", version = "0.1", diff --git a/src/accesslog.c b/src/accesslog.c index 5aa2574..02e5f1a 100644 --- a/src/accesslog.c +++ b/src/accesslog.c @@ -342,6 +342,7 @@ int main(int argc, char **argv) } } } + fcntl(fileno(out), F_SETFD, FD_CLOEXEC); if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) { flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno)); exit(1); diff --git a/src/callfcgi.c b/src/callfcgi.c index 1ff521e..27c9c5e 100644 --- a/src/callfcgi.c +++ b/src/callfcgi.c @@ -465,8 +465,8 @@ static void mkcgienv(struct hthead *req, struct charbuf *dst) } else { bufaddenv(dst, "SCRIPT_NAME", "%s", url); } - free(url); bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:""); + free(url); if((h = getheader(req, "Host")) != NULL) bufaddenv(dst, "SERVER_NAME", "%s", h); if((h = getheader(req, "X-Ash-Server-Port")) != NULL) diff --git a/src/callscgi.c b/src/callscgi.c index e91759e..07064f3 100644 --- a/src/callscgi.c +++ b/src/callscgi.c @@ -428,8 +428,8 @@ static void mkcgienv(struct hthead *req, struct charbuf *dst) } else { bufaddenv(dst, "SCRIPT_NAME", "%s", url); } - free(url); bufaddenv(dst, "QUERY_STRING", "%s", qp?qp:""); + free(url); if((h = getheader(req, "Host")) != NULL) bufaddenv(dst, "SERVER_NAME", "%s", h); if((h = getheader(req, "X-Ash-Server-Port")) != NULL) diff --git a/src/errlogger.c b/src/errlogger.c index 9e7692c..bc0d0e5 100644 --- a/src/errlogger.c +++ b/src/errlogger.c @@ -58,7 +58,7 @@ int main(int argc, char **argv) name = NULL; prio = LOG_WARNING; fac = LOG_DAEMON; - while((c = getopt(argc, argv, "hn:p:f:")) >= 0) { + while((c = getopt(argc, argv, "+hn:p:f:")) >= 0) { switch(c) { case 'n': name = optarg; diff --git a/src/multifscgi.c b/src/multifscgi.c index 69448c5..6cc232f 100644 --- a/src/multifscgi.c +++ b/src/multifscgi.c @@ -142,7 +142,7 @@ int main(int argc, char **argv) { int c; - while((c = getopt(argc, argv, "h")) >= 0) { + while((c = getopt(argc, argv, "+h")) >= 0) { switch(c) { case 'h': usage(stdout); diff --git a/src/plaintcp.c b/src/plaintcp.c index cf46b29..321be41 100644 --- a/src/plaintcp.c +++ b/src/plaintcp.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,7 @@ int listensock4(int port) close(fd); return(-1); } + fcntl(fd, F_SETFD, FD_CLOEXEC); return(fd); } @@ -92,6 +94,7 @@ int listensock6(int port) close(fd); return(-1); } + fcntl(fd, F_SETFD, FD_CLOEXEC); return(fd); } diff --git a/src/userplex.c b/src/userplex.c index 3a2da87..b66490f 100644 --- a/src/userplex.c +++ b/src/userplex.c @@ -112,7 +112,7 @@ static int forkchild(char *usrnm) { struct passwd *pwd; pid_t pid; - int i, fd[2]; + int fd[2]; /* XXX: There should be a way for the child to report errors (like * 404 when htpub doesn't exist), but for now I don't bother with @@ -126,17 +126,15 @@ static int forkchild(char *usrnm) if((pid = fork()) < 0) return(-1); if(pid == 0) { - for(i = 3; i < FD_SETSIZE; i++) { - if(i != fd[0]) - close(i); - } dup2(fd[0], 0); close(fd[0]); + close(fd[1]); login(pwd); execchild(pwd); exit(127); } close(fd[0]); + fcntl(fd[1], F_SETFD, FD_CLOEXEC); return(fd[1]); }