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