Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions libpromises/evalfunction.c
Original file line number Diff line number Diff line change
Expand Up @@ -7674,6 +7674,71 @@ static FnCallResult FnCallStrToTime(ARG_UNUSED EvalContext *ctx, ARG_UNUSED cons

/*********************************************************************/

static FnCallResult FnCallFileOlderThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
{
assert(fp != NULL);

if (finalargs == NULL)
{
Log(LOG_LEVEL_ERR, "Function '%s' requires path as first argument",
fp->name);
return FnFailure();
}
const char *filename = RlistScalarValue(finalargs);

if (finalargs->next == NULL)
{
Log(LOG_LEVEL_ERR, "Function '%s' requires date or date offset as second argument",
fp->name);
return FnFailure();
}
const char *date = RlistScalarValue(finalargs->next);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
const char *option = (finalargs->next->next != NULL) ? RlistScalarValue(finalargs->next->next) : "modification";

struct stat statbuf;

if (stat(filename, &statbuf) != 0)
{
Log(LOG_LEVEL_ERR, "'%s': Couldn't stat '%s'", fp->name, filename);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return FnFailure();
}

time_t file_ts;

if (StringEqual_IgnoreCase(option, "modification") || StringEqual_IgnoreCase(option, "modif") )
{
file_ts = statbuf.st_mtime;
}
else if (StringEqual_IgnoreCase(option, "access"))
{
file_ts = statbuf.st_atime;
}
else if (StringEqual_IgnoreCase(option, "change"))
{
file_ts = statbuf.st_ctime;
}
else
{
ProgrammingError("Unknown option for %s\n", fp->name);
}

time_t input_time;
int ret = ParseDate(date, &input_time);

if (ret != 0)
{
Log(LOG_LEVEL_ERR, "'%s': Couldn't parse '%s'", fp->name, date);
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
return FnFailure();
}

time_t now = time(NULL);
time_t offset = input_time - now; // convert date to an offset

return FnReturnContext(file_ts + offset <= now);
}

/*********************************************************************/

static FnCallResult FnCallEval(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
{
if (finalargs == NULL)
Expand Down Expand Up @@ -11738,6 +11803,14 @@ static const FnCallArg ISREADABLE_ARGS[] =
{NULL, CF_DATA_TYPE_NONE, NULL}
};

static const FnCallArg FILE_OLDER_THAN_ARGS[] =
{
{CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Path to file"},
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "Date string"},
{"modification,modif,access,change", CF_DATA_TYPE_OPTION, "file timespec"},
{NULL, CF_DATA_TYPE_NONE, NULL}
};

static const FnCallArg DATATYPE_ARGS[] =
{
{CF_ANYSTRING, CF_DATA_TYPE_STRING, "Variable identifier"},
Expand Down Expand Up @@ -12188,6 +12261,9 @@ const FnCallType CF_FNCALL_TYPES[] =
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL, ARGC(2, 3)),
FnCallTypeNew("isreadable", CF_DATA_TYPE_CONTEXT, ISREADABLE_ARGS, &FnCallIsReadable, "Check if file is readable. Timeout immediately or after optional timeout interval",
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL, ARGC(1, 2)),
FnCallTypeNew("file_older_than", CF_DATA_TYPE_CONTEXT, FILE_OLDER_THAN_ARGS, &FnCallFileOlderThan, "Check if file is older than a time offset or date.",
FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL, ARGC(2, 3)),


// Datatype functions
FnCallTypeNew("type", CF_DATA_TYPE_STRING, DATATYPE_ARGS, &FnCallDatatype, "Get type description as string",
Expand Down
64 changes: 64 additions & 0 deletions tests/acceptance/01_vars/02_functions/file_older_than.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#######################################################
#
# Test file_older_than function
#
#######################################################
body common control
{
inputs => { "../../default.sub.cf" };
bundlesequence => { default("$(this.promise_filename)") };
version => "1.0";
}

#######################################################
bundle agent init
{
files:
"$(G.testdir)/somefile.txt"
create => "true",
content => "Hello world!";
}

#######################################################
bundle agent test
{
classes:
"long_time_past"
expression => file_older_than(
"$(G.testdir)/somefile.txt", "100 years ago"
),
if => fileexists("$(G.testdir)/somefile.txt");

"long_time_future"
expression => not(
file_older_than("$(G.testdir)/somefile.txt", "100 years")
),
if => fileexists("$(G.testdir)/somefile.txt");

"newer_than_a_week"
expression => file_older_than(
"$(G.testdir)/somefile.txt", "-1 week", "modif"
),
if => fileexists("$(G.testdir)/somefile.txt");

"ok"
expression => and(
"long_time_past", "long_time_future", "newer_than_a_week"
),
if => fileexists("$(G.testdir)/somefile.txt"),
scope => "namespace";
}

#######################################################
bundle agent check
{
files:
"$(G.testdir)/somefile.txt" delete => tidy;

reports:
ok::
"$(this.promise_filename) Pass";

!ok::
"$(this.promise_filename) FAIL";
}
Loading