From 1a849f2ed2a26e9c81b49c084c60b112399c9860 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Tue, 28 Jan 2025 15:09:38 +0100 Subject: [PATCH] Added is_type policy function Signed-off-by: Victor Moene Changelog: Added is_type policy function to check type of a variable. Ticket: CFE-3641 --- examples/is_type.cf | 43 +++++++ libpromises/evalfunction.c | 115 ++++++++++++++---- .../01_vars/02_functions/is_type.cf | 89 ++++++++++++++ 3 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 examples/is_type.cf create mode 100644 tests/acceptance/01_vars/02_functions/is_type.cf diff --git a/examples/is_type.cf b/examples/is_type.cf new file mode 100644 index 0000000000..e4ab0e4ea4 --- /dev/null +++ b/examples/is_type.cf @@ -0,0 +1,43 @@ +#+begin_src cfengine3 +body common control +{ + bundlesequence => { "example" }; +} + +bundle agent example +{ + vars: + "foo" + data => '{ "bar": true }'; + "a" string => "hello"; + classes: + "isdata" + expression => is_type("foo", "data"); + "isdata_boolean" + expression => is_type("foo[bar]", "data boolean"); + "isstring" + expression => is_type("a", "string"); + "isint" + expression => is_type("a", "int"); + + + reports: + isdata:: + "'foo' is of type 'data'"; + isdata_boolean:: + "'foo[bar]' is of type 'data boolean'"; + isstring:: + "'a' is of type 'string'"; + isint:: + "'a' is of type 'int'"; +} + +#+end_src +############################################################################# +#+begin_src example_output +#@ ``` +#@ R: 'foo' is of type 'data' +#@ R: 'foo[bar]' is of type 'data boolean' +#@ R: 'a' is of type 'string' +#@ ``` +#+end_src diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index fe3c4bc92a..caaa179667 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -5276,38 +5276,21 @@ static FnCallResult FnCallFold(EvalContext *ctx, /*********************************************************************/ -static FnCallResult FnCallDatatype(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +static char *DataTypeStringFromVarName(EvalContext *ctx, const char *var_name, bool detail) { - assert(fp != NULL); - assert(fp->name != NULL); + assert(var_name != NULL); - if (finalargs == NULL) - { - Log(LOG_LEVEL_ERR, - "Function %s requires variable identifier as first argument", - fp->name); - return FnFailure(); - } - const char* const var_name = RlistScalarValue(finalargs); - - VarRef* const var_ref = VarRefParse(var_name); + VarRef *const var_ref = VarRefParse(var_name); DataType type; const void *value = EvalContextVariableGet(ctx, var_ref, &type); VarRefDestroy(var_ref); - /* detail argument defaults to false */ - bool detail = false; - if (finalargs->next != NULL) - { - detail = BooleanFromString(RlistScalarValue(finalargs->next)); - } - const char *const type_str = (type == CF_DATA_TYPE_NONE) ? "none" : DataTypeToString(type); if (!detail) { - return FnReturn(type_str); + return SafeStringDuplicate(type_str); } if (type == CF_DATA_TYPE_CONTAINER) @@ -5339,15 +5322,87 @@ static FnCallResult FnCallDatatype(EvalContext *ctx, ARG_UNUSED const Policy *po subtype_str = "null"; break; default: - Log(LOG_LEVEL_ERR, - "Function %s failed to get subtype of type data", fp->name); - return FnFailure(); + return NULL; } - return FnReturnF("%s %s", type_str, subtype_str); + return StringConcatenate(3, type_str, " ", subtype_str); + } + return StringConcatenate(2, "policy ", type_str); +} + +static FnCallResult FnCallDatatype(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + assert(fp->name != NULL); + + if (finalargs == NULL) + { + Log(LOG_LEVEL_ERR, + "Function %s requires variable identifier as first argument", + fp->name); + return FnFailure(); + } + const char *const var_name = RlistScalarValue(finalargs); + + /* detail argument defaults to false */ + bool detail = false; + if (finalargs->next != NULL) + { + detail = BooleanFromString(RlistScalarValue(finalargs->next)); + } + char *const output_string = DataTypeStringFromVarName(ctx, var_name, detail); + + if (output_string == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s could not parse var type", + fp->name); + return FnFailure(); + } + + return FnReturnNoCopy(output_string); +} + +/*********************************************************************/ + +static FnCallResult FnCallIsDatatype(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs) +{ + assert(fp != NULL); + assert(fp->name != NULL); + + // check args + const Rlist *const var_arg = finalargs; + if (var_arg == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s requires a variable as first argument", + fp->name); + return FnFailure(); + } + + assert(finalargs != NULL); // assumes finalargs is already checked by var_arg + const Rlist *const type_arg = finalargs->next; + if (type_arg == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s requires a type as second argument", + fp->name); + return FnFailure(); } - return FnReturnF("policy %s", type_str); + const char *const var_name = RlistScalarValue(var_arg); + const char *const type_name = RlistScalarValue(type_arg); + bool detail = StringContainsChar(type_name, ' '); + + char *const type_string = DataTypeStringFromVarName(ctx, var_name, detail); + + if (type_string == NULL) + { + Log(LOG_LEVEL_ERR, "Function %s could not determine type of the variable '%s'", + fp->name, var_name); + return FnFailure(); + } + const bool matching = StringEqual(type_name, type_string); + free(type_string); + + return FnReturnContext(matching); } /*********************************************************************/ @@ -10303,6 +10358,12 @@ static const FnCallArg DATATYPE_ARGS[] = {CF_BOOL, CF_DATA_TYPE_OPTION, "Enable detailed type decription"}, {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg IS_DATATYPE_ARGS[] = +{ + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Variable identifier"}, + {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Type"}, + {NULL, CF_DATA_TYPE_NONE, NULL} +}; /*********************************************************/ @@ -10715,6 +10776,8 @@ const FnCallType CF_FNCALL_TYPES[] = // Datatype functions FnCallTypeNew("type", CF_DATA_TYPE_STRING, DATATYPE_ARGS, &FnCallDatatype, "Get type description as string", FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("is_type", CF_DATA_TYPE_STRING, IS_DATATYPE_ARGS, &FnCallIsDatatype, "Compare type of variable with type", + FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), FnCallTypeNewNull() }; diff --git a/tests/acceptance/01_vars/02_functions/is_type.cf b/tests/acceptance/01_vars/02_functions/is_type.cf new file mode 100644 index 0000000000..cc6332d9c3 --- /dev/null +++ b/tests/acceptance/01_vars/02_functions/is_type.cf @@ -0,0 +1,89 @@ +body common control +{ + bundlesequence => { test, check }; + version => "1.0"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-3641" } + string => "Test policy function is_type"; +} + +bundle agent check +{ + vars: + "mystring" string => "one"; + "myint" int => "1"; + "myreal" real => "0.2"; + "myslist" slist => { "one", "two", "three" }; + "myilist" ilist => { "1", "2", "3" }; + "myrlist" rlist => { "0.1", "0.2", "0.3" }; + "mydata" data => '{ "dstring": "two", "dint": 2, "darray": [1,2,3], "dreal": 0.3, "dbool": true, "dnull": null }'; + + classes: + "isstring" + expression => is_type("mystring", "string"); + "isint" + expression => is_type("myint", "int"); + "isreal" + expression => is_type("myreal", "real"); + "isslist" + expression => is_type("myslist", "slist"); + "isilist" + expression => is_type("myilist", "ilist"); + "isrlist" + expression => is_type("myrlist", "rlist"); + "ispolicystring" + expression => is_type("mystring", "policy string"); + "ispolicyint" + expression => is_type("myint", "policy int"); + "ispolicyreal" + expression => is_type("myreal", "policy real"); + "ispolicyslist" + expression => is_type("myslist", "policy slist"); + "ispolicyilist" + expression => is_type("myilist", "policy ilist"); + "ispolicyrlist" + expression => is_type("myrlist", "policy rlist"); + "isdata" + expression => is_type("mydata", "data"); + "isdataobject" + expression => is_type("mydata", "data object"); + "isdatastring" + expression => is_type("mydata[dstring]", "data string"); + "isdataint" + expression => is_type("mydata[dint]", "data int"); + "isdataarray" + expression => is_type("mydata[darray]", "data array"); + "isdatareal" + expression => is_type("mydata[dreal]", "data real"); + "isdataboolean" + expression => is_type("mydata[dbool]", "data boolean"); + "isdatanull" + expression => is_type("mydata[dnull]", "data null"); + + "isnottype" + expression => not(is_type("mystring", "int")); + "containerisnottype" + expression => not(is_type("mydata[darray]", "string")); + "isnotcontainer" + expression => not(is_type("myreal", "data array")); + "containerisnotcontainer" + expression => not(is_type("mydata[dbool]", "data int")); + + + "ok" + and => { "isstring", "isint", "isreal", "isslist", "isilist", "isrlist", + "ispolicystring", "ispolicyint", "ispolicyreal", "ispolicyslist", "ispolicyilist", + "isdata", "isdataobject", "isdatastring", "isdataint", "isdataarray", + "isdatareal", "isdataboolean", "isdatanull", "isnottype", "containerisnottype", + "isnotcontainer", "containerisnotcontainer" }; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +}