xref: /lighttpd1.4/src/mod_accesslog.c (revision 3392e8fb)
1 #include "first.h"
2 
3 #include "sys-time.h"
4 
5 #include "base.h"
6 #include "fdevent.h"
7 #include "log.h"
8 #include "buffer.h"
9 #include "http_header.h"
10 #include "sock_addr.h"
11 
12 #include "plugin.h"
13 
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 
17 #include <stdlib.h>
18 #include <string.h>
19 #include <fcntl.h>
20 #include <unistd.h>
21 
22 #ifdef HAVE_SYSLOG_H
23 # include <syslog.h>
24 #endif
25 
26 typedef struct {
27 	char key;
28 	enum {
29 		FORMAT_UNSET,
30 			FORMAT_UNSUPPORTED,
31 			FORMAT_PERCENT,
32 			FORMAT_REMOTE_HOST,
33 			FORMAT_REMOTE_IDENT,
34 			FORMAT_REMOTE_USER,
35 			FORMAT_TIMESTAMP,
36 			FORMAT_REQUEST_LINE,
37 			FORMAT_STATUS,
38 			FORMAT_BYTES_OUT_NO_HEADER,
39 			FORMAT_HEADER,
40 
41 			FORMAT_REMOTE_ADDR,
42 			FORMAT_LOCAL_ADDR,
43 			FORMAT_COOKIE,
44 			FORMAT_TIME_USED_US,
45 			FORMAT_ENV,
46 			FORMAT_FILENAME,
47 			FORMAT_REQUEST_PROTOCOL,
48 			FORMAT_REQUEST_METHOD,
49 			FORMAT_SERVER_PORT,
50 			FORMAT_QUERY_STRING,
51 			FORMAT_TIME_USED,
52 			FORMAT_URL,
53 			FORMAT_SERVER_NAME,
54 			FORMAT_HTTP_HOST,
55 			FORMAT_CONNECTION_STATUS,
56 			FORMAT_BYTES_IN,
57 			FORMAT_BYTES_OUT,
58 
59 			FORMAT_KEEPALIVE_COUNT,
60 			FORMAT_RESPONSE_HEADER,
61 			FORMAT_NOTE
62 	} type;
63 } format_mapping;
64 
65 /**
66  *
67  *
68  * "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
69  *
70  */
71 
72 static const format_mapping fmap[] =
73 {
74 	{ '%', FORMAT_PERCENT },
75 	{ 'h', FORMAT_REMOTE_HOST },
76 	{ 'l', FORMAT_REMOTE_IDENT },
77 	{ 'u', FORMAT_REMOTE_USER },
78 	{ 't', FORMAT_TIMESTAMP },
79 	{ 'r', FORMAT_REQUEST_LINE },
80 	{ 's', FORMAT_STATUS },
81 	{ 'b', FORMAT_BYTES_OUT_NO_HEADER },
82 	{ 'i', FORMAT_HEADER },
83 
84 	{ 'a', FORMAT_REMOTE_ADDR },
85 	{ 'A', FORMAT_LOCAL_ADDR },
86 	{ 'B', FORMAT_BYTES_OUT_NO_HEADER },
87 	{ 'C', FORMAT_COOKIE },
88 	{ 'D', FORMAT_TIME_USED_US },
89 	{ 'e', FORMAT_ENV },
90 	{ 'f', FORMAT_FILENAME },
91 	{ 'H', FORMAT_REQUEST_PROTOCOL },
92 	{ 'k', FORMAT_KEEPALIVE_COUNT },
93 	{ 'm', FORMAT_REQUEST_METHOD },
94 	{ 'n', FORMAT_NOTE },
95 	{ 'p', FORMAT_SERVER_PORT },
96 	{ 'P', FORMAT_UNSUPPORTED }, /* we are only one process */
97 	{ 'q', FORMAT_QUERY_STRING },
98 	{ 'T', FORMAT_TIME_USED },
99 	{ 'U', FORMAT_URL }, /* w/o querystring */
100 	{ 'v', FORMAT_SERVER_NAME },
101 	{ 'V', FORMAT_HTTP_HOST },
102 	{ 'X', FORMAT_CONNECTION_STATUS },
103 	{ 'I', FORMAT_BYTES_IN },
104 	{ 'O', FORMAT_BYTES_OUT },
105 
106 	{ 'o', FORMAT_RESPONSE_HEADER },
107 
108 	{ '\0', FORMAT_UNSET }
109 };
110 
111 
112 enum e_optflags_time {
113 	/* format string is passed to strftime unless other format optflags set
114 	 * (besides FORMAT_FLAG_TIME_BEGIN or FORMAT_FLAG_TIME_END) */
115 	FORMAT_FLAG_TIME_END       = 0x00,/* use request end time (default) */
116 	FORMAT_FLAG_TIME_BEGIN     = 0x01,/* use request start time */
117 	FORMAT_FLAG_TIME_SEC       = 0x02,/* request time as num  sec since epoch */
118 	FORMAT_FLAG_TIME_MSEC      = 0x04,/* request time as num msec since epoch */
119 	FORMAT_FLAG_TIME_USEC      = 0x08,/* request time as num usec since epoch */
120 	FORMAT_FLAG_TIME_NSEC      = 0x10,/* request time as num nsec since epoch */
121 	FORMAT_FLAG_TIME_MSEC_FRAC = 0x20,/* request time msec fraction */
122 	FORMAT_FLAG_TIME_USEC_FRAC = 0x40,/* request time usec fraction */
123 	FORMAT_FLAG_TIME_NSEC_FRAC = 0x80 /* request time nsec fraction */
124 };
125 
126 enum e_optflags_port {
127 	FORMAT_FLAG_PORT_LOCAL     = 0x01,/* (default) */
128 	FORMAT_FLAG_PORT_REMOTE    = 0x02
129 };
130 
131 
132 typedef struct {
133     enum { FIELD_UNSET, FIELD_STRING, FIELD_FORMAT } type;
134     int field;
135     int opt;
136     buffer string;
137 } format_field;
138 
139 typedef struct {
140     time_t last_generated_accesslog_ts;
141     buffer ts_accesslog_str;
142   #if defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L /* C99 */
143     format_field ptr[];  /* C99 VLA */
144   #else
145     format_field ptr[1];
146   #endif
147 } format_fields;
148 
149 typedef struct {
150 	int    log_access_fd;
151 	char use_syslog; /* syslog has global buffer */
152 	char piped_logger;
153 	unsigned short syslog_level;
154 	buffer *access_logbuffer; /* each logfile has a separate buffer */
155 	const buffer *access_logfile;
156 
157 	format_fields *parsed_format;
158 } plugin_config;
159 
160 typedef struct {
161     int log_access_fd;
162     char piped_logger;
163     const buffer *access_logfile;
164     buffer access_logbuffer; /* each logfile has a separate buffer */
165 } accesslog_st;
166 
167 typedef struct {
168     PLUGIN_DATA;
169     plugin_config defaults;
170     plugin_config conf;
171 
172     buffer syslog_logbuffer; /* syslog has global buffer. no caching, always written directly */
173     log_error_st *errh; /* copy of srv->errh */
174     format_fields *default_format;/* allocated if default format */
175 } plugin_data;
176 
177 INIT_FUNC(mod_accesslog_init) {
178     return calloc(1, sizeof(plugin_data));
179 }
180 
181 static int accesslog_write_all(const int fd, buffer * const b) {
182     const ssize_t wr = (fd >= 0) ? write_all(fd, CONST_BUF_LEN(b)) : 0;
183     buffer_clear(b); /*(clear buffer, even if fd < 0)*/
184     return (-1 != wr);
185 }
186 
187 static void accesslog_append_escaped_str(buffer * const dest, const char * const str, const size_t len) {
188 	const char *ptr, *start, *end;
189 
190 	/* replaces non-printable chars with \xHH where HH is the hex representation of the byte */
191 	/* exceptions: " => \", \ => \\, whitespace chars => \n \t etc. */
192 	if (0 == len) return;
193 	buffer_string_prepare_append(dest, len);
194 
195 	for (ptr = start = str, end = str+len; ptr < end; ++ptr) {
196 		unsigned char const c = *(const unsigned char *)ptr;
197 		if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
198 			/* nothing to change, add later as one block */
199 		} else {
200 			/* copy previous part */
201 			if (start < ptr) {
202 				buffer_append_string_len(dest, start, ptr - start);
203 			}
204 			start = ptr + 1;
205 
206 			switch (c) {
207 			case '"':
208 				BUFFER_APPEND_STRING_CONST(dest, "\\\"");
209 				break;
210 			case '\\':
211 				BUFFER_APPEND_STRING_CONST(dest, "\\\\");
212 				break;
213 			case '\b':
214 				BUFFER_APPEND_STRING_CONST(dest, "\\b");
215 				break;
216 			case '\n':
217 				BUFFER_APPEND_STRING_CONST(dest, "\\n");
218 				break;
219 			case '\r':
220 				BUFFER_APPEND_STRING_CONST(dest, "\\r");
221 				break;
222 			case '\t':
223 				BUFFER_APPEND_STRING_CONST(dest, "\\t");
224 				break;
225 			case '\v':
226 				BUFFER_APPEND_STRING_CONST(dest, "\\v");
227 				break;
228 			default: {
229 					/* non printable char => \xHH */
230 					char hh[5] = {'\\','x',0,0,0};
231 					char h = c / 16;
232 					hh[2] = (h > 9) ? (h - 10 + 'A') : (h + '0');
233 					h = c % 16;
234 					hh[3] = (h > 9) ? (h - 10 + 'A') : (h + '0');
235 					buffer_append_string_len(dest, &hh[0], 4);
236 				}
237 				break;
238 			}
239 		}
240 	}
241 
242 	if (start < end) {
243 		buffer_append_string_len(dest, start, end - start);
244 	}
245 }
246 
247 static void accesslog_append_escaped(buffer *dest, const buffer *str) {
248 	accesslog_append_escaped_str(dest, CONST_BUF_LEN(str));
249 }
250 
251 __attribute_cold__
252 static format_fields * accesslog_parse_format_err(log_error_st *errh, const char *file, unsigned int line, format_field *f, const char *msg) {
253     log_error(errh, file, line, "%s", msg);
254     for (; f->type != FIELD_UNSET; ++f) free(f->string.ptr);
255     return NULL;
256 }
257 
258 static format_fields * accesslog_parse_format(const char * const format, const size_t flen, log_error_st * const errh) {
259 	/* common log format (the default) results in 18 elements,
260 	 * so 127 should be enough except for obscene custom usage */
261 	size_t i, j, k = 0, start = 0;
262 	uint32_t used = 0;
263 	const uint32_t sz = 127;/* (sz+1 must match fptr[] num elts below) */
264 	format_field *f;
265 	format_field fptr[128]; /* (128 elements takes 4k on stack in 64-bit) */
266 	memset(fptr, 0, sizeof(fptr));
267 	if (0 != FIELD_UNSET) return NULL;
268 
269 	if (0 == flen) return NULL;
270 
271 	for (i = 0; i < flen; ++i) {
272 		if (format[i] != '%') continue;
273 
274 			if (i > 0 && start != i) {
275 				/* copy the string before this % */
276 				if (used == sz)
277 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
278 					         "too many fields (>= 127) in accesslog.format");
279 
280 				f = fptr+used;
281 				f->type = FIELD_STRING;
282 				memset(&f->string, 0, sizeof(buffer));
283 				buffer_copy_string_len(&f->string, format + start, i - start);
284 
285 				++used;
286 			}
287 
288 			/* we need a new field */
289 			if (used == sz)
290 				return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
291 				         "too many fields (>= 127) in accesslog.format");
292 
293 			/* search for the terminating command */
294 			switch (format[i+1]) {
295 			case '>':
296 			case '<':
297 				/* after the } has to be a character */
298 				if (format[i+2] == '\0') {
299 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
300 					         "%< and %> have to be followed by a format-specifier");
301 				}
302 
303 
304 				for (j = 0; fmap[j].key != '\0'; j++) {
305 					if (fmap[j].key != format[i+2]) continue;
306 
307 					/* found key */
308 
309 					f = fptr+used;
310 					f->type = FIELD_FORMAT;
311 					f->field = fmap[j].type;
312 					f->opt = 0;
313 					f->string.ptr = NULL;
314 
315 					++used;
316 
317 					break;
318 				}
319 
320 				if (fmap[j].key == '\0') {
321 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
322 					         "%< and %> have to be followed by a valid format-specifier");
323 				}
324 
325 				start = i + 3;
326 				i = start - 1; /* skip the string */
327 
328 				break;
329 			case '{':
330 				/* go forward to } */
331 
332 				for (k = i+2; k < flen; ++k) {
333 					if (format[k] == '}') break;
334 				}
335 
336 				if (k == flen) {
337 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
338 					         "%{ has to be terminated by a }");
339 				}
340 
341 				/* after the } has to be a character */
342 				if (format[k+1] == '\0') {
343 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
344 					         "%{...} has to be followed by a format-specifier");
345 				}
346 
347 				if (k == i + 2) {
348 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
349 					         "%{...} has to contain a string");
350 				}
351 
352 				for (j = 0; fmap[j].key != '\0'; j++) {
353 					if (fmap[j].key != format[k+1]) continue;
354 
355 					/* found key */
356 
357 					f = fptr+used;
358 					f->type = FIELD_FORMAT;
359 					f->field = fmap[j].type;
360 					f->opt = 0;
361 					memset(&f->string, 0, sizeof(buffer));
362 					buffer_copy_string_len(&f->string, format + i + 2, k - (i + 2));
363 
364 					++used;
365 
366 					break;
367 				}
368 
369 				if (fmap[j].key == '\0') {
370 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
371 					         "%{...} has to be followed by a valid format-specifier");
372 				}
373 
374 				start = k + 2;
375 				i = start - 1; /* skip the string */
376 
377 				break;
378 			default:
379 				/* after the % has to be a character */
380 				if (format[i+1] == '\0') {
381 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
382 					         "% has to be followed by a format-specifier");
383 				}
384 
385 				for (j = 0; fmap[j].key != '\0'; j++) {
386 					if (fmap[j].key != format[i+1]) continue;
387 
388 					/* found key */
389 
390 					f = fptr+used;
391 					f->type = FIELD_FORMAT;
392 					f->field = fmap[j].type;
393 					f->string.ptr = NULL;
394 					f->opt = 0;
395 
396 					++used;
397 
398 					break;
399 				}
400 
401 				if (fmap[j].key == '\0') {
402 					return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
403 					         "% has to be followed by a valid format-specifier");
404 				}
405 
406 				start = i + 2;
407 				i = start - 1; /* skip the string */
408 
409 				break;
410 			}
411 	}
412 
413 	if (start < i) {
414 		/* copy the string */
415 		if (used == sz)
416 			return accesslog_parse_format_err(errh, __FILE__, __LINE__, fptr,
417 			         "too many fields (>= 127) in accesslog.format");
418 
419 		f = fptr+used;
420 		f->type = FIELD_STRING;
421 		memset(&f->string, 0, sizeof(buffer));
422 		buffer_copy_string_len(&f->string, format + start, i - start);
423 
424 		++used;
425 	}
426 
427 	format_fields * const fields =
428 	  malloc(sizeof(format_fields) + ((used+1) * sizeof(format_field)));
429 	force_assert(fields);
430 	memset(fields, 0, sizeof(format_fields));
431 	memcpy(fields->ptr, fptr, (used+1) * sizeof(format_field));
432 	return fields;
433 }
434 
435 static void mod_accesslog_free_accesslog(accesslog_st * const x, plugin_data *p) {
436     /*(piped loggers are closed in fdevent_close_logger_pipes())*/
437     if (!x->piped_logger && -1 != x->log_access_fd) {
438         if (!accesslog_write_all(x->log_access_fd, &x->access_logbuffer)) {
439             log_perror(p->errh, __FILE__, __LINE__,
440               "writing access log entry failed: %s", x->access_logfile->ptr);
441         }
442         close(x->log_access_fd);
443     }
444     free(x->access_logbuffer.ptr);
445     free(x);
446 }
447 
448 static void mod_accesslog_free_format_fields(format_fields * const ff) {
449     for (format_field *f = ff->ptr; f->type != FIELD_UNSET; ++f)
450         free(f->string.ptr);
451     free(ff->ts_accesslog_str.ptr);
452     free(ff);
453 }
454 
455 FREE_FUNC(mod_accesslog_free) {
456     plugin_data * const p = p_d;
457     free(p->syslog_logbuffer.ptr);
458     if (NULL == p->cvlist) return;
459     /* (init i to 0 if global context; to 1 to skip empty global context) */
460     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
461         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
462         for (; -1 != cpv->k_id; ++cpv) {
463             if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
464             switch (cpv->k_id) {
465               case 0: /* accesslog.filename */
466                 mod_accesslog_free_accesslog(cpv->v.v, p);
467                 break;
468               case 1: /* accesslog.format */
469                 mod_accesslog_free_format_fields(cpv->v.v);
470                 break;
471               default:
472                 break;
473             }
474         }
475     }
476 
477     if (NULL != p->default_format) {
478         mod_accesslog_free_format_fields(p->default_format);
479     }
480 }
481 
482 static void mod_accesslog_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
483     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
484       case 0:{/* accesslog.filename */
485         if (cpv->vtype != T_CONFIG_LOCAL) break;
486         accesslog_st * const x = cpv->v.v;
487         pconf->log_access_fd    = x->log_access_fd;
488         pconf->piped_logger     = x->piped_logger;
489         pconf->access_logfile   = x->access_logfile;
490         pconf->access_logbuffer = &x->access_logbuffer;
491         break;
492       }
493       case 1:{/* accesslog.format */
494         if (cpv->vtype != T_CONFIG_LOCAL) break;
495         pconf->parsed_format = cpv->v.v;
496         break;
497       }
498       case 2: /* accesslog.use-syslog */
499         pconf->use_syslog = (int)cpv->v.u;
500         break;
501       case 3: /* accesslog.syslog-level */
502         pconf->syslog_level = cpv->v.shrt;
503         break;
504       default:/* should not happen */
505         return;
506     }
507 }
508 
509 static void mod_accesslog_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
510     do {
511         mod_accesslog_merge_config_cpv(pconf, cpv);
512     } while ((++cpv)->k_id != -1);
513 }
514 
515 static void mod_accesslog_patch_config(request_st * const r, plugin_data * const p) {
516     memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
517     for (int i = 1, used = p->nconfig; i < used; ++i) {
518         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
519             mod_accesslog_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
520     }
521 }
522 
523 static format_fields * mod_accesslog_process_format(const char * const format, const size_t flen, server * const srv);
524 
525 SETDEFAULTS_FUNC(mod_accesslog_set_defaults) {
526     static const config_plugin_keys_t cpk[] = {
527       { CONST_STR_LEN("accesslog.filename"),
528         T_CONFIG_STRING,
529         T_CONFIG_SCOPE_CONNECTION }
530      ,{ CONST_STR_LEN("accesslog.format"),
531         T_CONFIG_STRING,
532         T_CONFIG_SCOPE_CONNECTION }
533      ,{ CONST_STR_LEN("accesslog.use-syslog"),
534         T_CONFIG_BOOL,
535         T_CONFIG_SCOPE_CONNECTION }
536      ,{ CONST_STR_LEN("accesslog.syslog-level"),
537         T_CONFIG_SHORT,
538         T_CONFIG_SCOPE_CONNECTION }
539      ,{ NULL, 0,
540         T_CONFIG_UNSET,
541         T_CONFIG_SCOPE_UNSET }
542     };
543 
544     plugin_data * const p = p_d;
545     p->errh = srv->errh;
546     if (!config_plugin_values_init(srv, p, cpk, "mod_accesslog"))
547         return HANDLER_ERROR;
548 
549     /* process and validate config directives
550      * (init i to 0 if global context; to 1 to skip empty global context) */
551     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
552         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
553         int use_syslog = 0;
554         accesslog_st *x = NULL;
555         for (; -1 != cpv->k_id; ++cpv) {
556             switch (cpv->k_id) {
557               case 0: /* accesslog.filename */
558                 x = calloc(1, sizeof(accesslog_st));
559                 force_assert(x);
560                 x->log_access_fd = -1;
561                 x->piped_logger = (cpv->v.b->ptr[0] == '|');
562                 x->access_logfile = cpv->v.b;
563                 cpv->vtype = T_CONFIG_LOCAL;
564                 cpv->v.v = x;
565                 break;
566               case 1: /* accesslog.format */
567                 if (NULL != strchr(cpv->v.b->ptr, '\\')) {
568                     /* process basic backslash-escapes in format string */
569                     buffer *b;
570                     *(const buffer **)&b = cpv->v.b;
571                     char *t = b->ptr;
572                     for (char *s = t; *s; ++s) {
573                         if (s[0] != '\\') { *t++ = *s; continue; }
574                         if (s[1] == '\0') continue; /*(ignore dangling '\\')*/
575                         switch (*++s) {
576                           case 'a': *t++ = '\a'; break; /* bell */
577                           case 'b': *t++ = '\b'; break; /* backspace */
578                           case 'f': *t++ = '\f'; break; /* form feed */
579                           case 'n': *t++ = '\n'; break; /* newline */
580                           case 'r': *t++ = '\r'; break; /* carriage return */
581                           case 't': *t++ = '\t'; break; /* horizontal tab */
582                           case 'v': *t++ = '\v'; break; /* vertical tab */
583                           /*case '"':*/
584                           /*case '\\':*/
585                           default:  *t++ = *s;   break; /*(use literal char)*/
586                         }
587                     }
588                     buffer_string_set_length(b, (size_t)(t - b->ptr));
589                 }
590                 cpv->v.v =
591                   mod_accesslog_process_format(CONST_BUF_LEN(cpv->v.b), srv);
592                 if (NULL == cpv->v.v) return HANDLER_ERROR;
593                 cpv->vtype = T_CONFIG_LOCAL;
594                 break;
595               case 2: /* accesslog.use-syslog */
596                 use_syslog = (int)cpv->v.u;
597                 break;
598               case 3: /* accesslog.syslog-level */
599                 break;
600               default:/* should not happen */
601                 break;
602             }
603         }
604 
605         if (srv->srvconf.preflight_check) continue;
606 
607         if (use_syslog) continue; /* ignore the next checks */
608         if (NULL == x || buffer_string_is_empty(x->access_logfile)) continue;
609 
610         x->log_access_fd = fdevent_open_logger(x->access_logfile->ptr);
611         if (-1 == x->log_access_fd) {
612             log_perror(srv->errh, __FILE__, __LINE__,
613               "opening log '%s' failed", x->access_logfile->ptr);
614             return HANDLER_ERROR;
615         }
616     }
617 
618     p->defaults.log_access_fd = -1;
619     p->defaults.syslog_level = LOG_INFO;
620 
621     /* initialize p->defaults from global config context */
622     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
623         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
624         if (-1 != cpv->k_id)
625             mod_accesslog_merge_config(&p->defaults, cpv);
626     }
627 
628     if (NULL == p->defaults.parsed_format) {
629         /* (set default format even if p->use_syslog since
630          *  some other condition might enable logfile) */
631         static const char fmt[] =
632           "%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"";
633         p->defaults.parsed_format = p->default_format =
634           mod_accesslog_process_format(CONST_STR_LEN(fmt), srv);
635         if (NULL == p->default_format) return HANDLER_ERROR;
636     }
637 
638     return HANDLER_GO_ON;
639 }
640 
641 static format_fields * mod_accesslog_process_format(const char * const format, const size_t flen, server * const srv) {
642 			format_fields * const parsed_format =
643 			  accesslog_parse_format(format, flen, srv->errh);
644 			if (NULL == parsed_format) {
645 				log_error(srv->errh, __FILE__, __LINE__,
646 					"parsing accesslog-definition failed: %s", format);
647 				return NULL;
648 			}
649 
650 			uint32_t tcount = 0;
651 			for (format_field *f = parsed_format->ptr; f->type != FIELD_UNSET; ++f) {
652 				const buffer * const fstr = &f->string;
653 				if (FIELD_FORMAT != f->type) continue;
654 				if (FORMAT_TIMESTAMP == f->field) {
655 					if (!buffer_string_is_empty(fstr)) {
656 						const char *ptr = fstr->ptr;
657 						if (0 == strncmp(ptr, "begin:", sizeof("begin:")-1)) {
658 							f->opt |= FORMAT_FLAG_TIME_BEGIN;
659 							ptr += sizeof("begin:")-1;
660 						} else if (0 == strncmp(ptr, "end:", sizeof("end:")-1)) {
661 							f->opt |= FORMAT_FLAG_TIME_END;
662 							ptr += sizeof("end:")-1;
663 						}
664 						if      (0 == strcmp(ptr, "sec"))       f->opt |= FORMAT_FLAG_TIME_SEC;
665 						else if (0 == strcmp(ptr, "msec"))      f->opt |= FORMAT_FLAG_TIME_MSEC;
666 						else if (0 == strcmp(ptr, "usec"))      f->opt |= FORMAT_FLAG_TIME_USEC;
667 						else if (0 == strcmp(ptr, "nsec"))      f->opt |= FORMAT_FLAG_TIME_NSEC;
668 						else if (0 == strcmp(ptr, "msec_frac")) f->opt |= FORMAT_FLAG_TIME_MSEC_FRAC;
669 						else if (0 == strcmp(ptr, "usec_frac")) f->opt |= FORMAT_FLAG_TIME_USEC_FRAC;
670 						else if (0 == strcmp(ptr, "nsec_frac")) f->opt |= FORMAT_FLAG_TIME_NSEC_FRAC;
671 						else if (NULL == strchr(ptr, '%')) {
672 							log_error(srv->errh, __FILE__, __LINE__,
673 								"constant string for time format (misspelled token? or missing '%%'): %s", format);
674 							mod_accesslog_free_format_fields(parsed_format);
675 							return NULL;
676 						}
677 					}
678 
679 					/* make sure they didn't try to send the timestamp in twice
680 					 * (would invalidate pconf->parsed_format.ts_accesslog_str cache of timestamp str) */
681 					if (!(f->opt & ~(FORMAT_FLAG_TIME_BEGIN|FORMAT_FLAG_TIME_END|FORMAT_FLAG_TIME_SEC)) && ++tcount > 1) {
682 						log_error(srv->errh, __FILE__, __LINE__,
683 							"you may not use strftime timestamp format %%{}t twice in the same access log: %s", format);
684 						mod_accesslog_free_format_fields(parsed_format);
685 						return NULL;
686 					}
687 
688 					if (f->opt & FORMAT_FLAG_TIME_BEGIN) srv->srvconf.high_precision_timestamps = 1;
689 				} else if (FORMAT_TIME_USED_US == f->field) {
690 					f->opt |= FORMAT_FLAG_TIME_USEC;
691 					srv->srvconf.high_precision_timestamps = 1;
692 				} else if (FORMAT_TIME_USED == f->field) {
693 					if (buffer_string_is_empty(fstr)
694 					      || buffer_is_equal_string(fstr, CONST_STR_LEN("s"))
695 					      || buffer_is_equal_string(fstr, CONST_STR_LEN("sec")))  f->opt |= FORMAT_FLAG_TIME_SEC;
696 					else if (buffer_is_equal_string(fstr, CONST_STR_LEN("ms"))
697 					      || buffer_is_equal_string(fstr, CONST_STR_LEN("msec"))) f->opt |= FORMAT_FLAG_TIME_MSEC;
698 					else if (buffer_is_equal_string(fstr, CONST_STR_LEN("us"))
699 					      || buffer_is_equal_string(fstr, CONST_STR_LEN("usec"))) f->opt |= FORMAT_FLAG_TIME_USEC;
700 					else if (buffer_is_equal_string(fstr, CONST_STR_LEN("ns"))
701 					      || buffer_is_equal_string(fstr, CONST_STR_LEN("nsec"))) f->opt |= FORMAT_FLAG_TIME_NSEC;
702 					else {
703 						log_error(srv->errh, __FILE__, __LINE__,
704 							"invalid time unit in %%{UNIT}T: %s", format);
705 						mod_accesslog_free_format_fields(parsed_format);
706 						return NULL;
707 					}
708 
709 					if (f->opt & ~(FORMAT_FLAG_TIME_SEC)) srv->srvconf.high_precision_timestamps = 1;
710 				} else if (FORMAT_COOKIE == f->field) {
711 					if (buffer_string_is_empty(fstr)) f->type = FIELD_STRING; /*(blank)*/
712 				} else if (FORMAT_SERVER_PORT == f->field) {
713 					if (buffer_string_is_empty(fstr))
714 						f->opt |= FORMAT_FLAG_PORT_LOCAL;
715 					else if (buffer_is_equal_string(fstr, CONST_STR_LEN("canonical")))
716 						f->opt |= FORMAT_FLAG_PORT_LOCAL;
717 					else if (buffer_is_equal_string(fstr, CONST_STR_LEN("local")))
718 						f->opt |= FORMAT_FLAG_PORT_LOCAL;
719 					else if (buffer_is_equal_string(fstr, CONST_STR_LEN("remote")))
720 						f->opt |= FORMAT_FLAG_PORT_REMOTE;
721 					else {
722 						log_error(srv->errh, __FILE__, __LINE__,
723 							"invalid format %%{canonical,local,remote}p: %s", format);
724 						mod_accesslog_free_format_fields(parsed_format);
725 						return NULL;
726 					}
727 				} else if (FORMAT_HEADER == f->field
728 				           || FORMAT_RESPONSE_HEADER == f->field) {
729 					f->opt = http_header_hkey_get(CONST_BUF_LEN(fstr));
730 				}
731 			}
732 
733 			return parsed_format;
734 }
735 
736 static void log_access_flush(plugin_data * const p) {
737     /* future: might be slightly faster to have allocated array of open files
738      * rather than walking config, but only might matter with many directives */
739     /* (init i to 0 if global context; to 1 to skip empty global context) */
740     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
741         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
742         for (; -1 != cpv->k_id; ++cpv) {
743             switch (cpv->k_id) {
744               case 0: /* accesslog.filename */
745                 if (cpv->vtype == T_CONFIG_LOCAL && NULL != cpv->v.v) {
746                     accesslog_st * const x = cpv->v.v;
747                     if (buffer_string_is_empty(&x->access_logbuffer)) continue;
748                     if (!accesslog_write_all(x->log_access_fd,
749                                              &x->access_logbuffer)) {
750                         log_perror(p->errh, __FILE__, __LINE__,
751                           "writing access log entry failed: %s",
752                           x->access_logfile->ptr);
753                     }
754                 }
755                 break;
756               default:
757                 break;
758             }
759         }
760     }
761 }
762 
763 TRIGGER_FUNC(log_access_periodic_flush) {
764     /* flush buffered access logs every 4 seconds */
765     if (0 == (log_monotonic_secs & 3)) log_access_flush((plugin_data *)p_d);
766     UNUSED(srv);
767     return HANDLER_GO_ON;
768 }
769 
770 SIGHUP_FUNC(log_access_cycle) {
771     plugin_data * const p = p_d;
772 
773     log_access_flush(p);
774 
775     /* future: might be slightly faster to have allocated array of open files
776      * rather than walking config, but only might matter with many directives */
777     /* (init i to 0 if global context; to 1 to skip empty global context) */
778     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
779         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
780         for (; -1 != cpv->k_id; ++cpv) {
781             if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
782             switch (cpv->k_id) {
783               case 0:{/* accesslog.filename */
784                 accesslog_st * const x = cpv->v.v;
785                 if (x->piped_logger) continue;
786                 if (buffer_string_is_empty(x->access_logfile)) continue;
787                 if (-1 == fdevent_cycle_logger(x->access_logfile->ptr,
788                                                &x->log_access_fd)) {
789                     log_perror(srv->errh, __FILE__, __LINE__,
790                       "cycling access log failed: %s", x->access_logfile->ptr);
791                 }
792                 /*(update p->defaults after cycling log)*/
793                 if (0 == i) p->defaults.log_access_fd = x->log_access_fd;
794                 break;
795               }
796               default:
797                 break;
798             }
799         }
800     }
801 
802     return HANDLER_GO_ON;
803 }
804 
805 static int log_access_record (const request_st * const r, buffer * const b, format_fields * const parsed_format) {
806 	const connection * const con = r->con;
807 	const buffer *vb;
808 	struct timespec ts = { 0, 0 };
809 	int flush = 0;
810 
811 	for (const format_field *f = parsed_format->ptr; f->type != FIELD_UNSET; ++f) {
812 		switch(f->type) {
813 		case FIELD_STRING:
814 			buffer_append_string_buffer(b, &f->string);
815 			break;
816 		case FIELD_FORMAT:
817 			switch(f->field) {
818 			case FORMAT_TIMESTAMP:
819 
820 				if (f->opt & ~(FORMAT_FLAG_TIME_BEGIN|FORMAT_FLAG_TIME_END)) {
821 					if (f->opt & FORMAT_FLAG_TIME_SEC) {
822 						time_t t = (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) ? log_epoch_secs : r->start_hp.tv_sec;
823 						buffer_append_int(b, (intmax_t)t);
824 					} else if (f->opt & (FORMAT_FLAG_TIME_MSEC|FORMAT_FLAG_TIME_USEC|FORMAT_FLAG_TIME_NSEC)) {
825 						off_t t; /*(expected to be 64-bit since large file support enabled)*/
826 						long ns;
827 						if (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) {
828 							if (0 == ts.tv_sec) log_clock_gettime_realtime(&ts);
829 							t = (off_t)ts.tv_sec;
830 							ns = ts.tv_nsec;
831 						} else {
832 							t = (off_t)r->start_hp.tv_sec;
833 							ns = r->start_hp.tv_nsec;
834 						}
835 						if (f->opt & FORMAT_FLAG_TIME_MSEC) {
836 							t *= 1000;
837 							t += (ns + 999999) / 1000000; /* ceil */
838 						} else if (f->opt & FORMAT_FLAG_TIME_USEC) {
839 							t *= 1000000;
840 							t += (ns + 999) / 1000; /* ceil */
841 						} else {/*(f->opt & FORMAT_FLAG_TIME_NSEC)*/
842 							t *= 1000000000;
843 							t += ns;
844 						}
845 						buffer_append_int(b, (intmax_t)t);
846 					} else { /*(FORMAT_FLAG_TIME_MSEC_FRAC|FORMAT_FLAG_TIME_USEC_FRAC|FORMAT_FLAG_TIME_NSEC_FRAC)*/
847 						long ns;
848 						char *ptr;
849 						if (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) {
850 							if (0 == ts.tv_sec) log_clock_gettime_realtime(&ts);
851 							ns = ts.tv_nsec;
852 						} else {
853 							ns = r->start_hp.tv_nsec;
854 						}
855 						/*assert(t < 1000000000);*/
856 						if (f->opt & FORMAT_FLAG_TIME_MSEC_FRAC) {
857 							ns +=  999999; /* ceil */
858 							ns /= 1000000;
859 							buffer_append_string_len(b, CONST_STR_LEN("000"));
860 						} else if (f->opt & FORMAT_FLAG_TIME_USEC_FRAC) {
861 							ns +=  999; /* ceil */
862 							ns /= 1000;
863 							buffer_append_string_len(b, CONST_STR_LEN("000000"));
864 						} else {/*(f->opt & FORMAT_FLAG_TIME_NSEC_FRAC)*/
865 							buffer_append_string_len(b, CONST_STR_LEN("000000000"));
866 						}
867 						for (ptr = b->ptr + buffer_string_length(b); ns > 0; ns /= 10)
868 							*--ptr = (ns % 10) + '0';
869 					}
870 				} else {
871 					buffer * const ts_accesslog_str = &parsed_format->ts_accesslog_str;
872 					/* cache the generated timestamp (only if ! FORMAT_FLAG_TIME_BEGIN) */
873 					time_t t;
874 					struct tm tm;
875 
876 					if (!(f->opt & FORMAT_FLAG_TIME_BEGIN)) {
877 						const time_t cur_ts = log_epoch_secs;
878 						if (parsed_format->last_generated_accesslog_ts == cur_ts) {
879 							buffer_append_string_buffer(b, ts_accesslog_str);
880 							break;
881 						}
882 						t = parsed_format->last_generated_accesslog_ts = cur_ts;
883 						flush = 1;
884 					} else {
885 						t = r->start_hp.tv_sec;
886 					}
887 
888 					const char *fmt = buffer_string_is_empty(&f->string)
889 					  ? NULL
890 					  : f->string.ptr;
891 					buffer_clear(ts_accesslog_str);
892 				      #if defined(HAVE_STRUCT_TM_GMTOFF)
893 					buffer_append_strftime(ts_accesslog_str,
894 					                       fmt ? fmt : "[%d/%b/%Y:%T %z]",
895 					                       localtime_r(&t, &tm));
896 				      #else /* HAVE_STRUCT_TM_GMTOFF */
897 					buffer_append_strftime(ts_accesslog_str,
898 					                       fmt ? fmt : "[%d/%b/%Y:%T +0000]",
899 					                       gmtime_r(&t, &tm));
900 				      #endif /* HAVE_STRUCT_TM_GMTOFF */
901 					buffer_append_string_buffer(b, ts_accesslog_str);
902 				}
903 				break;
904 			case FORMAT_TIME_USED:
905 			case FORMAT_TIME_USED_US:
906 				if (f->opt & FORMAT_FLAG_TIME_SEC) {
907 					buffer_append_int(b, log_epoch_secs - r->start_hp.tv_sec);
908 				} else {
909 					const struct timespec * const bs = &r->start_hp;
910 					off_t tdiff; /*(expected to be 64-bit since large file support enabled)*/
911 					if (0 == ts.tv_sec) log_clock_gettime_realtime(&ts);
912 					tdiff = (off_t)(ts.tv_sec - bs->tv_sec)*1000000000 + (ts.tv_nsec - bs->tv_nsec);
913 					if (tdiff <= 0) {
914 						/* sanity check for time moving backwards
915 						 * (daylight savings adjustment or leap seconds or ?) */
916 						tdiff  = -1;
917 					} else if (f->opt & FORMAT_FLAG_TIME_MSEC) {
918 						tdiff +=  999999; /* ceil */
919 						tdiff /= 1000000;
920 					} else if (f->opt & FORMAT_FLAG_TIME_USEC) {
921 						tdiff +=  999; /* ceil */
922 						tdiff /= 1000;
923 					} /* else (f->opt & FORMAT_FLAG_TIME_NSEC) */
924 					buffer_append_int(b, (intmax_t)tdiff);
925 				}
926 				break;
927 			case FORMAT_REMOTE_ADDR:
928 			case FORMAT_REMOTE_HOST:
929 				buffer_append_string_buffer(b, con->dst_addr_buf);
930 				break;
931 			case FORMAT_REMOTE_IDENT:
932 				/* ident */
933 				buffer_append_string_len(b, CONST_STR_LEN("-"));
934 				break;
935 			case FORMAT_REMOTE_USER:
936 				if (NULL != (vb = http_header_env_get(r, CONST_STR_LEN("REMOTE_USER")))) {
937 					accesslog_append_escaped(b, vb);
938 				} else {
939 					buffer_append_string_len(b, CONST_STR_LEN("-"));
940 				}
941 				break;
942 			case FORMAT_REQUEST_LINE:
943 				/*(attempt to reconstruct request line)*/
944 				http_method_append(b, r->http_method);
945 				buffer_append_string_len(b, CONST_STR_LEN(" "));
946 				accesslog_append_escaped(b, &r->target_orig);
947 				buffer_append_string_len(b, CONST_STR_LEN(" "));
948 				http_version_append(b, r->http_version);
949 				break;
950 			case FORMAT_STATUS:
951 				buffer_append_int(b, r->http_status);
952 				break;
953 
954 			case FORMAT_BYTES_OUT_NO_HEADER:
955 			{
956 				off_t bytes = r->http_version <= HTTP_VERSION_1_1
957 				  ? con->bytes_written - r->bytes_written_ckpt
958 				  : r->write_queue.bytes_out;
959 				if (bytes > 0) {
960 					bytes -= (off_t)r->resp_header_len;
961 					buffer_append_int(b, bytes > 0 ? bytes : 0);
962 				} else {
963 					buffer_append_string_len(b, CONST_STR_LEN("-"));
964 				}
965 				break;
966 			}
967 			case FORMAT_HEADER:
968 				if (NULL != (vb = http_header_request_get(r, f->opt, CONST_BUF_LEN(&f->string)))) {
969 					accesslog_append_escaped(b, vb);
970 				} else {
971 					buffer_append_string_len(b, CONST_STR_LEN("-"));
972 				}
973 				break;
974 			case FORMAT_RESPONSE_HEADER:
975 				if (NULL != (vb = http_header_response_get(r, f->opt, CONST_BUF_LEN(&f->string)))) {
976 					accesslog_append_escaped(b, vb);
977 				} else {
978 					buffer_append_string_len(b, CONST_STR_LEN("-"));
979 				}
980 				break;
981 			case FORMAT_ENV:
982 			case FORMAT_NOTE:
983 				if (NULL != (vb = http_header_env_get(r, CONST_BUF_LEN(&f->string)))) {
984 					accesslog_append_escaped(b, vb);
985 				} else {
986 					buffer_append_string_len(b, CONST_STR_LEN("-"));
987 				}
988 				break;
989 			case FORMAT_FILENAME:
990 				if (!buffer_string_is_empty(&r->physical.path)) {
991 					buffer_append_string_buffer(b, &r->physical.path);
992 				} else {
993 					buffer_append_string_len(b, CONST_STR_LEN("-"));
994 				}
995 				break;
996 			case FORMAT_BYTES_OUT:
997 			{
998 				off_t bytes = r->http_version <= HTTP_VERSION_1_1
999 				  ? con->bytes_written - r->bytes_written_ckpt
1000 				  : r->write_queue.bytes_out;
1001 				if (bytes > 0) {
1002 					buffer_append_int(b, bytes);
1003 				} else {
1004 					buffer_append_string_len(b, CONST_STR_LEN("-"));
1005 				}
1006 				break;
1007 			}
1008 			case FORMAT_BYTES_IN:
1009 			{
1010 				off_t bytes = r->http_version <= HTTP_VERSION_1_1
1011 				  ? con->bytes_read - r->bytes_read_ckpt
1012 				  : r->read_queue.bytes_in + (off_t)r->rqst_header_len;
1013 				if (bytes > 0) {
1014 					buffer_append_int(b, bytes);
1015 				} else {
1016 					buffer_append_string_len(b, CONST_STR_LEN("-"));
1017 				}
1018 				break;
1019 			}
1020 			case FORMAT_SERVER_NAME:
1021 				if (!buffer_string_is_empty(r->server_name)) {
1022 					buffer_append_string_buffer(b, r->server_name);
1023 				} else {
1024 					buffer_append_string_len(b, CONST_STR_LEN("-"));
1025 				}
1026 				break;
1027 			case FORMAT_HTTP_HOST:
1028 				if (!buffer_string_is_empty(&r->uri.authority)) {
1029 					accesslog_append_escaped(b, &r->uri.authority);
1030 				} else {
1031 					buffer_append_string_len(b, CONST_STR_LEN("-"));
1032 				}
1033 				break;
1034 			case FORMAT_REQUEST_PROTOCOL:
1035 				http_version_append(b, r->http_version);
1036 				break;
1037 			case FORMAT_REQUEST_METHOD:
1038 				http_method_append(b, r->http_method);
1039 				break;
1040 			case FORMAT_PERCENT:
1041 				buffer_append_string_len(b, CONST_STR_LEN("%"));
1042 				break;
1043 			case FORMAT_LOCAL_ADDR:
1044 				{
1045 					/* (perf: not using getsockname() and
1046 					 *  sock_addr_cache_inet_ntop_copy_buffer())
1047 					 * (still useful if admin has configured explicit listen IPs) */
1048 					const server_socket * const srv_sock = con->srv_socket;
1049 					buffer_append_string_len(b, srv_sock->srv_token->ptr,
1050 					                         srv_sock->srv_token_colon);
1051 				}
1052 				break;
1053 			case FORMAT_SERVER_PORT:
1054 				if (f->opt & FORMAT_FLAG_PORT_REMOTE) {
1055 					buffer_append_int(b, sock_addr_get_port(&con->dst_addr));
1056 				} else { /* if (f->opt & FORMAT_FLAG_PORT_LOCAL) *//*(default)*/
1057 					const server_socket * const srv_sock = con->srv_socket;
1058 					const buffer * const srv_token = srv_sock->srv_token;
1059 					const size_t tlen = buffer_string_length(srv_token);
1060 					size_t colon = srv_sock->srv_token_colon;
1061 					if (colon < tlen) /*(colon != tlen)*/
1062 						buffer_append_string_len(b, srv_token->ptr+colon+1,
1063 						                         tlen - (colon+1));
1064 				}
1065 				break;
1066 			case FORMAT_QUERY_STRING:
1067 				accesslog_append_escaped(b, &r->uri.query);
1068 				break;
1069 			case FORMAT_URL:
1070 				{
1071 					const uint32_t len = buffer_string_length(&r->target);
1072 					const char * const qmark = memchr(r->target.ptr, '?', len);
1073 					accesslog_append_escaped_str(b, r->target.ptr, qmark ? (uint32_t)(qmark - r->target.ptr) : len);
1074 				}
1075 				break;
1076 			case FORMAT_CONNECTION_STATUS:
1077 				if (r->state == CON_STATE_RESPONSE_END) {
1078 					if (r->keep_alive <= 0) {
1079 						buffer_append_string_len(b, CONST_STR_LEN("-"));
1080 					} else {
1081 						buffer_append_string_len(b, CONST_STR_LEN("+"));
1082 					}
1083 				} else { /* CON_STATE_ERROR */
1084 					buffer_append_string_len(b, CONST_STR_LEN("X"));
1085 				}
1086 				break;
1087 			case FORMAT_KEEPALIVE_COUNT:
1088 				if (con->request_count > 1) {
1089 					buffer_append_int(b, (intmax_t)(con->request_count-1));
1090 				} else {
1091 					buffer_append_string_len(b, CONST_STR_LEN("0"));
1092 				}
1093 				break;
1094 			case FORMAT_COOKIE:
1095 				if (NULL != (vb = http_header_request_get(r, HTTP_HEADER_COOKIE, CONST_STR_LEN("Cookie")))) {
1096 					char *str = vb->ptr;
1097 					size_t len = buffer_string_length(&f->string);
1098 					do {
1099 						while (*str == ' ' || *str == '\t') ++str;
1100 						if (0 == strncmp(str, f->string.ptr, len) && str[len] == '=') {
1101 							char *v = str+len+1;
1102 							for (str = v; *str != '\0' && *str != ';'; ++str) ;
1103 							if (str == v) break;
1104 							do { --str; } while (str > v && (*str == ' ' || *str == '\t'));
1105 							accesslog_append_escaped_str(b, v, str - v + 1);
1106 							break;
1107 						} else {
1108 							while (*str != ';' && *str != ' ' && *str != '\t' && *str != '\0') ++str;
1109 						}
1110 						while (*str == ' ' || *str == '\t') ++str;
1111 					} while (*str++ == ';');
1112 				}
1113 				break;
1114 			default:
1115 				break;
1116 			}
1117 			break;
1118 		default:
1119 			break;
1120 		}
1121 	}
1122 
1123 	return flush;
1124 }
1125 
1126 REQUESTDONE_FUNC(log_access_write) {
1127 	plugin_data * const p = p_d;
1128 	mod_accesslog_patch_config(r, p);
1129 
1130 	/* No output device, nothing to do */
1131 	if (!p->conf.use_syslog && p->conf.log_access_fd == -1) return HANDLER_GO_ON;
1132 
1133 	buffer * const b = (p->conf.use_syslog)
1134 	  ? &p->syslog_logbuffer
1135 	  : p->conf.access_logbuffer;
1136 
1137 	const int flush = p->conf.piped_logger
1138 	                | log_access_record(r, b, p->conf.parsed_format);
1139 
1140 	if (p->conf.use_syslog) { /* syslog doesn't cache */
1141 #ifdef HAVE_SYSLOG_H
1142 		if (!buffer_string_is_empty(b)) {
1143 			/*(syslog appends a \n on its own)*/
1144 			syslog(p->conf.syslog_level, "%s", b->ptr);
1145 		}
1146 #endif
1147 		buffer_clear(b);
1148 	}
1149 	else {
1150 		buffer_append_string_len(b, CONST_STR_LEN("\n"));
1151 
1152 		if (flush || buffer_string_length(b) >= 8192) {
1153 			if (!accesslog_write_all(p->conf.log_access_fd, b)) {
1154 				log_perror(r->conf.errh, __FILE__, __LINE__,
1155 				  "writing access log entry failed: %s",
1156 				  p->conf.access_logfile->ptr);
1157 			}
1158 		}
1159 	}
1160 
1161 	return HANDLER_GO_ON;
1162 }
1163 
1164 
1165 int mod_accesslog_plugin_init(plugin *p);
1166 int mod_accesslog_plugin_init(plugin *p) {
1167 	p->version     = LIGHTTPD_VERSION_ID;
1168 	p->name        = "accesslog";
1169 
1170 	p->init        = mod_accesslog_init;
1171 	p->set_defaults= mod_accesslog_set_defaults;
1172 	p->cleanup     = mod_accesslog_free;
1173 
1174 	p->handle_request_done  = log_access_write;
1175 	p->handle_trigger       = log_access_periodic_flush;
1176 	p->handle_sighup        = log_access_cycle;
1177 
1178 	return 0;
1179 }
1180