suricata
output-json-stats.c
Go to the documentation of this file.
1 /* Copyright (C) 2014-2020 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 /**
19  * \file
20  *
21  * \author Tom DeCanio <td@npulsetech.com>
22  *
23  * Implements JSON stats counters logging portion of the engine.
24  */
25 
26 #include "suricata-common.h"
27 #include "detect.h"
28 #include "pkt-var.h"
29 #include "conf.h"
30 #include "detect-engine.h"
31 
32 #include "threads.h"
33 #include "threadvars.h"
34 #include "tm-threads.h"
35 
36 #include "util-print.h"
37 #include "util-time.h"
38 #include "util-unittest.h"
39 
40 #include "util-debug.h"
41 #include "output.h"
42 #include "util-privs.h"
43 #include "util-buffer.h"
44 
45 #include "util-logopenfile.h"
46 
47 #include "output-json.h"
48 #include "output-json-stats.h"
49 
50 #define MODULE_NAME "JsonStatsLog"
51 
52 extern bool stats_decoder_events;
53 extern const char *stats_decoder_events_prefix;
54 
55 /**
56  * specify which engine info will be printed in stats log.
57  * ALL means both last reload and ruleset stats.
58  */
59 typedef enum OutputEngineInfo_ {
64 
65 typedef struct OutputStatsCtx_ {
67  uint8_t flags; /** Store mode */
69 
70 typedef struct JsonStatsLogThread_ {
75 
76 static json_t *EngineStats2Json(const DetectEngineCtx *de_ctx,
77  const OutputEngineInfo output)
78 {
79  char timebuf[64];
80  const SigFileLoaderStat *sig_stat = NULL;
81 
82  json_t *jdata = json_object();
83  if (jdata == NULL) {
84  return NULL;
85  }
86 
87  if (output == OUTPUT_ENGINE_LAST_RELOAD || output == OUTPUT_ENGINE_ALL) {
89  CreateIsoTimeString(last_reload, timebuf, sizeof(timebuf));
90  json_object_set_new(jdata, "last_reload", json_string(timebuf));
91  }
92 
93  sig_stat = &de_ctx->sig_stat;
94  if ((output == OUTPUT_ENGINE_RULESET || output == OUTPUT_ENGINE_ALL) &&
95  sig_stat != NULL)
96  {
97  json_object_set_new(jdata, "rules_loaded",
98  json_integer(sig_stat->good_sigs_total));
99  json_object_set_new(jdata, "rules_failed",
100  json_integer(sig_stat->bad_sigs_total));
101  }
102 
103  return jdata;
104 }
105 
106 static TmEcode OutputEngineStats2Json(json_t **jdata, const OutputEngineInfo output)
107 {
109  if (de_ctx == NULL) {
110  goto err1;
111  }
112  /* Since we need to deference de_ctx pointer, we don't want to lost it. */
113  DetectEngineCtx *list = de_ctx;
114 
115  json_t *js_tenant_list = json_array();
116  json_t *js_tenant = NULL;
117 
118  if (js_tenant_list == NULL) {
119  goto err2;
120  }
121 
122  while(list) {
123  js_tenant = json_object();
124  if (js_tenant == NULL) {
125  goto err3;
126  }
127  json_object_set_new(js_tenant, "id", json_integer(list->tenant_id));
128 
129  json_t *js_stats = EngineStats2Json(list, output);
130  if (js_stats == NULL) {
131  goto err4;
132  }
133  json_object_update(js_tenant, js_stats);
134  json_array_append_new(js_tenant_list, js_tenant);
135  json_decref(js_stats);
136  list = list->next;
137  }
138 
140  *jdata = js_tenant_list;
141  return TM_ECODE_OK;
142 
143 err4:
144  json_object_clear(js_tenant);
145  json_decref(js_tenant);
146 
147 err3:
148  json_object_clear(js_tenant_list);
149  json_decref(js_tenant_list);
150 
151 err2:
153 
154 err1:
155  json_object_set_new(*jdata, "message", json_string("Unable to get info"));
156  return TM_ECODE_FAILED;
157 }
158 
160  return OutputEngineStats2Json(jdata, OUTPUT_ENGINE_LAST_RELOAD);
161 }
162 
164  return OutputEngineStats2Json(jdata, OUTPUT_ENGINE_RULESET);
165 }
166 
167 static json_t *OutputStats2Json(json_t *js, const char *key)
168 {
169  void *iter;
170 
171  const char *dot = strchr(key, '.');
172  if (dot == NULL)
173  return NULL;
174  if (strlen(dot) > 2) {
175  if (*(dot + 1) == '.' && *(dot + 2) != '\0')
176  dot = strchr(dot + 2, '.');
177  }
178 
179  size_t predot_len = (dot - key) + 1;
180  char s[predot_len];
181  strlcpy(s, key, predot_len);
182 
183  iter = json_object_iter_at(js, s);
184  const char *s2 = strchr(dot+1, '.');
185 
186  json_t *value = json_object_iter_value(iter);
187  if (value == NULL) {
188  value = json_object();
189 
190  if (!strncmp(s, "detect", 6)) {
191  json_t *js_engine = NULL;
192 
193  TmEcode ret = OutputEngineStats2Json(&js_engine, OUTPUT_ENGINE_ALL);
194  if (ret == TM_ECODE_OK && js_engine) {
195  json_object_set_new(value, "engines", js_engine);
196  }
197  }
198  json_object_set_new(js, s, value);
199  }
200  if (s2 != NULL) {
201  return OutputStats2Json(value, &key[dot-key+1]);
202  }
203  return value;
204 }
205 
206 /** \brief turn StatsTable into a json object
207  * \param flags JSON_STATS_* flags for controlling output
208  */
209 json_t *StatsToJSON(const StatsTable *st, uint8_t flags)
210 {
211  const char delta_suffix[] = "_delta";
212  struct timeval tval;
213  gettimeofday(&tval, NULL);
214 
215  json_t *js_stats = json_object();
216  if (unlikely(js_stats == NULL)) {
217  return NULL;
218  }
219 
220  /* Uptime, in seconds. */
221  double up_time_d = difftime(tval.tv_sec, st->start_time);
222  json_object_set_new(js_stats, "uptime",
223  json_integer((int)up_time_d));
224 
225  uint32_t u = 0;
226  if (flags & JSON_STATS_TOTALS) {
227  for (u = 0; u < st->nstats; u++) {
228  if (st->stats[u].name == NULL)
229  continue;
230  json_t *js_type = NULL;
231  const char *stat_name = st->stats[u].short_name;
232  /*
233  * When there's no short-name, the stat is added to
234  * the "global" stats namespace, just like "uptime"
235  */
236  if (st->stats[u].short_name == NULL) {
237  stat_name = st->stats[u].name;
238  js_type = js_stats;
239  } else {
240  js_type = OutputStats2Json(js_stats, st->stats[u].name);
241  }
242  if (js_type != NULL) {
243  json_object_set_new(js_type, stat_name, json_integer(st->stats[u].value));
244 
245  if (flags & JSON_STATS_DELTAS) {
246  char deltaname[strlen(stat_name) + strlen(delta_suffix) + 1];
247  snprintf(deltaname, sizeof(deltaname), "%s%s", stat_name, delta_suffix);
248  json_object_set_new(js_type, deltaname,
249  json_integer(st->stats[u].value - st->stats[u].pvalue));
250  }
251  }
252  }
253  }
254 
255  /* per thread stats - stored in a "threads" object. */
256  if (st->tstats != NULL && (flags & JSON_STATS_THREADS)) {
257  /* for each thread (store) */
258  json_t *threads = json_object();
259  if (unlikely(threads == NULL)) {
260  json_decref(js_stats);
261  return NULL;
262  }
263  uint32_t x;
264  for (x = 0; x < st->ntstats; x++) {
265  uint32_t offset = x * st->nstats;
266 
267  /* for each counter */
268  for (u = offset; u < (offset + st->nstats); u++) {
269  if (st->tstats[u].name == NULL)
270  continue;
271 
272  json_t *js_type = NULL;
273  const char *stat_name = st->tstats[u].short_name;
274  if (st->tstats[u].short_name == NULL) {
275  stat_name = st->tstats[u].name;
276  js_type = threads;
277  } else {
278  char str[256];
279  snprintf(str, sizeof(str), "%s.%s", st->tstats[u].tm_name, st->tstats[u].name);
280  js_type = OutputStats2Json(threads, str);
281  }
282 
283  if (js_type != NULL) {
284  json_object_set_new(js_type, stat_name, json_integer(st->tstats[u].value));
285 
286  if (flags & JSON_STATS_DELTAS) {
287  char deltaname[strlen(stat_name) + strlen(delta_suffix) + 1];
288  snprintf(deltaname, sizeof(deltaname), "%s%s", stat_name, delta_suffix);
289  json_object_set_new(js_type, deltaname,
290  json_integer(st->tstats[u].value - st->tstats[u].pvalue));
291  }
292  }
293  }
294  }
295  json_object_set_new(js_stats, "threads", threads);
296  }
297  return js_stats;
298 }
299 
300 static int JsonStatsLogger(ThreadVars *tv, void *thread_data, const StatsTable *st)
301 {
302  SCEnter();
303  JsonStatsLogThread *aft = (JsonStatsLogThread *)thread_data;
304 
305  struct timeval tval;
306  gettimeofday(&tval, NULL);
307 
308  json_t *js = json_object();
309  if (unlikely(js == NULL))
310  return 0;
311  char timebuf[64];
312  CreateIsoTimeString(SCTIME_FROM_TIMEVAL(&tval), timebuf, sizeof(timebuf));
313  json_object_set_new(js, "timestamp", json_string(timebuf));
314  json_object_set_new(js, "event_type", json_string("stats"));
315 
316  json_t *js_stats = StatsToJSON(st, aft->statslog_ctx->flags);
317  if (js_stats == NULL) {
318  json_decref(js);
319  return 0;
320  }
321 
322  json_object_set_new(js, "stats", js_stats);
323 
324  OutputJSONBuffer(js, aft->file_ctx, &aft->buffer);
325  MemBufferReset(aft->buffer);
326 
327  json_object_clear(js_stats);
328  json_object_del(js, "stats");
329  json_object_clear(js);
330  json_decref(js);
331 
332  SCReturnInt(0);
333 }
334 
335 static TmEcode JsonStatsLogThreadInit(ThreadVars *t, const void *initdata, void **data)
336 {
338  if (unlikely(aft == NULL))
339  return TM_ECODE_FAILED;
340 
341  if(initdata == NULL)
342  {
343  SCLogDebug("Error getting context for EveLogStats. \"initdata\" argument NULL");
344  goto error_exit;
345  }
346 
348  if (aft->buffer == NULL) {
349  goto error_exit;
350  }
351 
352  /* Use the Output Context (file pointer and mutex) */
353  aft->statslog_ctx = ((OutputCtx *)initdata)->data;
354 
356  if (!aft->file_ctx) {
357  goto error_exit;
358  }
359 
360  *data = (void *)aft;
361  return TM_ECODE_OK;
362 
363 error_exit:
364  if (aft->buffer != NULL) {
365  MemBufferFree(aft->buffer);
366  }
367  SCFree(aft);
368  return TM_ECODE_FAILED;
369 }
370 
371 static TmEcode JsonStatsLogThreadDeinit(ThreadVars *t, void *data)
372 {
373  JsonStatsLogThread *aft = (JsonStatsLogThread *)data;
374  if (aft == NULL) {
375  return TM_ECODE_OK;
376  }
377 
378  MemBufferFree(aft->buffer);
379 
380  /* clear memory */
381  memset(aft, 0, sizeof(JsonStatsLogThread));
382 
383  SCFree(aft);
384  return TM_ECODE_OK;
385 }
386 
387 static void OutputStatsLogDeinitSub(OutputCtx *output_ctx)
388 {
389  OutputStatsCtx *stats_ctx = output_ctx->data;
390  SCFree(stats_ctx);
391  SCFree(output_ctx);
392 }
393 
394 static OutputInitResult OutputStatsLogInitSub(ConfNode *conf, OutputCtx *parent_ctx)
395 {
396  OutputInitResult result = { NULL, false };
397  OutputJsonCtx *ajt = parent_ctx->data;
398 
399  if (!StatsEnabled()) {
400  SCLogError("eve.stats: stats are disabled globally: set stats.enabled to true. "
401  "See %s/configuration/suricata-yaml.html#stats",
402  GetDocURL());
403  return result;
404  }
405 
406  OutputStatsCtx *stats_ctx = SCMalloc(sizeof(OutputStatsCtx));
407  if (unlikely(stats_ctx == NULL))
408  return result;
409 
410  if (stats_decoder_events &&
411  strcmp(stats_decoder_events_prefix, "decoder") == 0) {
412  SCLogWarning("eve.stats will not display "
413  "all decoder events correctly. See ticket #2225. Set a prefix in "
414  "stats.decoder-events-prefix.");
415  }
416 
417  stats_ctx->flags = JSON_STATS_TOTALS;
418 
419  if (conf != NULL) {
420  const char *totals = ConfNodeLookupChildValue(conf, "totals");
421  const char *threads = ConfNodeLookupChildValue(conf, "threads");
422  const char *deltas = ConfNodeLookupChildValue(conf, "deltas");
423  SCLogDebug("totals %s threads %s deltas %s", totals, threads, deltas);
424 
425  if ((totals != NULL && ConfValIsFalse(totals)) &&
426  (threads != NULL && ConfValIsFalse(threads))) {
427  SCFree(stats_ctx);
428  SCLogError("Cannot disable both totals and threads in stats logging");
429  return result;
430  }
431 
432  if (totals != NULL && ConfValIsFalse(totals)) {
433  stats_ctx->flags &= ~JSON_STATS_TOTALS;
434  }
435  if (threads != NULL && ConfValIsTrue(threads)) {
436  stats_ctx->flags |= JSON_STATS_THREADS;
437  }
438  if (deltas != NULL && ConfValIsTrue(deltas)) {
439  stats_ctx->flags |= JSON_STATS_DELTAS;
440  }
441  SCLogDebug("stats_ctx->flags %08x", stats_ctx->flags);
442  }
443 
444  OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
445  if (unlikely(output_ctx == NULL)) {
446  SCFree(stats_ctx);
447  return result;
448  }
449 
450  SCLogDebug("Preparing file context for stats submodule logger");
451  /* Share output slot with thread 1 */
452  stats_ctx->file_ctx = LogFileEnsureExists(ajt->file_ctx);
453  if (!stats_ctx->file_ctx) {
454  SCFree(stats_ctx);
455  SCFree(output_ctx);
456  return result;
457  }
458 
459  output_ctx->data = stats_ctx;
460  output_ctx->DeInit = OutputStatsLogDeinitSub;
461 
462  result.ctx = output_ctx;
463  result.ok = true;
464  return result;
465 }
466 
468  /* register as child of eve-log */
470  "eve-log.stats", OutputStatsLogInitSub, JsonStatsLogger,
471  JsonStatsLogThreadInit, JsonStatsLogThreadDeinit, NULL);
472 }
tm-threads.h
StatsTable_::ntstats
uint32_t ntstats
Definition: output-stats.h:43
detect-engine.h
OutputRegisterStatsSubModule
void OutputRegisterStatsSubModule(LoggerId id, const char *parent_name, const char *name, const char *conf_name, OutputInitSubFunc InitFunc, StatsLogger StatsLogFunc, ThreadInitFunc ThreadInit, ThreadDeinitFunc ThreadDeinit, ThreadExitPrintStatsFunc ThreadExitPrintStats)
Register a stats data output sub-module.
Definition: output.c:747
offset
uint64_t offset
Definition: util-streaming-buffer.h:0
DetectEngineDeReference
void DetectEngineDeReference(DetectEngineCtx **de_ctx)
Definition: detect-engine.c:4553
CreateIsoTimeString
void CreateIsoTimeString(const SCTime_t ts, char *str, size_t size)
Definition: util-time.c:209
unlikely
#define unlikely(expr)
Definition: util-optimize.h:35
LOGGER_JSON_STATS
@ LOGGER_JSON_STATS
Definition: suricata-common.h:483
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:269
JSON_OUTPUT_BUFFER_SIZE
#define JSON_OUTPUT_BUFFER_SIZE
Definition: output-json.h:63
threads.h
JsonStatsLogThread
struct JsonStatsLogThread_ JsonStatsLogThread
OutputJsonCtx_
Definition: output-json.h:81
LogFileCtx_
Definition: util-logopenfile.h:72
DetectEngineCtx_
main detection engine ctx
Definition: detect.h:826
OutputStatsCtx_::flags
uint8_t flags
Definition: output-json-stats.c:67
DetectEngineGetCurrent
DetectEngineCtx * DetectEngineGetCurrent(void)
Definition: detect-engine.c:3765
util-privs.h
JSON_STATS_TOTALS
#define JSON_STATS_TOTALS
Definition: output-json-stats.h:29
StatsRecord_::pvalue
int64_t pvalue
Definition: output-stats.h:36
TM_ECODE_FAILED
@ TM_ECODE_FAILED
Definition: tm-threads-common.h:85
JsonStatsLogRegister
void JsonStatsLogRegister(void)
Definition: output-json-stats.c:467
util-unittest.h
ConfValIsTrue
int ConfValIsTrue(const char *val)
Check if a value is true.
Definition: conf.c:537
OutputCtx_::data
void * data
Definition: tm-modules.h:87
TM_ECODE_OK
@ TM_ECODE_OK
Definition: tm-threads-common.h:84
OutputCtx_
Definition: tm-modules.h:84
strlcpy
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: util-strlcpyu.c:43
OutputEngineInfo_
OutputEngineInfo_
Definition: output-json-stats.c:59
stats_decoder_events_prefix
const char * stats_decoder_events_prefix
Definition: counters.c:102
util-debug.h
de_ctx
DetectEngineCtx * de_ctx
Definition: fuzz_siginit.c:17
OutputInitResult_::ctx
OutputCtx * ctx
Definition: output.h:47
output-json.h
DetectEngineCtx_::last_reload
struct timeval last_reload
Definition: detect.h:995
JsonStatsLogThread_::buffer
MemBuffer * buffer
Definition: output-json-stats.c:73
util-print.h
SCEnter
#define SCEnter(...)
Definition: util-debug.h:271
StatsRecord_::name
const char * name
Definition: output-stats.h:32
detect.h
ThreadVars_
Per thread variable structure.
Definition: threadvars.h:57
LogFileEnsureExists
LogFileCtx * LogFileEnsureExists(LogFileCtx *parent_ctx)
LogFileEnsureExists() Ensure a log file context for the thread exists.
Definition: util-logopenfile.c:708
SCTIME_FROM_TIMEVAL
#define SCTIME_FROM_TIMEVAL(tv)
Definition: util-time.h:71
pkt-var.h
util-time.h
OutputInitResult_::ok
bool ok
Definition: output.h:48
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:249
SigFileLoaderStat_::bad_sigs_total
int bad_sigs_total
Definition: detect.h:793
OutputEngineStatsReloadTime
TmEcode OutputEngineStatsReloadTime(json_t **jdata)
Definition: output-json-stats.c:159
StatsTable_
Definition: output-stats.h:39
StatsRecord_::value
int64_t value
Definition: output-stats.h:35
StatsToJSON
json_t * StatsToJSON(const StatsTable *st, uint8_t flags)
turn StatsTable into a json object
Definition: output-json-stats.c:209
conf.h
SCTime_t
Definition: util-time.h:40
TmEcode
TmEcode
Definition: tm-threads-common.h:83
StatsTable_::start_time
time_t start_time
Definition: output-stats.h:44
MemBuffer_
Definition: util-buffer.h:27
JsonStatsLogThread_::file_ctx
LogFileCtx * file_ctx
Definition: output-json-stats.c:72
StatsTable_::stats
StatsRecord * stats
Definition: output-stats.h:40
MemBufferReset
#define MemBufferReset(mem_buffer)
Reset the mem buffer.
Definition: util-buffer.h:42
JsonStatsLogThread_
Definition: output-json-stats.c:70
OutputInitResult_
Definition: output.h:46
DetectEngineCtx_::sig_stat
SigFileLoaderStat sig_stat
Definition: detect.h:998
flags
uint8_t flags
Definition: decode-gre.h:0
suricata-common.h
OutputCtx_::DeInit
void(* DeInit)(struct OutputCtx_ *)
Definition: tm-modules.h:90
output-json-stats.h
OUTPUT_ENGINE_LAST_RELOAD
@ OUTPUT_ENGINE_LAST_RELOAD
Definition: output-json-stats.c:60
DetectEngineCtx_::next
struct DetectEngineCtx_ * next
Definition: detect.h:954
OUTPUT_ENGINE_ALL
@ OUTPUT_ENGINE_ALL
Definition: output-json-stats.c:62
MemBufferFree
void MemBufferFree(MemBuffer *buffer)
Definition: util-buffer.c:87
StatsEnabled
bool StatsEnabled(void)
Definition: counters.c:118
JSON_STATS_DELTAS
#define JSON_STATS_DELTAS
Definition: output-json-stats.h:31
StatsRecord_::tm_name
const char * tm_name
Definition: output-stats.h:34
JSON_STATS_THREADS
#define JSON_STATS_THREADS
Definition: output-json-stats.h:30
StatsTable_::tstats
StatsRecord * tstats
Definition: output-stats.h:41
tv
ThreadVars * tv
Definition: fuzz_decodepcapfile.c:32
threadvars.h
SCMalloc
#define SCMalloc(sz)
Definition: util-mem.h:47
str
#define str(s)
Definition: suricata-common.h:286
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:261
SCFree
#define SCFree(p)
Definition: util-mem.h:61
SigFileLoaderStat_::good_sigs_total
int good_sigs_total
Definition: detect.h:792
ConfNode_
Definition: conf.h:32
util-logopenfile.h
util-buffer.h
ConfValIsFalse
int ConfValIsFalse(const char *val)
Check if a value is false.
Definition: conf.c:562
StatsRecord_::short_name
const char * short_name
Definition: output-stats.h:33
OutputStatsCtx_::file_ctx
LogFileCtx * file_ctx
Definition: output-json-stats.c:66
OutputJsonCtx_::file_ctx
LogFileCtx * file_ctx
Definition: output-json.h:82
OutputJSONBuffer
int OutputJSONBuffer(json_t *js, LogFileCtx *file_ctx, MemBuffer **buffer)
Definition: output-json.c:898
OutputEngineInfo
enum OutputEngineInfo_ OutputEngineInfo
GetDocURL
const char * GetDocURL(void)
Definition: suricata.c:1105
OUTPUT_ENGINE_RULESET
@ OUTPUT_ENGINE_RULESET
Definition: output-json-stats.c:61
OutputEngineStatsRuleset
TmEcode OutputEngineStatsRuleset(json_t **jdata)
Definition: output-json-stats.c:163
JsonStatsLogThread_::statslog_ctx
OutputStatsCtx * statslog_ctx
Definition: output-json-stats.c:71
stats_decoder_events
bool stats_decoder_events
Definition: counters.c:101
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
MODULE_NAME
#define MODULE_NAME
Definition: output-json-stats.c:50
SCReturnInt
#define SCReturnInt(x)
Definition: util-debug.h:275
OutputStatsCtx
struct OutputStatsCtx_ OutputStatsCtx
DetectEngineCtx_::tenant_id
uint32_t tenant_id
Definition: detect.h:832
SigFileLoaderStat_
Signature loader statistics.
Definition: detect.h:788
StatsTable_::nstats
uint32_t nstats
Definition: output-stats.h:42
MemBufferCreateNew
MemBuffer * MemBufferCreateNew(uint32_t size)
Definition: util-buffer.c:32
output.h
OutputStatsCtx_
Definition: output-json-stats.c:65
ConfNodeLookupChildValue
const char * ConfNodeLookupChildValue(const ConfNode *node, const char *name)
Lookup the value of a child configuration node by name.
Definition: conf.c:814