1 /*
2 Copyright 2020 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
26 #include <history.h>
27
28 #include <monitoring.h> /* MakeTimekey */
29 #include <actuator.h>
30 #include <promises.h>
31 #include <ornaments.h>
32 #include <locks.h>
33 #include <policy.h>
34 #include <vars.h>
35 #include <known_dirs.h>
36 #include <sysinfo.h>
37 #include <unix.h>
38 #include <scope.h>
39 #include <eval_context.h>
40 #include <item_lib.h>
41 #include <exec_tools.h>
42 #include <conversion.h>
43 #include <instrumentation.h>
44 #include <files_interfaces.h>
45 #include <pipes.h>
46 #include <matching.h>
47 #include <string_lib.h>
48 #include <regex.h> /* StringMatchFull */
49 #include <timeout.h>
50 #include <constants.h>
51 #include <time_classes.h>
52 #include <file_lib.h>
53 #include <assert.h>
54
55
56 #define CF_DUNBAR_WORK 30
57
58
59 typedef struct
60 {
61 char *path;
62 Item *output;
63 } CustomMeasurement;
64
65 static int MONITOR_RESTARTED = true;
66 static CustomMeasurement ENTERPRISE_DATA[CF_DUNBAR_WORK];
67
PutRecordForTime(CF_DB * db,time_t time,const Averages * values)68 static void PutRecordForTime(CF_DB *db, time_t time, const Averages *values)
69 {
70 char timekey[CF_MAXVARSIZE];
71
72 MakeTimekey(time, timekey);
73
74 WriteDB(db, timekey, values, sizeof(Averages));
75 }
76
Nova_SaveFilePosition(const char * handle,const char * name,long fileptr)77 static void Nova_SaveFilePosition(const char *handle, const char *name, long fileptr)
78 {
79 CF_DB *dbp;
80 char *key = StringConcatenate(2, handle, name);
81
82 if (!OpenDB(&dbp, dbid_static))
83 {
84 return;
85 }
86
87 Log(LOG_LEVEL_VERBOSE, "Saving state for %s at %ld", key, fileptr);
88 WriteDB(dbp, key, &fileptr, sizeof(long));
89 CloseDB(dbp);
90 free(key);
91 }
92
Nova_RestoreFilePosition(const char * handle,const char * name)93 static long Nova_RestoreFilePosition(const char *handle, const char *name)
94 {
95 CF_DB *dbp;
96 long fileptr;
97 char *key = StringConcatenate(2, handle, name);
98
99 if (!OpenDB(&dbp, dbid_static))
100 {
101 return 0L;
102 }
103
104 ReadDB(dbp, key, &fileptr, sizeof(long));
105 Log(LOG_LEVEL_VERBOSE, "Resuming state for %s at %ld", key, fileptr);
106 CloseDB(dbp);
107 free(key);
108 return fileptr;
109 }
110
Nova_DumpSlowlyVaryingObservations(void)111 static void Nova_DumpSlowlyVaryingObservations(void)
112 {
113 CF_DB *dbp;
114 CF_DBC *dbcp;
115 char *key;
116 void *stored;
117 int ksize, vsize;
118 char name[CF_BUFSIZE];
119
120 if (!OpenDB(&dbp, dbid_static))
121 {
122 return;
123 }
124
125 snprintf(name, CF_BUFSIZE - 1, "%s%cstatic_data", GetStateDir(), FILE_SEPARATOR);
126
127
128 FILE *fout = safe_fopen(name, "w");
129 if (fout == NULL)
130 {
131 Log(LOG_LEVEL_ERR, "Unable to save discovery data in '%s'. (fopen: %s)", name, GetErrorStr());
132 CloseDB(dbp);
133 return;
134 }
135
136 /* Acquire a cursor for the database. */
137
138 if (!NewDBCursor(dbp, &dbcp))
139 {
140 Log(LOG_LEVEL_INFO, "Unable to scan class db");
141 CloseDB(dbp);
142 return;
143 }
144
145 while (NextDB(dbcp, &key, &ksize, &stored, &vsize))
146 {
147 char buf[CF_MAXVARSIZE], lval[CF_MAXVARSIZE], rval[CF_BUFSIZE];
148
149 strncpy(buf, key, CF_MAXVARSIZE - 1);
150
151 sscanf(buf, "%s:", lval);
152
153 if (stored != NULL)
154 {
155 strncpy(rval, stored, CF_BUFSIZE - 1);
156 fprintf(fout, "%s:%s\n", lval, rval);
157 }
158 }
159
160 DeleteDBCursor(dbcp);
161 CloseDB(dbp);
162 fclose(fout);
163 }
164
Nova_HistoryUpdate(time_t time,const Averages * newvals)165 static void Nova_HistoryUpdate(time_t time, const Averages *newvals)
166 {
167 CF_DB *dbp;
168
169 if (!OpenDB(&dbp, dbid_history))
170 {
171 return;
172 }
173
174 PutRecordForTime(dbp, time, newvals);
175
176 CloseDB(dbp);
177 }
178
NovaReSample(EvalContext * ctx,int slot,const Attributes * attr,const Promise * pp,PromiseResult * result)179 static Item *NovaReSample(EvalContext *ctx, int slot, const Attributes *attr, const Promise *pp, PromiseResult *result)
180 {
181 assert(attr != NULL);
182 Attributes a = *attr; // TODO: try to remove this local copy
183 CfLock thislock;
184 char eventname[CF_BUFSIZE];
185 struct timespec start;
186 FILE *fin = NULL;
187 mode_t maskval = 0;
188 const char *handle = PromiseGetHandle(pp);
189
190 if (a.measure.stream_type && strcmp(a.measure.stream_type, "pipe") == 0)
191 {
192 if (!IsExecutable(CommandArg0(pp->promiser)))
193 {
194 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "%s promises to be executable but isn't\n", pp->promiser);
195 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
196 return NULL;
197 }
198 else
199 {
200 Log(LOG_LEVEL_VERBOSE, "Promiser string contains a valid executable (%s) - ok", CommandArg0(pp->promiser));
201 }
202 }
203
204 // Force a measurement if restarted:
205 const int ifelapsed = MONITOR_RESTARTED ? 0 : a.transaction.ifelapsed;
206 const int expireafter = a.transaction.expireafter;
207
208 CFSTARTTIME = time(NULL);
209
210 thislock = AcquireLock(ctx, pp->promiser, VUQNAME, CFSTARTTIME, ifelapsed, expireafter, pp, false);
211
212 if (thislock.lock == NULL)
213 {
214 if (strcmp(a.measure.history_type, "log") == 0)
215 {
216 DeleteItemList(ENTERPRISE_DATA[slot].output);
217 ENTERPRISE_DATA[slot].output = NULL;
218 }
219 else
220 {
221 /* If static or time-series, and too soon or busy then use a cached value
222 to avoid artificial gaps in the history */
223 }
224
225 MONITOR_RESTARTED = false;
226 return ENTERPRISE_DATA[slot].output;
227 }
228 else
229 {
230 DeleteItemList(ENTERPRISE_DATA[slot].output);
231 ENTERPRISE_DATA[slot].output = NULL;
232
233 Log(LOG_LEVEL_INFO, "Sampling \'%s\' ...(timeout=%d,owner=%ju,group=%ju)", pp->promiser, a.contain.timeout,
234 (uintmax_t)a.contain.owner, (uintmax_t)a.contain.group);
235
236 start = BeginMeasure();
237
238 if (a.contain.timeout != 0)
239 {
240 SetTimeOut(a.contain.timeout);
241 }
242
243 /* Stream types */
244
245 if (a.measure.stream_type && strcmp(a.measure.stream_type, "file") == 0)
246 {
247 long filepos = 0;
248 struct stat sb;
249
250 Log(LOG_LEVEL_VERBOSE, "Stream \"%s\" is a plain file", pp->promiser);
251
252 if (stat(pp->promiser, &sb) == -1)
253 {
254 Log(LOG_LEVEL_INFO, "Unable to find stream '%s'. (stat: %s)",
255 pp->promiser, GetErrorStr());
256 YieldCurrentLock(thislock);
257 MONITOR_RESTARTED = false;
258
259 return NULL;
260 }
261
262 fin = safe_fopen(pp->promiser, "r");
263
264 if (a.measure.growing)
265 {
266 filepos = Nova_RestoreFilePosition(handle, pp->promiser);
267
268 if (sb.st_size >= filepos)
269 {
270 fseek(fin, filepos, SEEK_SET);
271 }
272 }
273 }
274 else if (a.measure.stream_type && strcmp(a.measure.stream_type, "pipe") == 0)
275 {
276 Log(LOG_LEVEL_VERBOSE, "(Setting pipe umask to %jo)", (uintmax_t)a.contain.umask);
277 maskval = umask(a.contain.umask);
278
279 if (a.contain.umask == 0)
280 {
281 Log(LOG_LEVEL_VERBOSE, "Programming %s running with umask 0! Use umask= to set", pp->promiser);
282 }
283
284
285 // Mark: This is strange that we used these wrappers. Currently no way of setting these
286 a.contain.owner = -1;
287 a.contain.group = -1;
288 a.contain.chdir = NULL;
289 a.contain.chroot = NULL;
290 // Mark: they were unset, and would fail for non-root(!)
291
292 if (a.contain.shelltype == SHELL_TYPE_POWERSHELL)
293 {
294 #ifdef __MINGW32__
295 fin =
296 cf_popen_powershell_setuid(pp->promiser, "r", a.contain.owner, a.contain.group, a.contain.chdir,
297 a.contain.chroot, false);
298 #else // !__MINGW32__
299 Log(LOG_LEVEL_ERR, "Powershell is only supported on Windows");
300 YieldCurrentLock(thislock);
301 MONITOR_RESTARTED = false;
302 return NULL;
303 #endif // !__MINGW32__
304 }
305 else if (a.contain.shelltype == SHELL_TYPE_USE)
306 {
307 fin =
308 cf_popen_shsetuid(pp->promiser, "r", a.contain.owner, a.contain.group, a.contain.chdir,
309 a.contain.chroot, false);
310 }
311 else
312 {
313 fin =
314 cf_popensetuid(pp->promiser, "r", a.contain.owner, a.contain.group, a.contain.chdir,
315 a.contain.chroot, false);
316 }
317 }
318
319 /* generic file stream */
320
321 if (fin == NULL)
322 {
323 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a,
324 "Couldn't open pipe to command '%s'. (cf_popen: %s)", pp->promiser, GetErrorStr());
325 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
326 YieldCurrentLock(thislock);
327 MONITOR_RESTARTED = false;
328 return ENTERPRISE_DATA[slot].output;
329 }
330
331 size_t line_size = CF_BUFSIZE;
332 char *line = xmalloc(line_size);
333
334 for (;;)
335 {
336 ssize_t res = CfReadLine(&line, &line_size, fin);
337 if (res == -1)
338 {
339 if (!feof(fin))
340 {
341 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_TIMEOUT, pp, &a, "Sample stream '%s'. (fread: %s)",
342 pp->promiser, GetErrorStr());
343 *result = PromiseResultUpdate(*result, PROMISE_RESULT_TIMEOUT);
344 YieldCurrentLock(thislock);
345 free(line);
346 return ENTERPRISE_DATA[slot].output;
347 }
348 else
349 {
350 break;
351 }
352 }
353
354 AppendItem(&(ENTERPRISE_DATA[slot].output), line, NULL);
355 Log(LOG_LEVEL_INFO, "Sampling => %s", line);
356 }
357
358 free(line);
359
360 if (a.measure.stream_type && strcmp(a.measure.stream_type, "file") == 0)
361 {
362 long fileptr = ftell(fin);
363
364 fclose(fin);
365 Nova_SaveFilePosition(handle, pp->promiser, fileptr);
366 }
367 else if (a.measure.stream_type && strcmp(a.measure.stream_type, "pipe") == 0)
368 {
369 cf_pclose(fin);
370 }
371 }
372
373 if (a.contain.timeout != 0)
374 {
375 alarm(0);
376 signal(SIGALRM, SIG_DFL);
377 }
378
379 Log(LOG_LEVEL_INFO, "Collected sample of %s", pp->promiser);
380 umask(maskval);
381 YieldCurrentLock(thislock);
382 MONITOR_RESTARTED = false;
383
384 snprintf(eventname, CF_BUFSIZE - 1, "Sample(%s)", pp->promiser);
385 EndMeasure(eventname, start);
386 return ENTERPRISE_DATA[slot].output;
387 }
388
HistoryUpdate(EvalContext * ctx,const Averages * const newvals)389 void HistoryUpdate(EvalContext *ctx, const Averages *const newvals)
390 {
391 CfLock thislock;
392 time_t now = time(NULL);
393
394 /* We do this only once per hour - this should not be changed */
395
396 Banner("Update long-term history");
397
398 Policy *history_db_policy = PolicyNew();
399 Promise *pp = NULL;
400 {
401 Bundle *bp = PolicyAppendBundle(history_db_policy, NamespaceDefault(), "history_db_bundle", "agent", NULL, NULL);
402 BundleSection *sp = BundleAppendSection(bp, "history_db");
403
404 pp = BundleSectionAppendPromise(sp, "the long term memory", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, NULL, NULL);
405 }
406 assert(pp);
407
408 thislock = AcquireLock(ctx, pp->promiser, VUQNAME, now, 59, 0, pp, false);
409
410 if (thislock.lock == NULL)
411 {
412 PolicyDestroy(history_db_policy);
413 return;
414 }
415
416 /* Refresh the class context of the agent */
417
418 EvalContextClear(ctx);
419
420 DetectEnvironment(ctx);
421 time_t t = SetReferenceTime();
422 UpdateTimeClasses(ctx, t);
423
424 EvalContextHeapPersistentLoadAll(ctx);
425 LoadSystemConstants(ctx);
426
427 YieldCurrentLock(thislock);
428 PolicyDestroy(history_db_policy);
429
430 Nova_HistoryUpdate(CFSTARTTIME, newvals);
431
432 Nova_DumpSlowlyVaryingObservations();
433 }
434
NovaGetMeasurementStream(EvalContext * ctx,const Attributes * a,const Promise * pp,PromiseResult * result)435 static Item *NovaGetMeasurementStream(EvalContext *ctx, const Attributes *a, const Promise *pp, PromiseResult *result)
436 {
437 int i;
438
439 for (i = 0; i < CF_DUNBAR_WORK; i++)
440 {
441 if (ENTERPRISE_DATA[i].path == NULL)
442 {
443 break;
444 }
445
446 if (strcmp(ENTERPRISE_DATA[i].path, pp->promiser) == 0)
447 {
448 ENTERPRISE_DATA[i].output = NovaReSample(ctx, i, a, pp, result);
449 return ENTERPRISE_DATA[i].output;
450 }
451 }
452
453 ENTERPRISE_DATA[i].path = xstrdup(pp->promiser);
454 ENTERPRISE_DATA[i].output = NovaReSample(ctx, i, a, pp, result);
455 return ENTERPRISE_DATA[i].output;
456 }
457
NovaExtractValueFromStream(EvalContext * ctx,const char * handle,Item * stream,const Attributes * a,const Promise * pp,double * value_out)458 static PromiseResult NovaExtractValueFromStream(EvalContext *ctx, const char *handle,
459 Item *stream, const Attributes *a,
460 const Promise *pp, double *value_out)
461 {
462 char value[CF_MAXVARSIZE];
463 int count = 1, found = false, match_count = 0, done = false;
464 double real_val = 0;
465 Item *ip, *match = NULL;
466 bool ok_conversion = true;
467
468 for (ip = stream; ip != NULL; ip = ip->next)
469 {
470 if (count == a->measure.select_line_number)
471 {
472 found = true;
473 match = ip;
474 match_count++;
475 }
476
477 if (a->measure.select_line_matching && StringMatchFull(a->measure.select_line_matching, ip->name))
478 {
479 Log(LOG_LEVEL_VERBOSE, " Found regex '%s' matches line '%s'", a->measure.select_line_matching, ip->name);
480 found = true;
481 match = ip;
482
483 if (a->measure.extraction_regex)
484 {
485 switch (a->measure.data_type)
486 {
487 case CF_DATA_TYPE_INT:
488 case CF_DATA_TYPE_REAL:
489 case CF_DATA_TYPE_COUNTER:
490
491 strncpy(value, ExtractFirstReference(a->measure.extraction_regex, match->name), CF_MAXVARSIZE - 1);
492
493 if (strcmp(value, "CF_NOMATCH") == 0)
494 {
495 ok_conversion = false;
496 Log(LOG_LEVEL_VERBOSE, "Was not able to match a value with '%s' on '%s'",
497 a->measure.extraction_regex, match->name);
498 }
499 else
500 {
501 if (ok_conversion)
502 {
503 Log(LOG_LEVEL_VERBOSE, "Found candidate match value of '%s'", value);
504
505 if (a->measure.policy == MEASURE_POLICY_SUM || a->measure.policy == MEASURE_POLICY_AVERAGE)
506 {
507 double delta = 0;
508 if (DoubleFromString(value, &delta))
509 {
510 real_val += delta;
511 }
512 else
513 {
514 Log(LOG_LEVEL_ERR, "Error in double conversion from string value: %s", value);
515 return PROMISE_RESULT_FAIL;
516 }
517 }
518 else
519 {
520 if (!DoubleFromString(value, &real_val))
521 {
522 Log(LOG_LEVEL_ERR, "Error in double conversion from string value: %s", value);
523 return PROMISE_RESULT_FAIL;
524 }
525 }
526
527 match_count++;
528
529 if (a->measure.policy == MEASURE_POLICY_FIRST)
530 {
531 done = true;
532 }
533 }
534 }
535 break;
536
537 default:
538 Log(LOG_LEVEL_ERR, "Unexpected data type in data_type attribute: %d", a->measure.data_type);
539 }
540 }
541
542 }
543
544 count++;
545
546 if (done)
547 {
548 break;
549 }
550 }
551
552 if (!found)
553 {
554 cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_FAIL, pp, a, "Could not locate the line for promise '%s'", handle);
555 *value_out = 0.0;
556 return PROMISE_RESULT_FAIL;
557 }
558
559 switch (a->measure.data_type)
560 {
561 case CF_DATA_TYPE_COUNTER:
562
563 real_val = (double) match_count;
564 break;
565
566 case CF_DATA_TYPE_INT:
567
568 if (match_count > 1)
569 {
570 Log(LOG_LEVEL_INFO, "Warning: %d lines matched the line_selection \"%s\"- making best average",
571 match_count, a->measure.select_line_matching);
572 }
573
574 if (match_count > 0 && a->measure.policy == MEASURE_POLICY_AVERAGE) // If not "average" then "sum"
575 {
576 real_val /= match_count;
577 }
578 break;
579
580 case CF_DATA_TYPE_REAL:
581
582 if (match_count > 1)
583 {
584 Log(LOG_LEVEL_INFO, "Warning: %d lines matched the line_selection \"%s\"- making best average",
585 match_count, a->measure.select_line_matching);
586 }
587
588 if (match_count > 0)
589 {
590 real_val /= match_count;
591 }
592
593 break;
594
595 default:
596 Log(LOG_LEVEL_ERR, "Unexpected data type in data_type attribute: %d", a->measure.data_type);
597 }
598
599 if (!ok_conversion)
600 {
601 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Unable to extract a value from the matched line '%s'", match->name);
602 PromiseRef(LOG_LEVEL_INFO, pp);
603 *value_out = 0.0;
604 return PROMISE_RESULT_FAIL;
605 }
606
607 Log(LOG_LEVEL_INFO, "Extracted value \"%f\" for promise \"%s\"", real_val, handle);
608 *value_out = real_val;
609 return PROMISE_RESULT_NOOP;
610 }
611
NovaLogSymbolicValue(EvalContext * ctx,const char * handle,Item * stream,const Attributes * a,const Promise * pp,PromiseResult * result)612 static void NovaLogSymbolicValue(EvalContext *ctx, const char *handle, Item *stream,
613 const Attributes *a, const Promise *pp, PromiseResult *result)
614 {
615 char value[CF_BUFSIZE], sdate[CF_MAXVARSIZE], filename[CF_BUFSIZE];
616 int count = 1, found = false, match_count = 0;
617 Item *ip, *match = NULL, *matches = NULL;
618 time_t now = time(NULL);
619
620 if (stream == NULL)
621 {
622 Log(LOG_LEVEL_VERBOSE, "No stream to measure");
623 return;
624 }
625
626 Log(LOG_LEVEL_VERBOSE, "Locate and log sample ...");
627
628 for (ip = stream; ip != NULL; ip = ip->next)
629 {
630 if (ip->name == NULL)
631 {
632 continue;
633 }
634
635 if (count == a->measure.select_line_number)
636 {
637 Log(LOG_LEVEL_VERBOSE, "Found line %d by number...", count);
638 found = true;
639 match_count = 1;
640 match = ip;
641
642 if (a->measure.extraction_regex)
643 {
644 Log(LOG_LEVEL_VERBOSE, "Now looking for a matching extractor \"%s\"", a->measure.extraction_regex);
645 strncpy(value, ExtractFirstReference(a->measure.extraction_regex, match->name), CF_MAXVARSIZE - 1);
646 Log(LOG_LEVEL_INFO, "Extracted value \"%s\" for promise \"%s\"", value, handle);
647 AppendItem(&matches, value, NULL);
648 }
649 else
650 {
651 Log(LOG_LEVEL_INFO, "Using entire line \"%s\" for promise \"%s\"", match->name, handle);
652 AppendItem(&matches, match->name, NULL);
653 }
654 break;
655 }
656
657 if (a->measure.select_line_matching && StringMatchFull(a->measure.select_line_matching, ip->name))
658 {
659 Log(LOG_LEVEL_VERBOSE, "Found line %d by pattern...", count);
660 found = true;
661 match = ip;
662 match_count++;
663
664 if (a->measure.extraction_regex)
665 {
666 Log(LOG_LEVEL_VERBOSE, "Now looking for a matching extractor \"%s\"", a->measure.extraction_regex);
667 strncpy(value, ExtractFirstReference(a->measure.extraction_regex, match->name), CF_MAXVARSIZE - 1);
668 Log(LOG_LEVEL_INFO, "Extracted value \"%s\" for promise \"%s\"", value, handle);
669 AppendItem(&matches, value, NULL);
670 }
671 else
672 {
673 Log(LOG_LEVEL_INFO, "Using entire line \"%s\" for promise \"%s\"", match->name, handle);
674 AppendItem(&matches, match->name, NULL);
675 }
676 }
677
678 count++;
679 }
680
681 if (!found)
682 {
683 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Promiser '%s' found no matching line.", pp->promiser);
684 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
685 return;
686 }
687
688 if (match_count > 1)
689 {
690 Log(LOG_LEVEL_INFO, "Warning: %d lines matched the line_selection \"%s\"- matching to last", match_count,
691 a->measure.select_line_matching);
692 }
693
694 switch (a->measure.data_type)
695 {
696 case CF_DATA_TYPE_COUNTER:
697 Log(LOG_LEVEL_VERBOSE, "Counted %d for %s", match_count, handle);
698 snprintf(value, CF_MAXVARSIZE, "%d", match_count);
699 break;
700
701 case CF_DATA_TYPE_STRING_LIST:
702 ItemList2CSV_bound(matches, value, sizeof(value), ',');
703 break;
704
705 default:
706 snprintf(value, CF_BUFSIZE, "%s", matches->name);
707 }
708
709 DeleteItemList(matches);
710
711 if (a->measure.history_type && strcmp(a->measure.history_type, "log") == 0)
712 {
713 snprintf(filename, CF_BUFSIZE, "%s%c%s_measure.log", GetStateDir(), FILE_SEPARATOR, handle);
714
715 FILE *fout = safe_fopen(filename, "a");
716 if (fout == NULL)
717 {
718 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Unable to open the output log \"%s\"", filename);
719 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
720 PromiseRef(LOG_LEVEL_ERR, pp);
721 return;
722 }
723
724 strncpy(sdate, ctime(&now), CF_MAXVARSIZE - 1);
725 if (Chop(sdate, CF_EXPANDSIZE) == -1)
726 {
727 Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
728 }
729
730 fprintf(fout, "%s,%ld,%s\n", sdate, (long) now, value);
731 Log(LOG_LEVEL_VERBOSE, "Logging: %s,%s to %s", sdate, value, filename);
732
733 fclose(fout);
734 }
735 else // scalar or static
736 {
737 CF_DB *dbp;
738 char id[CF_MAXVARSIZE];
739
740 if (!OpenDB(&dbp, dbid_static))
741 {
742 return;
743 }
744
745 snprintf(id, CF_MAXVARSIZE - 1, "%s:%d", handle, a->measure.data_type);
746 WriteDB(dbp, id, value, strlen(value) + 1);
747 CloseDB(dbp);
748 }
749 }
750
VerifyMeasurement(EvalContext * ctx,double * this,const Attributes * a,const Promise * pp)751 PromiseResult VerifyMeasurement(EvalContext *ctx, double *this,
752 const Attributes *a, const Promise *pp)
753 {
754 const char *handle = PromiseGetHandle(pp);
755 Item *stream = NULL;
756 int slot = 0;
757 double new_value;
758
759 if (!handle)
760 {
761 Log(LOG_LEVEL_ERR, "The promised measurement has no handle to register it by.");
762 return PROMISE_RESULT_NOOP;
763 }
764 else
765 {
766 Log(LOG_LEVEL_VERBOSE, "Considering promise \"%s\"", handle);
767 }
768
769 PromiseResult result = PROMISE_RESULT_NOOP;
770 switch (a->measure.data_type)
771 {
772 case CF_DATA_TYPE_COUNTER:
773 case CF_DATA_TYPE_INT:
774 case CF_DATA_TYPE_REAL:
775
776 /* First see if we can accommodate this measurement */
777 Log(LOG_LEVEL_VERBOSE, "Promise '%s' is numerical in nature", handle);
778
779 stream = NovaGetMeasurementStream(ctx, a, pp, &result);
780
781 if (strcmp(a->measure.history_type, "weekly") == 0)
782 {
783 if ((slot = NovaRegisterSlot(handle, pp->comment ? pp->comment : "User defined measure",
784 a->measure.units ? a->measure.units : "unknown", 0.0f, 100.0f, true)) < 0)
785 {
786 return result;
787 }
788
789 result = PromiseResultUpdate(result, NovaExtractValueFromStream(ctx, handle, stream, a, pp, &this[slot]));
790 Log(LOG_LEVEL_VERBOSE, "Setting Nova slot %d=%s to %lf", slot, handle, this[slot]);
791 }
792 else if (strcmp(a->measure.history_type, "log") == 0)
793 {
794 Log(LOG_LEVEL_VERBOSE, "Promise to log a numerical value");
795 NovaLogSymbolicValue(ctx, handle, stream, a, pp, &result);
796 }
797 else /* static */
798 {
799 Log(LOG_LEVEL_VERBOSE, "Promise to store a static numerical value");
800 result = PromiseResultUpdate(result, NovaExtractValueFromStream(ctx, handle, stream, a, pp, &new_value));
801 NovaNamedEvent(handle, new_value);
802 }
803 break;
804
805 default:
806
807 Log(LOG_LEVEL_VERBOSE, "Promise '%s' is symbolic in nature", handle);
808 stream = NovaGetMeasurementStream(ctx, a, pp, &result);
809 NovaLogSymbolicValue(ctx, handle, stream, a, pp, &result);
810 break;
811 }
812
813 return result;
814
815 // stream gets de-allocated in ReSample
816 }
817