Coverage Report

Created: 2026-07-12 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/wsutil/wslog.c
Line
Count
Source
1
/*
2
 * Copyright 2021, João Valverde <j@v6e.pt>
3
 *
4
 * Wireshark - Network traffic analyzer
5
 * By Gerald Combs <gerald@wireshark.org>
6
 * Copyright 1998 Gerald Combs
7
 *
8
 * SPDX-License-Identifier: GPL-2.0-or-later
9
 */
10
11
#include "config.h"
12
13
#include "wslog.h"
14
15
#include <stdlib.h>
16
#include <string.h>
17
#include <errno.h>
18
#include <time.h>
19
/* Because ws_assert() depends on ws_error() we do not use it
20
 * here and fall back on assert() instead. */
21
#include <assert.h>
22
#ifdef HAVE_UNISTD_H
23
#include <unistd.h>
24
#endif
25
#ifdef _WIN32
26
#include <process.h>
27
#include <windows.h>
28
#include <conio.h>
29
#endif
30
31
#include "clopts_common.h"
32
#include "file_util.h"
33
#include "time_util.h"
34
#include "to_str.h"
35
#include "strtoi.h"
36
#ifdef _WIN32
37
#include "console_win32.h"
38
#endif
39
40
41
0
#define ASSERT(expr)    assert(expr)
42
43
/* Runtime log level. */
44
15
#define ENV_VAR_LEVEL       "WIRESHARK_LOG_LEVEL"
45
46
/* Log domains enabled/disabled. */
47
15
#define ENV_VAR_DOMAIN      "WIRESHARK_LOG_DOMAIN"
48
49
/* Alias "domain" and "domains". */
50
15
#define ENV_VAR_DOMAIN_S    "WIRESHARK_LOG_DOMAINS"
51
52
/* Log level that generates a trap and aborts. Can be "critical"
53
 * or "warning". */
54
15
#define ENV_VAR_FATAL       "WIRESHARK_LOG_FATAL"
55
56
/* Fatal log count before program is trapped. */
57
15
#define ENV_VAR_FATAL_COUNT "WIRESHARK_LOG_FATAL_COUNT"
58
59
/* Log domains that are fatal. */
60
15
#define ENV_VAR_FATAL_DOMAIN    "WIRESHARK_LOG_FATAL_DOMAIN"
61
62
/* Alias "domain" and "domains". */
63
15
#define ENV_VAR_FATAL_DOMAIN_S  "WIRESHARK_LOG_FATAL_DOMAINS"
64
65
/* Domains that will produce debug output, regardless of log level or
66
 * domain filter. */
67
15
#define ENV_VAR_DEBUG       "WIRESHARK_LOG_DEBUG"
68
69
/* Domains that will produce noisy output, regardless of log level or
70
 * domain filter. */
71
15
#define ENV_VAR_NOISY       "WIRESHARK_LOG_NOISY"
72
73
15
#define DEFAULT_LOG_LEVEL   LOG_LEVEL_MESSAGE
74
75
#define DEFAULT_PROGNAME    "PID"
76
77
25.8k
#define DOMAIN_UNDEFED(domain)    ((domain) == NULL || *(domain) == '\0')
78
37.3k
#define DOMAIN_DEFINED(domain)    (!DOMAIN_UNDEFED(domain))
79
80
/*
81
 * Note: I didn't measure it but I assume using a string array is faster than
82
 * a GHashTable for small number N of domains.
83
 */
84
typedef struct {
85
    char **domainv;
86
    bool positive;                  /* positive or negative match */
87
    enum ws_log_level min_level;    /* for level filters */
88
} log_filter_t;
89
90
91
/* If the module is not initialized by calling ws_log_init() all messages
92
 * will be printed regardless of log level. This is a feature, not a bug. */
93
static enum ws_log_level current_log_level = LOG_LEVEL_NONE;
94
95
static bool stdout_color_enabled;
96
97
static bool stderr_color_enabled;
98
99
/* Use stdout for levels "info" and below, for backward compatibility
100
 * with GLib. */
101
static bool stdout_logging_enabled;
102
103
static const char *registered_progname = DEFAULT_PROGNAME;
104
105
/* List of domains to filter. */
106
static log_filter_t *domain_filter;
107
108
/* List of domains to output debug level unconditionally. */
109
static log_filter_t *debug_filter;
110
111
/* List of domains to output noisy level unconditionally. */
112
static log_filter_t *noisy_filter;
113
114
/* List of domains that are fatal. */
115
static log_filter_t *fatal_filter;
116
117
static ws_log_writer_cb *registered_log_writer;
118
119
static void *registered_log_writer_data;
120
121
static ws_log_writer_free_data_cb *registered_log_writer_data_free;
122
123
static FILE *custom_log;
124
125
static enum ws_log_level fatal_log_level = LOG_LEVEL_ERROR;
126
127
static uint32_t fatal_log_count = 1;
128
129
#ifdef WS_DEBUG
130
static bool init_complete;
131
#endif
132
133
ws_log_console_open_pref ws_log_console_open = LOG_CONSOLE_OPEN_NEVER;
134
135
136
static void print_err(void (*vcmdarg_err)(const char *, va_list ap),
137
                        int exit_failure,
138
                        const char *fmt, ...) G_GNUC_PRINTF(3,4);
