diff --git a/examples/isnewerthan.cf b/examples/isnewerthan.cf index eef78f9dbf..bb928c9d2c 100644 --- a/examples/isnewerthan.cf +++ b/examples/isnewerthan.cf @@ -37,7 +37,7 @@ bundle agent example { classes: - "do_it" and => { isnewerthan("/tmp/later","/tmp/earlier"), "cfengine" }; + "do_it" expression => isnewerthan("/tmp/later","/tmp/earlier"); reports: diff --git a/examples/isnewerthantime.cf b/examples/isnewerthantime.cf new file mode 100644 index 0000000000..514159c762 --- /dev/null +++ b/examples/isnewerthantime.cf @@ -0,0 +1,33 @@ +#+begin_src prep +#@ ``` +#@ touch -t '200102031234.56' /tmp/file_a +#@ touch -t '200202031234.56' /tmp/file_b +#@ ``` +#+end_src +############################################################################### +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + classes: + "is_file_a_new" expression => isnewerthantime("/tmp/file_a", 1000000000); + "is_file_b_new" expression => isnewerthantime("/tmp/file_b", 1000000000); + + reports: + !is_file_a_new:: + "/tmp/file_a is not newer than 2001-09-09 01:46:40"; + is_file_b_new:: + "/tmp/file_b is newer than 2001-09-09 01:46:40"; +} +#+end_src +############################################################################### +#+begin_src example_output +#@ ``` +#@ R: /tmp/file_a is not newer than 2001-09-09 01:46:40 +#@ R: /tmp/file_b is newer than 2001-09-09 01:46:40 +#@ ``` +#+end_src diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index e0b1b3ffc6..ac8a239ad0 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -3068,7 +3068,7 @@ static FnCallResult FnCallIsConnectable(ARG_UNUSED EvalContext *ctx, } return FnReturnContext(sd > -1); -} +} /*********************************************************************/ @@ -4496,6 +4496,28 @@ static FnCallResult FnCallIsNewerThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co return FnReturnContext(frombuf.st_mtime > tobuf.st_mtime); } +static FnCallResult FnCallIsNewerThanTime(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) +{ + assert(finalargs != NULL); + + const char *arg_file = RlistScalarValue(finalargs); + // a comment in `FnCallStrftime`: "this will be a problem on 32-bit systems..." + const time_t arg_mtime = IntFromString(RlistScalarValue(finalargs->next)); + + struct stat file_buf; + int exit_code = stat(arg_file, &file_buf); + if (exit_code == -1) + { + return FnFailure(); + } + + time_t file_mtime = file_buf.st_mtime; + + bool result = file_mtime > arg_mtime; + + return FnReturnContext(result); +} + /*********************************************************************/ static FnCallResult FnCallIsAccessedBefore(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs) @@ -7349,7 +7371,7 @@ static FnCallResult ValidateDataGeneric(const char *const fname, { isvalid = isvalid && JsonGetElementType(json) != JSON_ELEMENT_TYPE_PRIMITIVE; } - + FnCallResult ret = FnReturnContext(isvalid); JsonDestroy(json); return ret; @@ -9840,6 +9862,13 @@ static const FnCallArg ISNEWERTHAN_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg FILE_TIME_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name"}, + {CF_VALRANGE, CF_DATA_TYPE_INT, "Time as a Unix epoch offset"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; + static const FnCallArg ISVARIABLE_ARGS[] = { {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Variable identifier"}, @@ -10677,6 +10706,8 @@ const FnCallType CF_FNCALL_TYPES[] = FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), FnCallTypeNew("isnewerthan", CF_DATA_TYPE_CONTEXT, ISNEWERTHAN_ARGS, &FnCallIsNewerThan, "True if arg1 is newer (modified later) than arg2 (mtime)", FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("isnewerthantime", CF_DATA_TYPE_CONTEXT, FILE_TIME_ARGS, &FnCallIsNewerThanTime, "True if arg1 is newer (modified later) (has larger mtime) than time arg2", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), FnCallTypeNew("isplain", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object is a plain/regular file", FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), FnCallTypeNew("isvariable", CF_DATA_TYPE_CONTEXT, ISVARIABLE_ARGS, &FnCallIsVariable, "True if the named variable is defined", diff --git a/tests/acceptance/02_classes/02_functions/isnewerthantime.cf b/tests/acceptance/02_classes/02_functions/isnewerthantime.cf new file mode 100644 index 0000000000..cf7ea5fccb --- /dev/null +++ b/tests/acceptance/02_classes/02_functions/isnewerthantime.cf @@ -0,0 +1,64 @@ +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} +####################################################### + +bundle agent check +{ + vars: + "test_file" + string => "$(this.promise_dirname)/isnewerthantime_file.txt"; + "nonexistent_file" + string => "$(this.promise_dirname)/thisfiledoesntexist_1747148781.txt"; + "test_file_mtime" + string => filestat("$(test_file)", mtime); + "test_file_time_just_before_mtime" + int => int(eval("$(test_file_mtime) - 1")); + "minus_one" + int => "-1"; + classes: + "ok1" + expression => isnewerthantime("$(test_file)", 0); + "ok2" + expression => isnewerthantime("$(nonexistent_file)", 0); + "ok3" + expression => isnewerthantime("$(test_file)", "$(test_file_time_just_before_mtime)"); + "ok4" + expression => isnewerthantime("$(test_file)", "$(test_file_mtime)"); + "ok5" + expression => isnewerthantime("$(nonexistent_file)", "$(test_file_mtime)"); + "ok6" + # as "inf" corresponds to 999999999, this is actually not False + expression => isnewerthantime("$(test_file)", "inf"); + "ok7" + expression => isnewerthantime("$(nonexistent_file)", "inf"); + "ok8" + expression => isnewerthantime("$(test_file)", "$(minus_one)"); + "ok" + expression => and("ok1", "!ok2", "ok3", "!ok4", "!ok5", "ok6", "!ok7", "ok8"); + + reports: + DEBUG.ok1:: + "1. pass"; + DEBUG.!ok2:: + "2. pass"; + DEBUG.ok3:: + "3. pass: $(test_file_time_just_before_mtime)"; + DEBUG.!ok4:: + "4. pass: $(test_file_mtime)"; + DEBUG.!ok5:: + "5. pass"; + DEBUG.ok6:: + "6. pass"; + DEBUG.!ok7:: + "7. pass"; + DEBUG.ok8:: + "8. pass"; + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} diff --git a/tests/acceptance/02_classes/02_functions/isnewerthantime_file.txt b/tests/acceptance/02_classes/02_functions/isnewerthantime_file.txt new file mode 100644 index 0000000000..e69de29bb2