/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program 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 Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "zbxexpr.h" #include "zbxnum.h" #include "zbxtime.h" #define ZBX_SCHEDULER_FILTER_DAY 1 #define ZBX_SCHEDULER_FILTER_HOUR 2 #define ZBX_SCHEDULER_FILTER_MINUTE 3 #define ZBX_SCHEDULER_FILTER_SECOND 4 typedef struct { int start_day; /* day of week when period starts */ int end_day; /* day of week when period ends, included */ int start_time; /* number of seconds from the beginning of the day when period starts */ int end_time; /* number of seconds from the beginning of the day when period ends, not included */ } zbx_time_period_t; typedef struct zbx_scheduler_filter { int start; int end; int step; struct zbx_scheduler_filter *next; } zbx_scheduler_filter_t; typedef struct zbx_scheduler_interval { zbx_scheduler_filter_t *mdays; zbx_scheduler_filter_t *wdays; zbx_scheduler_filter_t *hours; zbx_scheduler_filter_t *minutes; zbx_scheduler_filter_t *seconds; int filter_level; struct zbx_scheduler_interval *next; } zbx_scheduler_interval_t; typedef struct zbx_flexible_interval { zbx_time_period_t period; int delay; struct zbx_flexible_interval *next; } zbx_flexible_interval_t; struct zbx_custom_interval { zbx_flexible_interval_t *flexible; zbx_scheduler_interval_t *scheduling; }; /****************************************************************************** * * * Purpose: checks if current time is within given period * * * * Parameters: period - [IN] preprocessed time period * * tm - [IN] broken-down time for comparison * * * * Return value: FAIL - out of period, SUCCEED - within the period * * * ******************************************************************************/ static int check_time_period(const zbx_time_period_t period, const struct tm *tm) { int day, time; day = 0 == tm->tm_wday ? 7 : tm->tm_wday; time = SEC_PER_HOUR * tm->tm_hour + SEC_PER_MIN * tm->tm_min + tm->tm_sec; return period.start_day <= day && day <= period.end_day && period.start_time <= time && time < period.end_time ? SUCCEED : FAIL; } /****************************************************************************** * * * Purpose: returns delay value that is currently applicable * * * * Parameters: default_delay - [IN] default delay value, can be overridden * * flex_intervals - [IN] preprocessed flexible intervals * * now - [IN] current time * * * * Return value: delay value - either default or minimum delay value * * out of all applicable intervals * * * ******************************************************************************/ static int get_current_delay(int default_delay, const zbx_flexible_interval_t *flex_intervals, time_t now) { int current_delay = -1; while (NULL != flex_intervals) { if ((-1 == current_delay || flex_intervals->delay < current_delay) && SUCCEED == check_time_period(flex_intervals->period, zbx_localtime_now(&now))) { current_delay = flex_intervals->delay; } flex_intervals = flex_intervals->next; } return -1 == current_delay ? default_delay : current_delay; } /****************************************************************************** * * * Purpose: returns time when next delay settings take effect * * * * Parameters: flex_intervals - [IN] preprocessed flexible intervals * * now - [IN] current time * * next_interval - [OUT] start of next delay interval * * * * Return value: SUCCEED - there is next interval * * FAIL - otherwise (in this case, next_interval is * * unaffected) * * * ******************************************************************************/ static int get_next_delay_interval(const zbx_flexible_interval_t *flex_intervals, time_t now, time_t *next_interval) { int day, time, next = 0, candidate; const struct tm *tm; if (NULL == flex_intervals) return FAIL; tm = zbx_localtime_now(&now); day = 0 == tm->tm_wday ? 7 : tm->tm_wday; time = SEC_PER_HOUR * tm->tm_hour + SEC_PER_MIN * tm->tm_min + tm->tm_sec; for (; NULL != flex_intervals; flex_intervals = flex_intervals->next) { const zbx_time_period_t *p = &flex_intervals->period; if (p->start_day <= day && day <= p->end_day && time < p->end_time) /* will be active today */ { if (time < p->start_time) /* hasn't been active today yet */ candidate = p->start_time; else /* currently active */ candidate = p->end_time; } else if (day < p->end_day) /* will be active this week */ { if (day < p->start_day) /* hasn't been active this week yet */ candidate = SEC_PER_DAY * (p->start_day - day) + p->start_time; else /* has been active this week and will be active at least once more by the end of it */ candidate = SEC_PER_DAY + p->start_time; /* therefore will be active tomorrow */ } else /* will be active next week */ candidate = SEC_PER_DAY * (p->start_day + 7 - day) + p->start_time; if (0 == next || next > candidate) next = candidate; } if (0 == next) return FAIL; *next_interval = now - time + next; return SUCCEED; } /******************************************************************************* * * * Purpose: parses time of day * * * * Parameters: time - [OUT] Number of seconds since the beginning of * * the day corresponding to a given time of * * the day. * * text - [IN] text to parse * * len - [IN] number of characters available for parsing * * parsed_len - [OUT] number of characters recognized as time * * * * Return value: SUCCEED - text was successfully parsed as time of day * * FAIL - otherwise (time and parsed_len remain untouched) * * * * Comments: !!! Don't forget to sync code with PHP !!! * * Supported formats are hh:mm, h:mm and 0h:mm; 0 <= hours <= 24; * * 0 <= minutes <= 59; if hours == 24 then minutes must be 0. * * * *******************************************************************************/ static int time_parse(int *time, const char *text, int len, int *parsed_len) { const int old_len = len; const char *ptr; int hours, minutes; for (ptr = text; 0 < len && 0 != isdigit(*ptr) && 2 >= ptr - text; len--, ptr++) ; if (SUCCEED != zbx_is_uint_n_range(text, ptr - text, &hours, sizeof(hours), 0, 24)) return FAIL; if (0 >= len-- || ':' != *ptr++) return FAIL; for (text = ptr; 0 < len && 0 != isdigit(*ptr) && 2 >= ptr - text; len--, ptr++) ; if (2 != ptr - text) return FAIL; if (SUCCEED != zbx_is_uint_n_range(text, 2, &minutes, sizeof(minutes), 0, 59)) return FAIL; if (24 == hours && 0 != minutes) return FAIL; *parsed_len = old_len - len; *time = SEC_PER_HOUR * hours + SEC_PER_MIN * minutes; return SUCCEED; } /****************************************************************************** * * * Parameters: period - [OUT] time period structure * * text - [IN] text to parse * * len - [IN] number of characters available for parsing * * * * Return value: SUCCEED - text was successfully parsed as time period * * FAIL - otherwise * * * * Comments: !!! Don't forget to sync code with PHP !!! * * Supported format is d[-d],time-time where 1 <= d <= 7 * * * ******************************************************************************/ static int time_period_parse(zbx_time_period_t *period, const char *text, int len) { int parsed_len; if (0 >= len-- || '1' > *text || '7' < *text) return FAIL; period->start_day = *text++ - '0'; if (0 >= len) return FAIL; if ('-' == *text) { text++; len--; if (0 >= len-- || '1' > *text || '7' < *text) return FAIL; period->end_day = *text++ - '0'; if (period->start_day > period->end_day) return FAIL; } else period->end_day = period->start_day; if (0 >= len-- || ',' != *text++) return FAIL; if (SUCCEED != time_parse(&period->start_time, text, len, &parsed_len)) return FAIL; text += parsed_len; len -= parsed_len; if (0 >= len-- || '-' != *text++) return FAIL; if (SUCCEED != time_parse(&period->end_time, text, len, &parsed_len)) return FAIL; if (period->start_time >= period->end_time) return FAIL; if (0 != (len - parsed_len)) return FAIL; return SUCCEED; } /****************************************************************************** * * * Purpose: parses text string into scheduler filter * * * * Parameters: filter - [IN/OUT] first filter * * text - [IN] text to parse * * len - [IN/OUT] number of characters left to parse * * min - [IN] minimal time unit value * * max - [IN] maximal time unit value * * var_len - [IN] maximum number of characters for a filter * * variable (<from>, <to>, <step>) * * * * Return value: SUCCEED - filter was successfully parsed * * FAIL - otherwise * * * * Comments: This function recursively calls itself for each filter fragment. * * * ******************************************************************************/ static int scheduler_parse_filter_r(zbx_scheduler_filter_t **filter, const char *text, int *len, int min, int max, int var_len) { int start = 0, end = 0, step = 1; const char *pstart, *pend; zbx_scheduler_filter_t *filter_new; pstart = pend = text; while (0 != isdigit(*pend) && 0 < *len) { pend++; (*len)--; } if (pend != pstart) { if (pend - pstart > var_len) return FAIL; if (SUCCEED != zbx_is_uint_n_range(pstart, pend - pstart, &start, sizeof(start), min, max)) return FAIL; if ('-' == *pend) { pstart = pend + 1; do { pend++; (*len)--; } while (0 != isdigit(*pend) && 0 < *len); /* empty or too long value, fail */ if (pend == pstart || pend - pstart > var_len) return FAIL; if (SUCCEED != zbx_is_uint_n_range(pstart, pend - pstart, &end, sizeof(end), min, max)) return FAIL; if (end < start) return FAIL; } else { /* step is valid only for defined range */ if ('/' == *pend) return FAIL; end = start; } } else { start = min; end = max; } if ('/' == *pend) { pstart = pend + 1; do { pend++; (*len)--; } while (0 != isdigit(*pend) && 0 < *len); /* empty or too long step, fail */ if (pend == pstart || pend - pstart > var_len) return FAIL; if (SUCCEED != zbx_is_uint_n_range(pstart, pend - pstart, &step, sizeof(step), 1, end - start)) return FAIL; } else { if (pend == text) return FAIL; } if (',' == *pend) { /* no next filter after ',' */ if (0 == --(*len)) return FAIL; pend++; if (SUCCEED != scheduler_parse_filter_r(filter, pend, len, min, max, var_len)) return FAIL; } filter_new = (zbx_scheduler_filter_t *)zbx_malloc(NULL, sizeof(zbx_scheduler_filter_t)); filter_new->start = start; filter_new->end = end; filter_new->step = step; filter_new->next = *filter; *filter = filter_new; return SUCCEED; } /****************************************************************************** * * * Purpose: parses text string into scheduler filter * * * * Parameters: filter - [IN/OUT] first filter * * text - [IN] text to parse * * len - [IN/OUT] number of characters left to parse * * min - [IN] minimal time unit value * * max - [IN] maximal time unit value * * var_len - [IN] maximum number of characters for filter * * variable (<from>, <to>, <step>) * * * * Return value: SUCCEED - filter was successfully parsed * * FAIL - otherwise * * * * Comments: This function will fail if a filter already exists. This * * user from defining multiple filters of the same time unit in a * * single interval. For example: h0h12 is invalid filter and its * * parsing must fail. * * * ******************************************************************************/ static int scheduler_parse_filter(zbx_scheduler_filter_t **filter, const char *text, int *len, int min, int max, int var_len) { if (NULL != *filter) return FAIL; return scheduler_parse_filter_r(filter, text, len, min, max, var_len); } /****************************************************************************** * * * Parameters: interval - [IN/OUT] first interval * * text - [IN] text to parse * * len - [IN] text length * * * * Return value: SUCCEED - interval was successfully parsed * * FAIL - otherwise * * * ******************************************************************************/ static int scheduler_interval_parse(zbx_scheduler_interval_t *interval, const char *text, int len) { int ret = SUCCEED; if (0 == len) return FAIL; while (SUCCEED == ret && 0 != len) { int old_len = len--; switch (*text) { case '\0': return FAIL; case 'h': if (ZBX_SCHEDULER_FILTER_HOUR < interval->filter_level) return FAIL; ret = scheduler_parse_filter(&interval->hours, text + 1, &len, 0, 23, 2); interval->filter_level = ZBX_SCHEDULER_FILTER_HOUR; break; case 's': if (ZBX_SCHEDULER_FILTER_SECOND < interval->filter_level) return FAIL; ret = scheduler_parse_filter(&interval->seconds, text + 1, &len, 0, 59, 2); interval->filter_level = ZBX_SCHEDULER_FILTER_SECOND; break; case 'w': if ('d' != text[1]) return FAIL; if (ZBX_SCHEDULER_FILTER_DAY < interval->filter_level) return FAIL; len--; ret = scheduler_parse_filter(&interval->wdays, text + 2, &len, 1, 7, 1); interval->filter_level = ZBX_SCHEDULER_FILTER_DAY; break; case 'm': if ('d' == text[1]) { if (ZBX_SCHEDULER_FILTER_DAY < interval->filter_level || NULL != interval->wdays) { return FAIL; } len--; ret = scheduler_parse_filter(&interval->mdays, text + 2, &len, 1, 31, 2); interval->filter_level = ZBX_SCHEDULER_FILTER_DAY; } else { if (ZBX_SCHEDULER_FILTER_MINUTE < interval->filter_level) return FAIL; ret = scheduler_parse_filter(&interval->minutes, text + 1, &len, 0, 59, 2); interval->filter_level = ZBX_SCHEDULER_FILTER_MINUTE; } break; default: return FAIL; } text += old_len - len; } return ret; } /****************************************************************************** * * * Purpose: frees scheduler interval filter * * * * Parameters: filter - [IN] scheduler interval filter * * * ******************************************************************************/ static void scheduler_filter_free(zbx_scheduler_filter_t *filter) { zbx_scheduler_filter_t *filter_next; for (; NULL != filter; filter = filter_next) { filter_next = filter->next; zbx_free(filter); } } /****************************************************************************** * * * Parameters: interval - [IN] scheduler interval * * * ******************************************************************************/ static void scheduler_interval_free(zbx_scheduler_interval_t *interval) { zbx_scheduler_interval_t *interval_next; for (; NULL != interval; interval = interval_next) { interval_next = interval->next; scheduler_filter_free(interval->mdays); scheduler_filter_free(interval->wdays); scheduler_filter_free(interval->hours); scheduler_filter_free(interval->minutes); scheduler_filter_free(interval->seconds); zbx_free(interval); } } /****************************************************************************** * * * Parameters: interval - [IN/OUT] first interval * * text - [IN] text to parse * * len - [IN] text length * * * * Return value: SUCCEED - interval was successfully parsed * * FAIL - otherwise * * * * Comments: !!! Don't forget to sync code with PHP !!! * * Supported format is delay/period * * * ******************************************************************************/ static int flexible_interval_parse(zbx_flexible_interval_t *interval, const char *text, int len) { const char *ptr; for (ptr = text; 0 < len && '\0' != *ptr && '/' != *ptr; len--, ptr++) ; if (SUCCEED != zbx_is_time_suffix(text, &interval->delay, (int)(ptr - text))) return FAIL; if (0 >= len-- || '/' != *ptr++) return FAIL; return time_period_parse(&interval->period, ptr, len); } /****************************************************************************** * * * Parameters: interval - [IN] flexible interval * * * ******************************************************************************/ static void flexible_interval_free(zbx_flexible_interval_t *interval) { zbx_flexible_interval_t *interval_next; for (; NULL != interval; interval = interval_next) { interval_next = interval->next; zbx_free(interval); } } /****************************************************************************** * * * Purpose: parses item and low-level discovery rule update intervals * * * * Parameters: interval_str - [IN] update interval string to parse * * simple_interval - [OUT] simple update interval * * custom_intervals - [OUT] flexible and scheduling intervals * * error - [OUT] error message * * * * Return value: SUCCEED - intervals are valid * * FAIL - otherwise * * * * Comments: !!! Don't forget to sync code with PHP !!! * * Supported format: * * SimpleInterval, {";", FlexibleInterval | SchedulingInterval}; * * * ******************************************************************************/ int zbx_interval_preproc(const char *interval_str, int *simple_interval, zbx_custom_interval_t **custom_intervals, char **error) { zbx_flexible_interval_t *flexible = NULL; zbx_scheduler_interval_t *scheduling = NULL; const char *delim, *interval_type; if (SUCCEED != zbx_is_time_suffix(interval_str, simple_interval, (int)(NULL == (delim = strchr(interval_str, ';')) ? ZBX_LENGTH_UNLIMITED : delim - interval_str))) { interval_type = "update"; goto fail; } if (NULL == custom_intervals) /* caller wasn't interested in custom intervals, don't parse them */ return SUCCEED; while (NULL != delim) { interval_str = delim + 1; delim = strchr(interval_str, ';'); if (0 != isdigit(*interval_str)) { zbx_flexible_interval_t *new_interval; new_interval = (zbx_flexible_interval_t *)zbx_malloc(NULL, sizeof(zbx_flexible_interval_t)); if (SUCCEED != flexible_interval_parse(new_interval, interval_str, (NULL == delim ? (int)strlen(interval_str) : (int)(delim - interval_str))) || (0 == *simple_interval && 0 == new_interval->delay)) { zbx_free(new_interval); interval_type = "flexible"; goto fail; } new_interval->next = flexible; flexible = new_interval; } else { zbx_scheduler_interval_t *new_interval; new_interval = (zbx_scheduler_interval_t *)zbx_malloc(NULL, sizeof(zbx_scheduler_interval_t)); memset(new_interval, 0, sizeof(zbx_scheduler_interval_t)); if (SUCCEED != scheduler_interval_parse(new_interval, interval_str, (NULL == delim ? (int)strlen(interval_str) : (int)(delim - interval_str)))) { scheduler_interval_free(new_interval); interval_type = "scheduling"; goto fail; } new_interval->next = scheduling; scheduling = new_interval; } } if ((NULL == flexible && NULL == scheduling && 0 == *simple_interval) || SEC_PER_DAY < *simple_interval) { interval_type = "update"; goto fail; } *custom_intervals = (zbx_custom_interval_t *)zbx_malloc(NULL, sizeof(zbx_custom_interval_t)); (*custom_intervals)->flexible = flexible; (*custom_intervals)->scheduling = scheduling; return SUCCEED; fail: if (NULL != error) { *error = zbx_dsprintf(*error, "Invalid %s interval \"%.*s\".", interval_type, (NULL == delim ? (int)strlen(interval_str) : (int)(delim - interval_str)), interval_str); } flexible_interval_free(flexible); scheduler_interval_free(scheduling); return FAIL; } /****************************************************************************** * * * Purpose: parses user macro and finds it's length * * * * Parameters: str - [IN] string to check * * len - [OUT] length simple interval string until separator * * sep - [IN] separator to calculate length * * value - [OUT] interval value * * * * Return Value: * * SUCCEED - macro was parsed successfully * * FAIL - Macro parsing failed, the content of output variables is not * * defined. * * * ******************************************************************************/ static int parse_simple_interval(const char *str, int *len, char sep, int *value) { const char *delim; if (SUCCEED != zbx_is_time_suffix(str, value, (int)(NULL == (delim = strchr(str, sep)) ? ZBX_LENGTH_UNLIMITED : delim - str))) { return FAIL; } *len = NULL == delim ? (int)strlen(str) : delim - str; return SUCCEED; } /****************************************************************************** * * * Purpose: parses user macro and finds it's length * * * * Parameters: str - [IN] string to check * * len - [OUT] length of macro * * * * Return Value: * * SUCCEED - macro was parsed successfully * * FAIL - Macro parsing failed, the content of output variables * * is not defined. * * * ******************************************************************************/ static int parse_user_macro(const char *str, int *len) { int macro_r, context_l, context_r; if ('{' != *str || '$' != *(str + 1) || SUCCEED != zbx_user_macro_parse(str, ¯o_r, &context_l, &context_r, NULL)) { return FAIL; } *len = macro_r + 1; return SUCCEED; } /****************************************************************************** * * * Purpose: validates update interval, flexible and scheduling intervals * * * * Parameters: str - [IN] string to check * * error - [OUT] validation error * * * * Return Value: * * SUCCEED - parsed successfully * * FAIL - parsing failed * * * ******************************************************************************/ int zbx_validate_interval(const char *str, char **error) { int simple_interval, interval, len, custom = 0, macro; const char *delim; if (SUCCEED == parse_user_macro(str, &len) && ('\0' == *(delim = str + len) || ';' == *delim)) { if ('\0' == *delim) delim = NULL; simple_interval = 1; } else if (SUCCEED == parse_simple_interval(str, &len, ';', &simple_interval)) { if ('\0' == *(delim = str + len)) delim = NULL; } else { *error = zbx_dsprintf(*error, "Invalid update interval \"%.*s\".", NULL == (delim = strchr(str, ';')) ? (int)strlen(str) : (int)(delim - str), str); return FAIL; } while (NULL != delim) { str = delim + 1; if ((SUCCEED == (macro = parse_user_macro(str, &len)) || SUCCEED == parse_simple_interval(str, &len, '/', &interval)) && '/' == *(delim = str + len)) { zbx_time_period_t period; custom = 1; if (SUCCEED == macro) interval = 1; if (0 == interval && 0 == simple_interval) { *error = zbx_dsprintf(*error, "Invalid flexible interval \"%.*s\".", (int)(delim - str), str); return FAIL; } str = delim + 1; if (SUCCEED == parse_user_macro(str, &len) && ('\0' == *(delim = str + len) || ';' == *delim)) { if ('\0' == *delim) delim = NULL; continue; } if (SUCCEED == time_period_parse(&period, str, NULL == (delim = strchr(str, ';')) ? (int)strlen(str) : (int)(delim - str))) { continue; } *error = zbx_dsprintf(*error, "Invalid flexible period \"%.*s\".", NULL == delim ? (int)strlen(str) : (int)(delim - str), str); return FAIL; } else { zbx_scheduler_interval_t *new_interval; custom = 1; if (SUCCEED == macro && ('\0' == *(delim = str + len) || ';' == *delim)) { if ('\0' == *delim) delim = NULL; continue; } new_interval = (zbx_scheduler_interval_t *)zbx_malloc(NULL, sizeof(zbx_scheduler_interval_t)); memset(new_interval, 0, sizeof(zbx_scheduler_interval_t)); if (SUCCEED == scheduler_interval_parse(new_interval, str, NULL == (delim = strchr(str, ';')) ? (int)strlen(str) : (int)(delim - str))) { scheduler_interval_free(new_interval); continue; } scheduler_interval_free(new_interval); *error = zbx_dsprintf(*error, "Invalid custom interval \"%.*s\".", NULL == delim ? (int)strlen(str) : (int)(delim - str), str); return FAIL; } } if ((0 == custom && 0 == simple_interval) || SEC_PER_DAY < simple_interval) { *error = zbx_dsprintf(*error, "Invalid update interval \"%d\"", simple_interval); return FAIL; } return SUCCEED; } /****************************************************************************** * * * Purpose: checks if custom interval contains scheduling interval * * * * Parameters: custom_intervals - [IN] * * * * Return value: SUCCEED - if custom interval contains scheduling interval * * FAIL - otherwise * * * ******************************************************************************/ int zbx_custom_interval_is_scheduling(const zbx_custom_interval_t *custom_intervals) { return NULL == custom_intervals->scheduling ? FAIL : SUCCEED; } /****************************************************************************** * * * Purpose: frees custom update intervals * * * * Parameters: custom_intervals - [IN] * * * ******************************************************************************/ void zbx_custom_interval_free(zbx_custom_interval_t *custom_intervals) { flexible_interval_free(custom_intervals->flexible); scheduler_interval_free(custom_intervals->scheduling); zbx_free(custom_intervals); } /****************************************************************************** * * * Purpose: increments struct tm value by one second * * * * Parameters: tm - [IN/OUT] tm structure to increment * * * ******************************************************************************/ static void scheduler_tm_inc(struct tm *tm) { if (60 > ++tm->tm_sec) return; tm->tm_sec = 0; if (60 > ++tm->tm_min) return; tm->tm_min = 0; if (24 > ++tm->tm_hour) return; tm->tm_hour = 0; if (zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1) >= ++tm->tm_mday) return; tm->tm_mday = 1; if (12 > ++tm->tm_mon) return; tm->tm_mon = 0; tm->tm_year++; return; } /****************************************************************************** * * * Purpose: finds daylight saving change time inside specified time period * * * * Parameters: time_start - [IN] time period start * * time_end - [IN] time period end * * * * Return Value: Time when the daylight saving changes should occur. * * * * Comments: The calculated time is cached and reused if it first the * * specified period. * * * ******************************************************************************/ static time_t scheduler_find_dst_change(time_t time_start, time_t time_end) { static time_t time_dst = 0; struct tm *tm; time_t time_mid; int start, end, mid, dst_start; if (time_dst < time_start || time_dst > time_end) { /* assume that daylight saving will change only on 0 seconds */ start = time_start / 60; end = time_end / 60; tm = localtime(&time_start); dst_start = tm->tm_isdst; while (end > start + 1) { mid = (start + end) / 2; time_mid = mid * 60; tm = localtime(&time_mid); if (tm->tm_isdst == dst_start) start = mid; else end = mid; } time_dst = end * 60; } return time_dst; } /****************************************************************************** * * * Parameters: year - [IN] year (>1752) * * mon - [IN] month (1-12) * * mday - [IN] month day (1-31) * * * * Return value: The day of week: 1 - Monday, 2 - Tuesday, ... * * * ******************************************************************************/ static int calculate_dayofweek(int year, int mon, int mday) { static int mon_table[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; if (mon < 3) year--; return (year + year / 4 - year / 100 + year / 400 + mon_table[mon - 1] + mday - 1) % 7 + 1; } /****************************************************************************** * * * Purpose: checks if specified date satisfies week day filter * * * * Parameters: interval - [IN] scheduler interval * * tm - [IN] date & time to validate * * * * Return value: SUCCEED - input date satisfies week day filter * * FAIL - otherwise * * * ******************************************************************************/ static int scheduler_validate_wday_filter(const zbx_scheduler_interval_t *interval, struct tm *tm) { const zbx_scheduler_filter_t *filter; int value; if (NULL == interval->wdays) return SUCCEED; value = calculate_dayofweek(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); /* check if the value match week day filter */ for (filter = interval->wdays; NULL != filter; filter = filter->next) { if (filter->start <= value && value <= filter->end) { int next = value, offset; /* apply step */ offset = (next - filter->start) % filter->step; if (0 != offset) next += filter->step - offset; /* succeed if the calculated value is still in filter range */ if (next <= filter->end) return SUCCEED; } } return FAIL; } /****************************************************************************** * * * Purpose: gets next nearest value that satisfies filter chain * * * * Parameters: filter - [IN] filter chain * * value - [IN] current value * * [OUT] next nearest value (>= than input value) * * * * Return value: SUCCEED - next nearest value was successfully found * * FAIL - otherwise * * * ******************************************************************************/ static int scheduler_get_nearest_filter_value(const zbx_scheduler_filter_t *filter, int *value) { const zbx_scheduler_filter_t *filter_next = NULL; for (; NULL != filter; filter = filter->next) { /* find matching filter */ if (filter->start <= *value && *value <= filter->end) { int next = *value, offset; /* apply step */ offset = (next - filter->start) % filter->step; if (0 != offset) next += filter->step - offset; /* succeed if the calculated value is still in filter range */ if (next <= filter->end) { *value = next; return SUCCEED; } } /* find the next nearest filter */ if (filter->start > *value && (NULL == filter_next || filter_next->start > filter->start)) filter_next = filter; } /* The value is not in a range of any filters, but we have next nearest filter. */ if (NULL != filter_next) { *value = filter_next->start; return SUCCEED; } return FAIL; } /****************************************************************************** * * * Purpose: calculates next day that satisfies week day filter * * * * Parameters: interval - [IN] scheduler interval * * tm - [IN/OUT] input/output date & time * * * * Return value: SUCCEED - next day was found * * FAIL - next day satisfying week day filter was not * * found in current month * * * ******************************************************************************/ static int scheduler_get_wday_nextcheck(const zbx_scheduler_interval_t *interval, struct tm *tm) { int value_now, value_next; if (NULL == interval->wdays) return SUCCEED; value_now = value_next = calculate_dayofweek(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); /* get the nearest week day from the current week day*/ if (SUCCEED != scheduler_get_nearest_filter_value(interval->wdays, &value_next)) { /* in the case of failure move month day to the next week, reset week day and try again */ tm->tm_mday += 7 - value_now + 1; value_now = value_next = 1; if (SUCCEED != scheduler_get_nearest_filter_value(interval->wdays, &value_next)) { /* a valid week day filter must always match some day of a new week */ THIS_SHOULD_NEVER_HAPPEN; return FAIL; } } /* adjust the month day by the week day offset */ tm->tm_mday += value_next - value_now; /* check if the resulting month day is valid */ return (tm->tm_mday <= zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1) ? SUCCEED : FAIL); } /****************************************************************************** * * * Purpose: calculates next day that satisfies month and week day filters * * * * Parameters: interval - [IN] scheduler interval * * tm - [IN/OUT] input/output date & time * * * * Return value: SUCCEED - next day was found * * FAIL - next day satisfying day filters was not * * found in the current month * * * ******************************************************************************/ static int scheduler_get_day_nextcheck(const zbx_scheduler_interval_t *interval, struct tm *tm) { int tmp; /* first check if the provided tm structure has valid date format */ if (FAIL == zbx_utc_time(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, &tmp)) { return FAIL; } if (NULL == interval->mdays) return scheduler_get_wday_nextcheck(interval, tm); /* iterate through month days until week day filter matches or we have run out of month days */ while (SUCCEED == scheduler_get_nearest_filter_value(interval->mdays, &tm->tm_mday)) { /* check if the date is still valid - we haven't run out of month days */ if (tm->tm_mday > zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1)) break; if (SUCCEED == scheduler_validate_wday_filter(interval, tm)) return SUCCEED; tm->tm_mday++; /* check if the date is still valid - we haven't run out of month days */ if (tm->tm_mday > zbx_day_in_month(tm->tm_year + 1900, tm->tm_mon + 1)) break; } return FAIL; } /****************************************************************************** * * * Purpose: calculates time/day that satisfies specified filter * * * * Parameters: interval - [IN] scheduler interval * * level - [IN] filter level, see ZBX_SCHEDULER_FILTER_* * * defines * * tm - [IN/OUT] input/output date & time * * * * Return value: SUCCEED - next time/day was found * * FAIL - next time/day was not found on current filter * * level * * * ******************************************************************************/ static int scheduler_get_filter_nextcheck(const zbx_scheduler_interval_t *interval, int level, struct tm *tm) { const zbx_scheduler_filter_t *filter; int max, *value; /* initialize data depending on filter level */ switch (level) { case ZBX_SCHEDULER_FILTER_DAY: return scheduler_get_day_nextcheck(interval, tm); case ZBX_SCHEDULER_FILTER_HOUR: max = 23; filter = interval->hours; value = &tm->tm_hour; break; case ZBX_SCHEDULER_FILTER_MINUTE: max = 59; filter = interval->minutes; value = &tm->tm_min; break; case ZBX_SCHEDULER_FILTER_SECOND: max = 59; filter = interval->seconds; value = &tm->tm_sec; break; default: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } if (max < *value) return FAIL; /* handle unspecified (default) filter */ if (NULL == filter) { /* Empty filter matches all valid values if the filter level is less than */ /* interval filter level. For example if interval filter level is minutes - m30, */ /* then hour filter matches all hours. */ if (interval->filter_level > level) return SUCCEED; /* If the filter level is greater than interval filter level, then filter */ /* matches only 0 value. For example if interval filter level is minutes - m30, */ /* then seconds filter matches the 0th second. */ return 0 == *value ? SUCCEED : FAIL; } return scheduler_get_nearest_filter_value(filter, value); } /****************************************************************************** * * * Purpose: Applies the day filter to the specified time/day calculating the * * next scheduled check. * * * * Parameters: interval - [IN] scheduler interval * * tm - [IN/OUT] input/output date & time * * * ******************************************************************************/ static void scheduler_apply_day_filter(zbx_scheduler_interval_t *interval, struct tm *tm) { int day = tm->tm_mday, mon = tm->tm_mon, year = tm->tm_year; while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_DAY, tm)) { if (11 < ++tm->tm_mon) { tm->tm_mon = 0; tm->tm_year++; } tm->tm_mday = 1; } /* reset hours, minutes and seconds if the day has been changed */ if (tm->tm_mday != day || tm->tm_mon != mon || tm->tm_year != year) { tm->tm_hour = 0; tm->tm_min = 0; tm->tm_sec = 0; } } /****************************************************************************** * * * Purpose: Applies the hour filter to the specified time/day calculating the * * next scheduled check * * * * Parameters: interval - [IN] scheduler interval * * tm - [IN/OUT] input/output date & time * * * ******************************************************************************/ static void scheduler_apply_hour_filter(zbx_scheduler_interval_t *interval, struct tm *tm) { int hour = tm->tm_hour; while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_HOUR, tm)) { tm->tm_mday++; tm->tm_hour = 0; /* day has been changed, we have to reapply day filter */ scheduler_apply_day_filter(interval, tm); } /* reset minutes and seconds if hours has been changed */ if (tm->tm_hour != hour) { tm->tm_min = 0; tm->tm_sec = 0; } } /****************************************************************************** * * * Purpose: Applies the minute filter to the specified time/day calculating * * the next scheduled check. * * * * Parameters: interval - [IN] scheduler interval * * tm - [IN/OUT] input/output date & time * * * ******************************************************************************/ static void scheduler_apply_minute_filter(zbx_scheduler_interval_t *interval, struct tm *tm) { int min = tm->tm_min; while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_MINUTE, tm)) { tm->tm_hour++; tm->tm_min = 0; /* hours have been changed, we have to reapply hour filter */ scheduler_apply_hour_filter(interval, tm); } /* reset seconds if minutes has been changed */ if (tm->tm_min != min) tm->tm_sec = 0; } /****************************************************************************** * * * Purpose: Applies the second filter to the specified time/day calculating * * the next scheduled check * * * * Parameters: interval - [IN] scheduler interval * * tm - [IN/OUT] input/output date & time * * * ******************************************************************************/ static void scheduler_apply_second_filter(zbx_scheduler_interval_t *interval, struct tm *tm) { while (SUCCEED != scheduler_get_filter_nextcheck(interval, ZBX_SCHEDULER_FILTER_SECOND, tm)) { tm->tm_min++; tm->tm_sec = 0; /* minutes have been changed, we have to reapply minute filter */ scheduler_apply_minute_filter(interval, tm); } } /****************************************************************************** * * * Purpose: finds next timestamp satisfying one of intervals * * * * Parameters: interval - [IN] scheduler interval * * now - [IN] current timestamp * * * * Return Value: Timestamp when the next check must be scheduled. * * * ******************************************************************************/ static time_t scheduler_get_nextcheck(zbx_scheduler_interval_t *interval, time_t now) { struct tm tm_start, tm, tm_dst; time_t nextcheck = 0, current_nextcheck; tm_start = *zbx_localtime_now(&now); for (; NULL != interval; interval = interval->next) { tm = tm_start; do { scheduler_tm_inc(&tm); scheduler_apply_day_filter(interval, &tm); scheduler_apply_hour_filter(interval, &tm); scheduler_apply_minute_filter(interval, &tm); scheduler_apply_second_filter(interval, &tm); tm.tm_isdst = tm_start.tm_isdst; } while (-1 == (current_nextcheck = mktime(&tm))); tm_dst = *(localtime(¤t_nextcheck)); if (tm_dst.tm_isdst != tm_start.tm_isdst) { int dst = tm_dst.tm_isdst; time_t time_dst; time_dst = scheduler_find_dst_change(now, current_nextcheck); tm_dst = *localtime(&time_dst); scheduler_apply_day_filter(interval, &tm_dst); scheduler_apply_hour_filter(interval, &tm_dst); scheduler_apply_minute_filter(interval, &tm_dst); scheduler_apply_second_filter(interval, &tm_dst); tm_dst.tm_isdst = dst; current_nextcheck = mktime(&tm_dst); } if (0 == nextcheck || current_nextcheck < nextcheck) nextcheck = current_nextcheck; } return nextcheck; } /****************************************************************************** * * * Purpose: calculates nextcheck timestamp for item * * * * Parameters: seed - [IN] seed value applied to delay to spread * * item checks over delay period * * item_type - [IN] * * simple_interval - [IN] default delay value, can be overridden * * custom_intervals - [IN] preprocessed custom intervals * * now - [IN] current timestamp * * * * Return value: nextcheck value * * * * Comments: If item check is forbidden with delay=0 (default and flexible), * * a timestamp very far in the future is returned. * * * * Old algorithm: now+delay * * New one: preserve period, if delay==5, nextcheck = 0,5,10,15,... * * * ******************************************************************************/ int zbx_calculate_item_nextcheck(zbx_uint64_t seed, int item_type, int simple_interval, const zbx_custom_interval_t *custom_intervals, time_t now) { int nextcheck = 0; /* special processing of active items to see better view in queue */ if (ITEM_TYPE_ZABBIX_ACTIVE == item_type) { if (0 != simple_interval) nextcheck = (int)now + simple_interval; else nextcheck = ZBX_JAN_2038; } else { int current_delay, attempt = 0; time_t next_interval, t, tmax, scheduled_check = 0; /* first try to parse out and calculate scheduled intervals */ if (NULL != custom_intervals) scheduled_check = scheduler_get_nextcheck(custom_intervals->scheduling, now); /* Try to find the nearest 'nextcheck' value with condition */ /* 'now' < 'nextcheck' < 'now' + SEC_PER_YEAR. If it is not */ /* possible to check the item within a year, fail. */ t = now; tmax = now + SEC_PER_YEAR; while (t < tmax) { /* calculate 'nextcheck' value for the current interval */ if (NULL != custom_intervals) current_delay = get_current_delay(simple_interval, custom_intervals->flexible, t); else current_delay = simple_interval; if (0 != current_delay) { nextcheck = current_delay * (int)(t / (time_t)current_delay) + (int)(seed % (zbx_uint64_t)current_delay); if (0 == attempt) { while (nextcheck <= t) nextcheck += current_delay; } else { while (nextcheck < t) nextcheck += current_delay; } } else nextcheck = ZBX_JAN_2038; if (NULL == custom_intervals) break; /* 'nextcheck' < end of the current interval ? */ /* the end of the current interval is the beginning of the next interval - 1 */ if (FAIL != get_next_delay_interval(custom_intervals->flexible, t, &next_interval) && nextcheck >= next_interval) { /* 'nextcheck' is beyond the current interval */ t = next_interval; attempt++; } else break; /* nextcheck is within the current interval */ } if (0 != scheduled_check && scheduled_check < nextcheck) nextcheck = (int)scheduled_check; } return nextcheck; } /****************************************************************************** * * * Purpose: calculates nextcheck timestamp for item on unreachable host * * * * Parameters: simple_interval - [IN] default delay value, can be overridden * * custom_intervals - [IN] preprocessed custom intervals * * disable_until - [IN] timestamp for next check * * * * Return value: nextcheck value * * * ******************************************************************************/ int zbx_calculate_item_nextcheck_unreachable(int simple_interval, const zbx_custom_interval_t *custom_intervals, time_t disable_until) { int nextcheck = 0; time_t next_interval, tmax, scheduled_check = 0; /* first try to parse out and calculate scheduled intervals */ if (NULL != custom_intervals) scheduled_check = scheduler_get_nextcheck(custom_intervals->scheduling, disable_until); /* Try to find the nearest 'nextcheck' value with condition */ /* 'now' < 'nextcheck' < 'now' + SEC_PER_YEAR. If it is not */ /* possible to check the item within a year, fail. */ nextcheck = disable_until; tmax = disable_until + SEC_PER_YEAR; if (NULL != custom_intervals) { while (nextcheck < tmax) { if (0 != get_current_delay(simple_interval, custom_intervals->flexible, nextcheck)) break; /* find the flexible interval change */ if (FAIL == get_next_delay_interval(custom_intervals->flexible, nextcheck, &next_interval)) { nextcheck = ZBX_JAN_2038; break; } nextcheck = next_interval; } } if (0 != scheduled_check && scheduled_check < nextcheck) nextcheck = (int)scheduled_check; return nextcheck; } /****************************************************************************** * * * Purpose: validates time period and check if specified time is within it * * * * Parameters: period - [IN] semicolon-separated list of time periods in one * * of the following formats: * * d1-d2,h1:m1-h2:m2 * * or d1,h1:m1-h2:m2 * * time - [IN] time to check * * tz - [IN] * * res - [OUT] check result: * * SUCCEED - if time is within period * * FAIL - otherwise * * * * Return value: validation result (SUCCEED - valid, FAIL - invalid) * * * * Comments: !!! Don't forget to sync code with PHP !!! * * * ******************************************************************************/ int zbx_check_time_period(const char *period, time_t time, const char *tz, int *res) { int res_total = FAIL; const char *next; struct tm *tm; zbx_time_period_t tp; tm = zbx_localtime(&time, tz); next = strchr(period, ';'); while (SUCCEED == time_period_parse(&tp, period, (NULL == next ? (int)strlen(period) : (int)(next - period)))) { if (SUCCEED == check_time_period(tp, tm)) res_total = SUCCEED; /* no short-circuits, validate all periods before return */ if (NULL == next) { *res = res_total; return SUCCEED; } period = next + 1; next = strchr(period, ';'); } return FAIL; } /****************************************************************************** * * * Purpose: calculates item nextcheck for Zabbix agent type items * * * ******************************************************************************/ int zbx_get_agent_item_nextcheck(zbx_uint64_t itemid, const char *delay, int now, int *nextcheck, int *scheduling, char **error) { int simple_interval; zbx_custom_interval_t *custom_intervals; if (SUCCEED != zbx_interval_preproc(delay, &simple_interval, &custom_intervals, error)) { *nextcheck = ZBX_JAN_2038; return FAIL; } if (NULL != custom_intervals->scheduling) *scheduling = SUCCEED; else *scheduling = FAIL; *nextcheck = zbx_calculate_item_nextcheck(itemid, ITEM_TYPE_ZABBIX, simple_interval, custom_intervals, now); zbx_custom_interval_free(custom_intervals); return SUCCEED; } /****************************************************************************** * * * Purpose: calculates report nextcheck * * * * Parameters: now - [IN] current timestamp * * cycle - [IN] report cycle * * weekdays - [IN] week days report should be prepared, * * bitmask (0x01 - Monday, 0x02 - Tuesday...) * * start_time - [IN] report start time in seconds after * * midnight * * * * Return value: The timestamp when the report must be prepared or -1 if an * * error occurred. * * * ******************************************************************************/ int zbx_get_report_nextcheck(int now, unsigned char cycle, unsigned char weekdays, int start_time) { struct tm *tm; time_t yesterday = now - SEC_PER_DAY; int nextcheck, tm_hour, tm_min, tm_sec; if (NULL == (tm = localtime(&yesterday))) return -1; tm_sec = start_time % 60; start_time /= 60; tm_min = start_time % 60; start_time /= 60; tm_hour = start_time; do { /* handle midnight startup times */ if (0 == tm->tm_sec && 0 == tm->tm_min && 0 == tm->tm_hour) zbx_tm_add(tm, 1, ZBX_TIME_UNIT_DAY); switch (cycle) { case ZBX_REPORT_CYCLE_YEARLY: zbx_tm_round_up(tm, ZBX_TIME_UNIT_YEAR); break; case ZBX_REPORT_CYCLE_MONTHLY: zbx_tm_round_up(tm, ZBX_TIME_UNIT_MONTH); break; case ZBX_REPORT_CYCLE_WEEKLY: if (0 == weekdays) return -1; zbx_tm_round_up(tm, ZBX_TIME_UNIT_DAY); while (0 == (weekdays & (1 << (tm->tm_wday + 6) % 7))) zbx_tm_add(tm, 1, ZBX_TIME_UNIT_DAY); break; case ZBX_REPORT_CYCLE_DAILY: zbx_tm_round_up(tm, ZBX_TIME_UNIT_DAY); break; } tm->tm_sec = tm_sec; tm->tm_min = tm_min; tm->tm_hour = tm_hour; nextcheck = (int)mktime(tm); } while (-1 != nextcheck && nextcheck <= now); return nextcheck; }