139
140
static void ws_log_cleanup(void);
141
142
143
const char *ws_log_level_to_string(enum ws_log_level level)
144
905
{
145
905
    switch (level) {
146
0
        case LOG_LEVEL_NONE:
147
0
            return "(zero)";
148
0
        case LOG_LEVEL_ECHO:
149
0
            return "ECHO";
150
0
        case LOG_LEVEL_ERROR:
151
0
            return "ERROR";
152
0
        case LOG_LEVEL_CRITICAL:
153
0
            return "CRITICAL";
154
890
        case LOG_LEVEL_WARNING:
155
890
            return "WARNING";
156
15
        case LOG_LEVEL_MESSAGE:
157
15
            return "MESSAGE";
158
0
        case LOG_LEVEL_INFO:
159
0
            return "INFO";
160
0
        case LOG_LEVEL_DEBUG:
161
0
            return "DEBUG";
162
0
        case LOG_LEVEL_NOISY:
163
0
            return "NOISY";
164
0
        default:
165
0
            return "(BOGUS LOG LEVEL)";
166
905
    }
167
905
}
168
169
170
static enum ws_log_level string_to_log_level(const char *str_level)
171
0
{
172
0
    if (!str_level)
173
0
        return LOG_LEVEL_NONE;
174
175
0
    if (g_ascii_strcasecmp(str_level, "noisy") == 0)
176
0
        return LOG_LEVEL_NOISY;
177
0
    else if (g_ascii_strcasecmp(str_level, "debug") == 0)
178
0
        return LOG_LEVEL_DEBUG;
179
0
    else if (g_ascii_strcasecmp(str_level, "info") == 0)
180
0
        return LOG_LEVEL_INFO;
181
0
    else if (g_ascii_strcasecmp(str_level, "message") == 0)
182
0
        return LOG_LEVEL_MESSAGE;
183
0
    else if (g_ascii_strcasecmp(str_level, "warning") == 0)
184
0
        return LOG_LEVEL_WARNING;
185
0
    else if (g_ascii_strcasecmp(str_level, "critical") == 0)
186
0
        return LOG_LEVEL_CRITICAL;
187
0
    else if (g_ascii_strcasecmp(str_level, "error") == 0)
188
0
        return LOG_LEVEL_ERROR;
189
0
    else if (g_ascii_strcasecmp(str_level, "echo") == 0)
190
0
        return LOG_LEVEL_ECHO;
191
0
    else
192
0
        return LOG_LEVEL_NONE;
193
0
}
194
195
196
WS_RETNONNULL
197
static inline const char *domain_to_string(const char *domain)
198
905
{
199
905
    return DOMAIN_UNDEFED(domain) ? "(none)" : domain;
200
905
}
201
202
203
static inline bool filter_contains(log_filter_t *filter,
204
                                            const char *domain)
205
0
{
206
0
    if (filter == NULL || DOMAIN_UNDEFED(domain))
207
0
        return false;
208
209
0
    for (char **domv = filter->domainv; *domv != NULL; domv++) {
210
0
        if (g_ascii_strcasecmp(*domv, domain) == 0) {
211
0
            return true;
212
0
        }
213
0
    }
214
0
    return false;
215
0
}
216
217
218
static inline bool level_filter_matches(log_filter_t *filter,
219
                                        const char *domain,
220
                                        enum ws_log_level level,
221
                                        bool *active_ptr)
222
24.1k
{
223
24.1k
    if (filter == NULL || DOMAIN_UNDEFED(domain))
224
24.1k
        return false;
225
226
0
    if (!filter_contains(filter, domain))
227
0
        return false;
228
229
0
    if (filter->positive) {
230
0
        if (active_ptr)
231
0
            *active_ptr = level >= filter->min_level;
232
0
        return true;
233
0
    }
234
235
    /* negative match */
236
0
    if (level <= filter->min_level) {
237
0
        if (active_ptr)
238
0
            *active_ptr = false;
239
0
        return true;
240
0
    }
241
242
0
    return false;
243
0
}
244
245
246
static inline void
247
get_timestamp(struct timespec *ts)
248
905
{
249
905
    bool ok = false;
250
251
905
#if defined(HAVE_CLOCK_GETTIME)
252
905
    ok = (clock_gettime(CLOCK_REALTIME, ts) == 0);
253
#elif defined(HAVE_TIMESPEC_GET)
254
    ok = (timespec_get(ts, TIME_UTC) == TIME_UTC);
255
#endif
256
905
    if (ok)
257
905
        return;
258
259
    /* Fall back on time(). */
260
0
    ts->tv_sec = time(NULL);
261
0
    ts->tv_nsec = -1;
262
0
}
263
264
265
static inline void fill_manifest(ws_log_manifest_t *mft)
266
905
{
267
905
    struct timespec ts;
268
905
    get_timestamp(&ts);
269
905
    ws_localtime_r(&ts.tv_sec, &mft->tstamp_secs);
270
905
    mft->nanosecs = ts.tv_nsec;
271
905
    mft->pid = getpid();
272
905
}
273
274
275
static inline bool msg_is_active(const char *domain, enum ws_log_level level,
276
                                    ws_log_manifest_t *mft)
277
1.28k
{
278
1.28k
    bool is_active = ws_log_msg_is_active(domain, level);
279
1.28k
    if (is_active)
280
905
        fill_manifest(mft);
281
1.28k
    return is_active;
282
1.28k
}
283
284
285
bool ws_log_msg_is_active(const char *domain, enum ws_log_level level)
286
12.4k
{
287
    /*
288
     * Higher numerical levels have higher priority. Critical and above
289
     * are always enabled.
290
     */
291
12.4k
    if (level >= LOG_LEVEL_CRITICAL)
292
0
        return true;
293
294
    /*
295
     * Check if the level has been configured as fatal.
296
     */
297
12.4k
    if (level >= fatal_log_level)
298
0
        return true;
299
300
    /*
301
     * Check if the domain has been configured as fatal.
302
     */
303
12.4k
    if (DOMAIN_DEFINED(domain) && fatal_filter != NULL) {
304
0
        if (filter_contains(fatal_filter, domain) && fatal_filter->positive) {
305
0
            return true;
306
0
        }
307
0
    }
308
309
    /*
310
     * The debug/noisy filter overrides the other parameters.
311
     */
312
12.4k
    if (DOMAIN_DEFINED(domain)) {
313
12.0k
        bool active;
314
315
12.0k
        if (level_filter_matches(noisy_filter, domain, level, &active))
316
0
            return active;
317
12.0k
        if (level_filter_matches(debug_filter, domain, level, &active))
318
0
            return active;
319
12.0k
    }
320
321
    /*
322
     * If the priority is lower than the current minimum drop the
323
     * message.
324
     */
325
12.4k
    if (level < current_log_level)
326
11.5k
        return false;
327
328
    /*
329
     * If we don't have domain filtering enabled we are done.
330
     */
331
905
    if (domain_filter == NULL)
332
905
        return true;
333
334
    /*
335
     * We have a filter but we don't use it with the undefined domain,
336
     * pretty much every permanent call to ws_log should be using a
337
     * chosen domain.
338
     */
339
0
    if (DOMAIN_UNDEFED(domain))
340
0
        return true;
341
342
    /* Check if the domain filter matches. */
343
0
    if (filter_contains(domain_filter, domain))
344
0
        return domain_filter->positive;
345
346
    /* We have a domain filter but it didn't match. */
347
0
    return !domain_filter->positive;
348
0
}
349
350
351
enum ws_log_level ws_log_get_level(void)
352
0
{
353
0
    return current_log_level;
354
0
}
355
356
357
enum ws_log_level ws_log_set_level(enum ws_log_level level)
358
0
{
359
0
    if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST)
