/*****************************************************************************\ * cron.c ***************************************************************************** * Copyright (C) 2020 SchedMD LLC. * Written by Tim Wickberg * * This file is part of Slurm, a resource management program. * For details, see . * Please also read the included file: DISCLAIMER. * * Slurm is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * In addition, as a special exception, the copyright holders give permission * to link the code of portions of this program with the OpenSSL library under * certain conditions as described in each individual source file, and * distribute linked combinations including the two. You must obey the GNU * General Public License in all respects for all of the code used other than * OpenSSL. If you modify file(s) with this exception, you may extend this * exception to your version of the file(s), but you are not obligated to do * so. If you do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source files in * the program, then also delete it here. * * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with Slurm; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. \*****************************************************************************/ #include #include #include "src/common/bitstring.h" #include "src/common/cron.h" #include "src/common/log.h" #include "src/common/pack.h" #include "src/common/read_config.h" #include "src/common/slurm_time.h" #include "src/common/uid.h" #include "src/common/xmalloc.h" #include "src/common/xstring.h" extern cron_entry_t *new_cron_entry(void) { cron_entry_t *entry = xmalloc(sizeof(*entry)); entry->minute = bit_alloc(61); entry->hour = bit_alloc(25); entry->day_of_month = bit_alloc(32); entry->month = bit_alloc(13); entry->day_of_week = bit_alloc(8); return entry; } extern void free_cron_entry(void *in) { cron_entry_t *entry = (cron_entry_t *) in; if (!entry) return; xfree(entry->minute); xfree(entry->hour); xfree(entry->day_of_month); xfree(entry->month); xfree(entry->day_of_week); xfree(entry->command); xfree(entry); } extern bool valid_cron_entry(cron_entry_t *e) { int first_day_of_month; /* basic structure check */ if ((bit_size(e->minute) != 61) || (bit_size(e->hour) != 25) || (bit_size(e->day_of_month) != 32) || (bit_size(e->month) != 13) || (bit_size(e->day_of_week) != 8)) return false; /* * Clear top or lower bits (may have been set for wildcard processing). */ bit_clear(e->minute, 60); bit_clear(e->hour, 24); bit_clear(e->day_of_month, 0); bit_clear(e->month, 0); bit_clear(e->day_of_week, 7); /* * Missing some e. Need at least one bit set in each field or * the wildcard flag, otherwise calc_next_cron_start() will break. */ first_day_of_month = bit_ffs(e->day_of_month); if ((!(e->flags & CRON_WILD_MINUTE) && (bit_ffs(e->minute) == -1)) || (!(e->flags & CRON_WILD_HOUR) && (bit_ffs(e->hour) == -1)) || (!(e->flags & CRON_WILD_DOM) && (first_day_of_month == -1)) || (!(e->flags & CRON_WILD_MONTH) && (bit_ffs(e->month) == -1)) || (!(e->flags & CRON_WILD_DOW) && (bit_ffs(e->day_of_week) == -1))) return false; /* * Make sure the crontab isn't requesting a non-existent * combination of month and day. * * Note: we do allow you to schedule something to only run * on leap days, as crazy as that may seem. */ if (e->flags & CRON_WILD_DOM) { ; } else if (first_day_of_month == 31) { if (!bit_test(e->month, 1) && !bit_test(e->month, 3) && !bit_test(e->month, 5) && !bit_test(e->month, 7) && !bit_test(e->month, 8) && !bit_test(e->month, 10) && !bit_test(e->month, 12)) return false; } else if (first_day_of_month == 30) { /* Make sure the only month available isn't February. */ if ((bit_fls(e->month) == 2) && (bit_ffs(e->month) == 2)) return false; } return true; } extern char *cronspec_from_cron_entry(cron_entry_t *entry) { char *cronspec = NULL; char *fmt; if (entry->flags & CRON_WILD_MINUTE) { xstrcat(cronspec, "* "); } else { fmt = bit_fmt_full(entry->minute); xstrfmtcat(cronspec, "%s ", fmt); xfree(fmt); } if (entry->flags & CRON_WILD_HOUR) { xstrcat(cronspec, "* "); } else { fmt = bit_fmt_full(entry->hour); xstrfmtcat(cronspec, "%s ", fmt); xfree(fmt); } if (entry->flags & CRON_WILD_DOM) { xstrcat(cronspec, "* "); } else { fmt = bit_fmt_full(entry->day_of_month); xstrfmtcat(cronspec, "%s ", fmt); xfree(fmt); } if (entry->flags & CRON_WILD_MONTH) { xstrcat(cronspec, "* "); } else { fmt = bit_fmt_full(entry->month); xstrfmtcat(cronspec, "%s ", fmt); xfree(fmt); } if (entry->flags & CRON_WILD_DOW) { xstrcat(cronspec, "*"); } else { fmt = bit_fmt_full(entry->day_of_week); xstrfmtcat(cronspec, "%s", fmt); xfree(fmt); } return cronspec; } /* * One important note: struct tm has jan == 0, but the crontab * format and our bitstring have jan == 1. */ static int _next_month(cron_entry_t *entry, struct tm *tm) { int months_to_advance = 0; /* month is current valid, nice and easy, no major adjustments needed */ if (entry->flags & CRON_WILD_MONTH || bit_test(entry->month, tm->tm_mon + 1)) return 0; for (int i = tm->tm_mon; i < 12; i++) { if (bit_test(entry->month, i + 1)) goto found; months_to_advance++; } for (int i = 0; i < tm->tm_mon; i++) { if (bit_test(entry->month, i + 1)) goto found; months_to_advance++; } fatal("Could not find a valid month, this should be impossible"); found: /* * Next usable month is not this month. Reset other timing to midnight * on the first of the next valid month. */ tm->tm_mon += months_to_advance; tm->tm_hour = 0; tm->tm_min = 0; tm->tm_mday = 1; slurm_mktime(tm); return 0; } static int _next_day_of_week(cron_entry_t *entry, struct tm *tm) { int days_to_advance = 0; for (int i = tm->tm_wday; i < 7; i++) { if (bit_test(entry->day_of_week, i)) return days_to_advance; days_to_advance++; } for (int i = 0; i < tm->tm_wday; i++) { if (bit_test(entry->day_of_week, i)) return days_to_advance; days_to_advance++; } return 0; } static int _next_day_of_month(cron_entry_t *entry, struct tm *tm) { int days_to_advance = 0; for (int i = tm->tm_mday; i < 29; i++) { if (bit_test(entry->day_of_month, i)) return days_to_advance; days_to_advance++; } /* february == 1 */ if (tm->tm_mon != 1) { if (bit_test(entry->day_of_month, 29)) return days_to_advance; days_to_advance++; if (bit_test(entry->day_of_month, 30)) return days_to_advance; days_to_advance++; if ((tm->tm_mon == 0) || (tm->tm_mon == 2) || (tm->tm_mon == 4) || (tm->tm_mon == 6) || (tm->tm_mon == 7) || (tm->tm_mon == 9) || (tm->tm_mon == 11)) { if (bit_test(entry->day_of_month, 31)) return days_to_advance; days_to_advance++; } } else { /* (ab)use mktime() to figure out leap years for februrary */ struct tm test; test.tm_year = tm->tm_year; test.tm_mon = 1; test.tm_mday = 29; test.tm_hour = 0; test.tm_min = 0; slurm_mktime(&test); if (test.tm_mon == 1) { /* leap year! */ if (bit_test(entry->day_of_month, 29)) return days_to_advance; days_to_advance++; } } for (int i = 1; i < tm->tm_mday; i++) { if (bit_test(entry->day_of_month, i)) return days_to_advance; days_to_advance++; } return days_to_advance; } extern time_t calc_next_cron_start(cron_entry_t *entry) { struct tm tm; time_t now = time(NULL); localtime_r(&now, &tm); int validated_month, days_to_add; /* * Always move into the next minute to avoid running again this minute. */ tm.tm_sec = 0; tm.tm_min++; month: _next_month(entry, &tm); validated_month = tm.tm_mon; days_to_add = 0; if ((entry->flags & CRON_WILD_DOM) && (entry->flags & CRON_WILD_DOW)) { /* Wildcard for both is the easy path out. */ ; } else if (entry->flags & CRON_WILD_DOM) { /* * Only pay attention to the day of week. */ days_to_add = _next_day_of_week(entry, &tm); } else if (entry->flags & CRON_WILD_DOW) { /* * Only attention to the day of month. */ days_to_add = _next_day_of_month(entry, &tm); } else { /* * When both are specified, the defacto behavior is to * treat them as OR'd rather than AND'd, as trying to * resolve both simultaneously would result in the job * very rarely running. * So find the soonest time between them and use that. */ int dom_next = _next_day_of_month(entry, &tm); int dow_next = _next_day_of_week(entry, &tm); days_to_add = MIN(dom_next, dow_next); } if (days_to_add) { tm.tm_mday += days_to_add; tm.tm_hour = 0; tm.tm_min = 0; slurm_mktime(&tm); /* month slipped back, need to re-validate */ if (validated_month != tm.tm_mon) goto month; } hour: if (!(entry->flags & CRON_WILD_HOUR) && !bit_test(entry->hour, tm.tm_hour)) { /* must be in future, reset minutes */ tm.tm_min = 0; while (tm.tm_hour < 24) { if (bit_test(entry->hour, tm.tm_hour)) break; tm.tm_hour++; } if (tm.tm_hour == 24) { /* * tm_hour set to 24 rolls the day and possibly * the month at well. revalidate month + day. */ slurm_mktime(&tm); goto month; } } if (!(entry->flags & CRON_WILD_MINUTE) && !bit_test(entry->minute, tm.tm_min)) { while (tm.tm_min < 60) { if (bit_test(entry->minute, tm.tm_min)) break; tm.tm_min++; } if (tm.tm_min == 60 && tm.tm_hour == 23) { /* * this will roll into the next day, * which may also be a new month */ slurm_mktime(&tm); goto month; } else if (tm.tm_min == 60) { /* * next hour, but fortunately still * in the same day */ tm.tm_min = 0; tm.tm_hour++; goto hour; } } return slurm_mktime(&tm); } extern void pack_cron_entry(void *in, uint16_t protocol_version, buf_t *buffer) { uint8_t set = (in ? 1 : 0); cron_entry_t *entry = (cron_entry_t *) in; pack8(set, buffer); if (!set) return; if (protocol_version >= SLURM_20_11_PROTOCOL_VERSION) { pack32(entry->flags, buffer); pack_bit_str_hex(entry->minute, buffer); pack_bit_str_hex(entry->hour, buffer); pack_bit_str_hex(entry->day_of_month, buffer); pack_bit_str_hex(entry->month, buffer); pack_bit_str_hex(entry->day_of_week, buffer); packstr(entry->cronspec, buffer); /* command is not packed, only in struct for parsing */ pack32(entry->line_start, buffer); pack32(entry->line_end, buffer); } } extern int unpack_cron_entry(void **entry_ptr, uint16_t protocol_version, buf_t *buffer) { uint8_t set; uint32_t uint32_tmp; cron_entry_t *entry = NULL; xassert(entry_ptr); safe_unpack8(&set, buffer); if (!set) return SLURM_SUCCESS; entry = xmalloc(sizeof(*entry)); *entry_ptr = entry; if (protocol_version >= SLURM_20_11_PROTOCOL_VERSION) { safe_unpack32(&entry->flags, buffer); unpack_bit_str_hex(&entry->minute, buffer); unpack_bit_str_hex(&entry->hour, buffer); unpack_bit_str_hex(&entry->day_of_month, buffer); unpack_bit_str_hex(&entry->month, buffer); unpack_bit_str_hex(&entry->day_of_week, buffer); safe_unpackstr_xmalloc(&entry->cronspec, &uint32_tmp, buffer); /* command is not packed, only in struct for parsing */ safe_unpack32(&entry->line_start, buffer); safe_unpack32(&entry->line_end, buffer); } else { goto unpack_error; } return SLURM_SUCCESS; unpack_error: *entry_ptr = NULL; free_cron_entry(entry); return SLURM_ERROR; }