/* Try to create missing directories on the path of filename. * * Note that on a busy server there may theoretically be many cronolog * processes trying simultaneously to create the same subdirectories * so ignore any EEXIST errors on mkdir -- they probably just mean * that another process got there first. * * Unless CHECK_ALL_PREFIX_DIRS is defined, we save the directory of * the last file tested -- any common prefix should exist. This * probably only saves a few stat system calls at the start of each * log period, but it might as well be done. */ void create_subdirs(char *filename) { #ifndef CHECK_ALL_PREFIX_DIRS static char lastpath[PATH_MAX] = ""; #endif struct stat stat_buf; char dirname[PATH_MAX]; char *p; CRONO_DEBUG(("Creating missing components of \"%s\"\n", filename)); for (p = filename; (p = strchr(p, '/')); p++) { if (p == filename) { continue; /* Don't bother with the root directory */ } memcpy(dirname, filename, p - filename); dirname[p-filename] = '\0'; #ifndef CHECK_ALL_PREFIX_DIRS if (strncmp(dirname, lastpath, strlen(dirname)) == 0) { CRONO_DEBUG(("Initial prefix \"%s\" known to exist\n", dirname)); continue; } #endif CRONO_DEBUG(("Testing directory \"%s\"\n", dirname)); if (stat(dirname, &stat_buf) < 0) { if (errno != ENOENT) { perror(dirname); return; } else { CRONO_DEBUG(("Directory \"%s\" does not exist -- creating\n", dirname)); #ifndef _MSC_VER if ((mkdir(dirname, DIR_MODE) < 0) && (errno != EEXIST)) #else if ((mkdir(dirname) < 0) && (errno != EEXIST)) #endif { perror(dirname); return; } } } } #ifndef CHECK_ALL_PREFIX_DIRS strcpy(lastpath, dirname); #endif }
/* Open a new log file: determine the start of the current * period, generate the log file name from the fileTemplate, * determine the end of the period and open the new log file. * * Returns the file descriptor of the new log file and also sets the * name of the file and the start time of the next period via pointers * supplied. */ static FILE *new_log_file(const char *fileTemplate, const char *linkname, mode_t linktype, const char *prevlinkname, PERIODICITY periodicity, int period_multiple, int period_delay, char *pfilename, size_t pfilename_len, time_t time_now, time_t *pnext_period) { time_t start_of_period; struct tm *tm; int log_fd; start_of_period = start_of_this_period(time_now, periodicity, period_multiple); tm = localtime(&start_of_period); strftime(pfilename, pfilename_len, fileTemplate, tm); *pnext_period = start_of_next_period(start_of_period, periodicity, period_multiple) + period_delay; CRONO_DEBUG(("%s (%d): using log file \"%s\" from %s (%d) until %s (%d) " "(for %d secs)\n", timestamp(time_now), time_now, pfilename, timestamp(start_of_period), start_of_period, timestamp(*pnext_period), *pnext_period, *pnext_period - time_now)); log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE); #ifndef DONT_CREATE_SUBDIRS if ((log_fd < 0) && (errno == ENOENT)) { create_subdirs(pfilename); log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE); } #endif if (log_fd < 0) { perror(pfilename); return NULL; } if (linkname) { /* Create a relative symlink to logs under linkname's directory */ std::string dir = Util::safe_dirname(linkname); if (dir != "/") { dir.append("/"); } std::string filename; if (!strncmp(pfilename, dir.c_str(), dir.length())) { filename = pfilename + dir.length(); } else { filename = pfilename; } create_link(filename.c_str(), linkname, linktype, prevlinkname); } return fdopen(log_fd, "a"); }
/* Determine the time of the start of the period containing a given time. * Break down the time with localtime and subtract the number of * seconds since the start of the period. If the length of period is * equal or longer than a day then we have to check tht the * calculation is not thrown out by the start or end of daylight * saving time. */ time_t start_of_this_period(time_t start_time, PERIODICITY periodicity, int period_multiple) { struct tm tm_initial; struct tm tm_adjusted; int expected_mday; #ifndef _WIN32 localtime_r(&start_time, &tm_initial); #else struct tm * tempTime; tempTime = localtime(&start_time); if (nullptr != tempTime) { memcpy(&tm_initial, tempTime, sizeof(struct tm)); free(tempTime); tempTime = nullptr; } #endif switch (periodicity) { case YEARLY: case MONTHLY: case WEEKLY: case DAILY: switch (periodicity) { case YEARLY: start_time -= ( (tm_initial.tm_yday * SECS_PER_DAY) + (tm_initial.tm_hour * SECS_PER_HOUR) + (tm_initial.tm_min * SECS_PER_MIN) + (tm_initial.tm_sec)); expected_mday = 1; break; case MONTHLY: start_time -= ( ((tm_initial.tm_mday - 1) * SECS_PER_DAY) + ( tm_initial.tm_hour * SECS_PER_HOUR) + ( tm_initial.tm_min * SECS_PER_MIN) + ( tm_initial.tm_sec)); expected_mday = 1; break; case WEEKLY: if (weeks_start_on_mondays) { tm_initial.tm_wday = (6 + tm_initial.tm_wday) % 7; } start_time -= ( (tm_initial.tm_wday * SECS_PER_DAY) + (tm_initial.tm_hour * SECS_PER_HOUR) + (tm_initial.tm_min * SECS_PER_MIN) + (tm_initial.tm_sec)); expected_mday = tm_initial.tm_mday; break; case DAILY: start_time -= ( (tm_initial.tm_hour * SECS_PER_HOUR) + (tm_initial.tm_min * SECS_PER_MIN ) + tm_initial.tm_sec); expected_mday = tm_initial.tm_mday; break; default: fprintf(stderr, "software fault in start_of_this_period()\n"); exit(1); } /* If the time of day is not equal to midnight then we need to * adjust for daylight saving time. Adjust the time backwards * by the value of the hour, minute and second fields. If the * day of the month is not as expected one then we must have * adjusted back to the previous day so add 24 hours worth of * seconds. */ #ifndef _WIN32 localtime_r(&start_time, &tm_adjusted); #else tempTime = localtime(&start_time); if (nullptr != tempTime) { memcpy(&tm_adjusted, tempTime, sizeof(struct tm)); free(tempTime); tempTime = nullptr; } #endif if ( (tm_adjusted.tm_hour != 0) || (tm_adjusted.tm_min != 0) || (tm_adjusted.tm_sec != 0)) { char sign = '-'; time_t adjust = - ( (tm_adjusted.tm_hour * SECS_PER_HOUR) + (tm_adjusted.tm_min * SECS_PER_MIN) + (tm_adjusted.tm_sec)); if (tm_adjusted.tm_mday != expected_mday) { adjust += SECS_PER_DAY; sign = '+'; } start_time += adjust; if (adjust < 0) { adjust = -adjust; } CRONO_DEBUG(("Adjust for dst: %02d/%02d/%04d %02d:%02d:%02d -- %c%0d:%02d:%02d\n", tm_initial.tm_mday, tm_initial.tm_mon+1, tm_initial.tm_year+1900, tm_initial.tm_hour, tm_initial.tm_min, tm_initial.tm_sec, sign, adjust / SECS_PER_HOUR, (adjust / 60) % 60, adjust % SECS_PER_HOUR)); } break; case HOURLY: start_time -= (tm_initial.tm_sec + tm_initial.tm_min * SECS_PER_MIN); if (period_multiple > 1) { start_time -= SECS_PER_HOUR * (tm_initial.tm_hour - period_multiple * (tm_initial.tm_hour / period_multiple)); } break; case PER_MINUTE: start_time -= tm_initial.tm_sec; if (period_multiple > 1) { start_time -= SECS_PER_MIN * (tm_initial.tm_min - period_multiple * (tm_initial.tm_min / period_multiple)); } break; case PER_SECOND: /* No adjustment needed */ default: break; } return start_time; }
/* Examine the log file name specifier for strftime conversion * specifiers and determine the period between log files. * Smallest period allowed is per minute. */ PERIODICITY determine_periodicity(char *spec) { PERIODICITY periodicity = ONCE_ONLY; char ch; CRONO_DEBUG(("Determining periodicity of \"%s\"\n", spec)); while ((ch = *spec++) != 0) { if (ch == '%') { ch = *spec++; if (!ch) break; switch (ch) { case 'y': /* two digit year */ case 'Y': /* four digit year */ if (periodicity > YEARLY) { CRONO_DEBUG(("%%%c -> yearly\n", ch)); periodicity = YEARLY; } break; case 'b': /* abbreviated month name */ case 'h': /* abbreviated month name (non-standard) */ case 'B': /* full month name */ case 'm': /* month as two digit number (with leading zero) */ if (periodicity > MONTHLY) { CRONO_DEBUG(("%%%c -> monthly\n", ch)); periodicity = MONTHLY; } break; case 'U': /* week number (weeks start on Sunday) */ case 'W': /* week number (weeks start on Monday) */ if (periodicity > WEEKLY) { CRONO_DEBUG(("%%%c -> weeky\n", ch)); periodicity = WEEKLY; weeks_start_on_mondays = (ch == 'W'); } break; case 'a': /* abbreviated weekday name */ case 'A': /* full weekday name */ case 'd': /* day of the month (with leading zero) */ case 'e': /* day of the month (with leading space -- non-standard) */ case 'j': /* day of the year (with leading zeroes) */ case 'w': /* day of the week (0-6) */ case 'D': /* full date spec (non-standard) */ case 'x': /* full date spec */ if (periodicity > DAILY) { CRONO_DEBUG(("%%%c -> daily\n", ch)); periodicity = DAILY; } break; case 'H': /* hour (24 hour clock) */ case 'I': /* hour (12 hour clock) */ case 'p': /* AM/PM indicator */ if (periodicity > HOURLY) { CRONO_DEBUG(("%%%c -> hourly\n", ch)); periodicity = HOURLY; } break; case 'M': /* minute */ if (periodicity > PER_MINUTE) { CRONO_DEBUG(("%%%c -> per minute\n", ch)); periodicity = PER_MINUTE; } break; case 'S': /* second */ case 's': /* seconds since the epoch (GNU non-standard) */ case 'c': /* full time and date spec */ case 'T': /* full time spec */ case 'r': /* full time spec (non-standard) */ case 'R': /* full time spec (non-standard) */ CRONO_DEBUG(("%%%c -> per second", ch)); periodicity = PER_SECOND; default: /* ignore anything else */ CRONO_DEBUG(("ignoring %%%c\n", ch)); break; } } } return periodicity; }