360
0
        return LOG_LEVEL_NONE;
361
0
    if (level > LOG_LEVEL_CRITICAL)
362
0
        level = LOG_LEVEL_CRITICAL;
363
364
0
    current_log_level = level;
365
0
    return current_log_level;
366
0
}
367
368
369
enum ws_log_level ws_log_set_level_str(const char *str_level)
370
0
{
371
0
    enum ws_log_level level;
372
373
0
    level = string_to_log_level(str_level);
374
0
    return ws_log_set_level(level);
375
0
}
376
377
378
static void print_err(void (*vcmdarg_err)(const char *, va_list ap),
379
                        int exit_failure,
380
                        const char *fmt, ...)
381
0
{
382
0
    va_list ap;
383
384
0
    va_start(ap, fmt);
385
0
    if (vcmdarg_err)
386
0
        vcmdarg_err(fmt, ap);
387
0
    else
388
0
        vfprintf(stderr, fmt, ap);
389
0
    va_end(ap);
390
0
    if (exit_failure != LOG_ARGS_NOEXIT)
391
0
        exit(exit_failure);
392
0
}
393
394
int ws_log_parse_args(int* argc, char* argv[],
395
    const char* optstring, const struct ws_option* long_options,
396
    void (*vcmdarg_err)(const char*, va_list ap),
397
    int exit_failure)
398
15
{
399
15
    int opt;
400
401
     /* Save the global setting of erroring on unknown options, because this list will probably contain application options
402
        not handled here */
403
15
    int old_ws_opterr = ws_opterr;
404
405
    /* Clear erroring on unknown options */
406
15
    ws_opterr = 0;
407
408
675
    while ((opt = ws_getopt_long_only(*argc, argv, optstring, long_options, NULL)) != -1) {
409
410
660
        switch (opt)
411
660
        {
412
0
        case LONGOPT_WSLOG_LOG_LEVEL:
413
0
            if (ws_log_set_level_str(ws_optarg) == LOG_LEVEL_NONE) {
414
0
                print_err(vcmdarg_err, exit_failure,
415
0
                    "Invalid log level \"%s\".\n", ws_optarg);
416
0
            }
417
0
            break;
418
419
0
        case LONGOPT_WSLOG_LOG_DOMAIN:
420
0
            ws_log_set_domain_filter(ws_optarg);
421
0
            break;
422
423
0
        case LONGOPT_WSLOG_LOG_FILE:
424
0
        {
425
0
            FILE* fp = ws_fopen(ws_optarg, "w");
426
0
            if (fp == NULL) {
427
0
                print_err(vcmdarg_err, exit_failure,
428
0
                    "Error opening file '%s' for writing: %s.\n",
429
0
                    ws_optarg, g_strerror(errno));
430
0
            }
431
0
            else {
432
0
                ws_log_add_custom_file(fp);
433
0
            }
434
0
        }
435
0
        break;
436
0
        case LONGOPT_WSLOG_LOG_FATAL:
437
0
            if (ws_log_set_fatal_level_str(ws_optarg) == LOG_LEVEL_NONE) {
438
0
                print_err(vcmdarg_err, exit_failure,
439
0
                    "Fatal log level must be \"critical\" or \"warning\", not \"%s\".\n", ws_optarg);
440
0
            }
441
0
            break;
442
0
        case LONGOPT_WSLOG_LOG_FATAL_COUNT:
443
0
            if (!ws_log_set_fatal_count_str(ws_optarg)) {
444
0
                print_err(vcmdarg_err, exit_failure,
445
0
                    "Fatal log count must be decimal and larger than 0, not \"%s\".\n", ws_optarg);
446
0
            }
447
0
            break;
448
0
        case LONGOPT_WSLOG_LOG_FATAL_DOMAIN:
449
0
            ws_log_set_fatal_domain_filter(ws_optarg);
450
0
            break;
451
0
        case LONGOPT_WSLOG_LOG_DEBUG:
452
0
            ws_log_set_debug_filter(ws_optarg);
453
0
            break;
454
0
        case LONGOPT_WSLOG_LOG_NOISY:
455
0
            ws_log_set_noisy_filter(ws_optarg);
456
0
            break;
457
0
        case 'o':
458
0
            if (g_str_has_prefix(ws_optarg, "console.log.level:")) {
459
0
                uint32_t mask;
460
0
                enum ws_log_level level;
461
0
                const char* mask_str = ws_optarg + strlen("console.log.level:");
462
0
                if (*mask_str == '\0') {
463
0
                    print_err(vcmdarg_err, exit_failure,
464
0
                        "Missing value to 'console.log.level' option.");
465
0
                    break;
466
0
                }
467
468
0
                if (!ws_strtou32(mask_str, NULL, &mask)) {
469
0
                    print_err(vcmdarg_err, exit_failure,
470
0
                        "%s is not a valid decimal number.", mask_str);
471
0
                    break;
472
0
                }
473
474
                /*
475
                 * The lowest priority bit in the mask defines the level.
476
                 */
477
0
                if (mask & G_LOG_LEVEL_DEBUG)
478
0
                    level = LOG_LEVEL_DEBUG;
479
0
                else if (mask & G_LOG_LEVEL_INFO)
480
0
                    level = LOG_LEVEL_INFO;
481
0
                else if (mask & G_LOG_LEVEL_MESSAGE)
482
0
                    level = LOG_LEVEL_MESSAGE;
483
0
                else if (mask & G_LOG_LEVEL_WARNING)
484
0
                    level = LOG_LEVEL_WARNING;
485
0
                else if (mask & G_LOG_LEVEL_CRITICAL)
486
0
                    level = LOG_LEVEL_CRITICAL;
487
0
                else if (mask & G_LOG_LEVEL_ERROR)
488
0
                    level = LOG_LEVEL_ERROR;
489
0
                else
490
0
                    level = LOG_LEVEL_NONE;
491
492
0
                if (level != LOG_LEVEL_NONE) {
493
0
                    ws_log_set_level(level);
494
0
                } else {
495
                    /* Some values (like zero) might not contain any meaningful bits.
496
                     * Throwing an error in that case seems appropriate. */
497
0
                    print_err(vcmdarg_err, exit_failure,
498
0
                        "Value %s is not a valid log mask.", mask_str);
499
0
                }
500
0
            }
501
0
            break;
502
660
        default:
503
            /* Ignore options not found because they are probably supported by the application */
504
660
            break;
505
660
        }
506
660
    }
507
508
    /* Restore the global setting of erroring on unknown options */
509
15
    ws_opterr = old_ws_opterr;
510
15
    ws_optreset = 1;
511
512
15
    return 0;
513
15
}
514
515
bool ws_log_is_wslog_arg(int arg)
516
0
{
517
0
    static const struct ws_option test_options[] = {
518
0
        LONGOPT_WSLOG
519
0
        {0, 0, 0, 0 }
520
0
    };
521
522
0
    const struct ws_option* option = test_options;
523
0
    while (option->val != 0) {
524
0
        if (option->val == arg)
525
0
            return true;
526
527
0
        option++;
528
0
    }
529
530
0
    return false;
531
0
}
532
533
534
static void free_log_filter(log_filter_t **filter_ptr)
535
60
{
536
60
    if (filter_ptr == NULL || *filter_ptr == NULL)
537
60
        return;
538
0
    g_strfreev((*filter_ptr)->domainv);
539
0
    g_free(*filter_ptr);
540
0
    *filter_ptr = NULL;
541
0
}
542
543
544
static void tokenize_filter_str(log_filter_t **filter_ptr,
545
                                    const char *str_filter,
546
                                    enum ws_log_level min_level)
