2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
31 #include <sys/socket.h>
44 #define DEFFORMAT "%{%Y-%m-%d %H:%M:%S}t %m %u %A \"%G\""
46 static struct logdata {
47 struct hthead *req, *resp;
48 struct timeval start, end;
49 off_t bytesin, bytesout;
55 static int ch, filter;
56 static char *outname = NULL;
58 static int flush = 1, locklog = 1;
60 static volatile int reopen = 0;
62 static void qputs(char *sp, FILE *o)
64 unsigned char *s = (unsigned char *)sp;
69 } else if(*s == '\\') {
71 } else if(*s == '\n') {
73 } else if(*s == '\t') {
75 } else if((*s < 32) || (*s >= 128)) {
76 fprintf(o, "\\x%02x", (int)*s);
83 static void logitem(struct logdata *data, char o, char *d)
93 if((h = getheader(data->req, d)) == NULL) {
100 qputs(data->req->url, out);
103 strncpy(buf, data->req->url, sizeof(buf));
104 buf[sizeof(buf) - 1] = 0;
105 if((p = strchr(buf, '?')) != NULL)
110 qputs(data->req->method, out);
113 qputs(data->req->rest, out);
116 qputs(data->req->ver, out);
120 d = "%a, %d %b %Y %H:%M:%S %z";
121 strftime(buf, sizeof(buf), d, localtime(&data->start.tv_sec));
126 d = "%a, %d %b %Y %H:%M:%S %z";
127 strftime(buf, sizeof(buf), d, gmtime(&data->start.tv_sec));
131 fprintf(out, "%06i", (int)data->start.tv_usec);
137 fprintf(out, "%i", data->resp->code);
140 if(data->bytesin < 0)
143 fprintf(out, "%ji", (intmax_t)data->bytesin);
146 if(data->bytesout < 0)
149 fprintf(out, "%ji", (intmax_t)data->bytesout);
152 if((data->end.tv_sec == 0) && (data->end.tv_usec == 0))
155 fprintf(out, "%.6f", (data->end.tv_sec - data->start.tv_sec) + ((data->end.tv_usec - data->start.tv_usec) / 1000000.0));
158 logitem(data, 'h', "X-Ash-Address");
161 logitem(data, 'h', "Host");
164 logitem(data, 'h', "Referer");
167 logitem(data, 'h', "User-Agent");
172 static void logreq(struct logdata *data)
175 char d[strlen(format)];
184 if((p2 = strchr(p, '}')) == NULL)
186 memcpy(d, p, p2 - p);
205 static void serve(struct hthead *req, int fd)
211 gettimeofday(&data.start, NULL);
212 if(sendreq(ch, req, fd)) {
213 flog(LOG_ERR, "accesslog: could not pass request to child: %s", strerror(errno));
219 static int passdata(struct bufio *in, struct bufio *out, off_t *passed)
226 if((read = biordata(in)) > 0) {
227 if((read = biowritesome(out, in->rbuf.b + in->rh, read)) < 0)
232 if(biorspace(in) && (biofillsome(in) < 0))
240 static void filterreq(struct muth *mt, va_list args)
242 vavar(struct hthead *, req);
246 struct bufio *cl, *hd;
247 struct stdiofd *cli, *hdi;
254 gettimeofday(&data.start, NULL);
255 cl = mtbioopen(fd, 1, 600, "r+", &cli);
256 if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfds))
258 hd = mtbioopen(pfds[1], 1, 600, "r+", &hdi);
259 if(sendreq(ch, req, pfds[0])) {
265 if(passdata(cl, hd, &data.bytesin))
269 shutdown(pfds[1], SHUT_WR);
270 if((resp = parseresponseb(hd)) == NULL)
272 cli->sendrights = hdi->rights;
275 writerespb(cl, resp);
276 bioprintf(cl, "\r\n");
277 if(passdata(hd, cl, &data.bytesout))
279 gettimeofday(&data.end, NULL);
292 static void sighandler(int sig)
302 static int lockfile(FILE *file)
306 memset(&ld, 0, sizeof(ld));
308 ld.l_whence = SEEK_SET;
311 return(fcntl(fileno(file), F_SETLK, &ld));
314 static void fetchpid(char *filename)
319 if((fd = open(filename, O_WRONLY)) < 0) {
320 fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
323 memset(&ld, 0, sizeof(ld));
325 ld.l_whence = SEEK_SET;
328 ret = fcntl(fd, F_GETLK, &ld);
331 fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
334 if(ld.l_type == F_UNLCK) {
335 fprintf(stderr, "accesslog: %s: not locked\n", filename);
338 printf("%i\n", (int)ld.l_pid);
341 static void reopenlog(void)
344 struct stat olds, news;
346 if(outname == NULL) {
347 flog(LOG_WARNING, "accesslog: received SIGHUP but logging to stdout, so ignoring");
351 if(fstat(fileno(out), &olds)) {
352 flog(LOG_ERR, "accesslog: could not stat current logfile(?!): %s", strerror(errno));
355 if(!stat(outname, &news)) {
356 if((olds.st_dev == news.st_dev) && (olds.st_ino == news.st_ino)) {
358 * This needs to be ignored, because if the same logfile
359 * is opened and then closed, the lock is lost. To quote
360 * the Linux fcntl(2) manpage: "This is bad." No kidding.
362 * Technically, there is a race condition here when the
363 * file has been stat'ed but not yet opened, where the old
364 * log file, having been previously renamed, changes name
365 * back to the name accesslog knows and is thus reopened
366 * regardlessly, but I think that might fit under the
367 * idiom "pathological case". It should, at least, not be
368 * a security problem.
370 flog(LOG_INFO, "accesslog: received SIGHUP, but logfile has not changed, so ignoring");
375 if((new = fopen(outname, "a")) == NULL) {
376 flog(LOG_WARNING, "accesslog: could not reopen log file `%s' on SIGHUP: %s", outname, strerror(errno));
379 fcntl(fileno(new), F_SETFD, FD_CLOEXEC);
382 if((errno == EAGAIN) || (errno == EACCES)) {
383 flog(LOG_ERR, "accesslog: logfile is already locked; reverting to current log", strerror(errno));
387 flog(LOG_WARNING, "accesslog: could not lock logfile, so no lock will be held: %s", strerror(errno));
395 static void listenloop(struct muth *mt, va_list args)
402 block(lfd, EV_READ, 0);
403 if((fd = recvreq(lfd, &req)) < 0) {
405 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
408 mustart(filterreq, req, fd);
412 static void chwatch(struct muth *mt, va_list args)
416 block(cfd, EV_READ, 0);
420 static void floop(void)
422 mustart(listenloop, 0);
423 mustart(chwatch, ch);
436 static void sloop(void)
440 struct pollfd pfd[2];
447 memset(pfd, 0, sizeof(pfd));
449 pfd[0].events = POLLIN;
451 pfd[1].events = POLLHUP;
452 if((ret = poll(pfd, 2, -1)) < 0) {
454 flog(LOG_ERR, "accesslog: error in poll: %s", strerror(errno));
459 if((fd = recvreq(0, &req)) < 0) {
462 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
469 if(pfd[1].revents & POLLHUP)
474 static void usage(FILE *out)
476 fprintf(out, "usage: accesslog [-hFaL] [-f FORMAT] [-p PIDFILE] OUTFILE CHILD [ARGS...]\n");
477 fprintf(out, " accesslog -P LOGFILE\n");
480 int main(int argc, char **argv)
487 while((c = getopt(argc, argv, "+hFaeLf:p:P:")) >= 0) {
511 format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" %c %o \"%R\" \"%G\"";
518 if(argc - optind < 2) {
524 if(!strcmp(argv[optind], "-"))
527 outname = argv[optind];
528 if(outname == NULL) {
532 if((out = fopen(argv[optind], "a")) == NULL) {
533 flog(LOG_ERR, "accesslog: could not open %s for logging: %s", argv[optind], strerror(errno));
536 fcntl(fileno(out), F_SETFD, FD_CLOEXEC);
540 if((errno == EAGAIN) || (errno == EACCES)) {
541 flog(LOG_ERR, "accesslog: logfile is already locked", strerror(errno));
544 flog(LOG_WARNING, "accesslog: could not lock logfile: %s", strerror(errno));
548 if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
549 flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno));
552 signal(SIGHUP, sighandler);
554 if(!strcmp(pidfile, "-")) {
556 flog(LOG_ERR, "accesslog: cannot derive PID file name without an output file");
559 pidfile = sprintf2("%s.pid", outname);
561 if((pidout = fopen(pidfile, "w")) == NULL) {
562 flog(LOG_ERR, "accesslog: could not open PID file %s for writing: %s", pidfile);
565 fprintf(pidout, "%i\n", (int)getpid());