diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 9662bcd9e80..65ab7ff12ff 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -7597,6 +7597,83 @@ static FnCallResult FnCallStrftime(ARG_UNUSED EvalContext *ctx, /*********************************************************************/ +static int ParseDate(const char *input_string, time_t *out) +{ + char date_path[PATH_MAX]; + strncpy(date_path, GetBinDir(), sizeof(date_path) - 1); + JoinPaths(date_path, sizeof(date_path), "date"); + + char buffer[CF_BUFSIZE]; + int n = snprintf(buffer, sizeof(buffer), "%s --date '%s' +%%s", date_path, input_string); + + if (n < 0 || (size_t) n >= sizeof(buffer)) { + Log(LOG_LEVEL_ERR, "Truncation error: input string '%.10s...' is too long (%d >= %zu)", + input_string, n, sizeof(buffer)); + return -1; + } + + FILE *fd = cf_popen(buffer, "r", true); + if (fd == NULL) + { + Log(LOG_LEVEL_ERR, "Couldn't run command: '%s'", buffer); + return -1; + } + + size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1 , fd); + buffer[bytes_read] = '\0'; + + if (bytes_read == 0) + { + if (ferror(fd)) + { + Log(LOG_LEVEL_ERR, "Error reading output for '%s'", input_string); + } + else if (feof(fd)) + { + Log(LOG_LEVEL_DEBUG, "No output read for '%s'", input_string); + } + fclose(fd); + return -1; + } + fclose(fd); + + long time_value; + int ret = StringToLong(buffer, &time_value); + if (ret != 0) + { + LogStringToLongError(buffer, "ParseDate", ret); + return -1; + } + + *out = (time_t) time_value; + if ((long) *out != time_value) + { + Log(LOG_LEVEL_ERR, "Date value '%ld' does not fit in time_t", time_value); + return -1; + } + + return 0; +} + +static FnCallResult FnCallStrToTime(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + + const char *input_string = RlistScalarValue(finalargs); + time_t result; + int ret = ParseDate(input_string, &result); + + if (ret != 0) + { + Log(LOG_LEVEL_ERR, "'%s': Invalid date '%s'", fp->name, input_string); + return FnFailure(); + } + + return FnReturnF("%ld", result); +} + +/*********************************************************************/ + static FnCallResult FnCallEval(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) { if (finalargs == NULL) @@ -11427,6 +11504,12 @@ static const FnCallArg STRFTIME_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg STRTOTIME_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String to parse"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + static const FnCallArg STRING_REPLACE_ARGS[] = { {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source string"}, @@ -12003,6 +12086,8 @@ const FnCallType CF_FNCALL_TYPES[] = FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), FnCallTypeNew("strftime", CF_DATA_TYPE_STRING, STRFTIME_ARGS, &FnCallStrftime, "Format a date and time string", FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), + FnCallTypeNew("strtotime", CF_DATA_TYPE_INT, STRTOTIME_ARGS, &FnCallStrToTime, "Parse a timestamp from a string", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), FnCallTypeNew("sublist", CF_DATA_TYPE_STRING_LIST, SUBLIST_ARGS, &FnCallSublist, "Returns arg3 element from either the head or the tail (according to arg2) of list or array or data container arg1.", FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL, DEFAULT_ARGC), FnCallTypeNew("sysctlvalue", CF_DATA_TYPE_STRING, SYSCTLVALUE_ARGS, &FnCallSysctlValue, "Returns a value for sysctl key arg1 pair", diff --git a/tests/acceptance/01_vars/02_functions/strtotime.cf b/tests/acceptance/01_vars/02_functions/strtotime.cf new file mode 100644 index 00000000000..1b6d5eb98ac --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/strtotime.cf @@ -0,0 +1,29 @@ +####################################################### +# +# Test strtotime function +# +####################################################### +body common control +{ + inputs => { "../../default.sub.cf" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +####################################################### +bundle agent test +{ + vars: + "some_date" int => strtotime("2009-09-21T00:00:00Z"); + "epoch" int => int(1253491200); + + classes: + "ok" expression => strcmp("$(epoch)", "$(some_date)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/testall b/tests/acceptance/testall index 4166191fe56..4dc8385ebd7 100755 --- a/tests/acceptance/testall +++ b/tests/acceptance/testall @@ -106,6 +106,7 @@ CF_CHECK=${CF_CHECK:-} ; export CF_CHECK CF_RUNAGENT=${CF_RUNAGENT:-} ; export CF_RUNAGENT RPMVERCMP=${RPMVERCMP:-} ; export RPMVERCMP DIFF=${DIFF:-} ; export DIFF +DATE=${DATE:-} ; export DATE LIBTOOL=${LIBTOOL:-} ; export LIBTOOL INCLUDE_IN_WORKDIR=${INCLUDE_IN_WORKDIR:-} ; export INCLUDE_IN_WORKDIR @@ -443,6 +444,7 @@ runtest() { $LN_CMD "$RPMVERCMP" "$WORKDIR/bin" fi $LN_CMD "$DIFF" "$WORKDIR/bin" + $LN_CMD "$DATE" "$WORKDIR/bin" fi for inc in $INCLUDE_IN_WORKDIR do ( @@ -957,7 +959,7 @@ else NEED_RPMVERCMP="no" fi -if [ -n "$AGENT" -o -n "$CF_PROMISES" -o -n "$CF_SERVERD" -o -n "$CF_EXECD" -o -n "$CF_KEY" -o -n "$CF_SECRET" -o -n "$CF_NET" -o -n "$CF_CHECK" -o -n "$CF_RUNAGENT" -o \( "$NEED_RPMVERCMP" = "yes" -a -n "$RPMVERCMP" \) -o -n "$DIFF" ] +if [ -n "$AGENT" -o -n "$CF_PROMISES" -o -n "$CF_SERVERD" -o -n "$CF_EXECD" -o -n "$CF_KEY" -o -n "$CF_SECRET" -o -n "$CF_NET" -o -n "$CF_CHECK" -o -n "$CF_RUNAGENT" -o \( "$NEED_RPMVERCMP" = "yes" -a -n "$RPMVERCMP" \) -o -n "$DIFF" -o -n "$DATE" ] then if [ -n "$BINDIR" ] then @@ -981,6 +983,7 @@ find_default_binary() [ -x "`pwd`/$2/$2" ] && eval $1=\""`pwd`/$2/$2"\" [ -n "$BINDIR" -a -x "$BINDIR/$2" ] && eval $1=\""$BINDIR/$2"\" [ $2 = "diff" ] && eval $1=\""`command -v diff`"\" + [ $2 = "date" -a -z "`eval echo \\$$1`" ] && eval $1=\""`command -v date`"\" } find_default_binary DEFAGENT cf-agent find_default_binary DEFCF_PROMISES cf-promises @@ -996,6 +999,7 @@ then find_default_binary DEFRPMVERCMP rpmvercmp fi find_default_binary DIFF diff +find_default_binary DATE date [ -x "`pwd`/libtool" ] && DEFLIBTOOL="`pwd`/libtool" [ -x "`pwd`/../../libtool" ] && DEFLIBTOOL="`pwd`/../../libtool"