547
0
{
548
0
    const char *sep = ",;";
549
0
    bool negated = false;
550
0
    log_filter_t *filter;
551
552
0
    ASSERT(filter_ptr);
553
0
    ASSERT(*filter_ptr == NULL);
554
555
0
    if (str_filter == NULL)
556
0
        return;
557
558
0
    if (str_filter[0] == '!') {
559
0
        negated = true;
560
0
        str_filter += 1;
561
0
    }
562
0
    if (*str_filter == '\0')
563
0
        return;
564
565
0
    filter = g_new(log_filter_t, 1);
566
0
    filter->domainv = g_strsplit_set(str_filter, sep, -1);
567
0
    filter->positive = !negated;
568
0
    filter->min_level = min_level;
569
0
    *filter_ptr = filter;
570
0
}
571
572
573
void ws_log_set_domain_filter(const char *str_filter)
574
0
{
575
0
    free_log_filter(&domain_filter);
576
0
    tokenize_filter_str(&domain_filter, str_filter, LOG_LEVEL_NONE);
577
0
}
578
579
580
void ws_log_set_fatal_domain_filter(const char *str_filter)
581
0
{
582
0
    free_log_filter(&fatal_filter);
583
0
    tokenize_filter_str(&fatal_filter, str_filter, LOG_LEVEL_NONE);
584
0
}
585
586
587
void ws_log_set_debug_filter(const char *str_filter)
588
0
{
589
0
    free_log_filter(&debug_filter);
590
0
    tokenize_filter_str(&debug_filter, str_filter, LOG_LEVEL_DEBUG);
591
0
}
592
593
594
void ws_log_set_noisy_filter(const char *str_filter)
595
0
{
596
0
    free_log_filter(&noisy_filter);
597
0
    tokenize_filter_str(&noisy_filter, str_filter, LOG_LEVEL_NOISY);
598
0
}
599
600
601
enum ws_log_level ws_log_set_fatal_level(enum ws_log_level level)
602
0
{
603
0
    if (level <= LOG_LEVEL_NONE || level >= _LOG_LEVEL_LAST)
604
0
        return LOG_LEVEL_NONE;
605
0
    if (level > LOG_LEVEL_ERROR)
606
0
        level = LOG_LEVEL_ERROR;
607
0
    if (level < LOG_LEVEL_WARNING)
608
0
        level = LOG_LEVEL_WARNING;
609
610
0
    fatal_log_level = level;
611
0
    return fatal_log_level;
612
0
}
613
614
615
enum ws_log_level ws_log_set_fatal_level_str(const char *str_level)
616
0
{
617
0
    enum ws_log_level level;
618
619
0
    level = string_to_log_level(str_level);
620
0
    return ws_log_set_fatal_level(level);
621
0
}
622
623
void ws_log_set_fatal_count(uint32_t count)
624
0
{
625
0
    fatal_log_count = MIN(1, count);
626
0
}
627
628
bool ws_log_set_fatal_count_str(const char *str_count)
629
0
{
630
0
    const char *endptr;
631
0
    uint32_t count;
632
0
    bool result = ws_strtou32(str_count, &endptr, &count);
633
634
0
    if (result && count < 1)
635
0
        result = false;
636
637
0
    if (result)
638
0
        ws_log_set_fatal_count(count);
639
640
0
    return result;
641
0
}
642
643
void ws_log_set_writer(ws_log_writer_cb *writer)
644
0
{
645
0
    if (registered_log_writer_data_free)
646
0
        registered_log_writer_data_free(registered_log_writer_data);
647
648
0
    registered_log_writer = writer;
649
0
    registered_log_writer_data = NULL;
650
0
    registered_log_writer_data_free = NULL;
651
0
}
652
653
654
void ws_log_set_writer_with_data(ws_log_writer_cb *writer,
655
                        void *user_data,
656
                        ws_log_writer_free_data_cb *free_user_data)
657
0
{
658
0
    if (registered_log_writer_data_free)
659
0
        registered_log_writer_data_free(registered_log_writer_data);
660
661
0
    registered_log_writer = writer;
662
0
    registered_log_writer_data = user_data;
663
0
    registered_log_writer_data_free = free_user_data;
664
0
}
665
666
667
static void glib_log_handler(const char *domain, GLogLevelFlags flags,
668
                        const char *message, void * user_data _U_)
