accesslog: Added logfile locking.
[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>
ca170d77 29#include <sys/stat.h>
048ac115
FT
30
31#ifdef HAVE_CONFIG_H
32#include <config.h>
33#endif
34#include <utils.h>
35#include <log.h>
36#include <req.h>
37#include <proc.h>
38
39#define DEFFORMAT "%{%Y-%m-%d %H:%M:%S}t %m %u %A \"%G\""
40
41static int ch;
f99bcc64 42static char *outname = NULL;
048ac115 43static FILE *out;
ca170d77 44static int flush = 1, locklog = 1;
048ac115 45static char *format;
af222eef 46static struct timeval now;
f99bcc64 47static volatile int reopen = 0;
048ac115
FT
48
49static void qputs(char *s, FILE *o)
50{
51 for(; *s; s++) {
52 if(*s == '\"') {
53 fputs("\\\"", o);
54 } else if(*s == '\\') {
55 fputs("\\\\", o);
56 } else if(*s == '\n') {
57 fputs("\\n", o);
58 } else if(*s == '\t') {
59 fputs("\\t", o);
60 } else if((*s < 32) || (*s >= 128)) {
61 fprintf(o, "\\x%02x", *s);
62 } else {
63 fputc(*s, o);
64 }
65 }
66}
67
68static void logitem(struct hthead *req, char o, char *d)
69{
70 char *h, *p;
71 char buf[1024];
048ac115
FT
72
73 switch(o) {
74 case '%':
75 putc('%', out);
76 break;
77 case 'h':
78 if((h = getheader(req, d)) == NULL) {
79 putc('-', out);
80 } else {
81 qputs(h, out);
82 }
83 break;
84 case 'u':
85 qputs(req->url, out);
86 break;
87 case 'U':
88 strcpy(buf, req->url);
89 if((p = strchr(buf, '?')) != NULL)
90 *p = 0;
91 qputs(buf, out);
92 break;
93 case 'm':
94 qputs(req->method, out);
95 break;
96 case 'r':
97 qputs(req->rest, out);
98 break;
99 case 'v':
100 qputs(req->ver, out);
101 break;
102 case 't':
103 if(!*d)
104 d = "%a, %d %b %Y %H:%M:%S %z";
af222eef 105 strftime(buf, sizeof(buf), d, localtime(&now.tv_sec));
048ac115
FT
106 qputs(buf, out);
107 break;
108 case 'T':
109 if(!*d)
110 d = "%a, %d %b %Y %H:%M:%S %z";
af222eef 111 strftime(buf, sizeof(buf), d, gmtime(&now.tv_sec));
048ac115
FT
112 qputs(buf, out);
113 break;
114 case 's':
af222eef 115 fprintf(out, "%06i", (int)now.tv_usec);
048ac115
FT
116 break;
117 case 'A':
118 logitem(req, 'h', "X-Ash-Address");
119 break;
120 case 'H':
121 logitem(req, 'h', "Host");
122 break;
123 case 'R':
124 logitem(req, 'h', "Referer");
125 break;
126 case 'G':
127 logitem(req, 'h', "User-Agent");
128 break;
129 }
130}
131
132static void logreq(struct hthead *req)
133{
134 char *p, *p2;
135 char d[strlen(format)];
136 char o;
137
138 p = format;
139 while(*p) {
140 if(*p == '%') {
141 p++;
142 if(*p == '{') {
143 p++;
144 if((p2 = strchr(p, '}')) == NULL)
145 continue;
146 memcpy(d, p, p2 - p);
147 d[p2 - p] = 0;
148 p = p2 + 1;
149 } else {
150 d[0] = 0;
151 }
152 o = *p++;
153 if(o == 0)
154 break;
155 logitem(req, o, d);
156 } else {
157 fputc(*p++, out);
158 }
159 }
160 fputc('\n', out);
161 if(flush)
162 fflush(out);
163}
164
165static void serve(struct hthead *req, int fd)
166{
af222eef 167 gettimeofday(&now, NULL);
048ac115
FT
168 if(sendreq(ch, req, fd)) {
169 flog(LOG_ERR, "accesslog: could not pass request to child: %s", strerror(errno));
170 exit(1);
171 }
172 logreq(req);
173}
174
f99bcc64
FT
175static void sighandler(int sig)
176{
177 if(sig == SIGHUP)
178 reopen = 1;
179}
180
ca170d77
FT
181static int lockfile(FILE *file)
182{
183 struct flock ld;
184
185 memset(&ld, 0, sizeof(ld));
186 ld.l_type = F_WRLCK;
187 ld.l_whence = SEEK_SET;
188 ld.l_start = 0;
189 ld.l_len = 0;
190 return(fcntl(fileno(file), F_SETLK, &ld));
191}
192
193static void fetchpid(char *filename)
194{
195 int fd, ret;
196 struct flock ld;
197
198 if((fd = open(filename, O_WRONLY)) < 0) {
199 fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
200 exit(1);
201 }
202 memset(&ld, 0, sizeof(ld));
203 ld.l_type = F_WRLCK;
204 ld.l_whence = SEEK_SET;
205 ld.l_start = 0;
206 ld.l_len = 0;
207 ret = fcntl(fd, F_GETLK, &ld);
208 close(fd);
209 if(ret) {
210 fprintf(stderr, "accesslog: %s: %s\n", filename, strerror(errno));
211 exit(1);
212 }
213 if(ld.l_type == F_UNLCK) {
214 fprintf(stderr, "accesslog: %s: not locked\n", filename);
215 exit(1);
216 }
217 printf("%i\n", (int)ld.l_pid);
218}
219
f99bcc64
FT
220static void reopenlog(void)
221{
222 FILE *new;
ca170d77 223 struct stat olds, news;
f99bcc64
FT
224
225 if(outname == NULL) {
226 flog(LOG_WARNING, "accesslog: received SIGHUP but logging to stdout, so ignoring");
227 return;
228 }
ca170d77
FT
229 if(locklog) {
230 if(fstat(fileno(out), &olds)) {
231 flog(LOG_ERR, "accesslog: could not stat current logfile(?!): %s", strerror(errno));
232 return;
233 }
234 if(!stat(outname, &news)) {
235 if((olds.st_dev == news.st_dev) && (olds.st_ino == news.st_ino)) {
236 /*
237 * This needs to be ignored, because if the same logfile
238 * is opened and then closed, the lock is lost. To quote
239 * the Linux fcntl(2) manpage: "This is bad." No kidding.
240 *
241 * Technically, there is a race condition here when the
242 * file has been stat'ed but not yet opened, where the old
243 * log file, having been previously renamed, changes name
244 * back to the name accesslog knows and is thus reopened
245 * regardlessly, but I think that might fit under the
246 * idiom "pathological case". It should, at least, not be
247 * a security problem.
248 */
249 flog(LOG_INFO, "accesslog: received SIGHUP, but logfile has not changed, so ignoring");
250 return;
251 }
252 }
253 }
f99bcc64
FT
254 if((new = fopen(outname, "a")) == NULL) {
255 flog(LOG_WARNING, "accesslog: could not reopen log file `%s' on SIGHUP: %s", outname, strerror(errno));
256 return;
257 }
ca170d77
FT
258 if(locklog) {
259 if(lockfile(new)) {
260 if((errno == EAGAIN) || (errno == EACCES)) {
261 flog(LOG_ERR, "accesslog: logfile is already locked; reverting to current log", strerror(errno));
262 fclose(new);
263 return;
264 } else {
265 flog(LOG_WARNING, "accesslog: could not lock logfile, so no lock will be held: %s", strerror(errno));
266 }
267 }
268 }
f99bcc64
FT
269 fclose(out);
270 out = new;
271}
272
048ac115
FT
273static void usage(FILE *out)
274{
ca170d77 275 fprintf(out, "usage: accesslog [-hFaL] [-f FORMAT] [-p PIDFILE] OUTFILE CHILD [ARGS...]\n");
048ac115
FT
276}
277
278int main(int argc, char **argv)
279{
280 int c, ret;
281 struct hthead *req;
282 int fd;
283 struct pollfd pfd[2];
472abd3c
FT
284 char *pidfile;
285 FILE *pidout;
048ac115 286
472abd3c 287 pidfile = NULL;
ca170d77 288 while((c = getopt(argc, argv, "+hFaLf:p:P:")) >= 0) {
048ac115
FT
289 switch(c) {
290 case 'h':
291 usage(stdout);
292 exit(0);
048ac115
FT
293 case 'F':
294 flush = 0;
295 break;
ca170d77
FT
296 case 'L':
297 locklog = 0;
298 break;
048ac115
FT
299 case 'f':
300 format = optarg;
301 break;
ca170d77
FT
302 case 'P':
303 fetchpid(optarg);
304 exit(0);
472abd3c
FT
305 case 'p':
306 pidfile = optarg;
307 break;
048ac115
FT
308 case 'a':
309 format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" - - \"%R\" \"%G\"";
310 break;
311 default:
312 usage(stderr);
313 exit(1);
314 }
315 }
9701afc5 316 if(argc - optind < 2) {
048ac115
FT
317 usage(stderr);
318 exit(1);
319 }
320 if(format == NULL)
321 format = DEFFORMAT;
f99bcc64
FT
322 if(!strcmp(argv[optind], "-"))
323 outname = NULL;
324 else
325 outname = argv[optind];
326 if(outname == NULL) {
9701afc5
FT
327 out = stdout;
328 } else {
329 if((out = fopen(argv[optind], "a")) == NULL) {
330 flog(LOG_ERR, "accesslog: could not open %s for logging: %s", argv[optind], strerror(errno));
048ac115
FT
331 exit(1);
332 }
048ac115 333 }
ca170d77
FT
334 if(locklog) {
335 if(lockfile(out)) {
336 if((errno == EAGAIN) || (errno == EACCES)) {
337 flog(LOG_ERR, "accesslog: logfile is already locked", strerror(errno));
338 exit(1);
339 } else {
340 flog(LOG_WARNING, "accesslog: could not lock logfile: %s", strerror(errno));
341 }
342 }
343 }
9701afc5 344 if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
e3f12675 345 flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno));
048ac115
FT
346 exit(1);
347 }
f99bcc64 348 signal(SIGHUP, sighandler);
472abd3c
FT
349 if(pidfile) {
350 if(!strcmp(pidfile, "-")) {
351 if(!outname) {
352 flog(LOG_ERR, "accesslog: cannot derive PID file name without an output file");
353 exit(1);
354 }
355 pidfile = sprintf2("%s.pid", outname);
356 }
357 if((pidout = fopen(pidfile, "w")) == NULL) {
358 flog(LOG_ERR, "accesslog: could not open PID file %s for writing: %s", pidfile);
359 exit(1);
360 }
361 fprintf(pidout, "%i\n", (int)getpid());
362 fclose(pidout);
363 }
048ac115 364 while(1) {
f99bcc64
FT
365 if(reopen) {
366 reopenlog();
367 reopen = 0;
368 }
048ac115
FT
369 memset(pfd, 0, sizeof(pfd));
370 pfd[0].fd = 0;
371 pfd[0].events = POLLIN;
372 pfd[1].fd = ch;
373 pfd[1].events = POLLHUP;
374 if((ret = poll(pfd, 2, -1)) < 0) {
375 if(errno != EINTR) {
376 flog(LOG_ERR, "accesslog: error in poll: %s", strerror(errno));
377 exit(1);
378 }
379 }
380 if(pfd[0].revents) {
381 if((fd = recvreq(0, &req)) < 0) {
382 if(errno == 0)
383 break;
384 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
385 exit(1);
386 }
387 serve(req, fd);
388 freehthead(req);
389 close(fd);
390 }
391 if(pfd[1].revents & POLLHUP)
392 break;
393 }
472abd3c
FT
394 if(pidfile != NULL)
395 unlink(pidfile);
048ac115
FT
396 return(0);
397}