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