669
0
{
670
0
    enum ws_log_level level;
671
672
    /*
673
     * The highest priority bit in the mask defines the level. We
674
     * ignore the GLib fatal log level mask and use our own fatal
675
     * log level setting instead.
676
     */
677
678
0
    if (flags & G_LOG_LEVEL_ERROR)
679
0
        level = LOG_LEVEL_ERROR;
680
0
    else if (flags & G_LOG_LEVEL_CRITICAL)
681
0
        level = LOG_LEVEL_CRITICAL;
682
0
    else if (flags & G_LOG_LEVEL_WARNING)
683
0
        level = LOG_LEVEL_WARNING;
684
0
    else if (flags & G_LOG_LEVEL_MESSAGE)
685
0
        level = LOG_LEVEL_MESSAGE;
686
0
    else if (flags & G_LOG_LEVEL_INFO)
687
0
        level = LOG_LEVEL_INFO;
688
0
    else if (flags & G_LOG_LEVEL_DEBUG)
689
0
        level = LOG_LEVEL_DEBUG;
690
0
    else
691
0
        level = LOG_LEVEL_NONE; /* Should not happen. */
692
693
0
    ws_log(domain, level, "%s", message);
694
0
}
695
696
697
#ifdef _WIN32
698
static void load_registry(void)
699
{
700
    LONG lResult;
701
    DWORD ptype;
702
    DWORD data;
703
    DWORD data_size = sizeof(DWORD);
704
705
    lResult = RegGetValueA(HKEY_CURRENT_USER,
706
                            "Software\\Wireshark",
707
                            LOG_HKCU_CONSOLE_OPEN,
708
                            RRF_RT_REG_DWORD,
709
                            &ptype,
710
                            &data,
711
                            &data_size);
712
    if (lResult != ERROR_SUCCESS || ptype != REG_DWORD) {
713
        return;
714
    }
715
716
    ws_log_console_open = (ws_log_console_open_pref)data;
717
}
718
719
static const char* log_console_title = "Debug Console";
720
#endif
721
722
723
/*
724
 * We can't write to stderr in ws_log_init() because dumpcap uses stderr
725
 * to communicate with the parent and it will block. We have to use
726
 * vcmdarg_err to report errors.
727
 */
728
void ws_log_init(void (*vcmdarg_err)(const char *, va_list ap), const char* console_title _U_)
729
15
{
730
15
    const char *env;
731
15
    int fd;
732
733
15
    registered_progname = g_get_prgname();
734
735
15
    ws_tzset();
736
737
15
    current_log_level = DEFAULT_LOG_LEVEL;
738
739
15
    if ((fd = fileno(stdout)) >= 0)
740
15
        stdout_color_enabled = g_log_writer_supports_color(fd);
741
15
    if ((fd = fileno(stderr)) >= 0)
742
15
        stderr_color_enabled = g_log_writer_supports_color(fd);
743
744
    /* Set ourselves as the default log handler for all GLib domains. */
745
15
    g_log_set_default_handler(glib_log_handler, NULL);
746
747
#ifdef _WIN32
748
    log_console_title = console_title;
749
750
    load_registry();
751
752
    /* if the user wants a console to be always there, well, we should open one for him */
753
    if (ws_log_console_open == LOG_CONSOLE_OPEN_ALWAYS)
754
        create_console(log_console_title);
755
#endif
756
757
15
    atexit(ws_log_cleanup);
758
759
    /* Configure from environment. */
760
761
15
    env = g_getenv(ENV_VAR_LEVEL);
762
15
    if (env != NULL) {
763
0
        if (ws_log_set_level_str(env) == LOG_LEVEL_NONE) {
764
0
            print_err(vcmdarg_err, LOG_ARGS_NOEXIT,
765
0
                        "Ignoring invalid environment value %s=\"%s\"",
766
0
                        ENV_VAR_LEVEL, env);
767
0
        }
768
0
    }
769
770
15
    env = g_getenv(ENV_VAR_FATAL);
771
15
    if (env != NULL) {
772
0
        if (ws_log_set_fatal_level_str(env) == LOG_LEVEL_NONE) {
773
0
            print_err(vcmdarg_err, LOG_ARGS_NOEXIT,
774
0
                        "Ignoring invalid environment value %s=\"%s\"",
775
0
                        ENV_VAR_FATAL, env);
776
0
        }
777
0
    }
778
779
15
    env = g_getenv(ENV_VAR_FATAL_COUNT);
780
15
    if (env != NULL) {
781
0
        if (!ws_log_set_fatal_count_str(env)) {
782
0
            print_err(vcmdarg_err, LOG_ARGS_NOEXIT,
783
0
                        "Ignoring invalid environment value %s=\"%s\"",
784
0
                        ENV_VAR_FATAL_COUNT, env);
785
0
        }
786
0
    }
787
788
    /* Alias "domain" and "domains". The plural form wins. */
789
15
    if ((env = g_getenv(ENV_VAR_DOMAIN_S)) != NULL)
790
0
        ws_log_set_domain_filter(env);
791
15
    else if ((env = g_getenv(ENV_VAR_DOMAIN)) != NULL)
792
0
        ws_log_set_domain_filter(env);
793
794
    /* Alias "domain" and "domains". The plural form wins. */
795
15
    if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN_S)) != NULL)
796
0
        ws_log_set_fatal_domain_filter(env);
797
15
    else if ((env = g_getenv(ENV_VAR_FATAL_DOMAIN)) != NULL)
798
0
        ws_log_set_fatal_domain_filter(env);
799
800
15
    env = g_getenv(ENV_VAR_DEBUG);
801
15
    if (env != NULL)
802
0
        ws_log_set_debug_filter(env);
803
804
15
    env = g_getenv(ENV_VAR_NOISY);
805
15
    if (env != NULL)
806
0
        ws_log_set_noisy_filter(env);
807
808
#ifdef WS_DEBUG
809
    init_complete = true;
810
#endif
811
15
}
812
813
814
void ws_log_init_with_writer(ws_log_writer_cb *writer,
815
                            void (*vcmdarg_err)(const char *, va_list ap), const char* console_title)
816
0
{
817
0
    registered_log_writer = writer;
818
0
    ws_log_init(vcmdarg_err, console_title);
819
0
}
820
821
822
void ws_log_init_with_writer_and_data(ws_log_writer_cb *writer,
823
                            void *user_data,
824
                            ws_log_writer_free_data_cb *free_user_data,
825
                            void (*vcmdarg_err)(const char *, va_list ap), const char* console_title)
