]> granicus.if.org Git - apache/blob - support/rotatelogs.c
remove OS/2 platform support
[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 /*
18  * Simple program to rotate Apache logs without having to kill the server.
19  *
20  * Contributed by Ben Laurie <ben algroup.co.uk>
21  *
22  * 12 Mar 1996
23  *
24  * Ported to APR by Mladen Turk <mturk mappingsoft.com>
25  *
26  * 23 Sep 2001
27  *
28  * -l option added 2004-06-11
29  *
30  * -l causes the use of local time rather than GMT as the base for the
31  * interval.  NB: Using -l in an environment which changes the GMT offset
32  * (such as for BST or DST) can lead to unpredictable results!
33  *
34  * -f option added Feb, 2008. This causes rotatelog to open/create
35  *    the logfile as soon as it's started, not as soon as it sees
36  *    data.
37  *
38  * -v option added Feb, 2008. Verbose output of command line parsing.
39  */
40
41
42 #include "apr.h"
43 #include "apr_lib.h"
44 #include "apr_strings.h"
45 #include "apr_errno.h"
46 #include "apr_file_io.h"
47 #include "apr_file_info.h"
48 #include "apr_general.h"
49 #include "apr_time.h"
50 #include "apr_getopt.h"
51
52 #if APR_HAVE_STDLIB_H
53 #include <stdlib.h>
54 #endif
55 #define APR_WANT_STRFUNC
56 #include "apr_want.h"
57
58 #define BUFSIZE         65536
59 #define ERRMSGSZ        256
60
61 #ifndef MAX_PATH
62 #define MAX_PATH        1024
63 #endif
64
65 #define ROTATE_NONE     0
66 #define ROTATE_NEW      1
67 #define ROTATE_TIME     2
68 #define ROTATE_SIZE     3
69 #define ROTATE_FORCE    4
70
71 static const char *ROTATE_REASONS[] = {
72     "None",
73     "Open a new file",
74     "Time interval expired",
75     "Maximum size reached",
76     "Forced rotation",
77     NULL
78 };
79
80 typedef struct rotate_config rotate_config_t;
81
82 struct rotate_config {
83     unsigned int sRotation;
84     int tRotation;
85     int utc_offset;
86     int use_localtime;
87     int use_strftime;
88     int force_open;
89     int verbose;
90     const char *szLogRoot;
91 };
92
93 typedef struct rotate_status rotate_status_t;
94
95 struct rotate_status {
96     apr_pool_t *pool;
97     apr_pool_t *pfile;
98     apr_pool_t *pfile_prev;
99     apr_file_t *nLogFD;
100     apr_file_t *nLogFDprev;
101     char filename[MAX_PATH];
102     char errbuf[ERRMSGSZ];
103     int rotateReason;
104     int tLogEnd;
105     int nMessCount;
106 };
107
108 static rotate_config_t config;
109 static rotate_status_t status;
110
111 static void usage(const char *argv0, const char *reason)
112 {
113     if (reason) {
114         fprintf(stderr, "%s\n", reason);
115     }
116     fprintf(stderr,
117             "Usage: %s [-v] [-l] [-f] <logfile> "
118             "{<rotation time in seconds>|<rotation size>(B|K|M|G)} "
119             "[offset minutes from UTC]\n\n",
120             argv0);
121     fprintf(stderr,
122             "Add this:\n\nTransferLog \"|%s /some/where 86400\"\n\n",
123             argv0);
124     fprintf(stderr,
125             "or \n\nTransferLog \"|%s /some/where 5M\"\n\n", argv0);
126     fprintf(stderr,
127             "to httpd.conf. The generated name will be /some/where.nnnn "
128             "where nnnn is the\nsystem time at which the log nominally "
129             "starts (N.B. if using a rotation time,\nthe time will always "
130             "be a multiple of the rotation time, so you can synchronize\n"
131             "cron scripts with it). At the end of each rotation time or "
132             "when the file size\nis reached a new log is started.\n");
133     exit(1);
134 }
135
136 /*
137  * Get the unix time with timezone corrections
138  * given in the config struct.
139  */
140 static int get_now(rotate_config_t *config)
141 {
142     apr_time_t tNow = apr_time_now();
143     int utc_offset = config->utc_offset;
144     if (config->use_localtime) {
145         /* Check for our UTC offset before using it, since it might
146          * change if there's a switch between standard and daylight
147          * savings time.
148          */
149         apr_time_exp_t lt;
150         apr_time_exp_lt(&lt, tNow);
151         utc_offset = lt.tm_gmtoff;
152     }
153     return (int)apr_time_sec(tNow) + utc_offset;
154 }
155
156 /*
157  * Close a file and destroy the associated pool.
158  */
159 static void closeFile(rotate_config_t *config, apr_pool_t *pool, apr_file_t *file)
160 {
161     if (file != NULL) {
162         if (config->verbose) {
163             apr_finfo_t finfo;
164             apr_int32_t wanted = APR_FINFO_NAME;
165             if (apr_file_info_get(&finfo, wanted, file) == APR_SUCCESS) {
166                 fprintf(stderr, "Closing file %s (%s)\n", finfo.name, finfo.fname);
167             }
168         }
169         apr_file_close(file);
170         if (pool) {
171             apr_pool_destroy(pool);
172         }
173     }
174 }
175
176 /*
177  * Dump the configuration parsing result to STDERR.
178  */
179 static void dumpConfig (rotate_config_t *config)
180 {
181     fprintf(stderr, "Rotation time interval:      %12d\n", config->tRotation);
182     fprintf(stderr, "Rotation size interval:      %12d\n", config->sRotation);
183     fprintf(stderr, "Rotation time UTC offset:    %12d\n", config->utc_offset);
184     fprintf(stderr, "Rotation based on localtime: %12s\n", config->use_localtime ? "yes" : "no");
185     fprintf(stderr, "Rotation file date pattern:  %12s\n", config->use_strftime ? "yes" : "no");
186     fprintf(stderr, "Rotation file forced open:   %12s\n", config->force_open ? "yes" : "no");
187     fprintf(stderr, "Rotation verbose:            %12s\n", config->verbose ? "yes" : "no");
188     fprintf(stderr, "Rotation file name: %21s\n", config->szLogRoot);
189 }
190
191 /*
192  * Check whether we need to rotate.
193  * Possible reasons are:
194  * - No log file open (ROTATE_NEW)
195  * - User forces us to rotate (ROTATE_FORCE)
196  * - Our log file size is already bigger than the
197  *   allowed maximum (ROTATE_SIZE)
198  * - The next log time interval expired (ROTATE_TIME)
199  *
200  * When size and time constraints are both given,
201  * it suffices that one of them is fulfilled.
202  */
203 static void checkRotate(rotate_config_t *config, rotate_status_t *status)
204 {
205
206     if (status->nLogFD == NULL) {
207         status->rotateReason = ROTATE_NEW;
208     }
209     else if (config->sRotation) {
210         apr_finfo_t finfo;
211         apr_off_t current_size = -1;
212
213         if (apr_file_info_get(&finfo, APR_FINFO_SIZE, status->nLogFD) == APR_SUCCESS) {
214             current_size = finfo.size;
215         }
216
217         if (current_size > config->sRotation) {
218             status->rotateReason = ROTATE_SIZE;
219         }
220         else if (config->tRotation) {
221             if (get_now(config) >= status->tLogEnd) {
222                 status->rotateReason = ROTATE_TIME;
223             }
224         }
225     }
226     else if (config->tRotation) {
227         if (get_now(config) >= status->tLogEnd) {
228             status->rotateReason = ROTATE_TIME;
229         }
230     }
231     else {
232         fprintf(stderr, "No rotation time or size specified\n");
233         exit(2);
234     }
235
236     if (status->rotateReason != ROTATE_NONE && config->verbose) {
237         fprintf(stderr, "File rotation needed, reason: %s\n", ROTATE_REASONS[status->rotateReason]);
238     }
239
240     return;
241 }
242
243 /*
244  * Open a new log file, and if successful
245  * also close the old one.
246  *
247  * The timestamp for the calculation of the file
248  * name of the new log file will be the actual millisecond
249  * timestamp, except when a regular rotation based on a time
250  * interval is configured and the previous interval
251  * is over. Then the timestamp is the starting time
252  * of the actual interval.
253  */
254 static void doRotate(rotate_config_t *config, rotate_status_t *status)
255 {
256
257     int now = get_now(config);
258     int tLogStart;
259     apr_status_t rv;
260
261     status->rotateReason = ROTATE_NONE;
262     status->nLogFDprev = status->nLogFD;
263     status->nLogFD = NULL;
264     status->pfile_prev = status->pfile;
265
266     if (config->tRotation) {
267         int tLogEnd;
268         tLogStart = (now / config->tRotation) * config->tRotation;
269         tLogEnd = tLogStart + config->tRotation;
270         /*
271          * Check if rotation was forced and the last rotation
272          * interval is not yet over. Use the value of now instead
273          * of the time interval boundary for the file name then.
274          */
275         if (tLogStart < status->tLogEnd) {
276             tLogStart = now;
277         }
278         status->tLogEnd = tLogEnd;
279     }
280     else {
281         tLogStart = now;
282     }
283
284     if (config->use_strftime) {
285         apr_time_t tNow = apr_time_from_sec(tLogStart);
286         apr_time_exp_t e;
287         apr_size_t rs;
288
289         apr_time_exp_gmt(&e, tNow);
290         apr_strftime(status->filename, &rs, sizeof(status->filename), config->szLogRoot, &e);
291     }
292     else {
293         sprintf(status->filename, "%s.%010d", config->szLogRoot, tLogStart);
294     }
295     apr_pool_create(&status->pfile, status->pool);
296     if (config->verbose) {
297         fprintf(stderr, "Opening file %s\n", status->filename);
298     }
299     rv = apr_file_open(&status->nLogFD, status->filename, APR_WRITE | APR_CREATE | APR_APPEND,
300                        APR_OS_DEFAULT, status->pfile);
301     if (rv != APR_SUCCESS) {
302         char error[120];
303
304         apr_strerror(rv, error, sizeof error);
305
306         /* Uh-oh. Failed to open the new log file. Try to clear
307          * the previous log file, note the lost log entries,
308          * and keep on truckin'. */
309         if (status->nLogFDprev == NULL) {
310             fprintf(stderr, "Could not open log file '%s' (%s)\n", status->filename, error);
311             exit(2);
312         }
313         else {
314             apr_size_t nWrite;
315             status->nLogFD = status->nLogFDprev;
316             apr_pool_destroy(status->pfile);
317             status->pfile = status->pfile_prev;
318             /* Try to keep this error message constant length
319              * in case it occurs several times. */
320             apr_snprintf(status->errbuf, sizeof status->errbuf,
321                          "Resetting log file due to error opening "
322                          "new log file, %10d messages lost: %-25.25s\n",
323                          status->nMessCount, error);
324             nWrite = strlen(status->errbuf);
325             apr_file_trunc(status->nLogFD, 0);
326             if (apr_file_write(status->nLogFD, status->errbuf, &nWrite) != APR_SUCCESS) {
327                 fprintf(stderr, "Error writing to the file %s\n", status->filename);
328                 exit(2);
329             }
330         }
331     }
332     else {
333         closeFile(config, status->pfile_prev, status->nLogFDprev);
334         status->nLogFDprev = NULL;
335         status->pfile_prev = NULL;
336     }
337     status->nMessCount = 0;
338 }
339
340 /*
341  * Get a size or time param from a string.
342  * Parameter 'last' indicates, whether the
343  * argument is the last commadnline argument.
344  * UTC offset is only allowed as a last argument
345  * in order to make is distinguishable from the
346  * rotation interval time.
347  */
348 static const char *get_time_or_size(rotate_config_t *config,
349                                     const char *arg, int last) {
350     char *ptr = NULL;
351     /* Byte multiplier */
352     unsigned int mult = 1;
353     if ((ptr = strchr(arg, 'B')) != NULL) { /* Found KB size */
354         mult = 1;
355     }
356     else if ((ptr = strchr(arg, 'K')) != NULL) { /* Found KB size */
357         mult = 1024;
358     }
359     else if ((ptr = strchr(arg, 'M')) != NULL) { /* Found MB size */
360         mult = 1024 * 1024;
361     }
362     else if ((ptr = strchr(arg, 'G')) != NULL) { /* Found GB size */
363         mult = 1024 * 1024 * 1024;
364     }
365     if (ptr) { /* rotation based on file size */
366         if (config->sRotation > 0) {
367             return "Rotation size parameter allowed only once";
368         }
369         if (*(ptr+1) == '\0') {
370             config->sRotation = atoi(arg) * mult;
371         }
372         if (config->sRotation == 0) {
373             return "Invalid rotation size parameter";
374         }
375     }
376     else if ((config->sRotation > 0 || config->tRotation > 0) && last) {
377         /* rotation based on elapsed time */
378         if (config->use_localtime) {
379             return "UTC offset parameter is not valid with -l";
380         }
381         config->utc_offset = atoi(arg) * 60;
382     }
383     else { /* rotation based on elapsed time */
384         if (config->tRotation > 0) {
385             return "Rotation time parameter allowed only once";
386         }
387         config->tRotation = atoi(arg);
388         if (config->tRotation <= 0) {
389             return "Invalid rotation time parameter";
390         }
391     }
392     return NULL;
393 }
394
395 int main (int argc, const char * const argv[])
396 {
397     char buf[BUFSIZE];
398     apr_size_t nRead, nWrite;
399     apr_file_t *f_stdin;
400     apr_getopt_t *opt;
401     apr_status_t rv;
402     char c;
403     const char *optarg;
404     const char *err = NULL;
405
406     apr_app_initialize(&argc, &argv, NULL);
407     atexit(apr_terminate);
408
409     config.sRotation = 0;
410     config.tRotation = 0;
411     config.utc_offset = 0;
412     config.use_localtime = 0;
413     config.use_strftime = 0;
414     config.force_open = 0;
415     config.verbose = 0;
416     status.pool = NULL;
417     status.pfile = NULL;
418     status.pfile_prev = NULL;
419     status.nLogFD = NULL;
420     status.nLogFDprev = NULL;
421     status.tLogEnd = 0;
422     status.rotateReason = ROTATE_NONE;
423     status.nMessCount = 0;
424
425     apr_pool_create(&status.pool, NULL);
426     apr_getopt_init(&opt, status.pool, argc, argv);
427     while ((rv = apr_getopt(opt, "lfv", &c, &optarg)) == APR_SUCCESS) {
428         switch (c) {
429         case 'l':
430             config.use_localtime = 1;
431             break;
432         case 'f':
433             config.force_open = 1;
434             break;
435         case 'v':
436             config.verbose = 1;
437             break;
438         }
439     }
440
441     if (rv != APR_EOF) {
442         usage(argv[0], NULL /* specific error message already issued */ );
443     }
444
445     /*
446      * After the initial flags we need 2 to 4 arguments,
447      * the file name, either the rotation interval time or size
448      * or both of them, and optionally the UTC offset.
449      */
450     if ((argc - opt->ind < 2) || (argc - opt->ind > 4) ) {
451         usage(argv[0], "Incorrect number of arguments");
452     }
453
454     config.szLogRoot = argv[opt->ind++];
455
456     /* Read in the remaining flags, namely time, size and UTC offset. */
457     for(; opt->ind < argc; opt->ind++) {
458         if ((err = get_time_or_size(&config, argv[opt->ind],
459                                     opt->ind < argc - 1 ? 0 : 1)) != NULL) {
460             usage(argv[0], err);
461         }
462     }
463
464     config.use_strftime = (strchr(config.szLogRoot, '%') != NULL);
465
466     if (apr_file_open_stdin(&f_stdin, status.pool) != APR_SUCCESS) {
467         fprintf(stderr, "Unable to open stdin\n");
468         exit(1);
469     }
470
471     /*
472      * Write out result of config parsing if verbose is set.
473      */
474     if (config.verbose) {
475         dumpConfig(&config);
476     }
477
478     /*
479      * Immediately open the logfile as we start, if we were forced
480      * to do so via '-f'.
481      */
482     if (config.force_open) {
483         doRotate(&config, &status);
484     }
485
486     for (;;) {
487         nRead = sizeof(buf);
488         rv = apr_file_read(f_stdin, buf, &nRead);
489         if (rv != APR_SUCCESS) {
490             exit(3);
491         }
492         checkRotate(&config, &status);
493         if (status.rotateReason != ROTATE_NONE) {
494             doRotate(&config, &status);
495         }
496
497         nWrite = nRead;
498         rv = apr_file_write(status.nLogFD, buf, &nWrite);
499         if (rv == APR_SUCCESS && nWrite != nRead) {
500             /* buffer partially written, which for rotatelogs means we encountered
501              * an error such as out of space or quota or some other limit reached;
502              * try to write the rest so we get the real error code
503              */
504             apr_size_t nWritten = nWrite;
505
506             nRead  = nRead - nWritten;
507             nWrite = nRead;
508             rv = apr_file_write(status.nLogFD, buf + nWritten, &nWrite);
509         }
510         if (nWrite != nRead) {
511             char strerrbuf[120];
512             apr_off_t cur_offset;
513
514             cur_offset = 0;
515             if (apr_file_seek(status.nLogFD, APR_CUR, &cur_offset) != APR_SUCCESS) {
516                 cur_offset = -1;
517             }
518             apr_strerror(rv, strerrbuf, sizeof strerrbuf);
519             status.nMessCount++;
520             apr_snprintf(status.errbuf, sizeof status.errbuf,
521                          "Error %d writing to log file at offset %" APR_OFF_T_FMT ". "
522                          "%10d messages lost (%s)\n",
523                          rv, cur_offset, status.nMessCount, strerrbuf);
524             nWrite = strlen(status.errbuf);
525             apr_file_trunc(status.nLogFD, 0);
526             if (apr_file_write(status.nLogFD, status.errbuf, &nWrite) != APR_SUCCESS) {
527                 fprintf(stderr, "Error writing to the file %s\n", status.filename);
528                 exit(2);
529             }
530         }
531         else {
532             status.nMessCount++;
533         }
534     }
535     /* Of course we never, but prevent compiler warnings */
536     return 0;
537 }