diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 9662bcd9e8..524e1f3803 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -7597,6 +7597,91 @@ static FnCallResult FnCallStrftime(ARG_UNUSED EvalContext *ctx, /*********************************************************************/ +#define TIME_ERROR ((time_t) -1) + +static const char *date_paths[] = { + "/usr/bin/date", + "/bin/date", + NULL +}; + +static time_t ParseDate(const char *input_string) +{ + const char *path = NULL; + for (size_t i = 0; date_paths[i] != NULL; i++) + { + const char *tmp = date_paths[i]; + + if (IsExecutable(tmp)) + { + path = tmp; + break; + } + } + + if (path == NULL) + { + Log(LOG_LEVEL_ERR, "Unable to find 'date' binary"); + return TIME_ERROR; + } + + char buffer[MAX_INPUT]; + int n = snprintf(buffer, sizeof(buffer), "--date=%s", input_string); + + if (n < 0 || (size_t) n >= sizeof(buffer)) { + Log(LOG_LEVEL_DEBUG, "Truncated input string '%s'", input_string); + return TIME_ERROR; + } + + const char *argv[] = {path, buffer, "+%s", NULL}; + // cf_popen escapes the date, which makes it fail to parse + FILE *fd = cf_popen_exact_args(argv, "r", true); + + if (fd == NULL) + { + Log(LOG_LEVEL_DEBUG, "Couldn't run date \"--date='%s' +%%s\"", input_string); + return TIME_ERROR; + } + + memset(buffer, 0, sizeof(buffer)); + + size_t bytes_read = fread(buffer, 1, sizeof(buffer), fd); + fclose(fd); + + if (bytes_read == 0) + { + Log(LOG_LEVEL_DEBUG, "No output read for '%s'", buffer); + return TIME_ERROR; + } + + long out; + if (StringToLong(buffer, &out) != 0) + { + Log(LOG_LEVEL_DEBUG, "Couldn't parse date: '%s'", buffer); + return TIME_ERROR; + } + + return (time_t) out; +} + +static FnCallResult FnCallStrToTime(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + + const char *string = RlistScalarValue(finalargs); + time_t result = ParseDate(string); + + if (result == TIME_ERROR) + { + Log(LOG_LEVEL_ERR, "'%s': Invalid date '%s'", fp->name, 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 +11512,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 +12094,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/libpromises/pipes.h b/libpromises/pipes.h index 5961ed0140..f5756e45b6 100644 --- a/libpromises/pipes.h +++ b/libpromises/pipes.h @@ -53,6 +53,8 @@ FILE *cf_popensetuid(const char *command, const Seq *arglist, const char *type, FILE *cf_popen_sh(const char *command, const char *type); FILE *cf_popen_sh_select(const char *command, const char *type, OutputSelect output_select); FILE *cf_popen_shsetuid(const char *command, const char *type, uid_t uid, gid_t gid, char *chdirv, char *chrootv, int background); +FILE *cf_popen_exact_args_select(const char **argv, const char *type, OutputSelect output_select); +FILE *cf_popen_exact_args(const char **argv, const char *type, bool capture_stderr); int cf_pclose(FILE *pp); void cf_pclose_nowait(FILE *pp); bool PipeToPid(pid_t *pid, FILE *pp); diff --git a/libpromises/pipes_unix.c b/libpromises/pipes_unix.c index a9aece3778..bb3243932f 100644 --- a/libpromises/pipes_unix.c +++ b/libpromises/pipes_unix.c @@ -371,18 +371,15 @@ IOData cf_popen_full_duplex(const char *command, bool capture_stderr, bool requi } } -FILE *cf_popen_select(const char *command, const char *type, OutputSelect output_select) +FILE *cf_popen_exact_args_select(const char **argv, const char *type, OutputSelect output_select) { int pd[2]; pid_t pid; FILE *pp = NULL; - char **argv = ArgSplitCommand(command, NULL); - pid = CreatePipeAndFork(type, pd); if (pid == (pid_t) -1) { - ArgFree(argv); return NULL; } @@ -427,7 +424,6 @@ FILE *cf_popen_select(const char *command, const char *type, OutputSelect output if ((pp = fdopen(pd[0], type)) == NULL) { cf_pwait(pid); - ArgFree(argv); return NULL; } break; @@ -439,13 +435,11 @@ FILE *cf_popen_select(const char *command, const char *type, OutputSelect output if ((pp = fdopen(pd[1], type)) == NULL) { cf_pwait(pid); - ArgFree(argv); return NULL; } } ChildrenFDSet(fileno(pp), pid); - ArgFree(argv); return pp; } @@ -453,6 +447,23 @@ FILE *cf_popen_select(const char *command, const char *type, OutputSelect output return NULL; } +FILE *cf_popen_exact_args(const char **argv, const char *type, bool capture_stderr) +{ + return cf_popen_exact_args_select( + argv, + type, + capture_stderr ? OUTPUT_SELECT_BOTH : OUTPUT_SELECT_STDOUT); +} + +FILE *cf_popen_select(const char *command, const char *type, OutputSelect output_select) +{ + char **argv = ArgSplitCommand(command, NULL); + FILE *ret = cf_popen_exact_args_select(argv, type, output_select); + ArgFree(argv); + + return ret; +} + FILE *cf_popen(const char *command, const char *type, bool capture_stderr) { return cf_popen_select( 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 0000000000..1b6d5eb98a --- /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"; +}