826
0
{
827
0
    registered_log_writer_data = user_data;
828
0
    registered_log_writer_data_free = free_user_data;
829
0
    ws_log_init_with_writer(writer, vcmdarg_err, console_title);
830
0
}
831
832
833
0
#define MAGENTA "\033[35m"
834
#define BLUE    "\033[34m"
835
0
#define CYAN    "\033[36m"
836
0
#define GREEN   "\033[32m"
837
0
#define YELLOW  "\033[33m"
838
0
#define RED     "\033[31m"
839
0
#define RESET   "\033[0m"
840
841
static inline const char *level_color_on(bool enable, enum ws_log_level level)
842
905
{
843
905
    if (!enable)
844
905
        return "";
845
846
0
    switch (level) {
847
0
        case LOG_LEVEL_NOISY:
848
0
        case LOG_LEVEL_DEBUG:
849
0
            return GREEN;
850
0
        case LOG_LEVEL_INFO:
851
0
        case LOG_LEVEL_MESSAGE:
852
0
            return CYAN;
853
0
        case LOG_LEVEL_WARNING:
854
0
            return YELLOW;
855
0
        case LOG_LEVEL_CRITICAL:
856
0
            return MAGENTA;
857
0
        case LOG_LEVEL_ERROR:
858
0
            return RED;
859
0
        case LOG_LEVEL_ECHO:
860
0
            return YELLOW;
861
0
        default:
862
0
            break;
863
0
    }
864
0
    return "";
865
0
}
866
867
static inline const char *color_off(bool enable)
868
905
{
869
905
    return enable ? RESET : "";
870
905
}
871
872
#define NANOSECS_IN_MICROSEC 1000
873
874
/*
875
 * We must not call anything that might log a message
876
 * in the log handler context (GLib might log a message if we register
877
 * our own handler for the GLib domain).
878
 */
879
static void log_write_do_work(FILE *fp, bool use_color,
880
                                struct tm *when, long nanosecs, intmax_t pid,
881
                                const char *domain, enum ws_log_level level,
882
                                const char *file, long line, const char *func,
883
                                const char *user_format, va_list user_ap)
884
905
{
885
905
    fputs(" **", fp);
886
887
#ifdef WS_DEBUG
888
    if (!init_complete)
889
        fputs(" no init!", fp);
890
#endif
891
892
    /* Process */
893
905
    fprintf(fp, " (%s:%"PRIdMAX")", registered_progname, pid);
894
895
    /* Timestamp */
896
905
    if (when != NULL) {
897
905
        fprintf(fp, " %02d:%02d:%02d",
898
905
                            when->tm_hour, when->tm_min, when->tm_sec);
899
905
        if (nanosecs >= 0) {
900
905
            fprintf(fp, ".%06ld", nanosecs / NANOSECS_IN_MICROSEC);
901
905
        }
902
905
    }
903
904
    /* Domain/level */
905
905
    fprintf(fp, " [%s %s%s%s]", domain_to_string(domain),
906
905
                                level_color_on(use_color, level),
907
905
                                ws_log_level_to_string(level),
908
905
                                color_off(use_color));
909
910
    /* File/line */
911
905
    if (file != NULL) {
912
374
        fprintf(fp, " %s", file);
913
374
        if (line >= 0) {
914
374
            fprintf(fp, ":%ld", line);
915
374
        }
916
374
    }
917
918
    /* Any formatting changes here need to be synced with ui/capture.c:capture_input_closed. */
919
905
    fputs(" --", fp);
920
921
    /* Function name */
922
905
    if (func != NULL)
923
905
        fprintf(fp, " %s():", func);
924
925
    /* User message */
926
905
    fputc(' ', fp);
927
905
    vfprintf(fp, user_format, user_ap);
928
905
    fputc('\n', fp);
929
905
    fflush(fp);
930
905
}
931
932
933
static inline FILE *console_file(enum ws_log_level level)
934
905
{
935
905
    if (level <= LOG_LEVEL_INFO && stdout_logging_enabled)
936
0
        return stdout;
937
905
    return stderr;
938
905
}
939
940
941
static inline bool console_color_enabled(enum ws_log_level level)
942
905
{
943
905
    if (level <= LOG_LEVEL_INFO && stdout_logging_enabled)
944
0
        return stdout_color_enabled;
945
905
    return stderr_color_enabled;
946
905
}
947
948
949
static void log_write_fatal_msg(FILE *fp, intmax_t pid, const char *msg)
950
0
{
951
    /* Process */
952
0
    fprintf(fp, " ** (%s:%"PRIdMAX") %s", registered_progname, pid, msg);
953
0
}
954
955
956
/*
957
 * We must not call anything that might log a message
958
 * in the log handler context (GLib might log a message if we register
959
 * our own handler for the GLib domain).
960
 */
961
static void log_write_dispatch(const char *domain, enum ws_log_level level,
962
                            const char *file, long line, const char *func,
963
                            ws_log_manifest_t *mft,
964
                            const char *user_format, va_list user_ap)
965
905
{
966
905
    bool fatal_event = false;
967
905
    const char *fatal_msg = NULL;
968
905
    va_list user_ap_copy;
969
970
905
    if (level >= fatal_log_level && level != LOG_LEVEL_ECHO) {
971
0
        fatal_event = true;
972
0
        fatal_msg = "Aborting on fatal log level exception\n";
973
0
    }
974
905
    else if (fatal_filter != NULL) {
975
0
        if (filter_contains(fatal_filter, domain) && fatal_filter->positive) {
976
0
            fatal_event = true;
977
0
            fatal_msg = "Aborting on fatal log domain exception\n";
978
0
        }
979
0
    }
980
981
#ifdef _WIN32
982
    if (ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) {
983
        create_console(log_console_title);
984
    }
985
#endif /* _WIN32 */
986
987
905
    if (custom_log) {
988
0
        va_copy(user_ap_copy, user_ap);
989
0
        log_write_do_work(custom_log, false,
990
0
                            &mft->tstamp_secs, mft->nanosecs, mft->pid,
991
0
                            domain, level, file, line, func,
992
0
                            user_format, user_ap_copy);
993
0
        va_end(user_ap_copy);
994
0
        if (fatal_msg) {
995
0
            log_write_fatal_msg(custom_log, mft->pid, fatal_msg);
996
0
        }
997
0
    }
998
999
905
    if (registered_log_writer) {
1000
0
        registered_log_writer(domain, level, file, line, func, fatal_msg, mft,
1001
0
                        user_format, user_ap, registered_log_writer_data);
1002
0
    }
1003
905
    else {
1004
905
        log_write_do_work(console_file(level), console_color_enabled(level),
1005
905
                            &mft->tstamp_secs, mft->nanosecs, mft->pid,
1006
905
                            domain, level, file, line, func,
1007
905
                            user_format, user_ap);
1008
905
        if (fatal_msg) {
1009
0
            log_write_fatal_msg(console_file(level), mft->pid, fatal_msg);
1010
0
        }
1011
905
    }
1012
1013
#ifdef _WIN32
1014
    if (fatal_event && ws_log_console_open != LOG_CONSOLE_OPEN_NEVER) {
1015
        /* wait for a key press before the following error handler will terminate the program
1016
            this way the user at least can read the error message */
1017
        printf("\n\nPress any key to exit\n");
1018
        _getch();
1019
    }
1020
#endif /* _WIN32 */
1021
1022
905
    if (fatal_event) {
1023
0
        if (--fatal_log_count == 0)
1024
0
            abort();
1025
0
    }
1026
905
}
1027
1028
1029
void ws_logv(const char *domain, enum ws_log_level level,
1030
                    const char *format, va_list ap)
