]> granicus.if.org Git - apache/blob - support/rotatelogs.c
Add support for the proxy modules to
[apache] / support / rotatelogs.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "apr.h"
18 #include "apr_lib.h"
19 #include "apr_strings.h"
20 #include "apr_errno.h"
21 #include "apr_file_io.h"
22 #include "apr_file_info.h"
23 #include "apr_general.h"
24 #include "apr_time.h"
25 #include "apr_getopt.h"
26 #include "apr_thread_proc.h"
27 #include "apr_signal.h"
28 #if APR_FILES_AS_SOCKETS
29 #include "apr_poll.h"
30 #endif
31
32 #if APR_HAVE_STDLIB_H
33 #include <stdlib.h>
34 #endif
35 #define APR_WANT_STRFUNC
36 #include "apr_want.h"
37
38 #define BUFSIZE         65536
39
40 #define ROTATE_NONE     0
41 #define ROTATE_NEW      1
42 #define ROTATE_TIME     2
43 #define ROTATE_SIZE     3
44 #define ROTATE_FORCE    4
45
46 static const char *const ROTATE_REASONS[] = {
47     "None",
48     "Open a new file",
49     "Time interval expired",
50     "Maximum size reached",
51     "Forced rotation",
52     NULL
53 };
54
55 typedef struct rotate_config rotate_config_t;
56
57 struct rotate_config {
58     unsigned int sRotation;
59     int tRotation;
60     int utc_offset;
61     int use_localtime;
62     int use_strftime;
63     int force_open;
64     int verbose;
65     int echo;
66     char *szLogRoot;
67     int truncate;
68     const char *linkfile;
69     const char *postrotate_prog;
70 #if APR_FILES_AS_SOCKETS
71     int create_empty;
72 #endif
73     int num_files;
74     int create_path;
75 };
76
77 typedef struct rotate_status rotate_status_t;
78
79 /* "adjusted_time_t" is used to store Unix time (seconds since epoch)
80  * which has been adjusted for some timezone fudge factor.  It should
81  * be used for storing the return values from get_now().  A typedef is
82  * used since this type is similar to time_t, but different. */
83 typedef long adjusted_time_t;
84
85 /* Structure to contain relevant logfile state: fd, pool and
86  * filename. */
87 struct logfile {
88     apr_pool_t *pool;
89     apr_file_t *fd;
90     char name[APR_PATH_MAX];
91 };
92
93 struct rotate_status {
94     struct logfile current; /* current logfile. */
95     apr_pool_t *pool; /* top-level pool */
96     int rotateReason;
97     adjusted_time_t tLogEnd;
98     int nMessCount;
99     int fileNum;
100 };
101
102 static rotate_config_t config;
103 static rotate_status_t status;
104
105 static void usage(const char *argv0, const char *reason)
106 {
107     if (reason) {
108         fprintf(stderr, "%s\n", reason);
109     }
110     fprintf(stderr,
111 #if APR_FILES_AS_SOCKETS
112             "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-f] [-D] [-t] [-e] [-c] [-n number] <logfile> "
113 #else
114             "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-f] [-D] [-t] [-e] [-n number] <logfile> "
115 #endif
116             "{<rotation time in seconds>|<rotation size>(B|K|M|G)} "
117             "[offset minutes from UTC]\n\n",
118             argv0);
119 #ifdef OS2
120     fprintf(stderr,
121             "Add this:\n\nTransferLog \"|%s.exe /some/where 86400\"\n\n",
122             argv0);
123 #else
124     fprintf(stderr,
125             "Add this:\n\nTransferLog \"|%s /some/where 86400\"\n\n",
126             argv0);
127     fprintf(stderr,
128             "or \n\nTransferLog \"|%s /some/where 5M\"\n\n", argv0);
129 #endif
130     fprintf(stderr,
131             "to httpd.conf. By default, the generated name will be\n"
132             "<logfile>.nnnn where nnnn is the system time at which the log\n"
133             "nominally starts (N.B. if using a rotation time, the time will\n"
134             "always be a multiple of the rotation time, so you can synchronize\n"
135             "cron scripts with it). If <logfile> contains strftime conversion\n"
136             "specifications, those will be used instead. At the end of each\n"
137             "rotation time or when the file size is reached a new log is\n"
138             "started.\n"
139             "\n"
140             "Options:\n"
141             "  -v       Verbose operation. Messages are written to stderr.\n"
142             "  -l       Base rotation on local time instead of UTC.\n"
143             "  -L path  Create hard link from current log to specified path.\n"
144             "  -p prog  Run specified program after opening a new log file. See below.\n"
145             "  -f       Force opening of log on program start.\n"
146             "  -D       Create parent directories of log file.\n" 
147             "  -t       Truncate logfile instead of rotating, tail friendly.\n"
148             "  -e       Echo log to stdout for further processing.\n"
149 #if APR_FILES_AS_SOCKETS
150             "  -c       Create log even if it is empty.\n"
151 #endif
152             "  -n num   Rotate file by adding suffixes '.0', '.1', ..., '.(num-1)'.\n"
153             "\n"
154             "The program for '-p' is invoked as \"[prog] <curfile> [<prevfile>]\"\n"
155             "where <curfile> is the filename of the newly opened logfile, and\n"
156             "<prevfile>, if given, is the filename of the previously used logfile.\n"
157             "\n");
158     exit(1);
159 }
160
161 /* This function returns the current Unix time (time_t) adjusted for
162  * any configured or derived local time offset.  The offset applied is
163  * returned via *offset. */
164 static adjusted_time_t get_now(rotate_config_t *config, apr_int32_t *offset)
165 {
166     apr_time_t tNow = apr_time_now();
167     apr_int32_t utc_offset;
168
169     if (config->use_localtime) {
170         /* Check for our UTC offset before using it, since it might
171          * change if there's a switch between standard and daylight
172          * savings time.
173          */
174         apr_time_exp_t lt;
175         apr_time_exp_lt(&lt, tNow);
176         utc_offset = lt.tm_gmtoff;
177     }
178     else {
179         utc_offset = config->utc_offset;
180     }
181
182     if (offset)
183         *offset = utc_offset;
184
185     return apr_time_sec(tNow) + utc_offset;
186 }
187
188 /*
189  * Close a file and destroy the associated pool.
190  */
191 static void close_logfile(rotate_config_t *config, struct logfile *logfile)
192 {
193     if (config->verbose) {
194         fprintf(stderr, "Closing file %s\n", logfile->name);
195     }
196     apr_file_close(logfile->fd);
197     apr_pool_destroy(logfile->pool);
198 }
199
200 /*
201  * Dump the configuration parsing result to STDERR.
202  */
203 static void dumpConfig (rotate_config_t *config)
204 {
205     fprintf(stderr, "Rotation time interval:      %12d\n", config->tRotation);
206     fprintf(stderr, "Rotation size interval:      %12d\n", config->sRotation);
207     fprintf(stderr, "Rotation time UTC offset:    %12d\n", config->utc_offset);
208     fprintf(stderr, "Rotation based on localtime: %12s\n", config->use_localtime ? "yes" : "no");
209     fprintf(stderr, "Rotation file date pattern:  %12s\n", config->use_strftime ? "yes" : "no");
210     fprintf(stderr, "Rotation file forced open:   %12s\n", config->force_open ? "yes" : "no");
211     fprintf(stderr, "Create parent directories:   %12s\n", config->create_path ? "yes" : "no");
212     fprintf(stderr, "Rotation verbose:            %12s\n", config->verbose ? "yes" : "no");
213 #if APR_FILES_AS_SOCKETS
214     fprintf(stderr, "Rotation create empty logs:  %12s\n", config->create_empty ? "yes" : "no");
215 #endif
216     fprintf(stderr, "Rotation file name: %21s\n", config->szLogRoot);
217     fprintf(stderr, "Post-rotation prog: %21s\n", config->postrotate_prog ? config->postrotate_prog : "not used");
218 }
219
220 /*
221  * Check whether we need to rotate.
222  * Possible reasons are:
223  * - No log file open (ROTATE_NEW)
224  * - User forces us to rotate (ROTATE_FORCE)
225  * - Our log file size is already bigger than the
226  *   allowed maximum (ROTATE_SIZE)
227  * - The next log time interval expired (ROTATE_TIME)
228  *
229  * When size and time constraints are both given,
230  * it suffices that one of them is fulfilled.
231  */
232 static void checkRotate(rotate_config_t *config, rotate_status_t *status)
233 {
234     if (status->current.fd == NULL) {
235         status->rotateReason = ROTATE_NEW;
236     }
237     else if (config->sRotation) {
238         apr_finfo_t finfo;
239         apr_off_t current_size = -1;
240
241         if (apr_file_info_get(&finfo, APR_FINFO_SIZE, status->current.fd) == APR_SUCCESS) {
242             current_size = finfo.size;
243         }
244
245         if (current_size > config->sRotation) {
246             status->rotateReason = ROTATE_SIZE;
247         }
248         else if (config->tRotation) {
249             if (get_now(config, NULL) >= status->tLogEnd) {
250                 status->rotateReason = ROTATE_TIME;
251             }
252         }
253     }
254     else if (config->tRotation) {
255         if (get_now(config, NULL) >= status->tLogEnd) {
256             status->rotateReason = ROTATE_TIME;
257         }
258     }
259     else {
260         fprintf(stderr, "No rotation time or size specified\n");
261         exit(2);
262     }
263
264     if (status->rotateReason != ROTATE_NONE && config->verbose) {
265         fprintf(stderr, "File rotation needed, reason: %s\n", ROTATE_REASONS[status->rotateReason]);
266     }
267 }
268
269 /*
270  * Handle post-rotate processing.
271  */
272 static void post_rotate(apr_pool_t *pool, struct logfile *newlog,
273                         rotate_config_t *config, rotate_status_t *status)
274 {
275     apr_status_t rv;
276     apr_procattr_t *pattr;
277     const char *argv[4];
278     apr_proc_t proc;
279
280     /* Handle link file, if configured. */
281     if (config->linkfile) {
282         apr_file_remove(config->linkfile, newlog->pool);
283         if (config->verbose) {
284             fprintf(stderr, "Linking %s to %s\n", newlog->name, config->linkfile);
285         }
286         rv = apr_file_link(newlog->name, config->linkfile);
287         if (rv != APR_SUCCESS) {
288             char *error = apr_psprintf(pool, "Error linking file %s to %s (%pm)\n",
289                                        newlog->name, config->linkfile, &rv);
290             fputs(error, stderr);
291             exit(2);
292         }
293     }
294
295     if (!config->postrotate_prog) {
296         /* Nothing more to do. */
297         return;
298     }
299
300     /* Collect any zombies from a previous run, but don't wait. */
301     while (apr_proc_wait_all_procs(&proc, NULL, NULL, APR_NOWAIT, pool) == APR_CHILD_DONE)
302         /* noop */;
303
304     if ((rv = apr_procattr_create(&pattr, pool)) != APR_SUCCESS) {
305         char *error = apr_psprintf(pool, "post_rotate: apr_procattr_create failed " \
306                                          "for '%s': %pm\n", config->postrotate_prog, &rv);
307         fputs(error, stderr);
308         return;
309     }
310
311     rv = apr_procattr_error_check_set(pattr, 1);
312     if (rv == APR_SUCCESS)
313         rv = apr_procattr_cmdtype_set(pattr, APR_PROGRAM_ENV);
314
315     if (rv != APR_SUCCESS) {
316         char *error = apr_psprintf(pool, "post_rotate: could not set up process " \
317                                    "attributes for '%s': %pm\n", config->postrotate_prog,
318                                    &rv);
319         fputs(error, stderr);
320         return;
321     }
322
323     argv[0] = config->postrotate_prog;
324     argv[1] = newlog->name;
325     if (status->current.fd) {
326         argv[2] = status->current.name;
327         argv[3] = NULL;
328     }
329     else {
330         argv[2] = NULL;
331     }
332
333     if (config->verbose)
334         fprintf(stderr, "Calling post-rotate program: %s\n", argv[0]);
335
336     rv = apr_proc_create(&proc, argv[0], argv, NULL, pattr, pool);
337     if (rv != APR_SUCCESS) {
338         char *error = apr_psprintf(pool, "Could not spawn post-rotate process " \
339                                    "'%s': %pm\n", config->postrotate_prog, &rv);
340         fputs(error, stderr);
341         return;
342     }
343 }
344
345 /* After a error, truncate the current file and write out an error
346  * message, which must be contained in message.  The process is
347  * terminated on failure.  */
348 static void truncate_and_write_error(rotate_status_t *status, const char *message)
349 {
350     apr_size_t buflen = strlen(message);
351
352     if (apr_file_trunc(status->current.fd, 0) != APR_SUCCESS) {
353         fprintf(stderr, "Error truncating the file %s\n", status->current.name);
354         exit(2);
355     }
356     if (apr_file_write_full(status->current.fd, message, buflen, NULL) != APR_SUCCESS) {
357         fprintf(stderr, "Error writing error (%s) to the file %s\n", 
358                 message, status->current.name);
359         exit(2);
360     }
361 }
362
363 /*
364  * Open a new log file, and if successful
365  * also close the old one.
366  *
367  * The timestamp for the calculation of the file
368  * name of the new log file will be the actual millisecond
369  * timestamp, except when a regular rotation based on a time
370  * interval is configured and the previous interval
371  * is over. Then the timestamp is the starting time
372  * of the actual interval.
373  */
374 static void doRotate(rotate_config_t *config, rotate_status_t *status)
375 {
376     apr_int32_t offset;
377     adjusted_time_t now, tLogStart;
378     apr_status_t rv;
379     struct logfile newlog;
380     int thisLogNum = -1;
381
382     /* Retrieve local-time-adjusted-Unix-time. */
383     now = get_now(config, &offset);
384
385     status->rotateReason = ROTATE_NONE;
386
387     if (config->tRotation) {
388         adjusted_time_t tLogEnd;
389
390         tLogStart = (now / config->tRotation) * config->tRotation;
391         tLogEnd = tLogStart + config->tRotation;
392         /*
393          * Check if rotation was forced and the last rotation
394          * interval is not yet over. Use the value of now instead
395          * of the time interval boundary for the file name then.
396          */
397         if (tLogStart < status->tLogEnd) {
398             tLogStart = now;
399         }
400         status->tLogEnd = tLogEnd;
401     }
402     else {
403         tLogStart = now;
404     }
405
406     if (config->use_strftime) {
407         apr_time_t tNow = apr_time_from_sec(tLogStart);
408         apr_time_exp_t e;
409         apr_size_t rs;
410
411         /* Explode the local-time-adjusted-Unix-time into a struct tm,
412          * first *reversing* local-time-adjustment applied by
413          * get_now() if we are using localtime. */
414         if (config->use_localtime)
415             apr_time_exp_lt(&e, tNow - apr_time_from_sec(offset));
416         else
417             apr_time_exp_gmt(&e, tNow);
418         apr_strftime(newlog.name, &rs, sizeof(newlog.name), config->szLogRoot, &e);
419     }
420     else {
421         if (config->truncate) {
422             apr_snprintf(newlog.name, sizeof(newlog.name), "%s", config->szLogRoot);
423         }
424         else if (config->num_files > 0) { 
425             if (status->fileNum == -1 || status->fileNum == (config->num_files - 1)) {
426                 thisLogNum = 0;
427                 apr_snprintf(newlog.name, sizeof(newlog.name), "%s", config->szLogRoot);
428             }
429             else { 
430                 thisLogNum = status->fileNum + 1;
431                 apr_snprintf(newlog.name, sizeof(newlog.name), "%s.%d", config->szLogRoot, thisLogNum);
432             }
433         }
434         else {
435             apr_snprintf(newlog.name, sizeof(newlog.name), "%s.%010ld", config->szLogRoot,
436                          tLogStart);
437         }
438     }
439     apr_pool_create(&newlog.pool, status->pool);
440     if (config->create_path) {
441         char *ptr = strrchr(newlog.name, '/');
442         if (ptr && ptr > newlog.name) {
443             char *path = apr_pstrmemdup(newlog.pool, newlog.name, ptr - newlog.name);
444             if (config->verbose) {
445                 fprintf(stderr, "Creating directory tree %s\n", path);
446             }
447             rv = apr_dir_make_recursive(path, APR_FPROT_OS_DEFAULT, newlog.pool);
448             if (rv != APR_SUCCESS) {
449                 char *error = apr_psprintf(newlog.pool,
450                                            "Could not create directory '%s' (%pm)\n",
451                                            path, &rv);
452                 fputs(error, stderr);
453                 exit(2);
454             }
455         }
456     }
457     if (config->verbose) {
458         fprintf(stderr, "Opening file %s\n", newlog.name);
459     }
460     rv = apr_file_open(&newlog.fd, newlog.name, APR_WRITE | APR_CREATE | APR_APPEND
461                        | (config->truncate || (config->num_files > 0 && status->current.fd) ? APR_TRUNCATE : 0), 
462                        APR_OS_DEFAULT, newlog.pool);
463     if (rv == APR_SUCCESS) {
464         /* Handle post-rotate processing. */
465         post_rotate(newlog.pool, &newlog, config, status);
466
467         status->fileNum = thisLogNum;
468         /* Close out old (previously 'current') logfile, if any. */
469         if (status->current.fd) {
470             close_logfile(config, &status->current);
471         }
472
473         /* New log file is now 'current'. */
474         status->current = newlog;
475     }
476     else {
477         char *error = apr_psprintf(newlog.pool, "%pm", &rv);
478         char *message;
479
480         /* Uh-oh. Failed to open the new log file. Try to clear
481          * the previous log file, note the lost log entries,
482          * and keep on truckin'. */
483         if (status->current.fd == NULL) {
484             fprintf(stderr, "Could not open log file '%s' (%s)\n", newlog.name, error);
485             exit(2);
486         }
487
488         /* Try to keep this error message constant length
489          * in case it occurs several times. */
490         message = apr_psprintf(newlog.pool,
491                                "Resetting log file due to error opening "
492                                "new log file, %10d messages lost: %-25.25s\n",
493                                status->nMessCount, error);
494
495         truncate_and_write_error(status, message);
496
497         /* Throw away new state; it isn't going to be used. */
498         apr_pool_destroy(newlog.pool);
499     }
500
501     status->nMessCount = 0;
502 }
503
504 /*
505  * Get a size or time param from a string.
506  * Parameter 'last' indicates, whether the
507  * argument is the last commadnline argument.
508  * UTC offset is only allowed as a last argument
509  * in order to make is distinguishable from the
510  * rotation interval time.
511  */
512 static const char *get_time_or_size(rotate_config_t *config,
513                                     const char *arg, int last) {
514     char *ptr = NULL;
515     /* Byte multiplier */
516     unsigned int mult = 1;
517     if ((ptr = strchr(arg, 'B')) != NULL) { /* Found KB size */
518         mult = 1;
519     }
520     else if ((ptr = strchr(arg, 'K')) != NULL) { /* Found KB size */
521         mult = 1024;
522     }
523     else if ((ptr = strchr(arg, 'M')) != NULL) { /* Found MB size */
524         mult = 1024 * 1024;
525     }
526     else if ((ptr = strchr(arg, 'G')) != NULL) { /* Found GB size */
527         mult = 1024 * 1024 * 1024;
528     }
529     if (ptr) { /* rotation based on file size */
530         if (config->sRotation > 0) {
531             return "Rotation size parameter allowed only once";
532         }
533         if (*(ptr+1) == '\0') {
534             config->sRotation = atoi(arg) * mult;
535         }
536         if (config->sRotation == 0) {
537             return "Invalid rotation size parameter";
538         }
539     }
540     else if ((config->sRotation > 0 || config->tRotation > 0) && last) {
541         /* rotation based on elapsed time */
542         if (config->use_localtime) {
543             return "UTC offset parameter is not valid with -l";
544         }
545         config->utc_offset = atoi(arg) * 60;
546     }
547     else { /* rotation based on elapsed time */
548         if (config->tRotation > 0) {
549             return "Rotation time parameter allowed only once";
550         }
551         config->tRotation = atoi(arg);
552         if (config->tRotation <= 0) {
553             return "Invalid rotation time parameter";
554         }
555     }
556     return NULL;
557 }
558
559 int main (int argc, const char * const argv[])
560 {
561     char buf[BUFSIZE];
562     apr_size_t nRead, nWrite;
563     apr_file_t *f_stdin;
564     apr_file_t *f_stdout;
565     apr_getopt_t *opt;
566     apr_status_t rv;
567     char c;
568     const char *opt_arg;
569     const char *err = NULL;
570 #if APR_FILES_AS_SOCKETS
571     apr_pollfd_t pollfd = { 0 };
572     apr_status_t pollret = APR_SUCCESS;
573     long polltimeout;
574 #endif
575
576     apr_app_initialize(&argc, &argv, NULL);
577     atexit(apr_terminate);
578
579     memset(&config, 0, sizeof config);
580     memset(&status, 0, sizeof status);
581     status.rotateReason = ROTATE_NONE;
582
583     apr_pool_create(&status.pool, NULL);
584     apr_getopt_init(&opt, status.pool, argc, argv);
585 #if APR_FILES_AS_SOCKETS
586     while ((rv = apr_getopt(opt, "lL:p:fDtvecn:", &c, &opt_arg)) == APR_SUCCESS) {
587 #else
588     while ((rv = apr_getopt(opt, "lL:p:fDtven:", &c, &opt_arg)) == APR_SUCCESS) {
589 #endif
590         switch (c) {
591         case 'l':
592             config.use_localtime = 1;
593             break;
594         case 'L':
595             config.linkfile = opt_arg;
596             break;
597         case 'p':
598             config.postrotate_prog = opt_arg;
599 #ifdef SIGCHLD
600             /* Prevent creation of zombies (on modern Unix systems). */
601             apr_signal(SIGCHLD, SIG_IGN);
602 #endif
603             break;
604         case 'f':
605             config.force_open = 1;
606             break;
607         case 'D':
608             config.create_path = 1;
609             break;
610         case 't':
611             config.truncate = 1;
612             break;
613         case 'v':
614             config.verbose = 1;
615             break;
616         case 'e':
617             config.echo = 1;
618             break;
619 #if APR_FILES_AS_SOCKETS
620         case 'c':
621             config.create_empty = 1;
622             break;
623 #endif
624         case 'n':
625             config.num_files = atoi(opt_arg);
626             status.fileNum = -1;
627             break;
628         }
629     }
630
631     if (rv != APR_EOF) {
632         usage(argv[0], NULL /* specific error message already issued */ );
633     }
634
635     /*
636      * After the initial flags we need 2 to 4 arguments,
637      * the file name, either the rotation interval time or size
638      * or both of them, and optionally the UTC offset.
639      */
640     if ((argc - opt->ind < 2) || (argc - opt->ind > 4) ) {
641         usage(argv[0], "Incorrect number of arguments");
642     }
643
644     rv = apr_filepath_merge(&config.szLogRoot, "", argv[opt->ind++],
645                             APR_FILEPATH_TRUENAME, status.pool);
646     if (rv != APR_SUCCESS && rv != APR_EPATHWILD) {
647         usage(argv[0], "Invalid filename given");
648     }
649
650     /* Read in the remaining flags, namely time, size and UTC offset. */
651     for(; opt->ind < argc; opt->ind++) {
652         if ((err = get_time_or_size(&config, argv[opt->ind],
653                                     opt->ind < argc - 1 ? 0 : 1)) != NULL) {
654             usage(argv[0], err);
655         }
656     }
657
658     config.use_strftime = (strchr(config.szLogRoot, '%') != NULL);
659
660     if (config.use_strftime && config.num_files > 0) { 
661         fprintf(stderr, "Cannot use -n with %% in filename\n");
662         exit(1);
663     }
664
665     if (status.fileNum == -1 && config.num_files < 1) { 
666         fprintf(stderr, "Invalid -n argument\n");
667         exit(1);
668     }
669
670     if (apr_file_open_stdin(&f_stdin, status.pool) != APR_SUCCESS) {
671         fprintf(stderr, "Unable to open stdin\n");
672         exit(1);
673     }
674
675     if (apr_file_open_stdout(&f_stdout, status.pool) != APR_SUCCESS) {
676         fprintf(stderr, "Unable to open stdout\n");
677         exit(1);
678     }
679
680     /*
681      * Write out result of config parsing if verbose is set.
682      */
683     if (config.verbose) {
684         dumpConfig(&config);
685     }
686
687 #if APR_FILES_AS_SOCKETS
688     if (config.create_empty && config.tRotation) {
689         pollfd.p = status.pool;
690         pollfd.desc_type = APR_POLL_FILE;
691         pollfd.reqevents = APR_POLLIN;
692         pollfd.desc.f = f_stdin;
693     }
694 #endif
695
696     /*
697      * Immediately open the logfile as we start, if we were forced
698      * to do so via '-f'.
699      */
700     if (config.force_open) {
701         doRotate(&config, &status);
702     }
703
704     for (;;) {
705         nRead = sizeof(buf);
706 #if APR_FILES_AS_SOCKETS
707         if (config.create_empty && config.tRotation) {
708             polltimeout = status.tLogEnd ? status.tLogEnd - get_now(&config, NULL) : config.tRotation;
709             if (polltimeout <= 0) {
710                 pollret = APR_TIMEUP;
711             }
712             else {
713                 pollret = apr_poll(&pollfd, 1, &pollret, apr_time_from_sec(polltimeout));
714             }
715         }
716         if (pollret == APR_SUCCESS) {
717             rv = apr_file_read(f_stdin, buf, &nRead);
718             if (APR_STATUS_IS_EOF(rv)) {
719                 break;
720             }
721             else if (rv != APR_SUCCESS) {
722                 exit(3);
723             }
724         }
725         else if (pollret == APR_TIMEUP) {
726             *buf = 0;
727             nRead = 0;
728         }
729         else {
730             fprintf(stderr, "Unable to poll stdin\n");
731             exit(5);
732         }
733 #else /* APR_FILES_AS_SOCKETS */
734         rv = apr_file_read(f_stdin, buf, &nRead);
735         if (APR_STATUS_IS_EOF(rv)) {
736             break;
737         }
738         else if (rv != APR_SUCCESS) {
739             exit(3);
740         }
741 #endif /* APR_FILES_AS_SOCKETS */
742         checkRotate(&config, &status);
743         if (status.rotateReason != ROTATE_NONE) {
744             doRotate(&config, &status);
745         }
746
747         nWrite = nRead;
748         rv = apr_file_write_full(status.current.fd, buf, nWrite, &nWrite);
749         if (nWrite != nRead) {
750             apr_off_t cur_offset;
751             apr_pool_t *pool;
752             char *error;
753
754             cur_offset = 0;
755             if (apr_file_seek(status.current.fd, APR_CUR, &cur_offset) != APR_SUCCESS) {
756                 cur_offset = -1;
757             }
758             status.nMessCount++;
759             apr_pool_create(&pool, status.pool);
760             error = apr_psprintf(pool, "Error %d writing to log file at offset %"
761                                  APR_OFF_T_FMT ". %10d messages lost (%pm)\n",
762                                  rv, cur_offset, status.nMessCount, &rv);
763
764             truncate_and_write_error(&status, error);
765             apr_pool_destroy(pool);
766         }
767         else {
768             status.nMessCount++;
769         }
770         if (config.echo) {
771             if (apr_file_write_full(f_stdout, buf, nRead, NULL)) {
772                 fprintf(stderr, "Unable to write to stdout\n");
773                 exit(4);
774             }
775         }
776     }
777
778     return 0; /* reached only at stdin EOF. */
779 }