doc: Added manpage for errlogger.
[ashd.git] / src / accesslog.c
CommitLineData
048ac115
FT
1/*
2 ashd - A Sane HTTP Daemon
3 Copyright (C) 2008 Fredrik Tolf <fredrik@dolda2000.com>
4
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.
9
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.
14
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/>.
17*/
18
19#include <stdlib.h>
20#include <stdio.h>
21#include <unistd.h>
22#include <string.h>
23#include <errno.h>
24#include <sys/poll.h>
25#include <time.h>
26#include <sys/time.h>
f99bcc64 27#include <signal.h>
048ac115
FT
28
29#ifdef HAVE_CONFIG_H
30#include <config.h>
31#endif
32#include <utils.h>
33#include <log.h>
34#include <req.h>
35#include <proc.h>
36
37#define DEFFORMAT "%{%Y-%m-%d %H:%M:%S}t %m %u %A \"%G\""
38
39static int ch;
f99bcc64 40static char *outname = NULL;
048ac115
FT
41static FILE *out;
42static int flush = 1;
43static char *format;
af222eef 44static struct timeval now;
f99bcc64 45static volatile int reopen = 0;
048ac115
FT
46
47static void qputs(char *s, FILE *o)
48{
49 for(; *s; s++) {
50 if(*s == '\"') {
51 fputs("\\\"", o);
52 } else if(*s == '\\') {
53 fputs("\\\\", o);
54 } else if(*s == '\n') {
55 fputs("\\n", o);
56 } else if(*s == '\t') {
57 fputs("\\t", o);
58 } else if((*s < 32) || (*s >= 128)) {
59 fprintf(o, "\\x%02x", *s);
60 } else {
61 fputc(*s, o);
62 }
63 }
64}
65
66static void logitem(struct hthead *req, char o, char *d)
67{
68 char *h, *p;
69 char buf[1024];
048ac115
FT
70
71 switch(o) {
72 case '%':
73 putc('%', out);
74 break;
75 case 'h':
76 if((h = getheader(req, d)) == NULL) {
77 putc('-', out);
78 } else {
79 qputs(h, out);
80 }
81 break;
82 case 'u':
83 qputs(req->url, out);
84 break;
85 case 'U':
86 strcpy(buf, req->url);
87 if((p = strchr(buf, '?')) != NULL)
88 *p = 0;
89 qputs(buf, out);
90 break;
91 case 'm':
92 qputs(req->method, out);
93 break;
94 case 'r':
95 qputs(req->rest, out);
96 break;
97 case 'v':
98 qputs(req->ver, out);
99 break;
100 case 't':
101 if(!*d)
102 d = "%a, %d %b %Y %H:%M:%S %z";
af222eef 103 strftime(buf, sizeof(buf), d, localtime(&now.tv_sec));
048ac115
FT
104 qputs(buf, out);
105 break;
106 case 'T':
107 if(!*d)
108 d = "%a, %d %b %Y %H:%M:%S %z";
af222eef 109 strftime(buf, sizeof(buf), d, gmtime(&now.tv_sec));
048ac115
FT
110 qputs(buf, out);
111 break;
112 case 's':
af222eef 113 fprintf(out, "%06i", (int)now.tv_usec);
048ac115
FT
114 break;
115 case 'A':
116 logitem(req, 'h', "X-Ash-Address");
117 break;
118 case 'H':
119 logitem(req, 'h', "Host");
120 break;
121 case 'R':
122 logitem(req, 'h', "Referer");
123 break;
124 case 'G':
125 logitem(req, 'h', "User-Agent");
126 break;
127 }
128}
129
130static void logreq(struct hthead *req)
131{
132 char *p, *p2;
133 char d[strlen(format)];
134 char o;
135
136 p = format;
137 while(*p) {
138 if(*p == '%') {
139 p++;
140 if(*p == '{') {
141 p++;
142 if((p2 = strchr(p, '}')) == NULL)
143 continue;
144 memcpy(d, p, p2 - p);
145 d[p2 - p] = 0;
146 p = p2 + 1;
147 } else {
148 d[0] = 0;
149 }
150 o = *p++;
151 if(o == 0)
152 break;
153 logitem(req, o, d);
154 } else {
155 fputc(*p++, out);
156 }
157 }
158 fputc('\n', out);
159 if(flush)
160 fflush(out);
161}
162
163static void serve(struct hthead *req, int fd)
164{
af222eef 165 gettimeofday(&now, NULL);
048ac115
FT
166 if(sendreq(ch, req, fd)) {
167 flog(LOG_ERR, "accesslog: could not pass request to child: %s", strerror(errno));
168 exit(1);
169 }
170 logreq(req);
171}
172
f99bcc64
FT
173static void sighandler(int sig)
174{
175 if(sig == SIGHUP)
176 reopen = 1;
177}
178
179static void reopenlog(void)
180{
181 FILE *new;
182
183 if(outname == NULL) {
184 flog(LOG_WARNING, "accesslog: received SIGHUP but logging to stdout, so ignoring");
185 return;
186 }
187 if((new = fopen(outname, "a")) == NULL) {
188 flog(LOG_WARNING, "accesslog: could not reopen log file `%s' on SIGHUP: %s", outname, strerror(errno));
189 return;
190 }
191 fclose(out);
192 out = new;
193}
194
048ac115
FT
195static void usage(FILE *out)
196{
9701afc5 197 fprintf(out, "usage: accesslog [-hFa] [-f FORMAT] OUTFILE CHILD [ARGS...]\n");
048ac115
FT
198}
199
200int main(int argc, char **argv)
201{
202 int c, ret;
203 struct hthead *req;
204 int fd;
205 struct pollfd pfd[2];
048ac115 206
9701afc5 207 while((c = getopt(argc, argv, "+hFaf:")) >= 0) {
048ac115
FT
208 switch(c) {
209 case 'h':
210 usage(stdout);
211 exit(0);
048ac115
FT
212 case 'F':
213 flush = 0;
214 break;
215 case 'f':
216 format = optarg;
217 break;
218 case 'a':
219 format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" - - \"%R\" \"%G\"";
220 break;
221 default:
222 usage(stderr);
223 exit(1);
224 }
225 }
9701afc5 226 if(argc - optind < 2) {
048ac115
FT
227 usage(stderr);
228 exit(1);
229 }
230 if(format == NULL)
231 format = DEFFORMAT;
f99bcc64
FT
232 if(!strcmp(argv[optind], "-"))
233 outname = NULL;
234 else
235 outname = argv[optind];
236 if(outname == NULL) {
9701afc5
FT
237 out = stdout;
238 } else {
239 if((out = fopen(argv[optind], "a")) == NULL) {
240 flog(LOG_ERR, "accesslog: could not open %s for logging: %s", argv[optind], strerror(errno));
048ac115
FT
241 exit(1);
242 }
048ac115 243 }
9701afc5 244 if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
e3f12675 245 flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno));
048ac115
FT
246 exit(1);
247 }
f99bcc64 248 signal(SIGHUP, sighandler);
048ac115 249 while(1) {
f99bcc64
FT
250 if(reopen) {
251 reopenlog();
252 reopen = 0;
253 }
048ac115
FT
254 memset(pfd, 0, sizeof(pfd));
255 pfd[0].fd = 0;
256 pfd[0].events = POLLIN;
257 pfd[1].fd = ch;
258 pfd[1].events = POLLHUP;
259 if((ret = poll(pfd, 2, -1)) < 0) {
260 if(errno != EINTR) {
261 flog(LOG_ERR, "accesslog: error in poll: %s", strerror(errno));
262 exit(1);
263 }
264 }
265 if(pfd[0].revents) {
266 if((fd = recvreq(0, &req)) < 0) {
267 if(errno == 0)
268 break;
269 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
270 exit(1);
271 }
272 serve(req, fd);
273 freehthead(req);
274 close(fd);
275 }
276 if(pfd[1].revents & POLLHUP)
277 break;
278 }
279 return(0);
280}