1031
0
{
1032
0
    ws_log_manifest_t mft;
1033
0
    if (!msg_is_active(domain, level, &mft))
1034
0
        return;
1035
1036
0
    log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap);
1037
0
}
1038
1039
1040
void ws_logv_full(const char *domain, enum ws_log_level level,
1041
                    const char *file, long line, const char *func,
1042
                    const char *format, va_list ap)
1043
0
{
1044
0
    ws_log_manifest_t mft;
1045
0
    if (!msg_is_active(domain, level, &mft))
1046
0
        return;
1047
1048
0
    log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1049
0
}
1050
1051
1052
void ws_log(const char *domain, enum ws_log_level level,
1053
                    const char *format, ...)
1054
516
{
1055
516
    ws_log_manifest_t mft;
1056
516
    if (!msg_is_active(domain, level, &mft))
1057
0
        return;
1058
1059
516
    va_list ap;
1060
1061
516
    va_start(ap, format);
1062
516
    log_write_dispatch(domain, level, NULL, -1, NULL, &mft, format, ap);
1063
516
    va_end(ap);
1064
516
}
1065
1066
1067
void ws_log_full(const char *domain, enum ws_log_level level,
1068
                    const char *file, long line, const char *func,
1069
                    const char *format, ...)
1070
765
{
1071
765
    ws_log_manifest_t mft;
1072
765
    if (!msg_is_active(domain, level, &mft))
1073
376
        return;
1074
1075
389
    va_list ap;
1076
1077
389
    va_start(ap, format);
1078
389
    log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1079
389
    va_end(ap);
1080
389
}
1081
1082
1083
void ws_log_fatal_full(const char *domain, enum ws_log_level level,
1084
                    const char *file, long line, const char *func,
1085
                    const char *format, ...)
1086
0
{
1087
0
    ws_log_manifest_t mft;
1088
0
    va_list ap;
1089
1090
0
    fill_manifest(&mft);
1091
0
    va_start(ap, format);
1092
0
    log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1093
0
    va_end(ap);
1094
0
    abort();
1095
0
}
1096
1097
1098
void ws_log_write_always_full(const char *domain, enum ws_log_level level,
1099
                    const char *file, long line, const char *func,
1100
                    const char *format, ...)
1101
0
{
1102
0
    ws_log_manifest_t mft;
1103
0
    va_list ap;
1104
1105
0
    fill_manifest(&mft);
1106
0
    va_start(ap, format);
1107
0
    log_write_dispatch(domain, level, file, line, func, &mft, format, ap);
1108
0
    va_end(ap);
1109
0
}
1110
1111
1112
static void
1113
append_trailer(const char *src, size_t src_length, wmem_strbuf_t *display, wmem_strbuf_t *underline)
1114
0
{
1115
0
    gunichar ch;
1116
0
    size_t hex_len;
1117
1118
0
    while (src_length > 0) {
1119
0
        ch = g_utf8_get_char_validated(src, src_length);
1120
0
        if (ch == (gunichar)-1 || ch == (gunichar)-2) {
1121
0
            wmem_strbuf_append_hex(display, *src);
1122
0
            wmem_strbuf_append_c_count(underline, '^', 4);
1123
0
            src += 1;
1124
0
            src_length -= 1;
1125
0
        }
1126
0
        else {
1127
0
            if (g_unichar_isprint(ch)) {
1128
0
                wmem_strbuf_append_unichar(display, ch);
1129
0
                wmem_strbuf_append_c_count(underline, ' ', 1);
1130
0
            }
1131
0
            else {
1132
0
                hex_len = wmem_strbuf_append_hex_unichar(display, ch);
1133
0
                wmem_strbuf_append_c_count(underline, ' ', hex_len);
1134
0
            }
1135
0
            const char *tmp = g_utf8_next_char(src);
1136
0
            src_length -= tmp - src;
1137
0
            src = tmp;
1138
0
        }
1139
0
    }
1140
0
}
1141
1142
1143
static char *
1144
make_utf8_display(const char *src, size_t src_length, size_t good_length)
1145
0
{
1146
0
    wmem_strbuf_t *display;
1147
0
    wmem_strbuf_t *underline;
1148
0
    gunichar ch;
1149
0
    size_t hex_len;
1150
1151
0
    display = wmem_strbuf_create(NULL);
1152
0
    underline = wmem_strbuf_create(NULL);
1153
1154
0
    for (const char *s = src; s < src + good_length; s = g_utf8_next_char(s)) {
1155
0
        ch = g_utf8_get_char(s);
1156
1157
0
        if (g_unichar_isprint(ch)) {
1158
0
            wmem_strbuf_append_unichar(display, ch);
1159
0
            wmem_strbuf_append_c(underline, ' ');
1160
0
        }
1161
0
        else {
1162
0
            hex_len = wmem_strbuf_append_hex_unichar(display, ch);
1163
0
            wmem_strbuf_append_c_count(underline, ' ', hex_len);
1164
0
        }
1165
0
    }
1166
1167
0
    append_trailer(&src[good_length], src_length - good_length, display, underline);
1168
1169
0
    wmem_strbuf_append_c(display, '\n');
1170
0
    wmem_strbuf_append(display, underline->str);
1171
0
    wmem_strbuf_destroy(underline);
1172
1173
0
    return wmem_strbuf_finalize(display);
1174
0
}
1175
1176
1177
void ws_log_utf8_full(const char *domain, enum ws_log_level level,
1178
                    const char *file, long line, const char *func,
1179
                    const char *string, ssize_t _length, const char *endptr)
