accesslog: Reopen the log file on SIGHUP (to support rotation).
[ashd.git] / src / accesslog.c
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>
27 #include <signal.h>
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
39 static int ch;
40 static char *outname = NULL;
41 static FILE *out;
42 static int flush = 1;
43 static char *format;
44 static time_t now;
45 static volatile int reopen = 0;
46
47 static 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
66 static void logitem(struct hthead *req, char o, char *d)
67 {
68     char *h, *p;
69     char buf[1024];
70     struct timeval tv;
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";
104         strftime(buf, sizeof(buf), d, localtime(&now));
105         qputs(buf, out);
106         break;
107     case 'T':
108         if(!*d)
109             d = "%a, %d %b %Y %H:%M:%S %z";
110         strftime(buf, sizeof(buf), d, gmtime(&now));
111         qputs(buf, out);
112         break;
113     case 's':
114         gettimeofday(&tv, NULL);
115         fprintf(out, "%06i", (int)tv.tv_usec);
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
132 static 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
165 static void serve(struct hthead *req, int fd)
166 {
167     now = time(NULL);
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
175 static void sighandler(int sig)
176 {
177     if(sig == SIGHUP)
178         reopen = 1;
179 }
180
181 static void reopenlog(void)
182 {
183     FILE *new;
184     
185     if(outname == NULL) {
186         flog(LOG_WARNING, "accesslog: received SIGHUP but logging to stdout, so ignoring");
187         return;
188     }
189     if((new = fopen(outname, "a")) == NULL) {
190         flog(LOG_WARNING, "accesslog: could not reopen log file `%s' on SIGHUP: %s", outname, strerror(errno));
191         return;
192     }
193     fclose(out);
194     out = new;
195 }
196
197 static void usage(FILE *out)
198 {
199     fprintf(out, "usage: accesslog [-hFa] [-f FORMAT] OUTFILE CHILD [ARGS...]\n");
200 }
201
202 int main(int argc, char **argv)
203 {
204     int c, ret;
205     struct hthead *req;
206     int fd;
207     struct pollfd pfd[2];
208     
209     optarg = NULL;
210     while((c = getopt(argc, argv, "+hFaf:")) >= 0) {
211         switch(c) {
212         case 'h':
213             usage(stdout);
214             exit(0);
215         case 'F':
216             flush = 0;
217             break;
218         case 'f':
219             format = optarg;
220             break;
221         case 'a':
222             format = "%A - - [%{%d/%b/%Y:%H:%M:%S %z}t] \"%m %u %v\" - - \"%R\" \"%G\"";
223             break;
224         default:
225             usage(stderr);
226             exit(1);
227         }
228     }
229     if(argc - optind < 2) {
230         usage(stderr);
231         exit(1);
232     }
233     if(format == NULL)
234         format = DEFFORMAT;
235     if(!strcmp(argv[optind], "-"))
236         outname = NULL;
237     else
238         outname = argv[optind];
239     if(outname == NULL) {
240         out = stdout;
241     } else {
242         if((out = fopen(argv[optind], "a")) == NULL) {
243             flog(LOG_ERR, "accesslog: could not open %s for logging: %s", argv[optind], strerror(errno));
244             exit(1);
245         }
246     }
247     if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
248         flog(LOG_ERR, "accesslog: could fork child: %s", strerror(errno));
249         exit(1);
250     }
251     signal(SIGHUP, sighandler);
252     while(1) {
253         if(reopen) {
254             reopenlog();
255             reopen = 0;
256         }
257         memset(pfd, 0, sizeof(pfd));
258         pfd[0].fd = 0;
259         pfd[0].events = POLLIN;
260         pfd[1].fd = ch;
261         pfd[1].events = POLLHUP;
262         if((ret = poll(pfd, 2, -1)) < 0) {
263             if(errno != EINTR) {
264                 flog(LOG_ERR, "accesslog: error in poll: %s", strerror(errno));
265                 exit(1);
266             }
267         }
268         if(pfd[0].revents) {
269             if((fd = recvreq(0, &req)) < 0) {
270                 if(errno == 0)
271                     break;
272                 flog(LOG_ERR, "accesslog: error in recvreq: %s", strerror(errno));
273                 exit(1);
274             }
275             serve(req, fd);
276             freehthead(req);
277             close(fd);
278         }
279         if(pfd[1].revents & POLLHUP)
280             break;
281     }
282     return(0);
283 }