1 /* ====================================================================
2 * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
16 * 3. All advertising materials mentioning features or use of this
17 * software must display the following acknowledgment:
18 * "This product includes software developed by the Apache Group
19 * for use in the Apache HTTP server project (http://www.apache.org/)."
21 * 4. The names "Apache Server" and "Apache Group" must not be used to
22 * endorse or promote products derived from this software without
23 * prior written permission. For written permission, please contact
26 * 5. Products derived from this software may not be called "Apache"
27 * nor may "Apache" appear in their names without prior written
28 * permission of the Apache Group.
30 * 6. Redistributions of any form whatsoever must retain the following
32 * "This product includes software developed by the Apache Group
33 * for use in the Apache HTTP server project (http://www.apache.org/)."
35 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46 * OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Group and was originally based
51 * on public domain software written at the National Center for
52 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53 * For more information on the Apache Group and the Apache HTTP server
54 * project, please see <http://www.apache.org/>.
58 * cronoutils -- utilities for the cronolog program
60 * Copyright (c) 1996-1999 by Ford & Mason Ltd
62 * This software was submitted by Ford & Mason Ltd to the Apache
63 * Software Foundation in December 1999. Future revisions and
64 * derivatives of this source code must acknowledge Ford & Mason Ltd
65 * as the original contributor of this module. All other licensing
66 * and usage conditions are those of the Apache Software Foundation.
68 * Originally written by Andrew Ford <A.Ford@ford-mason.co.uk>
72 #include "hphp/util/cronoutils.h"
73 extern char *tzname
[2];
76 #define DIR_MODE ( S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH )
83 /* Constants for seconds per minute, hour, day and week */
84 #define SECS_PER_MIN 60
85 #define SECS_PER_HOUR (60 * SECS_PER_MIN)
87 #define SECS_PER_DAY (24 * SECS_PER_HOUR)
89 #define SECS_PER_WEEK (7 * SECS_PER_DAY)
91 /* Allowances for daylight saving time changes and leap second.
92 * * Used for calculating the start of the next period.
93 * * (does Unix actually know about leap seconds?)
96 #define LEAP_SECOND_ALLOWANCE 2
97 #define DST_ALLOWANCE (3 * SECS_PER_HOUR + LEAP_SECOND_ALLOWANCE)
99 /* debug_file is the file to output debug messages to. No debug
100 * messages are output if it is set to NULL.
102 FILE *debug_file
= nullptr;
105 /* America and Europe disagree on whether weeks start on Sunday or
106 * Monday - weeks_start_on_mondays is set if a %U specifier is encountered.
108 int weeks_start_on_mondays
= 0;
111 /* periods[] is an array of the names of the periods.
113 const char *periods
[] =
122 "aeon" /* i.e. once only */
125 /* period_seconds[] is an array of the number of seconds in a period.
127 int period_seconds
[] =
138 /* Try to create missing directories on the path of filename.
140 * Note that on a busy server there may theoretically be many cronolog
141 * processes trying simultaneously to create the same subdirectories
142 * so ignore any EEXIST errors on mkdir -- they probably just mean
143 * that another process got there first.
145 * Unless CHECK_ALL_PREFIX_DIRS is defined, we save the directory of
146 * the last file tested -- any common prefix should exist. This
147 * probably only saves a few stat system calls at the start of each
148 * log period, but it might as well be done.
151 create_subdirs(char *filename
)
153 #ifndef CHECK_ALL_PREFIX_DIRS
154 static char lastpath
[PATH_MAX
] = "";
156 struct stat stat_buf
;
157 char dirname
[PATH_MAX
];
160 CRONO_DEBUG(("Creating missing components of \"%s\"\n", filename
));
161 for (p
= filename
; (p
= strchr(p
, '/')); p
++)
165 continue; /* Don't bother with the root directory */
168 memcpy(dirname
, filename
, p
- filename
);
169 dirname
[p
-filename
] = '\0';
171 #ifndef CHECK_ALL_PREFIX_DIRS
172 if (strncmp(dirname
, lastpath
, strlen(dirname
)) == 0)
174 CRONO_DEBUG(("Initial prefix \"%s\" known to exist\n", dirname
));
179 CRONO_DEBUG(("Testing directory \"%s\"\n", dirname
));
180 if (stat(dirname
, &stat_buf
) < 0)
189 CRONO_DEBUG(("Directory \"%s\" does not exist -- creating\n", dirname
));
190 if ((mkdir(dirname
, DIR_MODE
) < 0) && (errno
!= EEXIST
))
194 if ((mkdir(dirname
) < 0) && (errno
!= EEXIST
))
202 #ifndef CHECK_ALL_PREFIX_DIRS
203 strcpy(lastpath
, dirname
);
207 /* Create a hard or symbolic link to a filename according to the type specified.
209 * This function could do with more error checking!
212 create_link(const char *pfilename
,
213 const char *linkname
, mode_t linktype
,
214 const char *prevlinkname
)
216 struct stat stat_buf
;
218 if (lstat(prevlinkname
, &stat_buf
) == 0)
220 unlink(prevlinkname
);
222 if (lstat(linkname
, &stat_buf
) == 0)
225 rename(linkname
, prevlinkname
);
232 if (linktype
== S_IFLNK
)
234 if (symlink(pfilename
, linkname
) < 0) {
235 fprintf(stderr
, "Creating link from %s to %s failed", pfilename
, linkname
);
240 if (link(pfilename
, linkname
) < 0) {
241 fprintf(stderr
, "Creating link from %s to %s failed", pfilename
, linkname
);
245 fprintf(stderr
, "Creating link from %s to %s not supported", pfilename
, linkname
);
249 /* Examine the log file name specifier for strftime conversion
250 * specifiers and determine the period between log files.
251 * Smallest period allowed is per minute.
254 determine_periodicity(char *spec
)
256 PERIODICITY periodicity
= ONCE_ONLY
;
259 CRONO_DEBUG(("Determining periodicity of \"%s\"\n", spec
));
260 while ((ch
= *spec
++) != 0)
269 case 'y': /* two digit year */
270 case 'Y': /* four digit year */
271 if (periodicity
> YEARLY
)
273 CRONO_DEBUG(("%%%c -> yearly\n", ch
));
274 periodicity
= YEARLY
;
278 case 'b': /* abbreviated month name */
279 case 'h': /* abbreviated month name (non-standard) */
280 case 'B': /* full month name */
281 case 'm': /* month as two digit number (with
283 if (periodicity
> MONTHLY
)
285 CRONO_DEBUG(("%%%c -> monthly\n", ch
));
286 periodicity
= MONTHLY
;
290 case 'U': /* week number (weeks start on Sunday) */
291 case 'W': /* week number (weeks start on Monday) */
292 if (periodicity
> WEEKLY
)
294 CRONO_DEBUG(("%%%c -> weeky\n", ch
));
295 periodicity
= WEEKLY
;
296 weeks_start_on_mondays
= (ch
== 'W');
300 case 'a': /* abbreviated weekday name */
301 case 'A': /* full weekday name */
302 case 'd': /* day of the month (with leading zero) */
303 case 'e': /* day of the month (with leading space -- non-standard) */
304 case 'j': /* day of the year (with leading zeroes) */
305 case 'w': /* day of the week (0-6) */
306 case 'D': /* full date spec (non-standard) */
307 case 'x': /* full date spec */
308 if (periodicity
> DAILY
)
310 CRONO_DEBUG(("%%%c -> daily\n", ch
));
315 case 'H': /* hour (24 hour clock) */
316 case 'I': /* hour (12 hour clock) */
317 case 'p': /* AM/PM indicator */
318 if (periodicity
> HOURLY
)
320 CRONO_DEBUG(("%%%c -> hourly\n", ch
));
321 periodicity
= HOURLY
;
325 case 'M': /* minute */
326 if (periodicity
> PER_MINUTE
)
328 CRONO_DEBUG(("%%%c -> per minute\n", ch
));
329 periodicity
= PER_MINUTE
;
333 case 'S': /* second */
334 case 's': /* seconds since the epoch (GNU non-standard) */
335 case 'c': /* full time and date spec */
336 case 'T': /* full time spec */
337 case 'r': /* full time spec (non-standard) */
338 case 'R': /* full time spec (non-standard) */
339 CRONO_DEBUG(("%%%c -> per second", ch
));
340 periodicity
= PER_SECOND
;
342 default: /* ignore anything else */
343 CRONO_DEBUG(("ignoring %%%c\n", ch
));
354 parse_timespec(char *optarg
, int *p_period_multiple
)
356 PERIODICITY periodicity
= INVALID_PERIOD
;
357 int period_multiple
= 1;
360 /* Skip leading whitespace */
362 while (isspace(*p
)) { p
++; }
365 /* Parse a digit string */
368 period_multiple
= *p
++ - '0';
370 while (isdigit(*p
)) {
371 period_multiple
*= 10;
372 period_multiple
+= (*p
++ - '0');
377 /* Skip whitespace */
379 while (isspace(*p
)) { p
++; }
381 if (strncasecmp(p
, "sec", 3) == 0) {
382 if (period_multiple
< 60) {
383 periodicity
= PER_SECOND
;
386 else if (strncasecmp(p
, "min", 3) == 0) {
387 if (period_multiple
< 60) {
388 periodicity
= PER_MINUTE
;
391 else if (strncasecmp(p
, "hour", 4) == 0) {
392 if (period_multiple
< 24) {
393 periodicity
= HOURLY
;
396 else if (strncasecmp(p
, "day", 3) == 0) {
397 if (period_multiple
<= 31) {
401 else if (strncasecmp(p
, "week", 4) == 0) {
402 if (period_multiple
< 53) {
403 periodicity
= WEEKLY
;
406 else if (strncasecmp(p
, "mon", 3) == 0) {
407 if (period_multiple
<= 12) {
408 periodicity
= MONTHLY
;
411 *p_period_multiple
= period_multiple
;
415 /* To determine the time of the start of the next period add just
416 * enough to move beyond the start of the next period and then
417 * determine the time of the start of that period.
419 * There is a potential problem if the start or end of daylight saving
420 * time occurs during the current period.
423 start_of_next_period(time_t time_now
, PERIODICITY periodicity
, int period_multiple
)
430 start_time
= (time_now
+ 366 * SECS_PER_DAY
+ DST_ALLOWANCE
);
434 start_time
= (time_now
+ 31 * SECS_PER_DAY
+ DST_ALLOWANCE
);
438 start_time
= (time_now
+ SECS_PER_WEEK
+ DST_ALLOWANCE
);
442 start_time
= (time_now
+ SECS_PER_DAY
+ DST_ALLOWANCE
);
446 start_time
= time_now
+ period_multiple
* SECS_PER_HOUR
+ LEAP_SECOND_ALLOWANCE
;
450 start_time
= time_now
+ period_multiple
* SECS_PER_MIN
+ LEAP_SECOND_ALLOWANCE
;
454 start_time
= time_now
+ 1;
458 start_time
= FAR_DISTANT_FUTURE
;
461 return start_of_this_period(start_time
, periodicity
, period_multiple
);
464 /* Determine the time of the start of the period containing a given time.
465 * Break down the time with localtime and subtract the number of
466 * seconds since the start of the period. If the length of period is
467 * equal or longer than a day then we have to check tht the
468 * calculation is not thrown out by the start or end of daylight
472 start_of_this_period(time_t start_time
, PERIODICITY periodicity
, int period_multiple
)
474 struct tm tm_initial
;
475 struct tm tm_adjusted
;
479 localtime_r(&start_time
, &tm_initial
);
481 struct tm
* tempTime
;
483 tempTime
= localtime(&start_time
);
484 if (nullptr != tempTime
)
486 memcpy(&tm_initial
, tempTime
, sizeof(struct tm
));
501 start_time
-= ( (tm_initial
.tm_yday
* SECS_PER_DAY
)
502 + (tm_initial
.tm_hour
* SECS_PER_HOUR
)
503 + (tm_initial
.tm_min
* SECS_PER_MIN
)
504 + (tm_initial
.tm_sec
));
509 start_time
-= ( ((tm_initial
.tm_mday
- 1) * SECS_PER_DAY
)
510 + ( tm_initial
.tm_hour
* SECS_PER_HOUR
)
511 + ( tm_initial
.tm_min
* SECS_PER_MIN
)
512 + ( tm_initial
.tm_sec
));
517 if (weeks_start_on_mondays
)
519 tm_initial
.tm_wday
= (6 + tm_initial
.tm_wday
) % 7;
521 start_time
-= ( (tm_initial
.tm_wday
* SECS_PER_DAY
)
522 + (tm_initial
.tm_hour
* SECS_PER_HOUR
)
523 + (tm_initial
.tm_min
* SECS_PER_MIN
)
524 + (tm_initial
.tm_sec
));
525 expected_mday
= tm_initial
.tm_mday
;
529 start_time
-= ( (tm_initial
.tm_hour
* SECS_PER_HOUR
)
530 + (tm_initial
.tm_min
* SECS_PER_MIN
)
531 + tm_initial
.tm_sec
);
532 expected_mday
= tm_initial
.tm_mday
;
536 fprintf(stderr
, "software fault in start_of_this_period()\n");
540 /* If the time of day is not equal to midnight then we need to
541 * adjust for daylight saving time. Adjust the time backwards
542 * by the value of the hour, minute and second fields. If the
543 * day of the month is not as expected one then we must have
544 * adjusted back to the previous day so add 24 hours worth of
548 localtime_r(&start_time
, &tm_adjusted
);
550 tempTime
= localtime(&start_time
);
551 if (nullptr != tempTime
)
553 memcpy(&tm_adjusted
, tempTime
, sizeof(struct tm
));
559 if ( (tm_adjusted
.tm_hour
!= 0)
560 || (tm_adjusted
.tm_min
!= 0)
561 || (tm_adjusted
.tm_sec
!= 0))
564 time_t adjust
= - ( (tm_adjusted
.tm_hour
* SECS_PER_HOUR
)
565 + (tm_adjusted
.tm_min
* SECS_PER_MIN
)
566 + (tm_adjusted
.tm_sec
));
568 if (tm_adjusted
.tm_mday
!= expected_mday
)
570 adjust
+= SECS_PER_DAY
;
573 start_time
+= adjust
;
580 CRONO_DEBUG(("Adjust for dst: %02d/%02d/%04d %02d:%02d:%02d -- %c%0d:%02d:%02d\n",
581 tm_initial
.tm_mday
, tm_initial
.tm_mon
+1, tm_initial
.tm_year
+1900,
582 tm_initial
.tm_hour
, tm_initial
.tm_min
, tm_initial
.tm_sec
, sign
,
583 adjust
/ SECS_PER_HOUR
, (adjust
/ 60) % 60, adjust
% SECS_PER_HOUR
));
588 start_time
-= (tm_initial
.tm_sec
+ tm_initial
.tm_min
* SECS_PER_MIN
);
589 if (period_multiple
> 1) {
590 start_time
-= SECS_PER_HOUR
* (tm_initial
.tm_hour
-
591 period_multiple
* (tm_initial
.tm_hour
/ period_multiple
));
596 start_time
-= tm_initial
.tm_sec
;
597 if (period_multiple
> 1) {
598 start_time
-= SECS_PER_MIN
* (tm_initial
.tm_min
-
599 period_multiple
* (tm_initial
.tm_min
/ period_multiple
));
603 case PER_SECOND
: /* No adjustment needed */
610 /* Converts struct tm to time_t, assuming the data in tm is UTC rather
611 * than local timezone (as mktime assumes).
613 * Contributed by Roger Beeman <beeman@cisco.com>.
616 mktime_from_utc(struct tm
*t
)
621 tb
= mktime(gmtime(&tl
));
622 return (tl
<= tb
? (tl
+ (tl
- tb
)) : (tl
- (tb
- tl
)));
625 /* Check whether the string is processed well. It is processed if the
626 * pointer is non-NULL, and it is either at the `GMT', or at the end
630 check_end(const char *p
)
636 if (!*p
|| (p
[0] == 'G' && p
[1] == 'M' && p
[2] == 'T'))
642 /* NOTE: We don't use `%n' for white space, as OSF's strptime uses
643 it to eat all white space up to (and including) a newline, and
644 the function fails (!) if there is no newline.
646 Let's hope all strptime-s use ` ' to skipp *all* whitespace
647 instead of just one (it works that way on all the systems I've
650 static const char *european_date_formats
[] =
652 "%d %b %Y %T", /* dd mmm yyyy HH:MM:SS */
653 "%d %b %Y %H:%M", /* dd mmm yyyy HH:MM */
654 "%d %b %Y", /* dd mmm yyyy */
655 "%d-%b-%Y %T", /* dd-mmm-yyyy HH:MM:SS */
656 "%d-%b-%Y %H:%M", /* dd-mmm-yyyy HH:MM */
657 "%d-%b-%y %T", /* dd-mmm-yy HH:MM:SS */
658 "%d-%b-%y %H:%M", /* dd-mmm-yy HH:MM */
665 static const char *american_date_formats
[] =
667 "%b %d %Y %T", /* dd mmm yyyy HH:MM:SS */
668 "%b %d %Y %H:%M", /* dd mmm yyyy HH:MM */
669 "%b %d %Y", /* dd mmm yyyy */
670 "%b-%d-%Y %T", /* dd-mmm-yyyy HH:MM:SS */
671 "%b-%d-%Y %H:%M", /* dd-mmm-yyyy HH:MM */
682 parse_time(char *time_str
, int use_american_date_formats
)
685 const char **date_formats
;
687 memset(&tm
, 0, sizeof (tm
));
691 for (date_formats
= (use_american_date_formats
692 ? american_date_formats
693 : european_date_formats
);
697 if (check_end((const char *)strptime(time_str
, *date_formats
, &tm
)))
698 return mktime_from_utc(&tm
);
706 /* Simple debugging print function.
709 print_debug_msg(const char *msg
, ...)
714 vfprintf(debug_file
, msg
, ap
);
718 /* Build a timestamp and return a pointer to it.
719 * (has a number of static buffers that are rotated).
722 timestamp(time_t thetime
)
724 static int index
= 0;
725 static char buffer
[4][80];
729 retval
= buffer
[index
++];
732 tm
= localtime(&thetime
);
733 strftime(retval
, 80, "%Y/%m/%d-%H:%M:%S %Z", tm
);