accesslog: Added PID file option.
[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 #include <fcntl.h>
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
40 static int ch;
41 static char *outname = NULL;
42 static FILE *out;
43 static int flush = 1;
44 static char *format;
45 static struct timeval now;
46 static volatile int reopen = 0;
47
48 static 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
67 static void logitem(struct hthead *req, char o, char *d)
68 {
69     char *h, *p;
70     char buf[1024];
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.tv_sec));
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.tv_sec));
111         qputs(buf, out);
112         break;
113     case 's':
114         fprintf(out, "%06i", (int)now.tv_usec);
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
131 static 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
164 static void serve(struct hthead *req, int fd)
165 {
166     gettimeofday(&now, NULL);
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
174 static void sighandler(int sig)
175 {
176     if(sig == SIGHUP)
177         reopen = 1;
178 }
179
180 static 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
196 static void usage(FILE *out)
197 {
198     fprintf(out, "usage: accesslog [-hFa] [-f FORMAT] [-p PIDFILE] OUTFILE CHILD [ARGS...]\n");
199 }
200
201 int main(int argc, char **argv)
202 {
203     int c, ret;
204     struct hthead *req;
205     int fd;
206     struct pollfd pfd[2];
207     char *pidfile;
208     FILE *pidout;
209     
210     pidfile = NULL;
211     while((c = getopt(argc, argv, "+hFaf:p:")) >= 0) {
212         switch(c) {
213         case 'h':
214             usage(stdout);
215             exit(0);
216         case 'F':
217             flush = 0;
218             break;
219         case 'f':
220             format = optarg;
221             break;
222         case 'p':
223             pidfile = optarg;
224             break;
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     }
233     if(argc - optind < 2) {
234         usage(stderr);
235         exit(1);
236     }
237     if(format == NULL)
238         format = DEFFORMAT;
239     if(!strcmp(argv[optind], "-"))
240         outname = NULL;
241     else
242         outname = argv[optind];
243     if(outname == NULL) {
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));
248             exit(1);
249         }
250     }
251     if((ch = stdmkchild(argv + optind + 1, NULL, NULL)) < 0) {
252         flog(LOG_ERR, "accesslog: could not fork child: %s", strerror(errno));
253         exit(1);
254     }
255     signal(SIGHUP, sighandler);
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     }
271     while(1) {
272         if(reopen) {
273             reopenlog();
274             reopen = 0;
275         }
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     }
301     if(pidfile != NULL)
302         unlink(pidfile);
303     return(0);
304 }