diff --git a/CHANGES b/CHANGES index 4d55a2a49bafa3e9f0ec602c891060b71e5c875e..c2fd95a339a28702c4910c64c0a26d010fb43900 100644 --- a/CHANGES +++ b/CHANGES @@ -90,8 +90,16 @@ Miscellaneous which are interpreted as relative to the astvarlibdir setting in asterisk.conf. * All deprecated CLI commands are removed from the sourcecode. They are now handled by the new clialiases module. See cli_aliases.conf.sample file. + * Times within timespecs are now accurate down to the minute. This is a change + from historical Asterisk, which only provided timespecs rounded to the nearest + even (read: evenly divisible by 2) minute mark. * The realtime switch now supports an option flag, 'p', which disables searches for pattern matches. + * In addition to a time range and date range, timespecs now accept a 5th optional + argument, timezone. This allows you to perform time checks on alternate + timezones, especially if those daylight savings time ranges vary from your + machine's native timezone. See GotoIfTime, ExecIfTime, IFTIME(), and timed + includes. Asterisk Manager Interface -------------------------- diff --git a/configs/extensions.conf.sample b/configs/extensions.conf.sample index 0b82f3b3aa18543328bfcb35ec67e03252ef6238..167ae372cf0a5050974bf876287619ff494f759f 100644 --- a/configs/extensions.conf.sample +++ b/configs/extensions.conf.sample @@ -185,7 +185,7 @@ TRUNKMSD=1 ; MSD digits to strip (usually 1 or 0) ; ; Timing list for includes is ; -; <time range>,<days of week>,<days of month>,<months> +; <time range>,<days of week>,<days of month>,<months>[,<timezone>] ; ; Note that ranges may be specified to wrap around the ends. Also, minutes are ; fine-grained only down to the closest even minute. diff --git a/doc/api-1.6.2-changes.txt b/doc/api-1.6.2-changes.txt new file mode 100644 index 0000000000000000000000000000000000000000..34a3e5fc0ac1900ad3f383c6441b005cf8a486f0 --- /dev/null +++ b/doc/api-1.6.2-changes.txt @@ -0,0 +1,6 @@ +PBX changes +----------- + * If you use ast_build_timing() in your application, you should start calling + ast_destroy_timing() upon destruction of the structure, to avoid a memory + leak. + diff --git a/funcs/func_logic.c b/funcs/func_logic.c index ef644f43f68536c11d6e350c5ed6583019c9283e..e583d5439c5a2a3ab4c98bece162c4bebaf055c7 100644 --- a/funcs/func_logic.c +++ b/funcs/func_logic.c @@ -148,6 +148,7 @@ static int iftime(struct ast_channel *chan, const char *cmd, char *data, char *b if (!ast_build_timing(&timing, expr)) { ast_log(LOG_WARNING, "Invalid Time Spec.\n"); + ast_destroy_timing(&timing); return -1; } @@ -157,6 +158,7 @@ static int iftime(struct ast_channel *chan, const char *cmd, char *data, char *b iffalse = ast_strip_quoted(iffalse, "\"", "\""); ast_copy_string(buf, ast_check_timing(&timing) ? iftrue : iffalse, len); + ast_destroy_timing(&timing); return 0; } diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h index 7847bc59887f2b7aa11da3e9f4833f988932318d..a2ab925925c8921f5a534fad014482174a13a184 100644 --- a/include/asterisk/pbx.h +++ b/include/asterisk/pbx.h @@ -119,11 +119,28 @@ struct ast_timing { unsigned int daymask; /*!< Mask for date */ unsigned int dowmask; /*!< Mask for day of week (sun-sat) */ unsigned int minmask[48]; /*!< Mask for minute */ + char *timezone; /*!< NULL, or zoneinfo style timezone */ }; +/*!\brief Construct a timing bitmap, for use in time-based conditionals. + * \param i Pointer to an ast_timing structure. + * \param info Standard string containing a timerange, weekday range, monthday range, and month range, as well as an optional timezone. + * \retval Returns 1 on success or 0 on failure. + */ int ast_build_timing(struct ast_timing *i, const char *info); + +/*!\brief Evaluate a pre-constructed bitmap as to whether the current time falls within the range specified. + * \param i Pointer to an ast_timing structure. + * \retval Returns 1, if the time matches or 0, if the current time falls outside of the specified range. + */ int ast_check_timing(const struct ast_timing *i); +/*!\brief Deallocates memory structures associated with a timing bitmap. + * \param i Pointer to an ast_timing structure. + * \retval Returns 0 on success or a number suitable for passing into strerror, otherwise. + */ +int ast_destroy_timing(struct ast_timing *i); + struct ast_pbx { int dtimeoutms; /*!< Timeout between digits (milliseconds) */ int rtimeoutms; /*!< Timeout for response (milliseconds) */ diff --git a/main/pbx.c b/main/pbx.c index 840455c447c3c1b46ea999813d71a76250e9f748..273418366c085bfacfe093c68b622e267e210176 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -212,6 +212,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <argument name="weekdays" required="true" /> <argument name="mdays" required="true" /> <argument name="months" required="true" /> + <argument name="timezone" required="false" /> </parameter> <parameter name="appname" required="true" hasparams="optional"> <argument name="appargs" required="true" /> @@ -306,6 +307,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <argument name="weekdays" required="true" /> <argument name="mdays" required="true" /> <argument name="months" required="true" /> + <argument name="timezone" required="false" /> </parameter> <parameter name="destination" required="true" argsep=":"> <argument name="labeliftrue" /> @@ -4613,6 +4615,7 @@ int ast_context_remove_include2(struct ast_context *con, const char *include, co else con->includes = i->next; /* free include and return */ + ast_destroy_timing(&(i->timing)); ast_free(i); ret = 0; break; @@ -6793,15 +6796,32 @@ static char *months[] = int ast_build_timing(struct ast_timing *i, const char *info_in) { - char info_save[256]; - char *info; + char *info_save, *info; + int j, num_fields, last_sep = -1; /* Check for empty just in case */ - if (ast_strlen_zero(info_in)) + if (ast_strlen_zero(info_in)) { return 0; + } + /* make a copy just in case we were passed a static string */ - ast_copy_string(info_save, info_in, sizeof(info_save)); - info = info_save; + info_save = info = ast_strdupa(info_in); + + /* count the number of fields in the timespec */ + for (j = 0, num_fields = 1; info[j] != '\0'; j++) { + if (info[j] == ',') { + last_sep = j; + num_fields++; + } + } + + /* save the timezone, if it is specified */ + if (num_fields == 5) { + i->timezone = ast_strdup(info + last_sep + 1); + } else { + i->timezone = NULL; + } + /* Assume everything except time */ i->monthmask = 0xfff; /* 12 bits */ i->daymask = 0x7fffffffU; /* 31 bits */ @@ -6822,7 +6842,7 @@ int ast_check_timing(const struct ast_timing *i) struct ast_tm tm; struct timeval now = ast_tvnow(); - ast_localtime(&now, &tm, NULL); + ast_localtime(&now, &tm, i->timezone); /* If it's not the right month, return */ if (!(i->monthmask & (1 << tm.tm_mon))) @@ -6852,6 +6872,14 @@ int ast_check_timing(const struct ast_timing *i) return 1; } +int ast_destroy_timing(struct ast_timing *i) +{ + if (i->timezone) { + ast_free(i->timezone); + i->timezone = NULL; + } + return 0; +} /* * errno values * ENOMEM - out of memory @@ -6896,6 +6924,7 @@ int ast_context_add_include2(struct ast_context *con, const char *value, /* ... go to last include and check if context is already included too... */ for (i = con->includes; i; i = i->next) { if (!strcasecmp(i->name, new_include->name)) { + ast_destroy_timing(&(new_include->timing)); ast_free(new_include); ast_unlock_context(con); errno = EEXIST; @@ -8380,7 +8409,7 @@ static int pbx_builtin_gotoiftime(struct ast_channel *chan, void *data) struct ast_timing timing; if (ast_strlen_zero(data)) { - ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>?'labeliftrue':'labeliffalse'\n"); + ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>[,<timezone>]?'labeliftrue':'labeliffalse'\n"); return -1; } @@ -8396,6 +8425,7 @@ static int pbx_builtin_gotoiftime(struct ast_channel *chan, void *data) branch = branch1; else branch = branch2; + ast_destroy_timing(&timing); if (ast_strlen_zero(branch)) { ast_debug(1, "Not taking any branch\n"); @@ -8413,7 +8443,7 @@ static int pbx_builtin_execiftime(struct ast_channel *chan, void *data) char *s, *appname; struct ast_timing timing; struct ast_app *app; - static const char *usage = "ExecIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>?<appname>[(<appargs>)]"; + static const char *usage = "ExecIfTime requires an argument:\n <time range>,<days of week>,<days of month>,<months>[,<timezone>]?<appname>[(<appargs>)]"; if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "%s\n", usage); @@ -8430,11 +8460,15 @@ static int pbx_builtin_execiftime(struct ast_channel *chan, void *data) if (!ast_build_timing(&timing, s)) { ast_log(LOG_WARNING, "Invalid Time Spec: %s\nCorrect usage: %s\n", s, usage); + ast_destroy_timing(&timing); return -1; } - if (!ast_check_timing(&timing)) /* outside the valid time window, just return */ + if (!ast_check_timing(&timing)) { /* outside the valid time window, just return */ + ast_destroy_timing(&timing); return 0; + } + ast_destroy_timing(&timing); /* now split appname(appargs) */ if ((s = strchr(appname, '('))) { diff --git a/utils/extconf.c b/utils/extconf.c index 4a5c6c3e05e908a5ed465c39c25a3f1b6f82baa3..5d198f85b9ecc48cbcbf57606219f1e60f6bc5a9 100644 --- a/utils/extconf.c +++ b/utils/extconf.c @@ -3061,20 +3061,24 @@ int localized_pbx_builtin_setvar(struct ast_channel *chan, void *data) * return the index of the matching entry, starting from 1. * If names is not supplied, try numeric values. */ - static int lookup_name(const char *s, char *const names[], int max) { int i; - if (names) { + if (names && *s > '9') { for (i = 0; names[i]; i++) { - if (!strcasecmp(s, names[i])) - return i+1; + if (!strcasecmp(s, names[i])) { + return i; + } } - } else if (sscanf(s, "%d", &i) == 1 && i >= 1 && i <= max) { - return i; } - return 0; /* error return */ + + /* Allow months and weekdays to be specified as numbers, as well */ + if (sscanf(s, "%d", &i) == 1 && i >= 1 && i <= max) { + /* What the array offset would have been: "1" would be at offset 0 */ + return i - 1; + } + return -1; /* error return */ } /*! \brief helper function to return a range up to max (7, 12, 31 respectively). @@ -3082,44 +3086,42 @@ static int lookup_name(const char *s, char *const names[], int max) */ static unsigned get_range(char *src, int max, char *const names[], const char *msg) { - int s, e; /* start and ending position */ + int start, end; /* start and ending position */ unsigned int mask = 0; + char *part; /* Check for whole range */ if (ast_strlen_zero(src) || !strcmp(src, "*")) { - s = 0; - e = max - 1; - } else { + return (1 << max) - 1; + } + + while ((part = strsep(&src, "&"))) { /* Get start and ending position */ - char *c = strchr(src, '-'); - if (c) - *c++ = '\0'; + char *endpart = strchr(part, '-'); + if (endpart) { + *endpart++ = '\0'; + } /* Find the start */ - s = lookup_name(src, names, max); - if (!s) { - ast_log(LOG_WARNING, "Invalid %s '%s', assuming none\n", msg, src); - return 0; + if ((start = lookup_name(part, names, max)) < 0) { + ast_log(LOG_WARNING, "Invalid %s '%s', skipping element\n", msg, part); + continue; } - s--; - if (c) { /* find end of range */ - e = lookup_name(c, names, max); - if (!e) { - ast_log(LOG_WARNING, "Invalid end %s '%s', assuming none\n", msg, c); - return 0; + if (endpart) { /* find end of range */ + if ((end = lookup_name(endpart, names, max)) < 0) { + ast_log(LOG_WARNING, "Invalid end %s '%s', skipping element\n", msg, endpart); + continue; } - e--; - } else - e = s; - } - /* Fill the mask. Remember that ranges are cyclic */ - mask = 1 << e; /* initialize with last element */ - while (s != e) { - if (s >= max) { - s = 0; - mask |= (1 << s); } else { - mask |= (1 << s); - s++; + end = start; + } + /* Fill the mask. Remember that ranges are cyclic */ + mask |= (1 << end); /* initialize with last element */ + while (start != end) { + if (start >= max) { + start = 0; + } + mask |= (1 << start); + start++; } } return mask; @@ -3128,85 +3130,60 @@ static unsigned get_range(char *src, int max, char *const names[], const char *m /*! \brief store a bitmask of valid times, one bit each 2 minute */ static void get_timerange(struct ast_timing *i, char *times) { - char *e; + char *endpart, *part; int x; - int s1, s2; - int e1, e2; - /* int cth, ctm; */ + int st_h, st_m; + int endh, endm; + int minute_start, minute_end; /* start disabling all times, fill the fields with 0's, as they may contain garbage */ memset(i->minmask, 0, sizeof(i->minmask)); - /* 2-minutes per bit, since the mask has only 32 bits :( */ + /* 1-minute per bit */ /* Star is all times */ if (ast_strlen_zero(times) || !strcmp(times, "*")) { - for (x=0; x<24; x++) + /* 48, because each hour takes 2 integers; 30 bits each */ + for (x = 0; x < 48; x++) { i->minmask[x] = 0x3fffffff; /* 30 bits */ + } return; } /* Otherwise expect a range */ - e = strchr(times, '-'); - if (!e) { - ast_log(LOG_WARNING, "Time range is not valid. Assuming no restrictions based on time.\n"); - return; - } - *e++ = '\0'; - /* XXX why skip non digits ? */ - while (*e && !isdigit(*e)) - e++; - if (!*e) { - ast_log(LOG_WARNING, "Invalid time range. Assuming no restrictions based on time.\n"); - return; - } - if (sscanf(times, "%d:%d", &s1, &s2) != 2) { - ast_log(LOG_WARNING, "%s isn't a time. Assuming no restrictions based on time.\n", times); - return; - } - if (sscanf(e, "%d:%d", &e1, &e2) != 2) { - ast_log(LOG_WARNING, "%s isn't a time. Assuming no restrictions based on time.\n", e); - return; - } - /* XXX this needs to be optimized */ -#if 1 - s1 = s1 * 30 + s2/2; - if ((s1 < 0) || (s1 >= 24*30)) { - ast_log(LOG_WARNING, "%s isn't a valid start time. Assuming no time.\n", times); - return; - } - e1 = e1 * 30 + e2/2; - if ((e1 < 0) || (e1 >= 24*30)) { - ast_log(LOG_WARNING, "%s isn't a valid end time. Assuming no time.\n", e); - return; - } - /* Go through the time and enable each appropriate bit */ - for (x=s1;x != e1;x = (x + 1) % (24 * 30)) { - i->minmask[x/30] |= (1 << (x % 30)); - } - /* Do the last one */ - i->minmask[x/30] |= (1 << (x % 30)); -#else - for (cth=0; cth<24; cth++) { - /* Initialize masks to blank */ - i->minmask[cth] = 0; - for (ctm=0; ctm<30; ctm++) { - if ( - /* First hour with more than one hour */ - (((cth == s1) && (ctm >= s2)) && - ((cth < e1))) - /* Only one hour */ - || (((cth == s1) && (ctm >= s2)) && - ((cth == e1) && (ctm <= e2))) - /* In between first and last hours (more than 2 hours) */ - || ((cth > s1) && - (cth < e1)) - /* Last hour with more than one hour */ - || ((cth > s1) && - ((cth == e1) && (ctm <= e2))) - ) - i->minmask[cth] |= (1 << (ctm / 2)); + while ((part = strsep(×, "&"))) { + if (!(endpart = strchr(part, '-'))) { + if (sscanf(part, "%d:%d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) { + ast_log(LOG_WARNING, "%s isn't a valid time.\n", part); + continue; + } + i->minmask[st_h * 2 + (st_m >= 30 ? 1 : 0)] |= (1 << (st_m % 30)); + continue; } + *endpart++ = '\0'; + /* why skip non digits? Mostly to skip spaces */ + while (*endpart && !isdigit(*endpart)) { + endpart++; + } + if (!*endpart) { + ast_log(LOG_WARNING, "Invalid time range starting with '%s-'.\n", part); + continue; + } + if (sscanf(part, "%d:%d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) { + ast_log(LOG_WARNING, "'%s' isn't a valid start time.\n", part); + continue; + } + if (sscanf(endpart, "%d:%d", &endh, &endm) != 2 || endh < 0 || endh > 23 || endm < 0 || endm > 59) { + ast_log(LOG_WARNING, "'%s' isn't a valid end time.\n", endpart); + continue; + } + minute_start = st_h * 60 + st_m; + minute_end = endh * 60 + endm; + /* Go through the time and enable each appropriate bit */ + for (x = minute_start; x != minute_end; x = (x + 1) % (24 * 60)) { + i->minmask[x / 30] |= (1 << (x % 30)); + } + /* Do the last one */ + i->minmask[x / 30] |= (1 << (x % 30)); } -#endif /* All done */ return; } @@ -4313,29 +4290,46 @@ char *months[] = NULL, }; -static int ast_build_timing(struct ast_timing *i, const char *info_in) +int ast_build_timing(struct ast_timing *i, const char *info_in) { - char info_save[256]; - char *info; + char *info_save, *info; + int j, num_fields, last_sep = -1; /* Check for empty just in case */ - if (ast_strlen_zero(info_in)) + if (ast_strlen_zero(info_in)) { return 0; + } + /* make a copy just in case we were passed a static string */ - ast_copy_string(info_save, info_in, sizeof(info_save)); - info = info_save; + info_save = info = ast_strdupa(info_in); + + /* count the number of fields in the timespec */ + for (j = 0, num_fields = 1; info[j] != '\0'; j++) { + if (info[j] == ',') { + last_sep = j; + num_fields++; + } + } + + /* save the timezone, if it is specified */ + if (num_fields == 5) { + i->timezone = ast_strdup(info + last_sep + 1); + } else { + i->timezone = NULL; + } + /* Assume everything except time */ i->monthmask = 0xfff; /* 12 bits */ i->daymask = 0x7fffffffU; /* 31 bits */ i->dowmask = 0x7f; /* 7 bits */ /* on each call, use strsep() to move info to the next argument */ - get_timerange(i, strsep(&info, "|")); + get_timerange(i, strsep(&info, "|,")); if (info) - i->dowmask = get_range(strsep(&info, "|"), 7, days, "day of week"); + i->dowmask = get_range(strsep(&info, "|,"), 7, days, "day of week"); if (info) - i->daymask = get_range(strsep(&info, "|"), 31, NULL, "day"); + i->daymask = get_range(strsep(&info, "|,"), 31, NULL, "day"); if (info) - i->monthmask = get_range(strsep(&info, "|"), 12, months, "month"); + i->monthmask = get_range(strsep(&info, "|,"), 12, months, "month"); return 1; } @@ -4488,12 +4482,12 @@ static int ext_strncpy(char *dst, const char *src, int len) * Wrapper around _extension_match_core() to do performance measurement * using the profiling code. */ -static int ast_check_timing(const struct ast_timing *i) +int ast_check_timing(const struct ast_timing *i) { - struct tm tm; - time_t t = time(NULL); + struct ast_tm tm; + struct timeval now = ast_tvnow(); - localtime_r(&t,&tm); + ast_localtime(&now, &tm, i->timezone); /* If it's not the right month, return */ if (!(i->monthmask & (1 << tm.tm_mon))) @@ -4516,7 +4510,7 @@ static int ast_check_timing(const struct ast_timing *i) /* Now the tough part, we calculate if it fits in the right time based on min/hour */ - if (!(i->minmask[tm.tm_hour] & (1 << (tm.tm_min / 2)))) + if (!(i->minmask[tm.tm_hour * 2 + (tm.tm_min >= 30 ? 1 : 0)] & (1 << (tm.tm_min >= 30 ? tm.tm_min - 30 : tm.tm_min)))) return 0; /* If we got this far, then we're good */ @@ -5005,7 +4999,7 @@ static int ast_context_add_include2(struct ast_context *con, const char *value, /* Strip off timing info, and process if it is there */ if ( (c = strchr(p, '|')) ) { *c++ = '\0'; - new_include->hastime = ast_build_timing(&(new_include->timing), c); + new_include->hastime = ast_build_timing(&(new_include->timing), c); } new_include->next = NULL; new_include->registrar = registrar;