1180
0
{
1181
0
    if (!ws_log_msg_is_active(domain, level))
1182
0
        return;
1183
1184
0
    char *display;
1185
0
    size_t length;
1186
0
    size_t good_length;
1187
1188
0
    if (_length < 0)
1189
0
        length = strlen(string);
1190
0
    else
1191
0
        length = _length;
1192
1193
0
    if (endptr == NULL || endptr < string) {
1194
        /* Find the pointer to the first invalid byte. */
1195
0
        if (g_utf8_validate(string, length, &endptr)) {
1196
            /* Valid string - should not happen. */
1197
0
            return;
1198
0
        }
1199
0
    }
1200
0
    good_length = endptr - string;
1201
1202
0
    display = make_utf8_display(string, length, good_length);
1203
1204
0
    ws_log_write_always_full(domain, level, file, line, func,
1205
0
            "Invalid UTF-8 at address %p offset %zu (length = %zu):\n%s",
1206
0
            string, good_length, length, display);
1207
1208
0
    g_free(display);
1209
0
}
1210
1211
1212
void ws_log_buffer_full(const char *domain, enum ws_log_level level,
1213
                    const char *file, long line, const char *func,
1214
                    const uint8_t *ptr, size_t size,  size_t max_bytes_len,
1215
                    const char *msg)
1216
0
{
1217
0
    if (!ws_log_msg_is_active(domain, level))
1218
0
        return;
1219
1220
0
    char *bufstr = bytes_to_str_punct_maxlen(NULL, ptr, size, ' ', max_bytes_len);
1221
1222
0
    if (G_UNLIKELY(msg == NULL))
1223
0
        ws_log_write_always_full(domain, level, file, line, func,
1224
0
                                "<buffer:%p>: %s (%zu bytes)",
1225
0
                                ptr, bufstr, size);
1226
0
    else
1227
0
        ws_log_write_always_full(domain, level, file, line, func,
1228
0
                                "%s: %s (%zu bytes)",
1229
0
                                msg, bufstr, size);
1230
0
    wmem_free(NULL, bufstr);
1231
0
}
1232
1233
1234
void ws_log_file_writer(FILE *fp, const char *domain, enum ws_log_level level,
1235
                            const char *file, long line, const char *func,
1236
                            ws_log_manifest_t *mft,
1237
                            const char *user_format, va_list user_ap)
1238
0
{
1239
0
    log_write_do_work(fp, false,
1240
0
                        &mft->tstamp_secs, mft->nanosecs, mft->pid,
1241
0
                        domain, level, file, line, func,
1242
0
                        user_format, user_ap);
1243
0
}
1244
1245
1246
void ws_log_console_writer(const char *domain, enum ws_log_level level,
1247
                            const char *file, long line, const char *func,
1248
                            ws_log_manifest_t *mft,
1249
                            const char *user_format, va_list user_ap)
1250
0
{
1251
0
    log_write_do_work(console_file(level), console_color_enabled(level),
1252
0
                        &mft->tstamp_secs, mft->nanosecs, mft->pid,
1253
0
                        domain, level, file, line, func,
1254
0
                        user_format, user_ap);
1255
0
}
1256
1257
1258
WS_DLL_PUBLIC
1259
void ws_log_console_writer_set_use_stdout(bool use_stdout)
1260
0
{
1261
0
    stdout_logging_enabled = use_stdout;
1262
0
}
1263
1264
1265
static void ws_log_cleanup(void)
1266
15
{
1267
15
    if (registered_log_writer_data_free) {
1268
0
        registered_log_writer_data_free(registered_log_writer_data);
1269
0
        registered_log_writer_data = NULL;
1270
0
    }
1271
15
    if (custom_log) {
1272
0
        fclose(custom_log);
1273
0
        custom_log = NULL;
1274
0
    }
1275
15
    free_log_filter(&domain_filter);
1276
15
    free_log_filter(&debug_filter);
1277
15
    free_log_filter(&noisy_filter);
1278
15
    free_log_filter(&fatal_filter);
1279
15
}
1280
1281
1282
void ws_log_add_custom_file(FILE *fp)
1283
0
{
1284
0
    if (custom_log != NULL) {
1285
0
        fclose(custom_log);
1286
0
    }
1287
0
    custom_log = fp;
1288
0
}
1289
1290
1291
#define USAGE_LEVEL \
1292
    "sets the active log level (\"critical\", \"warning\", etc.)"
1293
1294
#define USAGE_FATAL \
1295
    "sets level to abort the program (\"critical\" or \"warning\")"
1296
1297
#define USAGE_FATAL_COUNT \
1298
    "sets the number of fatal errors before aborting the program"
1299
1300
#define USAGE_DOMAINS \
1301
    "comma-separated list of the active log domains"
1302
1303
#define USAGE_FATAL_DOMAINS \
1304
    "list of domains that cause the program to abort"
1305
1306
#define USAGE_DEBUG \
1307
    "list of domains with \"debug\" level"
1308
1309
#define USAGE_NOISY \
1310
    "list of domains with \"noisy\" level"
1311
1312
#define USAGE_FILE \
1313
    "file to output messages to (in addition to stderr)"
1314
1315
void ws_log_print_usage(FILE *fp)
1316
0
{
1317
0
    fprintf(fp, "Diagnostic output:\n");
1318
0
    fprintf(fp, "  --log-level <level>       " USAGE_LEVEL "\n");
1319
0
    fprintf(fp, "  --log-fatal <level>       " USAGE_FATAL "\n");
1320
0
    fprintf(fp, "  --log-fatal-count <count> " USAGE_FATAL_COUNT "\n");
1321
0
    fprintf(fp, "  --log-domains <[!]list>   " USAGE_DOMAINS "\n");
1322
0
    fprintf(fp, "  --log-fatal-domains <list>\n");
1323
0
    fprintf(fp, "                            " USAGE_FATAL_DOMAINS "\n");
1324
0
    fprintf(fp, "  --log-debug <[!]list>     " USAGE_DEBUG "\n");
1325
0
    fprintf(fp, "  --log-noisy <[!]list>     " USAGE_NOISY "\n");
1326
    fprintf(fp, "  --log-file <path>         " USAGE_FILE "\n");
1327
0
}