Skip to content
Draft
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
179 changes: 179 additions & 0 deletions libpromises/evalfunction.c
Original file line number Diff line number Diff line change
Expand Up @@ -7597,6 +7597,166 @@ static FnCallResult FnCallStrftime(ARG_UNUSED EvalContext *ctx,

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

static const char *date_paths[] = {
"/usr/bin/date",
"/bin/date",
NULL
};

static const char *LocateDateBinary()
{
for (size_t i = 0; date_paths[i] != NULL; i++)
{
const char *path = date_paths[i];

if (IsExecutable(path))
{
return path;
}
}
return NULL;
}

static int ParseDate(const char *input_string, time_t *out)
{
const char *date_path = LocateDateBinary();

if (date_path == NULL)
{
Log(LOG_LEVEL_ERR, "Unable to find 'date' binary");
return -1;
}

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_ERR, "Truncation error: input string '%.10s...' is too long (%d >= %zu)",
input_string, n, sizeof(buffer));
return -1;
}

const char *argv[] = {date_path, buffer, "+%s", NULL};
FILE *fd = cf_popen_exact_args(argv, "r", true);

if (fd == NULL)
{
Log(LOG_LEVEL_ERR, "Couldn't run date \"--date='%s' +%%s\"", input_string);
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);

int ret = StringToLong(buffer, (long *) out);
if (ret != 0)
{
LogStringToLongError(buffer, "ParseDate", ret);
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 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 @@ -11427,6 +11587,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"},
Expand Down Expand Up @@ -11655,6 +11821,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 @@ -12003,6 +12177,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",
Expand Down Expand Up @@ -12103,6 +12279,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
2 changes: 2 additions & 0 deletions libpromises/pipes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
27 changes: 20 additions & 7 deletions libpromises/pipes_unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -371,18 +371,16 @@ 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)
// do not use with user input
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;
}

Expand Down Expand Up @@ -427,7 +425,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;
Expand All @@ -439,20 +436,36 @@ 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;
}

ProgrammingError("Unreachable code");
return NULL;
}

// do not use with user input
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(
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";
}
29 changes: 29 additions & 0 deletions tests/acceptance/01_vars/02_functions/strtotime.cf
Original file line number Diff line number Diff line change
@@ -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";
}
Loading