This fixes a bug in PHP/HH's crypt_blowfish implementation that can cause a short...
[hiphop-php.git] / hphp / util / cronoutils.cpp
blob84408bed0855ce86943d24085721fb6522f008a6
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
6 * are met:
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
14 * distribution.
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
24 * apache@apache.org.
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
31 * acknowledgment:
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 #include "hphp/util/portability.h"
75 #include <folly/portability/Dirent.h>
76 #include <folly/portability/Fcntl.h>
77 #include <folly/portability/Stdlib.h>
78 #include <folly/portability/String.h>
79 #include <folly/portability/SysStat.h>
80 #include <folly/portability/Time.h>
81 #include <folly/portability/Unistd.h>
83 extern char *tzname[2];
85 #ifndef DIR_MODE
86 #define DIR_MODE ( S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH )
87 #endif
89 /* Constants for seconds per minute, hour, day and week */
90 #define SECS_PER_MIN 60
91 #define SECS_PER_HOUR (60 * SECS_PER_MIN)
92 #ifndef SECS_PER_DAY
93 #define SECS_PER_DAY (24 * SECS_PER_HOUR)
94 #endif
95 #define SECS_PER_WEEK (7 * SECS_PER_DAY)
97 /* Allowances for daylight saving time changes and leap second.
98 * * Used for calculating the start of the next period.
99 * * (does Unix actually know about leap seconds?)
100 * */
102 #define LEAP_SECOND_ALLOWANCE 2
103 #define DST_ALLOWANCE (3 * SECS_PER_HOUR + LEAP_SECOND_ALLOWANCE)
105 /* debug_file is the file to output debug messages to. No debug
106 * messages are output if it is set to NULL.
108 FILE *debug_file = nullptr;
111 /* America and Europe disagree on whether weeks start on Sunday or
112 * Monday - weeks_start_on_mondays is set if a %U specifier is encountered.
114 int weeks_start_on_mondays = 0;
117 /* periods[] is an array of the names of the periods.
119 const char *periods[] =
121 "second",
122 "minute",
123 "hour",
124 "day",
125 "week",
126 "month",
127 "year",
128 "aeon" /* i.e. once only */
131 /* period_seconds[] is an array of the number of seconds in a period.
133 int period_seconds[] =
137 60 * 60,
138 60 * 60 * 24,
139 60 * 60 * 24 * 7,
140 60 * 60 * 24 * 31,
141 60 * 60 * 24 * 365
144 /* Try to create missing directories on the path of filename.
146 * Note that on a busy server there may theoretically be many cronolog
147 * processes trying simultaneously to create the same subdirectories
148 * so ignore any EEXIST errors on mkdir -- they probably just mean
149 * that another process got there first.
151 * Unless CHECK_ALL_PREFIX_DIRS is defined, we save the directory of
152 * the last file tested -- any common prefix should exist. This
153 * probably only saves a few stat system calls at the start of each
154 * log period, but it might as well be done.
156 void
157 create_subdirs(char *filename)
159 #ifndef CHECK_ALL_PREFIX_DIRS
160 static char lastpath[PATH_MAX] = "";
161 #endif
162 struct stat stat_buf;
163 char dirname[PATH_MAX];
164 char *p;
166 CRONO_DEBUG(("Creating missing components of \"%s\"\n", filename));
167 for (p = filename; (p = strchr(p, '/')); p++)
169 if (p == filename)
171 continue; /* Don't bother with the root directory */
174 memcpy(dirname, filename, p - filename);
175 dirname[p-filename] = '\0';
177 #ifndef CHECK_ALL_PREFIX_DIRS
178 if (strncmp(dirname, lastpath, strlen(dirname)) == 0)
180 CRONO_DEBUG(("Initial prefix \"%s\" known to exist\n", dirname));
181 continue;
183 #endif
185 CRONO_DEBUG(("Testing directory \"%s\"\n", dirname));
186 if (stat(dirname, &stat_buf) < 0)
188 if (errno != ENOENT)
190 perror(dirname);
191 return;
193 else
195 CRONO_DEBUG(("Directory \"%s\" does not exist -- creating\n", dirname));
196 if ((mkdir(dirname, DIR_MODE) < 0) && (errno != EEXIST))
198 perror(dirname);
199 return;
204 #ifndef CHECK_ALL_PREFIX_DIRS
205 strcpy(lastpath, dirname);
206 #endif
209 /* Create a hard or symbolic link to a filename according to the type specified.
211 * This function could do with more error checking!
213 void
214 create_link(const char *pfilename,
215 const char *linkname, mode_t linktype,
216 const char *prevlinkname)
218 #ifndef _MSC_VER
219 struct stat stat_buf;
221 if (prevlinkname && lstat(prevlinkname, &stat_buf) == 0)
223 unlink(prevlinkname);
225 if (lstat(linkname, &stat_buf) == 0)
227 if (prevlinkname) {
228 rename(linkname, prevlinkname);
230 else {
231 unlink(linkname);
234 if (linktype == S_IFLNK)
236 if (symlink(pfilename, linkname) < 0) {
237 fprintf(stderr, "Creating link from %s to %s failed",
238 pfilename, linkname);
241 else
243 if (link(pfilename, linkname) < 0) {
244 fprintf(stderr, "Creating link from %s to %s failed",
245 pfilename, linkname);
248 #else
249 fprintf(stderr, "Creating link from %s to %s not supported",
250 pfilename, linkname);
251 #endif
254 /* Examine the log file name specifier for strftime conversion
255 * specifiers and determine the period between log files.
256 * Smallest period allowed is per minute.
258 PERIODICITY
259 determine_periodicity(char *spec)
261 PERIODICITY periodicity = ONCE_ONLY;
262 char ch;
264 CRONO_DEBUG(("Determining periodicity of \"%s\"\n", spec));
265 while ((ch = *spec++) != 0)
267 if (ch == '%')
269 ch = *spec++;
270 if (!ch) break;
272 switch (ch)
274 case 'y': /* two digit year */
275 case 'Y': /* four digit year */
276 if (periodicity > YEARLY)
278 CRONO_DEBUG(("%%%c -> yearly\n", ch));
279 periodicity = YEARLY;
281 break;
283 case 'b': /* abbreviated month name */
284 case 'h': /* abbreviated month name (non-standard) */
285 case 'B': /* full month name */
286 case 'm': /* month as two digit number (with
287 leading zero) */
288 if (periodicity > MONTHLY)
290 CRONO_DEBUG(("%%%c -> monthly\n", ch));
291 periodicity = MONTHLY;
293 break;
295 case 'U': /* week number (weeks start on Sunday) */
296 case 'W': /* week number (weeks start on Monday) */
297 if (periodicity > WEEKLY)
299 CRONO_DEBUG(("%%%c -> weeky\n", ch));
300 periodicity = WEEKLY;
301 weeks_start_on_mondays = (ch == 'W');
303 break;
305 case 'a': /* abbreviated weekday name */
306 case 'A': /* full weekday name */
307 case 'd': /* day of the month (with leading zero) */
308 case 'e': /* day of the month (with leading space -- non-standard) */
309 case 'j': /* day of the year (with leading zeroes) */
310 case 'w': /* day of the week (0-6) */
311 case 'D': /* full date spec (non-standard) */
312 case 'x': /* full date spec */
313 if (periodicity > DAILY)
315 CRONO_DEBUG(("%%%c -> daily\n", ch));
316 periodicity = DAILY;
318 break;
320 case 'H': /* hour (24 hour clock) */
321 case 'I': /* hour (12 hour clock) */
322 case 'p': /* AM/PM indicator */
323 if (periodicity > HOURLY)
325 CRONO_DEBUG(("%%%c -> hourly\n", ch));
326 periodicity = HOURLY;
328 break;
330 case 'M': /* minute */
331 if (periodicity > PER_MINUTE)
333 CRONO_DEBUG(("%%%c -> per minute\n", ch));
334 periodicity = PER_MINUTE;
336 break;
338 case 'S': /* second */
339 case 's': /* seconds since the epoch (GNU non-standard) */
340 case 'c': /* full time and date spec */
341 case 'T': /* full time spec */
342 case 'r': /* full time spec (non-standard) */
343 case 'R': /* full time spec (non-standard) */
344 CRONO_DEBUG(("%%%c -> per second", ch));
345 periodicity = PER_SECOND;
347 default: /* ignore anything else */
348 CRONO_DEBUG(("ignoring %%%c\n", ch));
349 break;
353 return periodicity;
358 PERIODICITY
359 parse_timespec(char *optarg, int *p_period_multiple)
361 PERIODICITY periodicity = INVALID_PERIOD;
362 int period_multiple = 1;
363 char *p = optarg;
365 /* Skip leading whitespace */
367 while (isspace(*p)) { p++; }
370 /* Parse a digit string */
372 if (isdigit(*p)) {
373 period_multiple = *p++ - '0';
375 while (isdigit(*p)) {
376 period_multiple *= 10;
377 period_multiple += (*p++ - '0');
382 /* Skip whitespace */
384 while (isspace(*p)) { p++; }
386 if (strncasecmp(p, "sec", 3) == 0) {
387 if (period_multiple < 60) {
388 periodicity = PER_SECOND;
391 else if (strncasecmp(p, "min", 3) == 0) {
392 if (period_multiple < 60) {
393 periodicity = PER_MINUTE;
396 else if (strncasecmp(p, "hour", 4) == 0) {
397 if (period_multiple < 24) {
398 periodicity = HOURLY;
401 else if (strncasecmp(p, "day", 3) == 0) {
402 if (period_multiple <= 31) {
403 periodicity = DAILY;
406 else if (strncasecmp(p, "week", 4) == 0) {
407 if (period_multiple < 53) {
408 periodicity = WEEKLY;
411 else if (strncasecmp(p, "mon", 3) == 0) {
412 if (period_multiple <= 12) {
413 periodicity = MONTHLY;
416 *p_period_multiple = period_multiple;
417 return periodicity;
420 /* To determine the time of the start of the next period add just
421 * enough to move beyond the start of the next period and then
422 * determine the time of the start of that period.
424 * There is a potential problem if the start or end of daylight saving
425 * time occurs during the current period.
427 time_t
428 start_of_next_period(time_t time_now, PERIODICITY periodicity, int period_multiple)
430 time_t start_time;
432 switch (periodicity)
434 case YEARLY:
435 start_time = (time_now + 366 * SECS_PER_DAY + DST_ALLOWANCE);
436 break;
438 case MONTHLY:
439 start_time = (time_now + 31 * SECS_PER_DAY + DST_ALLOWANCE);
440 break;
442 case WEEKLY:
443 start_time = (time_now + SECS_PER_WEEK + DST_ALLOWANCE);
444 break;
446 case DAILY:
447 start_time = (time_now + SECS_PER_DAY + DST_ALLOWANCE);
448 break;
450 case HOURLY:
451 start_time = time_now + period_multiple * SECS_PER_HOUR + LEAP_SECOND_ALLOWANCE;
452 break;
454 case PER_MINUTE:
455 start_time = time_now + period_multiple * SECS_PER_MIN + LEAP_SECOND_ALLOWANCE;
456 break;
458 case PER_SECOND:
459 start_time = time_now + 1;
460 break;
462 default:
463 start_time = FAR_DISTANT_FUTURE;
464 break;
466 return start_of_this_period(start_time, periodicity, period_multiple);
469 /* Determine the time of the start of the period containing a given time.
470 * Break down the time with localtime and subtract the number of
471 * seconds since the start of the period. If the length of period is
472 * equal or longer than a day then we have to check that the
473 * calculation is not thrown out by the start or end of daylight
474 * saving time.
476 time_t
477 start_of_this_period(time_t start_time, PERIODICITY periodicity, int period_multiple)
479 struct tm tm_initial;
480 struct tm tm_adjusted;
481 int expected_mday;
483 #ifndef _WIN32
484 localtime_r(&start_time, &tm_initial);
485 #else
486 struct tm * tempTime;
488 tempTime = localtime(&start_time);
489 if (nullptr != tempTime)
491 memcpy(&tm_initial, tempTime, sizeof(struct tm));
493 free(tempTime);
494 tempTime = nullptr;
496 #endif
497 switch (periodicity)
499 case YEARLY:
500 case MONTHLY:
501 case WEEKLY:
502 case DAILY:
503 switch (periodicity)
505 case YEARLY:
506 start_time -= ( (tm_initial.tm_yday * SECS_PER_DAY)
507 + (tm_initial.tm_hour * SECS_PER_HOUR)
508 + (tm_initial.tm_min * SECS_PER_MIN)
509 + (tm_initial.tm_sec));
510 expected_mday = 1;
511 break;
513 case MONTHLY:
514 start_time -= ( ((tm_initial.tm_mday - 1) * SECS_PER_DAY)
515 + ( tm_initial.tm_hour * SECS_PER_HOUR)
516 + ( tm_initial.tm_min * SECS_PER_MIN)
517 + ( tm_initial.tm_sec));
518 expected_mday = 1;
519 break;
521 case WEEKLY:
522 if (weeks_start_on_mondays)
524 tm_initial.tm_wday = (6 + tm_initial.tm_wday) % 7;
526 start_time -= ( (tm_initial.tm_wday * SECS_PER_DAY)
527 + (tm_initial.tm_hour * SECS_PER_HOUR)
528 + (tm_initial.tm_min * SECS_PER_MIN)
529 + (tm_initial.tm_sec));
530 expected_mday = tm_initial.tm_mday;
531 break;
533 case DAILY:
534 start_time -= ( (tm_initial.tm_hour * SECS_PER_HOUR)
535 + (tm_initial.tm_min * SECS_PER_MIN )
536 + tm_initial.tm_sec);
537 expected_mday = tm_initial.tm_mday;
538 break;
540 default:
541 fprintf(stderr, "software fault in start_of_this_period()\n");
542 exit(1);
545 /* If the time of day is not equal to midnight then we need to
546 * adjust for daylight saving time. Adjust the time backwards
547 * by the value of the hour, minute and second fields. If the
548 * day of the month is not as expected one then we must have
549 * adjusted back to the previous day so add 24 hours worth of
550 * seconds.
552 #ifndef _WIN32
553 localtime_r(&start_time, &tm_adjusted);
554 #else
555 tempTime = localtime(&start_time);
556 if (nullptr != tempTime)
558 memcpy(&tm_adjusted, tempTime, sizeof(struct tm));
560 free(tempTime);
561 tempTime = nullptr;
563 #endif
564 if ( (tm_adjusted.tm_hour != 0)
565 || (tm_adjusted.tm_min != 0)
566 || (tm_adjusted.tm_sec != 0))
568 char sign = '-';
569 time_t adjust = - ( (tm_adjusted.tm_hour * SECS_PER_HOUR)
570 + (tm_adjusted.tm_min * SECS_PER_MIN)
571 + (tm_adjusted.tm_sec));
573 if (tm_adjusted.tm_mday != expected_mday)
575 adjust += SECS_PER_DAY;
576 sign = '+';
578 start_time += adjust;
580 if (adjust < 0)
582 adjust = -adjust;
585 CRONO_DEBUG(("Adjust for dst: %02d/%02d/%04d %02d:%02d:%02d -- %c%0d:%02d:%02d\n",
586 tm_initial.tm_mday, tm_initial.tm_mon+1, tm_initial.tm_year+1900,
587 tm_initial.tm_hour, tm_initial.tm_min, tm_initial.tm_sec, sign,
588 adjust / SECS_PER_HOUR, (adjust / 60) % 60, adjust % SECS_PER_HOUR));
590 break;
592 case HOURLY:
593 start_time -= (tm_initial.tm_sec + tm_initial.tm_min * SECS_PER_MIN);
594 if (period_multiple > 1) {
595 start_time -= SECS_PER_HOUR * (tm_initial.tm_hour -
596 period_multiple * (tm_initial.tm_hour / period_multiple));
598 break;
600 case PER_MINUTE:
601 start_time -= tm_initial.tm_sec;
602 if (period_multiple > 1) {
603 start_time -= SECS_PER_MIN * (tm_initial.tm_min -
604 period_multiple * (tm_initial.tm_min / period_multiple));
606 break;
608 case PER_SECOND: /* No adjustment needed */
609 default:
610 break;
612 return start_time;
615 /* Converts struct tm to time_t, assuming the data in tm is UTC rather
616 * than local timezone (as mktime assumes).
618 * Contributed by Roger Beeman <beeman@cisco.com>.
620 time_t
621 mktime_from_utc(struct tm *t)
623 time_t tl, tb;
625 tl = mktime(t);
626 tb = mktime(gmtime(&tl));
627 return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl)));
630 /* Check whether the string is processed well. It is processed if the
631 * pointer is non-NULL, and it is either at the `GMT', or at the end
632 * of the string.
634 static int
635 check_end(const char *p)
637 if (!p)
638 return 0;
639 while (isspace(*p))
640 ++p;
641 if (!*p || (p[0] == 'G' && p[1] == 'M' && p[2] == 'T'))
642 return 1;
643 else
644 return 0;
647 /* NOTE: We don't use `%n' for white space, as OSF's strptime uses
648 it to eat all white space up to (and including) a newline, and
649 the function fails (!) if there is no newline.
651 Let's hope all strptime-s use ` ' to skip *all* whitespace
652 instead of just one (it works that way on all the systems I've
653 tested it on). */
655 static const char *european_date_formats[] =
657 "%d %b %Y %T", /* dd mmm yyyy HH:MM:SS */
658 "%d %b %Y %H:%M", /* dd mmm yyyy HH:MM */
659 "%d %b %Y", /* dd mmm yyyy */
660 "%d-%b-%Y %T", /* dd-mmm-yyyy HH:MM:SS */
661 "%d-%b-%Y %H:%M", /* dd-mmm-yyyy HH:MM */
662 "%d-%b-%y %T", /* dd-mmm-yy HH:MM:SS */
663 "%d-%b-%y %H:%M", /* dd-mmm-yy HH:MM */
664 "%d-%b-%Y",
665 "%b %d %T %Y",
666 "%b %d %Y",
667 nullptr
670 static const char *american_date_formats[] =
672 "%b %d %Y %T", /* dd mmm yyyy HH:MM:SS */
673 "%b %d %Y %H:%M", /* dd mmm yyyy HH:MM */
674 "%b %d %Y", /* dd mmm yyyy */
675 "%b-%d-%Y %T", /* dd-mmm-yyyy HH:MM:SS */
676 "%b-%d-%Y %H:%M", /* dd-mmm-yyyy HH:MM */
677 "%b-%d-%Y",
678 "%b/%d/%Y %T",
679 "%b/%d/%Y %H:%M",
680 "%b/%d/%Y",
681 nullptr
686 time_t
687 parse_time(char *time_str, int use_american_date_formats)
689 struct tm tm;
690 const char **date_formats;
692 memset(&tm, 0, sizeof (tm));
693 tm.tm_isdst = -1;
696 for (date_formats = (use_american_date_formats
697 ? american_date_formats
698 : european_date_formats);
699 *date_formats;
700 date_formats++)
702 if (check_end((const char *)strptime(time_str, *date_formats, &tm)))
703 return mktime_from_utc(&tm);
706 return -1;
711 /* Simple debugging print function.
713 void
714 print_debug_msg(const char *msg, ...)
716 va_list ap;
718 va_start(ap, msg);
719 vfprintf(debug_file, msg, ap);
723 /* Build a timestamp and return a pointer to it.
724 * (has a number of static buffers that are rotated).
726 char *
727 timestamp(time_t thetime)
729 static int index = 0;
730 static char buffer[4][80];
731 struct tm *tm;
732 char *retval;
734 retval = buffer[index++];
735 index %= 4;
737 tm = localtime(&thetime);
738 strftime(retval, 80, "%Y/%m/%d-%H:%M:%S %Z", tm);
739 return retval;