cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

fncall.c
Go to the documentation of this file.
1 /*
2  Copyright 2019 Northern.tech AS
3 
4  This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by the
8  Free Software Foundation; version 3.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18 
19  To the extent this program is licensed as part of the Enterprise
20  versions of CFEngine, the applicable Commercial Open Source License
21  (COSL) may apply to this file if you as a licensee so wish it. See
22  included file COSL.txt.
23 */
24 
25 #include <fncall.h>
26 
27 #include <eval_context.h>
28 #include <files_names.h>
29 #include <expand.h>
30 #include <vars.h>
31 #include <evalfunction.h>
32 #include <policy.h>
33 #include <string_lib.h>
34 #include <promises.h>
35 #include <syntax.h>
36 #include <audit.h>
37 #include <cleanup.h>
38 
39 /******************************************************************/
40 /* Argument propagation */
41 /******************************************************************/
42 
43 /*
44 
45 When formal parameters are passed, they should be literal strings, i.e.
46 values (check for this). But when the values are received the
47 receiving body should state only variable names without literal quotes.
48 That way we can feed in the received parameter name directly in as an lvalue
49 
50 e.g.
51  access => myaccess("$(person)"),
52 
53  body files myaccess(user)
54 
55 leads to Hash Association (lval,rval) => (user,"$(person)")
56 
57 */
58 
59 /******************************************************************/
60 
61 Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, const FnCallType *fp_type)
62 {
63  // Functions with delayed evaluation will call this themselves later
64  if (fp_type && fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION)
65  {
66  return RlistCopy(fp->args);
67  }
68 
69  const FnCallType *fn = FnCallTypeGet(fp->name);
70  if (fn == NULL)
71  {
72  FatalError(ctx, "Function call '%s' has unknown type", fp->name);
73  }
74  else
75  {
76  int len = RlistLen(fp->args);
77 
78  if (!(fn->options & FNCALL_OPTION_VARARG))
79  {
80  if (len != FnNumArgs(fn))
81  {
82  Log(LOG_LEVEL_ERR, "Arguments to function '%s' do not tally. Expected %d not %d",
83  fp->name, FnNumArgs(fn), len);
85  DoCleanupAndExit(EXIT_FAILURE);
86  }
87  }
88  }
89 
90  Rlist *expanded_args = NULL;
91  for (const Rlist *rp = fp->args; rp != NULL; rp = rp->next)
92  {
93  Rval rval;
94 
95  switch (rp->val.type)
96  {
97  case RVAL_TYPE_FNCALL:
98  {
99  FnCall *subfp = RlistFnCallValue(rp);
100  rval = FnCallEvaluate(ctx, policy, subfp, fp->caller).rval;
101  }
102  break;
103  default:
104  rval = ExpandPrivateRval(ctx, NULL, NULL, rp->val.item, rp->val.type);
105  assert(rval.item);
106  break;
107  }
108 
109  /*
110 
111  Collect compound values into containers only if the function
112  supports it.
113 
114  Functions without FNCALL_OPTION_COLLECTING don't collect
115  Rlist elements. So in the policy, you call
116  and(splitstring("a b")) and it ends up as and("a", "b").
117  This expansion happens once per FnCall, not for all
118  arguments.
119 
120  Functions with FNCALL_OPTION_COLLECTING instead collect all
121  the results of a FnCall into a single JSON array object. It
122  requires functions to expect it, but it's the only
123  reasonable way to preserve backwards compatibility for
124  functions like and() and allow nesting of calls to functions
125  that take and return compound data types.
126 
127  */
128  RlistAppendAllTypes(&expanded_args, rval.item, rval.type,
130  RvalDestroy(rval);
131  }
132 
133  return expanded_args;
134 }
135 
136 /*******************************************************************/
137 
139 {
140  FnCall *fp;
141 
142  if (rval.type != RVAL_TYPE_FNCALL)
143  {
144  return false;
145  }
146 
147  fp = (FnCall *) rval.item;
148 
149  if (FnCallTypeGet(fp->name))
150  {
151  return true;
152  }
153  else
154  {
155  return false;
156  }
157 }
158 
159 /*******************************************************************/
160 
161 FnCall *FnCallNew(const char *name, Rlist *args)
162 {
163  FnCall *fp = xmalloc(sizeof(FnCall));
164 
165  fp->name = xstrdup(name);
166  fp->args = args;
167 
168  return fp;
169 }
170 
171 /*******************************************************************/
172 
174 {
175  return FnCallNew(f->name, RlistCopyRewriter(f->args, map));
176 }
177 
179 {
180  return FnCallCopyRewriter(f, NULL);
181 }
182 
183 /*******************************************************************/
184 
186 {
187  if (fp)
188  {
189  free(fp->name);
190  RlistDestroy(fp->args);
191  }
192  free(fp);
193 }
194 
195 unsigned FnCallHash(const FnCall *fp, unsigned seed)
196 {
197  unsigned hash = StringHash(fp->name, seed);
198  return RlistHash(fp->args, hash);
199 }
200 
201 
202 FnCall *ExpandFnCall(EvalContext *ctx, const char *ns, const char *scope, const FnCall *f)
203 {
204  FnCall *result = NULL;
205  if (IsCf3VarString(f->name))
206  {
207  // e.g. usebundle => $(m)(arg0, arg1);
209  ExpandScalar(ctx, ns, scope, f->name, buf);
210 
211  result = FnCallNew(BufferData(buf), ExpandList(ctx, ns, scope, f->args, false));
212  BufferDestroy(buf);
213  }
214  else
215  {
216  result = FnCallNew(f->name, ExpandList(ctx, ns, scope, f->args, false));
217  }
218 
219  return result;
220 }
221 
222 void FnCallWrite(Writer *writer, const FnCall *call)
223 {
224  WriterWrite(writer, call->name);
225  WriterWriteChar(writer, '(');
226 
227  for (const Rlist *rp = call->args; rp != NULL; rp = rp->next)
228  {
229  switch (rp->val.type)
230  {
231  case RVAL_TYPE_SCALAR:
232  ScalarWrite(writer, RlistScalarValue(rp), true);
233  break;
234 
235  case RVAL_TYPE_FNCALL:
236  FnCallWrite(writer, RlistFnCallValue(rp));
237  break;
238 
239  default:
240  WriterWrite(writer, "(** Unknown argument **)\n");
241  break;
242  }
243 
244  if (rp->next != NULL)
245  {
246  WriterWriteChar(writer, ',');
247  }
248  }
249 
250  WriterWriteChar(writer, ')');
251 }
252 
253 /*******************************************************************/
254 
255 static FnCallResult CallFunction(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *expargs)
256 {
257  const Rlist *rp = fp->args;
258  const FnCallType *fncall_type = FnCallTypeGet(fp->name);
259  if (fncall_type == NULL)
260  {
261  FatalError(ctx, "Function call '%s' has unknown type", fp->name);
262  }
263 
264  int argnum = 0;
265  for (argnum = 0; rp != NULL && fncall_type->args[argnum].pattern != NULL; argnum++)
266  {
267  if (rp->val.type != RVAL_TYPE_FNCALL)
268  {
269  /* Nested functions will not match to lval so don't bother checking */
271  fncall_type->args[argnum].dtype,
272  fncall_type->args[argnum].pattern, 1);
274  {
275  FatalError(ctx, "In function '%s', error in variable '%s', '%s'", fp->name, (const char *)rp->val.item, SyntaxTypeMatchToString(err));
276  }
277  }
278 
279  rp = rp->next;
280  }
281 
282  if (argnum != RlistLen(expargs) && !(fncall_type->options & FNCALL_OPTION_VARARG))
283  {
284  char *args_str = RlistToString(expargs);
285  Log(LOG_LEVEL_ERR, "Argument template mismatch handling function %s(%s)", fp->name, args_str);
286  free(args_str);
287 
288  rp = expargs;
289  for (int i = 0; i < argnum; i++)
290  {
291  if (rp != NULL)
292  {
293  char *rval_str = RvalToString(rp->val);
294  Log(LOG_LEVEL_ERR, " arg[%d] range %s\t %s ", i, fncall_type->args[i].pattern, rval_str);
295  free(rval_str);
296  }
297  else
298  {
299  Log(LOG_LEVEL_ERR, " arg[%d] range %s\t ? ", i, fncall_type->args[i].pattern);
300  }
301  }
302 
303  FatalError(ctx, "Bad arguments");
304  }
305 
306  return (*fncall_type->impl) (ctx, policy, fp, expargs);
307 }
308 
309 FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller)
310 {
311  assert(ctx);
312  assert(policy);
313  assert(fp);
314  fp->caller = caller;
315 
317  {
318  Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because evaluation was turned off in the evaluator",
319  fp->name);
321  }
322 
323  const FnCallType *fp_type = FnCallTypeGet(fp->name);
324 
325  if (!fp_type)
326  {
327  if (caller)
328  {
329  Log(LOG_LEVEL_ERR, "No such FnCall '%s' in promise '%s' near line %zu",
330  fp->name, PromiseGetBundle(caller)->source_path, caller->offset.line);
331  }
332  else
333  {
334  Log(LOG_LEVEL_ERR, "No such FnCall '%s', context info unavailable", fp->name);
335  }
336 
338  }
339 
340  Rlist *expargs = NewExpArgs(ctx, policy, fp, fp_type);
341 
342  Writer *fncall_writer = NULL;
343  const char *fncall_string = "";
345  {
346  fncall_writer = StringWriter();
347  FnCallWrite(fncall_writer, fp);
348  fncall_string = StringWriterData(fncall_writer);
349  }
350 
351  // Check if arguments are resolved, except for delayed evaluation functions
352  if ( ! (fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION) &&
353  RlistIsUnresolved(expargs))
354  {
355  // Special case: ifelse(isvariable("x"), $(x), "default")
356  // (the first argument will come down expanded as "!any")
357  if (strcmp(fp->name, "ifelse") == 0 &&
358  RlistLen(expargs) == 3 &&
359  strcmp("!any", RlistScalarValueSafe(expargs)) == 0 &&
360  !RlistIsUnresolved(expargs->next->next))
361  {
362  Log(LOG_LEVEL_DEBUG, "Allowing ifelse() function evaluation even"
363  " though its arguments contain unresolved variables: %s",
364  fncall_string);
365  }
366  else
367  {
369  {
370  Log(LOG_LEVEL_DEBUG, "Skipping function evaluation for now,"
371  " arguments contain unresolved variables: %s",
372  fncall_string);
373  WriterClose(fncall_writer);
374  }
375  RlistDestroy(expargs);
377  }
378  }
379 
380  Rval cached_rval;
381  if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval))
382  {
384  {
386  "Using previously cached result for function: %s",
387  fncall_string);
388  WriterClose(fncall_writer);
389  }
390  Writer *w = StringWriter();
391  FnCallWrite(w, fp);
392  WriterClose(w);
393  RlistDestroy(expargs);
394 
395  return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) };
396  }
397 
399  {
400  Log(LOG_LEVEL_DEBUG, "Evaluating function: %s",
401  fncall_string);
402  WriterClose(fncall_writer);
403  }
404 
405  FnCallResult result = CallFunction(ctx, policy, fp, expargs);
406 
407  if (result.status == FNCALL_FAILURE)
408  {
409  RlistDestroy(expargs);
411  }
412 
413  if (fp_type->options & FNCALL_OPTION_CACHED)
414  {
415  Writer *w = StringWriter();
416  FnCallWrite(w, fp);
417  Log(LOG_LEVEL_VERBOSE, "Caching result for function '%s'", StringWriterData(w));
418  WriterClose(w);
419 
420  EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval);
421  }
422 
423  RlistDestroy(expargs);
424 
425  return result;
426 }
427 
428 /*******************************************************************/
429 
430 const FnCallType *FnCallTypeGet(const char *name)
431 {
432  int i;
433 
434  for (i = 0; CF_FNCALL_TYPES[i].name != NULL; i++)
435  {
436  if (strcmp(CF_FNCALL_TYPES[i].name, name) == 0)
437  {
438  return CF_FNCALL_TYPES + i;
439  }
440  }
441 
442  return NULL;
443 }
void * xmalloc(size_t size)
Definition: alloc-mini.c:46
char * xstrdup(const char *str)
Definition: alloc-mini.c:56
void FatalError(const EvalContext *ctx, char *s,...)
Definition: audit.c:94
void BufferDestroy(Buffer *buffer)
Destroys a buffer and frees the memory associated with it.
Definition: buffer.c:72
const char * BufferData(const Buffer *buffer)
Provides a pointer to the internal data.
Definition: buffer.c:470
Buffer * BufferNewWithCapacity(unsigned int initial_capacity)
Allocates and setup a buffer with a capacity different than the default capacity.
Definition: buffer.c:35
@ RVAL_TYPE_SCALAR
Definition: cf3.defs.h:606
@ RVAL_TYPE_FNCALL
Definition: cf3.defs.h:608
void free(void *)
void DoCleanupAndExit(int ret)
Definition: cleanup.c:57
#define CF_MAXVARSIZE
Definition: definitions.h:36
void EvalContextFunctionCachePut(EvalContext *ctx, const FnCall *fp, const Rlist *args, const Rval *rval)
bool EvalContextFunctionCacheGet(const EvalContext *ctx, const FnCall *fp, const Rlist *args, Rval *rval_out)
bool EvalContextGetEvalOption(EvalContext *ctx, EvalContextOption option)
@ EVAL_OPTION_EVAL_FUNCTIONS
Definition: eval_context.h:108
const FnCallType CF_FNCALL_TYPES[]
int FnNumArgs(const FnCallType *call_type)
Definition: evalfunction.c:106
Rlist * ExpandList(EvalContext *ctx, const char *ns, const char *scope, const Rlist *list, int expandnaked)
Definition: expand.c:466
char * ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, const char *string, Buffer *out)
Definition: expand.c:516
Rval ExpandPrivateRval(EvalContext *ctx, const char *ns, const char *scope, const void *rval_item, RvalType rval_type)
Definition: expand.c:313
static FnCallResult CallFunction(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *expargs)
Definition: fncall.c:255
const FnCallType * FnCallTypeGet(const char *name)
Definition: fncall.c:430
FnCall * FnCallNew(const char *name, Rlist *args)
Definition: fncall.c:161
FnCall * FnCallCopyRewriter(const FnCall *f, JsonElement *map)
Definition: fncall.c:173
FnCall * FnCallCopy(const FnCall *f)
Definition: fncall.c:178
FnCall * ExpandFnCall(EvalContext *ctx, const char *ns, const char *scope, const FnCall *f)
Definition: fncall.c:202
void FnCallDestroy(FnCall *fp)
Definition: fncall.c:185
unsigned FnCallHash(const FnCall *fp, unsigned seed)
Definition: fncall.c:195
void FnCallWrite(Writer *writer, const FnCall *call)
Definition: fncall.c:222
Rlist * NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, const FnCallType *fp_type)
Definition: fncall.c:61
FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller)
Definition: fncall.c:309
bool FnCallIsBuiltIn(Rval rval)
Definition: fncall.c:138
@ FNCALL_SUCCESS
Definition: fncall.h:40
@ FNCALL_FAILURE
Definition: fncall.h:41
@ FNCALL_OPTION_COLLECTING
Definition: fncall.h:69
@ FNCALL_OPTION_CACHED
Definition: fncall.h:65
@ FNCALL_OPTION_DELAYED_EVALUATION
Definition: fncall.h:72
@ FNCALL_OPTION_VARARG
Definition: fncall.h:62
#define NULL
Definition: getopt1.c:56
LogLevel LogGetGlobalLevel(void)
Definition: logging.c:581
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_ERR
Definition: logging.h:42
@ LOG_LEVEL_DEBUG
Definition: logging.h:47
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
const Bundle * PromiseGetBundle(const Promise *pp)
Definition: policy.c:2671
void PromiseRef(LogLevel level, const Promise *pp)
Definition: promises.c:769
FnCall * RlistFnCallValue(const Rlist *rlist)
Definition: rlist.c:105
Rlist * RlistCopyRewriter(const Rlist *rp, JsonElement *map)
Definition: rlist.c:481
char * RlistScalarValue(const Rlist *rlist)
Definition: rlist.c:83
char * RlistScalarValueSafe(const Rlist *rlist)
Definition: rlist.c:93
Rlist * RlistAppendAllTypes(Rlist **start, const void *item, RvalType type, bool allow_all_types)
Definition: rlist.c:566
char * RvalToString(Rval rval)
Definition: rlist.c:1396
void RlistDestroy(Rlist *rl)
Definition: rlist.c:501
void RvalDestroy(Rval rval)
Definition: rlist.c:940
unsigned int RlistHash(const Rlist *list, unsigned seed)
Definition: rlist.c:1428
int RlistLen(const Rlist *start)
Definition: rlist.c:672
char * RlistToString(const Rlist *rlist)
Definition: rlist.c:1403
void ScalarWrite(Writer *writer, const char *s, bool quote)
Definition: rlist.c:1335
Rlist * RlistCopy(const Rlist *rp)
Definition: rlist.c:494
Rval RvalCopy(Rval rval)
Definition: rlist.c:474
unsigned int StringHash(const char *str, unsigned int seed)
Definition: string_lib.c:90
Definition: buffer.h:50
DataType dtype
Definition: fncall.h:53
const char * pattern
Definition: fncall.h:52
FnCallStatus status
Definition: fncall.h:46
Rval rval
Definition: fncall.h:47
FnCallOption options
Definition: fncall.h:82
FnCallResult(* impl)(EvalContext *, const Policy *, const FnCall *, const Rlist *)
Definition: fncall.h:80
const FnCallArg * args
Definition: fncall.h:79
const char * name
Definition: fncall.h:77
Definition: fncall.h:31
const Promise * caller
Definition: fncall.h:35
char * name
Definition: fncall.h:32
Rlist * args
Definition: fncall.h:33
Definition: policy.h:53
SourceOffset offset
Definition: policy.h:121
Definition: rlist.h:35
Rval val
Definition: rlist.h:36
Rlist * next
Definition: rlist.h:37
Definition: cf3.defs.h:614
RvalType type
Definition: cf3.defs.h:616
void * item
Definition: cf3.defs.h:615
size_t line
Definition: policy.h:65
Definition: writer.c:45
const char * SyntaxTypeMatchToString(SyntaxTypeMatch result)
Definition: syntax.c:262
SyntaxTypeMatch CheckConstraintTypeMatch(const char *lval, Rval rval, DataType dt, const char *range, int level)
Definition: syntax.c:300
SyntaxTypeMatch
Definition: syntax.h:41
@ SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED
Definition: syntax.h:44
@ SYNTAX_TYPE_MATCH_OK
Definition: syntax.h:42
bool RlistIsUnresolved(const Rlist *list)
Definition: vars.c:41
bool IsCf3VarString(const char *str)
Definition: vars.c:123
size_t WriterWrite(Writer *writer, const char *str)
Definition: writer.c:193
const char * StringWriterData(const Writer *writer)
Definition: writer.c:229
size_t WriterWriteChar(Writer *writer, char c)
Definition: writer.c:200
void WriterClose(Writer *writer)
Definition: writer.c:242
Writer * StringWriter(void)
Definition: writer.c:67