Coverage Report

Created: 2026-08-31 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/trace.c
Line
Count
Source
1
/*
2
 * Runtime tracing API
3
 *
4
 * Copyright (C) 2000-2019 Willy Tarreau - w@1wt.eu
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation, version 2.1
9
 * exclusively.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
 */
20
21
#include <import/ist.h>
22
#include <haproxy/api.h>
23
#include <haproxy/buf.h>
24
#include <haproxy/cfgparse.h>
25
#include <haproxy/cli.h>
26
#include <haproxy/errors.h>
27
#include <haproxy/istbuf.h>
28
#include <haproxy/list.h>
29
#include <haproxy/log.h>
30
#include <haproxy/global.h>
31
#include <haproxy/quic_conn-t.h>
32
#include <haproxy/sink.h>
33
#include <haproxy/trace.h>
34
35
struct list trace_sources = LIST_HEAD_INIT(trace_sources);
36
THREAD_LOCAL struct buffer trace_buf = { };
37
38
struct trace_cmd {
39
  struct list next;
40
  const char *arg;
41
};
42
43
/* List of arguments to "-dt" options for deferred processing. */
44
static struct list trace_cmds = LIST_HEAD_INIT(trace_cmds);
45
46
/* allocates the trace buffers. Returns 0 in case of failure. It is safe to
47
 * call to call this function multiple times if the size changes.
48
 */
49
static int alloc_trace_buffers_per_thread()
50
0
{
51
0
  chunk_init(&trace_buf, my_realloc2(trace_buf.area, global.tune.bufsize), global.tune.bufsize);
52
0
  return !!trace_buf.area;
53
0
}
54
55
static void free_trace_buffers_per_thread()
56
0
{
57
0
  chunk_destroy(&trace_buf);
58
0
}
59
60
REGISTER_PER_THREAD_ALLOC(alloc_trace_buffers_per_thread);
61
REGISTER_PER_THREAD_FREE(free_trace_buffers_per_thread);
62
63
/* pick the lowest non-null argument with a non-null arg_def mask */
64
static inline const void *trace_pick_arg(uint32_t arg_def, const void *a1, const void *a2, const void *a3, const void *a4)
65
0
{
66
0
  if (arg_def & 0x0000FFFF) {
67
0
    if ((arg_def & 0x000000FF) && a1)
68
0
      return a1;
69
0
    if ((arg_def & 0x0000FF00) && a2)
70
0
      return a2;
71
0
  }
72
73
0
  if (arg_def & 0xFFFF0000) {
74
0
    if ((arg_def & 0x00FF0000) && a3)
75
0
      return a3;
76
0
    if ((arg_def & 0xFF000000) && a4)
77
0
      return a4;
78
0
  }
79
80
0
  return NULL;
81
0
}
82
83
/* Reports whether the trace is enabled for the specified arguments, needs to enable
84
 * or disable tracking. It gets the same API as __trace() except for <cb> and <msg>
85
 * which are not used and were dropped, and plockptr which is an optional pointer to
86
 * the lockptr to be updated (or NULL) for tracking. The function returns:
87
 *   0 if the trace is not enabled for the module or these values
88
 *  <0 if the trace matches some locking criteria but don't have the proper level.
89
 *     In this case the interested caller might have to consider disabling tracking.
90
 *  >0 if the trace is enabled for the given criteria.
91
 * In all cases, <plockptr> will only be set if non-null and if a locking criterion
92
 * matched. It will be up to the caller to enable tracking if desired. A casual
93
 * tester not interested in adjusting tracking (i.e. calling the function before
94
 * deciding so prepare a buffer to be dumped) will only need to pass 0 for plockptr
95
 * and check if the result is >0.
96
 */
97
int __trace_enabled(enum trace_level level, uint64_t mask, struct trace_source *src,
98
        const struct ist where, const struct ist ist_func,
99
        const void *a1, const void *a2, const void *a3, const void *a4,
100
        const void **plockptr)
101
0
{
102
0
  const void *lockon_ptr = NULL;
103
0
  const struct trace_source *origin = NULL;
104
0
  struct trace_ctx ctx = { };
105
106
  /* in case we also follow another one (e.g. session) */
107
0
  origin = HA_ATOMIC_LOAD(&src->follow);
108
109
  /* Trace can be temporarily disabled via trace_disable(). */
110
0
  if (likely(src->state == TRACE_STATE_STOPPED) && !origin)
111
0
    return 0;
112
113
0
  if (th_ctx->trc_disable_ctr)
114
0
    return 0;
115
116
  /* check that at least one action is interested by this event */
117
0
  if (((src->report_events | src->start_events | src->pause_events | src->stop_events) & mask) == 0)
118
0
    return 0;
119
120
  /* retrieve available information from the caller's arguments */
121
0
  if (src->arg_def & TRC_ARGS_CONN)
122
0
    ctx.conn = trace_pick_arg(src->arg_def & TRC_ARGS_CONN, a1, a2, a3, a4);
123
124
0
  if (src->arg_def & TRC_ARGS_SESS)
125
0
    ctx.sess = trace_pick_arg(src->arg_def & TRC_ARGS_SESS, a1, a2, a3, a4);
126
127
0
  if (src->arg_def & TRC_ARGS_STRM)
128
0
    ctx.strm = trace_pick_arg(src->arg_def & TRC_ARGS_STRM, a1, a2, a3, a4);
129
130
0
  if (src->arg_def & TRC_ARGS_CHK)
131
0
    ctx.check = trace_pick_arg(src->arg_def & TRC_ARGS_CHK, a1, a2, a3, a4);
132
133
0
  if (src->arg_def & TRC_ARGS_QCON)
134
0
    ctx.qc = trace_pick_arg(src->arg_def & TRC_ARGS_QCON, a1, a2, a3, a4);
135
136
0
  if (src->arg_def & TRC_ARGS_APPCTX)
137
0
    ctx.appctx = trace_pick_arg(src->arg_def & TRC_ARGS_APPCTX, a1, a2, a3, a4);
138
139
0
  if (src->arg_def & TRC_ARGS_HSTRM)
140
0
    ctx.hs = trace_pick_arg(src->arg_def & TRC_ARGS_HSTRM, a1, a2, a3, a4);
141
142
0
  if (src->arg_def & TRC_ARGS_HLDSTRM)
143
0
    ctx.hldstrm = trace_pick_arg(src->arg_def & TRC_ARGS_HLDSTRM, a1, a2, a3, a4);
144
145
0
  if (src->fill_ctx)
146
0
    src->fill_ctx(&ctx, src, a1, a2, a3, a4);
147
148
#ifdef USE_QUIC
149
  if (ctx.qc && !ctx.conn)
150
    ctx.conn = ctx.qc->conn;
151
#endif
152
0
  if (!ctx.sess && ctx.strm)
153
0
    ctx.sess = ctx.strm->sess;
154
0
  else if (!ctx.sess && ctx.conn && conn_is_back(ctx.conn) && LIST_INLIST(&ctx.conn->sess_el))
155
0
    ctx.sess = ctx.conn->owner;
156
0
  else if (!ctx.sess && ctx.check)
157
0
    ctx.sess = ctx.check->sess;
158
0
  else if (!ctx.sess && ctx.appctx)
159
0
    ctx.sess = ctx.appctx->sess;
160
161
0
  if (ctx.sess) {
162
0
    ctx.fe = ctx.sess->fe;
163
0
    ctx.li = ctx.sess->listener;
164
0
  }
165
166
0
  if (!ctx.li && ctx.conn)
167
0
    ctx.li = objt_listener(ctx.conn->target);
168
169
0
  if (ctx.li && !ctx.fe)
170
0
    ctx.fe = ctx.li->bind_conf->frontend;
171
172
0
  if (ctx.strm) {
173
0
    ctx.be = ctx.strm->be;
174
0
    ctx.srv = ctx.strm->srv_conn;
175
0
  }
176
0
  if (ctx.check) {
177
0
    ctx.srv = ctx.check->server;
178
0
    ctx.be = (ctx.srv ? ctx.srv->proxy : NULL);
179
0
  }
180
181
0
  if (!ctx.srv && ctx.conn)
182
0
    ctx.srv = objt_server(ctx.conn->target);
183
184
0
  if (ctx.srv && !ctx.be)
185
0
    ctx.be = ctx.srv->proxy;
186
187
  /* TODO: add handling of filters here, return if no match (not even update states) */
188
189
  /* check if we need to start the trace now */
190
0
  if (src->state == TRACE_STATE_WAITING) {
191
0
    if ((src->start_events & mask) == 0)
192
0
      return 0;
193
194
    /* TODO: add update of lockon+lockon_ptr here */
195
0
    HA_ATOMIC_STORE(&src->state, TRACE_STATE_RUNNING);
196
0
  }
197
198
  /* we may want to lock on a particular object */
199
0
  if (src->lockon != TRACE_LOCKON_NOTHING) {
200
0
    switch (src->lockon) {
201
0
    case TRACE_LOCKON_BACKEND:    lockon_ptr = ctx.be;     break;
202
0
    case TRACE_LOCKON_CONNECTION: lockon_ptr = ctx.conn;   break;
203
0
    case TRACE_LOCKON_FRONTEND:   lockon_ptr = ctx.fe;     break;
204
0
    case TRACE_LOCKON_LISTENER:   lockon_ptr = ctx.li;     break;
205
0
    case TRACE_LOCKON_SERVER:     lockon_ptr = ctx.srv;    break;
206
0
    case TRACE_LOCKON_SESSION:    lockon_ptr = ctx.sess;   break;
207
0
    case TRACE_LOCKON_STREAM:     lockon_ptr = ctx.strm;   break;
208
0
    case TRACE_LOCKON_CHECK:      lockon_ptr = ctx.check;  break;
209
0
    case TRACE_LOCKON_THREAD:     lockon_ptr = ti;         break;
210
0
    case TRACE_LOCKON_QCON:       lockon_ptr = ctx.qc;     break;
211
0
    case TRACE_LOCKON_APPCTX:     lockon_ptr = ctx.appctx; break;
212
0
    case TRACE_LOCKON_ARG1:       lockon_ptr = a1;         break;
213
0
    case TRACE_LOCKON_ARG2:       lockon_ptr = a2;         break;
214
0
    case TRACE_LOCKON_ARG3:       lockon_ptr = a3;         break;
215
0
    case TRACE_LOCKON_ARG4:       lockon_ptr = a4;         break;
216
0
    default: break; // silence stupid gcc -Wswitch
217
0
    }
218
219
0
    if (src->lockon_ptr && src->lockon_ptr != lockon_ptr)
220
0
      return 0;
221
222
0
    if (plockptr && !src->lockon_ptr && lockon_ptr && src->state == TRACE_STATE_RUNNING)
223
0
      *plockptr = lockon_ptr;
224
0
  }
225
226
  /* or we may also follow another source's locked pointer */
227
0
  if (origin) {
228
0
    if (!origin->lockon_ptr)
229
0
      return 0;
230
231
0
    switch (origin->lockon) {
232
0
    case TRACE_LOCKON_BACKEND:    lockon_ptr = ctx.be;     break;
233
0
    case TRACE_LOCKON_CONNECTION: lockon_ptr = ctx.conn;   break;
234
0
    case TRACE_LOCKON_FRONTEND:   lockon_ptr = ctx.fe;     break;
235
0
    case TRACE_LOCKON_LISTENER:   lockon_ptr = ctx.li;     break;
236
0
    case TRACE_LOCKON_SERVER:     lockon_ptr = ctx.srv;    break;
237
0
    case TRACE_LOCKON_SESSION:    lockon_ptr = ctx.sess;   break;
238
0
    case TRACE_LOCKON_STREAM:     lockon_ptr = ctx.strm;   break;
239
0
    case TRACE_LOCKON_CHECK:      lockon_ptr = ctx.check;  break;
240
0
    case TRACE_LOCKON_THREAD:     lockon_ptr = ti;         break;
241
0
    case TRACE_LOCKON_QCON:       lockon_ptr = ctx.qc;     break;
242
0
    case TRACE_LOCKON_APPCTX:     lockon_ptr = ctx.appctx; break;
243
0
    case TRACE_LOCKON_ARG1:       lockon_ptr = a1;         break;
244
0
    case TRACE_LOCKON_ARG2:       lockon_ptr = a2;         break;
245
0
    case TRACE_LOCKON_ARG3:       lockon_ptr = a3;         break;
246
0
    case TRACE_LOCKON_ARG4:       lockon_ptr = a4;         break;
247
0
    default: break; // silence stupid gcc -Wswitch
248
0
    }
249
250
0
    if (origin->lockon_ptr != lockon_ptr)
251
0
      return 0;
252
0
  }
253
254
  /* here the trace is running and is tracking a desired item */
255
0
  if ((src->report_events & mask) == 0 || level > src->level) {
256
    /* tracking did match, and might have to be disabled */
257
0
    return -1;
258
0
  }
259
260
  /* OK trace still enabled */
261
0
  return 1;
262
0
}
263
264
/* write a message for the given trace source */
265
void __trace(enum trace_level level, uint64_t mask, struct trace_source *src,
266
             const struct ist where, const struct ist ist_func,
267
             const void *a1, const void *a2, const void *a3, const void *a4,
268
             void (*cb)(enum trace_level level, uint64_t mask, const struct trace_source *src,
269
                        const struct ist where, const struct ist func,
270
                        const void *a1, const void *a2, const void *a3, const void *a4),
271
             const struct ist msg)
272
0
{
273
0
  const void *lockon_ptr;
274
0
  char tnum[4];
275
0
  struct ist line[12];
276
0
  int words = 0;
277
0
  int ret;
278
279
0
  lockon_ptr = NULL;
280
0
  ret = __trace_enabled(level, mask, src, where, ist_func, a1, a2, a3, a4, &lockon_ptr);
281
0
  if (lockon_ptr)
282
0
    HA_ATOMIC_STORE(&src->lockon_ptr, lockon_ptr);
283
284
0
  if (ret <= 0) {
285
0
    if (ret < 0) // may have to disable tracking
286
0
      goto end;
287
0
    return;
288
0
  }
289
290
  /* log the logging location truncated to 10 chars from the right so that
291
   * the line number and the end of the file name are there.
292
   */
293
0
  line[words++] = ist("[");
294
0
  tnum[0] = '0' + tid / 10;
295
0
  tnum[1] = '0' + tid % 10;
296
0
  tnum[2] = '|';
297
0
  tnum[3] = 0;
298
0
  line[words++] = ist2(tnum, 3);
299
0
  line[words++] = src->name;
300
0
  line[words++] = ist("|");
301
0
  line[words++] = ist2("012345" + level, 1); // "0" to "5"
302
0
  line[words++] = ist("|");
303
0
  line[words] = where;
304
0
  if (line[words].len > 13) {
305
0
    line[words].ptr += (line[words].len - 13);
306
0
    line[words].len = 13;
307
0
  }
308
0
  words++;
309
0
  line[words++] = ist("] ");
310
311
0
  if (isttest(ist_func)) {
312
0
    line[words++] = ist_func;
313
0
    line[words++] = ist("(): ");
314
0
  }
315
316
0
  if (!cb)
317
0
    cb = src->default_cb;
318
319
0
  if (cb && src->verbosity) {
320
    /* decode function passed, we want to pre-fill the
321
     * buffer with the message and let the decode function
322
     * do its job, possibly even overwriting it.
323
     */
324
0
    b_reset(&trace_buf);
325
0
    b_istput(&trace_buf, msg);
326
0
    cb(level, mask, src, where, ist_func, a1, a2, a3, a4);
327
0
    line[words] = ist2(trace_buf.area, trace_buf.data);
328
0
    words++;
329
0
  }
330
0
  else {
331
    /* Note that here we could decide to print some args whose type
332
     * is known, when verbosity is above the quiet level, and even
333
     * to print the name and values of those which are declared for
334
     * lock-on.
335
     */
336
0
    line[words++] = msg;
337
0
  }
338
339
0
  if (src->sink)
340
0
    sink_write(src->sink, LOG_HEADER_NONE, 0, line, words);
341
342
0
 end:
343
  /* check if we need to stop the trace now */
344
0
  if ((src->stop_events & mask) != 0) {
345
0
    HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
346
0
    HA_ATOMIC_STORE(&src->state, TRACE_STATE_STOPPED);
347
0
  }
348
0
  else if ((src->pause_events & mask) != 0) {
349
0
    HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
350
0
    HA_ATOMIC_STORE(&src->state, TRACE_STATE_WAITING);
351
0
  }
352
0
}
353
354
/* this callback may be used when no output modification is desired */
355
void trace_no_cb(enum trace_level level, uint64_t mask, const struct trace_source *src,
356
     const struct ist where, const struct ist func,
357
     const void *a1, const void *a2, const void *a3, const void *a4)
358
0
{
359
  /* do nothing */
360
0
}
361
362
static void trace_source_reset(struct trace_source *source)
363
0
{
364
0
  source->lockon = TRACE_LOCKON_NOTHING;
365
0
  source->level = TRACE_LEVEL_USER;
366
0
  source->verbosity = 1;
367
0
  source->sink = NULL;
368
0
  source->state = TRACE_STATE_STOPPED;
369
0
  source->lockon_ptr = NULL;
370
0
  source->cmdline = 0;
371
0
}
372
373
/* registers trace source <source>. Modifies the list element!
374
 * The {start,pause,stop,report} events are not changed so the source may
375
 * preset them.
376
 */
377
void trace_register_source(struct trace_source *source)
378
0
{
379
0
  trace_source_reset(source);
380
0
  LIST_APPEND(&trace_sources, &source->source_link);
381
0
}
382
383
struct trace_source *trace_find_source(const char *name)
384
0
{
385
0
  struct trace_source *src;
386
0
  const struct ist iname = ist(name);
387
388
0
  list_for_each_entry(src, &trace_sources, source_link)
389
0
    if (isteq(src->name, iname) || isteq(src->alias, iname))
390
0
      return src;
391
0
  return NULL;
392
0
}
393
394
const struct trace_event *trace_find_event(const struct trace_event *ev, const char *name)
395
0
{
396
0
  for (; ev && ev->mask; ev++)
397
0
    if (strcmp(ev->name, name) == 0)
398
0
      return ev;
399
0
  return NULL;
400
0
}
401
402
/* Returns the level value or a negative error code. */
403
static int trace_parse_level(const char *level)
404
0
{
405
0
  if (!level)
406
0
    return -1;
407
408
0
  if (strcmp(level, "error") == 0)
409
0
    return TRACE_LEVEL_ERROR;
410
0
  else if (strcmp(level, "user") == 0)
411
0
    return TRACE_LEVEL_USER;
412
0
  else if (strcmp(level, "proto") == 0)
413
0
    return TRACE_LEVEL_PROTO;
414
0
  else if (strcmp(level, "state") == 0)
415
0
    return TRACE_LEVEL_STATE;
416
0
  else if (strcmp(level, "data") == 0)
417
0
    return TRACE_LEVEL_DATA;
418
0
  else if (strcmp(level, "developer") == 0)
419
0
    return TRACE_LEVEL_DEVELOPER;
420
0
  else
421
0
    return -1;
422
0
}
423
424
/* Returns the verbosity value or a negative error code. */
425
static int trace_source_parse_verbosity(struct trace_source *src,
426
                                        const char *verbosity)
427
0
{
428
0
  const struct name_desc *nd;
429
0
  int ret;
430
431
  /* Only "quiet" is defined for all sources. Other identifiers are
432
   * specific to trace source.
433
   */
434
0
  if (strcmp(verbosity, "quiet") == 0) {
435
0
    ret = 0;
436
0
    goto end;
437
0
  }
438
439
0
  if (!src)
440
0
    return -1;
441
442
0
  if (!src->decoding || !src->decoding[0].name) {
443
0
    if (strcmp(verbosity, "default") != 0)
444
0
      return -1;
445
446
0
    ret = 1;
447
0
  }
448
0
  else {
449
0
    for (nd = src->decoding; nd->name && nd->desc; nd++)
450
0
      if (strcmp(verbosity, nd->name) == 0)
451
0
        break;
452
453
0
    if (!nd->name || !nd->desc)
454
0
      return -1;
455
456
0
    ret = nd - src->decoding + 1;
457
0
  }
458
459
0
 end:
460
0
  return ret;
461
0
}
462
463
/* helper to get trace source sink name. Behavior is different during parsing
464
 * time (<file> != NULL) and during runtime: this is to make sure that during
465
 * parsing time sink name is properly postresolved
466
 *
467
 * Returns the sink pointer on success and NULL on error. <msg> will be set
468
 * in case of error.
469
 */
470
static struct sink *_trace_get_sink(const char *name, char **msg,
471
                                    const char *file, int line)
472
0
{
473
0
  struct sink *sink = NULL;
474
475
0
  if (file) {
476
    /* only during parsing time */
477
0
    sink = sink_find_early(name, "traces", file, line);
478
0
    if (!sink) {
479
0
      memprintf(msg, "Memory error while setting up sink '%s' \n", name);
480
0
      return NULL;
481
0
    }
482
0
  } else {
483
    /* runtime */
484
0
    sink = sink_find(name);
485
0
    if (!sink) {
486
0
      memprintf(msg, "No such trace sink '%s' \n", name);
487
0
      return NULL;
488
0
    }
489
0
  }
490
0
  return sink;
491
0
}
492
493
/* Returns true if <src> trace source configuration can be changed. */
494
static int trace_enforce_origin_priority(const struct trace_source *src)
495
0
{
496
  /* Trace cannot be modified via configuration file (during startup) if
497
   * already activated via -dt command line argument.
498
   */
499
0
  return !src->cmdline || !(global.mode & MODE_STARTING);
500
0
}
501
502
/* Parse a "trace" statement. Returns a severity as a LOG_* level and a status
503
 * message that may be delivered to the user, in <msg>. The message will be
504
 * nulled first and msg must be an allocated pointer. A null status message output
505
 * indicates no error. Be careful not to use the return value as a boolean, as
506
 * LOG_* values are not ordered as one could imagine (LOG_EMERG is zero). The
507
 * function may/will use the trash buffer as the storage for the response
508
 * message so that the caller never needs to release anything.
509
 */
510
static int _trace_parse_statement(char **args, char **msg, const char *file, int line)
511
0
{
512
0
  struct trace_source *orig_src, *src;
513
0
  uint64_t *ev_ptr = NULL;
514
0
  int cur_arg;
515
516
  /* no error by default */
517
0
  *msg = NULL;
518
519
0
  if (!*args[1]) {
520
    /* no arg => report the list of supported sources as a warning */
521
0
    chunk_printf(&trash,
522
0
           "Supported trace sources and states (.=stopped, w=waiting, R=running) :\n"
523
0
           " [.] 0          : not a source, will immediately stop all traces\n"
524
0
           " [.] all        : all sources below, only for 'sink', 'level' and 'follow'\n"
525
0
           );
526
527
0
    list_for_each_entry(src, &trace_sources, source_link)
528
0
      chunk_appendf(&trash, " [%c] %-10s : %s\n", trace_state_char(src->state), src->name.ptr, src->desc);
529
530
0
    trash.area[trash.data] = 0;
531
0
    *msg = strdup(trash.area);
532
0
    return LOG_WARNING;
533
0
  }
534
535
0
  if (strcmp(args[1], "0") == 0) {
536
    /* emergency stop of all traces */
537
0
    list_for_each_entry(src, &trace_sources, source_link)
538
0
      HA_ATOMIC_STORE(&src->state, TRACE_STATE_STOPPED);
539
0
    *msg = strdup("All traces now stopped");
540
0
    return LOG_NOTICE;
541
0
  }
542
543
0
  if (strcmp(args[1], "all") == 0) {
544
0
    orig_src = NULL;
545
0
  }
546
0
  else {
547
0
    orig_src = trace_find_source(args[1]);
548
0
    if (!orig_src) {
549
0
      memprintf(msg, "No such trace source '%s'", args[1]);
550
0
      return LOG_ERR;
551
0
    }
552
0
  }
553
554
0
  cur_arg = 2;
555
0
  if (!*args[cur_arg]) {
556
0
    *msg =  "Supported commands:\n"
557
0
      "  event     : list/enable/disable source-specific event reporting\n"
558
      //"  filter    : list/enable/disable generic filters\n"
559
0
      "  level     : list/set trace reporting level\n"
560
0
      "  lock      : automatic lock on thread/connection/stream/...\n"
561
0
      "  follow    : passively follow another source's locked pointer (e.g. session)\n"
562
0
      "  pause     : pause and automatically restart after a specific event\n"
563
0
      "  sink      : list/set event sinks\n"
564
0
      "  start     : start immediately or after a specific event\n"
565
0
      "  stop      : stop immediately or after a specific event\n"
566
0
      "  verbosity : list/set trace output verbosity\n";
567
0
    *msg = strdup(*msg);
568
0
    return LOG_WARNING;
569
0
  }
570
571
0
  next_stmt:
572
0
  if (!*args[cur_arg])
573
0
    goto out;
574
575
0
  src = orig_src;
576
0
  if (src == NULL &&
577
0
      strcmp(args[cur_arg], "follow") != 0 &&
578
0
      strcmp(args[cur_arg], "sink") != 0 &&
579
0
      strcmp(args[cur_arg], "level") != 0) {
580
0
    memprintf(msg, "'%s' not applicable to meta-source 'all'", args[cur_arg]);
581
0
    return LOG_ERR;
582
0
  }
583
584
0
  if (src && !trace_enforce_origin_priority(src))
585
0
    goto out;
586
587
0
  if (strcmp(args[cur_arg], "follow") == 0) {
588
0
    const struct trace_source *origin = src ? HA_ATOMIC_LOAD(&src->follow) : NULL;
589
590
0
    if (!*args[cur_arg+1]) {
591
      /* no arg => report the list of supported sources as a warning */
592
0
      if (origin)
593
0
        chunk_printf(&trash, "Currently following source '%s'.\n", origin->name.ptr);
594
0
      else if (src)
595
0
        chunk_printf(&trash, "Not currently following any other source.\n");
596
0
      else
597
0
        chunk_reset(&trash);
598
599
0
      chunk_appendf(&trash,
600
0
             "Please specify another source to follow, among the following ones:\n"
601
0
             " [.] none       : follow no other source\n"
602
0
             );
603
604
0
      list_for_each_entry(origin, &trace_sources, source_link)
605
0
        chunk_appendf(&trash, " [%c] %-10s : %s\n", trace_state_char(origin->state), origin->name.ptr, origin->desc);
606
607
0
      trash.area[trash.data] = 0;
608
0
      *msg = strdup(trash.area);
609
0
      return LOG_WARNING;
610
0
    }
611
612
0
    origin = NULL;
613
0
    if (strcmp(args[cur_arg+1], "none") != 0) {
614
0
      origin = trace_find_source(args[cur_arg+1]);
615
0
      if (!origin) {
616
0
        memprintf(msg, "No such trace source '%s'", args[cur_arg+1]);
617
0
        return LOG_ERR;
618
0
      }
619
0
    }
620
621
0
    if (src) {
622
0
      HA_ATOMIC_STORE(&src->follow, origin);
623
0
    }
624
0
    else {
625
0
      list_for_each_entry(src, &trace_sources, source_link) {
626
0
        if (src != origin && trace_enforce_origin_priority(src))
627
0
          HA_ATOMIC_STORE(&src->follow, origin);
628
0
      }
629
0
    }
630
0
    cur_arg += 2;
631
0
    goto next_stmt;
632
0
  }
633
0
  else if ((strcmp(args[cur_arg], "event") == 0 && (ev_ptr = &src->report_events)) ||
634
0
           (strcmp(args[cur_arg], "pause") == 0 && (ev_ptr = &src->pause_events)) ||
635
0
           (strcmp(args[cur_arg], "start") == 0 && (ev_ptr = &src->start_events)) ||
636
0
           (strcmp(args[cur_arg], "stop")  == 0 && (ev_ptr = &src->stop_events))) {
637
0
    const struct trace_event *ev;
638
0
    const char *name = args[cur_arg+1];
639
0
    int neg = 0;
640
0
    int i;
641
642
    /* skip prefix '!', '-', '+' and remind negation */
643
0
    while (*name) {
644
0
      if (*name == '!' || *name == '-')
645
0
        neg = 1;
646
0
      else if (*name == '+')
647
0
        neg = 0;
648
0
      else
649
0
        break;
650
0
      name++;
651
0
    }
652
653
0
    if (!*name) {
654
0
      chunk_printf(&trash, "Supported events for source %s (+=enabled, -=disabled):\n", src->name.ptr);
655
0
      if (ev_ptr != &src->report_events)
656
0
        chunk_appendf(&trash, "  - now          : don't wait for events, immediately change the state\n");
657
0
      chunk_appendf(&trash, "  - none         : disable all event types\n");
658
0
      chunk_appendf(&trash, "  - any          : enable all event types\n");
659
0
      for (i = 0; src->known_events && src->known_events[i].mask; i++) {
660
0
        chunk_appendf(&trash, "  %c %-12s : %s\n",
661
0
                trace_event_char(*ev_ptr, src->known_events[i].mask),
662
0
                src->known_events[i].name, src->known_events[i].desc);
663
0
      }
664
0
      trash.area[trash.data] = 0;
665
0
      *msg = strdup(trash.area);
666
0
      return LOG_WARNING;
667
0
    }
668
669
    /* state transitions:
670
     *   - "start now" => TRACE_STATE_RUNNING
671
     *   - "stop now"  => TRACE_STATE_STOPPED
672
     *   - "pause now" => TRACE_STATE_WAITING
673
     *   - "start <evt>" && STATE_STOPPED => TRACE_STATE_WAITING
674
     */
675
676
0
    if (strcmp(name, "now") == 0 && ev_ptr != &src->report_events) {
677
0
      HA_ATOMIC_STORE(ev_ptr, 0);
678
0
      if (ev_ptr == &src->pause_events) {
679
0
        HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
680
0
        HA_ATOMIC_STORE(&src->state, TRACE_STATE_WAITING);
681
0
      }
682
0
      else if (ev_ptr == &src->start_events) {
683
0
        HA_ATOMIC_STORE(&src->state, TRACE_STATE_RUNNING);
684
0
      }
685
0
      else if (ev_ptr == &src->stop_events) {
686
0
        HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
687
0
        HA_ATOMIC_STORE(&src->state, TRACE_STATE_STOPPED);
688
0
      }
689
0
    }
690
0
    else if (strcmp(name, "none") == 0)
691
0
      HA_ATOMIC_STORE(ev_ptr, 0);
692
0
    else if (strcmp(name, "any") == 0) {
693
0
      enum trace_state old = TRACE_STATE_STOPPED;
694
695
0
      HA_ATOMIC_STORE(ev_ptr, ~0);
696
0
      if (ev_ptr == &src->start_events)
697
0
        HA_ATOMIC_CAS(&src->state, &old, TRACE_STATE_WAITING);
698
0
    }
699
0
    else {
700
0
      enum trace_state old = TRACE_STATE_STOPPED;
701
702
0
      ev = trace_find_event(src->known_events, name);
703
0
      if (!ev) {
704
0
        memprintf(msg, "No such trace event '%s'", name);
705
0
        return LOG_ERR;
706
0
      }
707
708
0
      if (!neg)
709
0
        HA_ATOMIC_OR(ev_ptr, ev->mask);
710
0
      else
711
0
        HA_ATOMIC_AND(ev_ptr, ~ev->mask);
712
713
0
      if (ev_ptr == &src->start_events && HA_ATOMIC_LOAD(ev_ptr) != 0)
714
0
        HA_ATOMIC_CAS(&src->state, &old, TRACE_STATE_WAITING);
715
0
    }
716
717
0
    cur_arg += 2;
718
0
    goto next_stmt;
719
0
  }
720
0
  else if (strcmp(args[cur_arg], "sink") == 0) {
721
0
    const char *name = args[cur_arg+1];
722
0
    struct sink *sink;
723
724
0
    if (!*name) {
725
0
      chunk_printf(&trash, "Supported sinks for source %s (*=current):\n", src ? src->name.ptr : "all");
726
0
      chunk_appendf(&trash, "  %c none       : no sink\n", src && src->sink ? ' ' : '*');
727
0
      list_for_each_entry(sink, &sink_list, sink_list) {
728
0
        chunk_appendf(&trash, "  %c %-10s : %s\n",
729
0
                src && src->sink == sink ? '*' : ' ',
730
0
                sink->name, sink->desc);
731
0
      }
732
0
      if (file)
733
0
        chunk_appendf(&trash, "(forward-declared sinks are not displayed here!)\n");
734
0
      trash.area[trash.data] = 0;
735
0
      *msg = strdup(trash.area);
736
0
      return LOG_WARNING;
737
0
    }
738
739
0
    if (strcmp(name, "none") == 0)
740
0
      sink = NULL;
741
0
    else {
742
0
      sink = _trace_get_sink(name, msg, file, line);
743
0
      if (!sink)
744
0
        return LOG_ERR;
745
0
    }
746
747
0
    if (src) {
748
0
      HA_ATOMIC_STORE(&src->sink, sink);
749
0
    }
750
0
    else {
751
0
      list_for_each_entry(src, &trace_sources, source_link) {
752
0
        if (trace_enforce_origin_priority(src))
753
0
          HA_ATOMIC_STORE(&src->sink, sink);
754
0
      }
755
0
    }
756
757
0
    cur_arg += 2;
758
0
    goto next_stmt;
759
0
  }
760
0
  else if (strcmp(args[cur_arg], "level") == 0) {
761
0
    const char *name = args[cur_arg+1];
762
0
    int level = -1;
763
764
0
    if (*name)
765
0
      level = trace_parse_level(name);
766
767
0
    if (level < 0) {
768
0
      chunk_reset(&trash);
769
0
      if (*name)
770
0
        chunk_appendf(&trash, "No such trace level '%s'. ", name);
771
0
      chunk_appendf(&trash, "Supported trace levels for source %s:\n", src ? src->name.ptr : "all");
772
0
      chunk_appendf(&trash, "  %c error      : report errors\n",
773
0
              src && src->level == TRACE_LEVEL_ERROR ? '*' : ' ');
774
0
      chunk_appendf(&trash, "  %c user       : also information useful to the end user\n",
775
0
              src && src->level == TRACE_LEVEL_USER ? '*' : ' ');
776
0
      chunk_appendf(&trash, "  %c proto      : also protocol-level updates\n",
777
0
              src && src->level == TRACE_LEVEL_PROTO ? '*' : ' ');
778
0
      chunk_appendf(&trash, "  %c state      : also report internal state changes\n",
779
0
              src && src->level == TRACE_LEVEL_STATE ? '*' : ' ');
780
0
      chunk_appendf(&trash, "  %c data       : also report data transfers\n",
781
0
              src && src->level == TRACE_LEVEL_DATA ? '*' : ' ');
782
0
      chunk_appendf(&trash, "  %c developer  : also report information useful only to the developer\n",
783
0
              src && src->level == TRACE_LEVEL_DEVELOPER ? '*' : ' ');
784
0
      trash.area[trash.data] = 0;
785
0
      *msg = strdup(trash.area);
786
0
      return *name ? LOG_ERR : LOG_WARNING;
787
0
    }
788
789
0
    if (src) {
790
0
      HA_ATOMIC_STORE(&src->level, level);
791
0
    }
792
0
    else {
793
0
      list_for_each_entry(src, &trace_sources, source_link) {
794
0
        if (trace_enforce_origin_priority(src))
795
0
          HA_ATOMIC_STORE(&src->level, level);
796
0
      }
797
0
    }
798
799
0
    cur_arg += 2;
800
0
    goto next_stmt;
801
0
  }
802
0
  else if (strcmp(args[cur_arg], "lock") == 0) {
803
0
    const char *name = args[cur_arg+1];
804
805
0
    if (!*name) {
806
0
      chunk_printf(&trash, "Supported lock-on criteria for source %s:\n", src->name.ptr);
807
0
      if (src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_STRM))
808
0
        chunk_appendf(&trash, "  %c backend    : lock on the backend that started the trace\n",
809
0
                      src->lockon == TRACE_LOCKON_BACKEND ? '*' : ' ');
810
811
0
      if (src->arg_def & TRC_ARGS_CHK)
812
0
        chunk_appendf(&trash, "  %c check      : lock on the check that started the trace\n",
813
0
                      src->lockon == TRACE_LOCKON_CHECK ? '*' : ' ');
814
815
0
      if (src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON))
816
0
        chunk_appendf(&trash, "  %c connection : lock on the connection that started the trace\n",
817
0
                      src->lockon == TRACE_LOCKON_CONNECTION ? '*' : ' ');
818
819
0
      if (src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON|TRC_ARGS_SESS|TRC_ARGS_STRM))
820
0
        chunk_appendf(&trash, "  %c frontend   : lock on the frontend that started the trace\n",
821
0
                      src->lockon == TRACE_LOCKON_FRONTEND ? '*' : ' ');
822
823
0
      if (src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON|TRC_ARGS_SESS|TRC_ARGS_STRM))
824
0
        chunk_appendf(&trash, "  %c listener   : lock on the listener that started the trace\n",
825
0
                      src->lockon == TRACE_LOCKON_LISTENER ? '*' : ' ');
826
827
0
      chunk_appendf(&trash, "  %c nothing    : do not lock on anything\n",
828
0
              src->lockon == TRACE_LOCKON_NOTHING ? '*' : ' ');
829
830
0
      if (src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_STRM))
831
0
        chunk_appendf(&trash, "  %c server     : lock on the server that started the trace\n",
832
0
                      src->lockon == TRACE_LOCKON_SERVER ? '*' : ' ');
833
#ifdef USE_QUIC
834
      if (src->arg_def & TRC_ARGS_QCON)
835
        chunk_appendf(&trash, "  %c qconn      : lock on the QUIC connection that started the trace\n",
836
                      src->lockon == TRACE_LOCKON_QCON ? '*' : ' ');
837
#endif
838
0
      if (src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON|TRC_ARGS_SESS|TRC_ARGS_STRM))
839
0
        chunk_appendf(&trash, "  %c session    : lock on the session that started the trace\n",
840
0
                      src->lockon == TRACE_LOCKON_SESSION ? '*' : ' ');
841
842
0
      if (src->arg_def & TRC_ARGS_STRM)
843
0
        chunk_appendf(&trash, "  %c stream     : lock on the stream that started the trace\n",
844
0
                      src->lockon == TRACE_LOCKON_STREAM ? '*' : ' ');
845
846
0
      if (src->arg_def & TRC_ARGS_APPCTX)
847
0
        chunk_appendf(&trash, "  %c applet     : lock on the applet that started the trace\n",
848
0
                      src->lockon == TRACE_LOCKON_APPCTX ? '*' : ' ');
849
850
0
      chunk_appendf(&trash, "  %c thread     : lock on the thread that started the trace\n",
851
0
              src->lockon == TRACE_LOCKON_THREAD ? '*' : ' ');
852
853
0
      if (src->lockon_args && src->lockon_args[0].name)
854
0
        chunk_appendf(&trash, "  %c %-10s : %s\n",
855
0
                      src->lockon == TRACE_LOCKON_ARG1 ? '*' : ' ',
856
0
                      src->lockon_args[0].name, src->lockon_args[0].desc);
857
858
0
      if (src->lockon_args && src->lockon_args[1].name)
859
0
        chunk_appendf(&trash, "  %c %-10s : %s\n",
860
0
                      src->lockon == TRACE_LOCKON_ARG2 ? '*' : ' ',
861
0
                      src->lockon_args[1].name, src->lockon_args[1].desc);
862
863
0
      if (src->lockon_args && src->lockon_args[2].name)
864
0
        chunk_appendf(&trash, "  %c %-10s : %s\n",
865
0
                      src->lockon == TRACE_LOCKON_ARG3 ? '*' : ' ',
866
0
                      src->lockon_args[2].name, src->lockon_args[2].desc);
867
868
0
      if (src->lockon_args && src->lockon_args[3].name)
869
0
        chunk_appendf(&trash, "  %c %-10s : %s\n",
870
0
                      src->lockon == TRACE_LOCKON_ARG4 ? '*' : ' ',
871
0
                      src->lockon_args[3].name, src->lockon_args[3].desc);
872
873
0
      trash.area[trash.data] = 0;
874
0
      *msg = strdup(trash.area);
875
0
      return LOG_WARNING;
876
0
    }
877
0
    else if ((src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_STRM)) && strcmp(name, "backend") == 0) {
878
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_BACKEND);
879
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
880
0
    }
881
0
    else if ((src->arg_def & TRC_ARGS_CHK) && strcmp(name, "check") == 0) {
882
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_CHECK);
883
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
884
0
    }
885
0
    else if ((src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON)) && strcmp(name, "connection") == 0) {
886
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_CONNECTION);
887
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
888
0
    }
889
0
    else if ((src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON|TRC_ARGS_SESS|TRC_ARGS_STRM)) && strcmp(name, "frontend") == 0) {
890
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_FRONTEND);
891
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
892
0
    }
893
0
    else if ((src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON|TRC_ARGS_SESS|TRC_ARGS_STRM)) && strcmp(name, "listener") == 0) {
894
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_LISTENER);
895
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
896
0
    }
897
0
    else if (strcmp(name, "nothing") == 0) {
898
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_NOTHING);
899
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
900
0
    }
901
0
    else if ((src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_STRM)) && strcmp(name, "server") == 0) {
902
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_SERVER);
903
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
904
0
    }
905
0
    else if ((src->arg_def & (TRC_ARGS_CONN|TRC_ARGS_QCON|TRC_ARGS_SESS|TRC_ARGS_STRM)) && strcmp(name, "session") == 0) {
906
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_SESSION);
907
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
908
0
    }
909
0
    else if ((src->arg_def & TRC_ARGS_QCON) && strcmp(name, "qconn") == 0) {
910
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_QCON);
911
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
912
0
    }
913
0
    else if ((src->arg_def & TRC_ARGS_STRM) && strcmp(name, "stream") == 0) {
914
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_STREAM);
915
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
916
0
    }
917
0
    else if ((src->arg_def & TRC_ARGS_APPCTX) && strcmp(name, "appctx") == 0) {
918
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_APPCTX);
919
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
920
0
    }
921
0
    else if (strcmp(name, "thread") == 0) {
922
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_THREAD);
923
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
924
0
    }
925
0
    else if (src->lockon_args && src->lockon_args[0].name && strcmp(name, src->lockon_args[0].name) == 0) {
926
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_ARG1);
927
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
928
0
    }
929
0
    else if (src->lockon_args && src->lockon_args[1].name && strcmp(name, src->lockon_args[1].name) == 0) {
930
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_ARG2);
931
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
932
0
    }
933
0
    else if (src->lockon_args && src->lockon_args[2].name && strcmp(name, src->lockon_args[2].name) == 0) {
934
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_ARG3);
935
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
936
0
    }
937
0
    else if (src->lockon_args && src->lockon_args[3].name && strcmp(name, src->lockon_args[3].name) == 0) {
938
0
      HA_ATOMIC_STORE(&src->lockon, TRACE_LOCKON_ARG4);
939
0
      HA_ATOMIC_STORE(&src->lockon_ptr, NULL);
940
0
    }
941
0
    else {
942
0
      memprintf(msg, "Unsupported lock-on criterion '%s'", name);
943
0
      return LOG_ERR;
944
0
    }
945
946
0
    cur_arg += 2;
947
0
    goto next_stmt;
948
0
  }
949
0
  else if (strcmp(args[cur_arg], "verbosity") == 0) {
950
0
    const char *name = args[cur_arg+1];
951
0
    const struct name_desc *nd;
952
0
    int verbosity = -1;
953
954
0
    if (*name)
955
0
      verbosity = trace_source_parse_verbosity(src, name);
956
957
0
    if (verbosity < 0) {
958
0
      chunk_reset(&trash);
959
0
      if (*name)
960
0
        chunk_appendf(&trash, "No such verbosity level '%s'. ", name);
961
0
      chunk_appendf(&trash, "Supported trace verbosities for source %s:\n", src->name.ptr);
962
0
      chunk_appendf(&trash, "  %c quiet      : only report basic information with no decoding\n",
963
0
              src->verbosity == 0 ? '*' : ' ');
964
0
      if (!src->decoding || !src->decoding[0].name) {
965
0
        chunk_appendf(&trash, "  %c default    : report extra information when available\n",
966
0
                src->verbosity > 0 ? '*' : ' ');
967
0
      } else {
968
0
        for (nd = src->decoding; nd->name && nd->desc; nd++)
969
0
          chunk_appendf(&trash, "  %c %-10s : %s\n",
970
0
                        nd == (src->decoding + src->verbosity - 1) ? '*' : ' ',
971
0
                  nd->name, nd->desc);
972
0
      }
973
0
      trash.area[trash.data] = 0;
974
0
      *msg = strdup(trash.area);
975
0
      return *name ? LOG_ERR : LOG_WARNING;
976
0
    }
977
978
0
    HA_ATOMIC_STORE(&src->verbosity, verbosity);
979
980
0
    cur_arg += 2;
981
0
    goto next_stmt;
982
0
  }
983
0
  else {
984
0
    memprintf(msg, "Unknown trace keyword '%s'", args[cur_arg]);
985
0
    return LOG_ERR;
986
0
  }
987
988
0
  out:
989
0
  return 0;
990
991
0
}
992
993
/* same as _trace_parse_statement but when no file:line context is available
994
 * (during runtime)
995
 */
996
static int trace_parse_statement(char **args, char **msg)
997
0
{
998
0
  return _trace_parse_statement(args, msg, NULL, 0);
999
0
}
1000
1001
int trace_add_cmd(const char *arg_src, char **errmsg)
1002
0
{
1003
0
  struct trace_cmd *cmd;
1004
1005
0
  cmd = malloc(sizeof(*cmd));
1006
0
  if (!cmd) {
1007
0
    memprintf(errmsg, "Can't allocate trace cmd!");
1008
0
    return -1;
1009
0
  }
1010
1011
0
  cmd->arg = arg_src;
1012
0
  LIST_APPEND(&trace_cmds, &cmd->next);
1013
0
  return 0;
1014
0
}
1015
1016
void _trace_parse_cmd(struct trace_source *src, int level, int verbosity)
1017
0
{
1018
0
  trace_source_reset(src);
1019
0
  src->sink = sink_find("stderr");
1020
0
  src->level = level >= 0 ? level : TRACE_LEVEL_ERROR;
1021
0
  src->verbosity = verbosity >= 0 ? verbosity : 1;
1022
0
  src->state = TRACE_STATE_RUNNING;
1023
0
  src->cmdline = 1;
1024
0
}
1025
1026
/* Parse a process argument specified via "-dt".
1027
 *
1028
 * Returns 0 on success else non-zero.
1029
 */
1030
static int trace_parse_cmd(const char *arg_src, char **errmsg)
1031
0
{
1032
0
  char *str;
1033
0
  char *arg, *oarg;
1034
0
  char *saveptr;
1035
1036
0
  if (!arg_src || !*arg_src) {
1037
    /* No trace specification, activate all sources on error level. */
1038
0
    struct trace_source *src = NULL;
1039
1040
0
    list_for_each_entry(src, &trace_sources, source_link)
1041
0
      _trace_parse_cmd(src, -1, -1);
1042
0
    return 0;
1043
0
  }
1044
1045
0
  if (strcmp(arg_src, "help") == 0) {
1046
0
    memprintf(errmsg,
1047
0
              "-dt activates traces on stderr output via the command-line.\n"
1048
0
              "Without argument, all registered trace sources are activated with error level as filter.\n"
1049
0
              "A list can be specified as argument to configure several trace sources with comma as separator.\n"
1050
0
              "Each entry can contains the trace name, a log level and a verbosity using colon as separator.\n"
1051
0
              "Every fields are optional and can be left empty, or with a colon to specify the next one.\n\n"
1052
0
              "An empty name or the alias 'all' will activate all registered sources.\n"
1053
0
              "Verbosity cannot be configured in this case except 'quiet' as their values are specific to each source.\n\n"
1054
0
              "Examples:\n"
1055
0
              "-dt           activate every sources on error level\n"
1056
0
              "-dt all:user  activate every sources on user level\n"
1057
0
              "-dt h1        activate HTTP/1 traces on error level\n"
1058
0
              "-dt h2:data   activate HTTP/2 traces on data level\n"
1059
0
              "-dt quic::clean,qmux::minimal\n    activate both QUIC transport and MUX traces on error level with their custom verbosity\n");
1060
0
    return -1;
1061
0
  }
1062
1063
  /* keep a copy of the ptr for strtok */
1064
0
  oarg = arg = strdup(arg_src);
1065
0
  if (!arg) {
1066
0
    memprintf(errmsg, "Can't allocate trace source!");
1067
0
    return -2;
1068
0
  }
1069
1070
0
  while ((str = strtok_r(arg, ",", &saveptr))) {
1071
0
    struct trace_source *src = NULL;
1072
0
    char *field, *name;
1073
0
    char *sep;
1074
0
    int level = -1, verbosity = -1;
1075
1076
    /* 1. name */
1077
0
    name = str;
1078
0
    sep = strchr(str, ':');
1079
0
    if (sep) {
1080
0
      str = sep + 1;
1081
0
      *sep = '\0';
1082
0
    }
1083
0
    else {
1084
0
      str = NULL;
1085
0
    }
1086
1087
0
    if (strlen(name) && strcmp(name, "all") != 0) {
1088
0
      src = trace_find_source(name);
1089
0
      if (!src) {
1090
0
        memprintf(errmsg, "unknown trace source '%s'", name);
1091
0
        ha_free(&oarg);
1092
0
        return -2;
1093
0
      }
1094
0
    }
1095
1096
0
    if (!str || !strlen(str))
1097
0
      goto parse;
1098
1099
    /* 2. level */
1100
0
    field = str;
1101
0
    sep = strchr(str, ':');
1102
0
    if (sep) {
1103
0
      str = sep + 1;
1104
0
      *sep = '\0';
1105
0
    }
1106
0
    else {
1107
0
      str = NULL;
1108
0
    }
1109
1110
0
    if (strlen(field)) {
1111
0
      level = trace_parse_level(field);
1112
0
      if (level < 0) {
1113
0
        memprintf(errmsg, "no such trace level '%s', available levels are 'error', 'user', 'proto', 'state', 'data', and 'developer'", field);
1114
0
        ha_free(&oarg);
1115
0
        return -2;
1116
0
      }
1117
0
    }
1118
1119
0
    if (!str || !strlen(str))
1120
0
      goto parse;
1121
1122
    /* 3. verbosity */
1123
0
    field = str;
1124
0
    if (strchr(field, ':')) {
1125
0
      memprintf(errmsg, "too many colon separators in trace definition");
1126
0
      ha_free(&oarg);
1127
0
      return -2;
1128
0
    }
1129
1130
0
    verbosity = trace_source_parse_verbosity(src, field);
1131
0
    if (verbosity < 0) {
1132
0
      const struct name_desc *nd;
1133
1134
0
      if (!src) {
1135
0
        memprintf(errmsg, "trace source must be specified for verbosity other than 'quiet'");
1136
0
      }
1137
0
      else {
1138
0
        memprintf(errmsg, "no such trace verbosity '%s' for source '%s', available verbosities for this source are: 'quiet'", field, name);
1139
0
        for (nd = src->decoding; nd->name && nd->desc; nd++)
1140
0
          memprintf(errmsg, "%s, %s'%s'", *errmsg, (nd + 1)->name ? "" : "and ", nd->name);
1141
0
      }
1142
1143
0
      ha_free(&oarg);
1144
0
      return -2;
1145
0
    }
1146
1147
0
 parse:
1148
0
    if (src) {
1149
0
      _trace_parse_cmd(src, level, verbosity);
1150
0
    }
1151
0
    else {
1152
0
      list_for_each_entry(src, &trace_sources, source_link)
1153
0
        _trace_parse_cmd(src, level, verbosity);
1154
0
    }
1155
1156
    /* Reset arg to NULL for strtok. */
1157
0
    arg = NULL;
1158
0
  }
1159
0
  ha_free(&oarg);
1160
0
  return 0;
1161
0
}
1162
1163
void trace_parse_cmds(void)
1164
0
{
1165
0
  struct trace_cmd *cmd;
1166
0
  char *errmsg = NULL;
1167
0
  int ret = 0;
1168
1169
0
  while (!LIST_ISEMPTY(&trace_cmds)) {
1170
0
    cmd = LIST_ELEM(trace_cmds.n, struct trace_cmd *, next);
1171
0
    if (!ret)
1172
0
      ret = trace_parse_cmd(cmd->arg, &errmsg);
1173
0
    LIST_DELETE(&cmd->next);
1174
0
    free(cmd);
1175
0
  }
1176
1177
0
  if (ret <= -1) {
1178
0
    if (ret < -1) {
1179
0
      ha_alert("-dt: %s.\n", errmsg);
1180
0
      ha_free(&errmsg);
1181
0
      exit(EXIT_FAILURE);
1182
0
    }
1183
0
    else {
1184
0
      printf("%s\n", errmsg);
1185
0
      ha_free(&errmsg);
1186
0
      exit(0);
1187
0
    }
1188
0
  }
1189
0
}
1190
1191
/* parse a "trace" statement in the "global" section, returns -1 on error, zero otherwise */
1192
static int cfg_parse_trace(char **args, int section_type, struct proxy *curpx,
1193
         const struct proxy *defpx, const char *file, int line,
1194
         char **err)
1195
0
{
1196
0
  char *msg;
1197
0
  int severity;
1198
1199
0
  severity = _trace_parse_statement(args, &msg, file, line);
1200
0
  if (msg) {
1201
0
    if (severity >= LOG_NOTICE)
1202
0
      ha_notice("parsing [%s:%d] : '%s': %s\n", file, line, args[0], msg);
1203
0
    else if (severity >= LOG_WARNING)
1204
0
      ha_warning("parsing [%s:%d] : '%s': %s\n", file, line, args[0], msg);
1205
0
    else {
1206
      /* let the caller free the message */
1207
0
      *err = msg;
1208
0
      return -1;
1209
0
    }
1210
0
    ha_free(&msg);
1211
0
  }
1212
1213
0
  return 0;
1214
0
}
1215
1216
/*
1217
 * parse a line in a <traces> section. Returns the error code, 0 if OK, or
1218
 * any combination of :
1219
 *  - ERR_ABORT: must abort ASAP
1220
 *  - ERR_FATAL: we can continue parsing but not start the service
1221
 *  - ERR_WARN: a warning has been emitted
1222
 *  - ERR_ALERT: an alert has been emitted
1223
 * Only the two first ones can stop processing, the two others are just
1224
 * indicators.
1225
 */
1226
int cfg_parse_traces(const char *file, int linenum, char **args, int inv)
1227
0
{
1228
0
  int err_code = 0;
1229
0
  char *errmsg = NULL;
1230
1231
0
  if (strcmp(args[0], "traces") == 0) {  /* new section */
1232
    /* no option, nothing special to do */
1233
0
    alertif_too_many_args(0, file, linenum, args, &err_code);
1234
0
    goto out;
1235
0
  }
1236
0
  else {
1237
0
    struct cfg_kw_list *kwl;
1238
0
    const char *best;
1239
0
    int index;
1240
0
    int rc;
1241
1242
0
    list_for_each_entry(kwl, &cfg_keywords.list, list) {
1243
0
      for (index = 0; kwl->kw[index].kw != NULL; index++) {
1244
0
        if (kwl->kw[index].section != CFG_TRACES)
1245
0
          continue;
1246
0
        if (strcmp(kwl->kw[index].kw, args[0]) == 0) {
1247
0
          if (check_kw_experimental(&kwl->kw[index], file, linenum, &errmsg)) {
1248
0
            ha_alert("%s\n", errmsg);
1249
0
            err_code |= ERR_ALERT | ERR_FATAL;
1250
0
            goto out;
1251
0
          }
1252
1253
0
          rc = kwl->kw[index].parse(args, CFG_TRACES, NULL, NULL, file, linenum, &errmsg);
1254
0
          if (rc < 0) {
1255
0
            ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg);
1256
0
            err_code |= ERR_ALERT | ERR_FATAL;
1257
0
          }
1258
0
          else if (rc > 0) {
1259
0
            ha_warning("parsing [%s:%d] : %s\n", file, linenum, errmsg);
1260
0
            err_code |= ERR_WARN;
1261
0
          }
1262
0
          goto out;
1263
0
        }
1264
0
      }
1265
0
    }
1266
1267
0
    best = cfg_find_best_match(args[0], &cfg_keywords.list, CFG_TRACES, NULL);
1268
0
    if (best)
1269
0
      ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section; did you mean '%s' maybe ?\n", file, linenum, args[0], cursection, best);
1270
0
    else
1271
0
      ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "global");
1272
0
    err_code |= ERR_ALERT | ERR_FATAL;
1273
0
  }
1274
1275
0
  out:
1276
0
  free(errmsg);
1277
0
  return err_code;
1278
0
}
1279
1280
/* parse the command, returns 1 if a message is returned, otherwise zero */
1281
static int cli_parse_trace(char **args, char *payload, struct appctx *appctx, void *private)
1282
0
{
1283
0
  char *msg;
1284
0
  int severity;
1285
1286
0
  if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1287
0
    return 1;
1288
1289
0
  severity = trace_parse_statement(args, &msg);
1290
0
  if (msg)
1291
0
    return cli_dynmsg(appctx, severity, msg);
1292
1293
  /* total success */
1294
0
  return 0;
1295
0
}
1296
1297
/* parse the command, returns 1 if a message is returned, otherwise zero */
1298
static int cli_parse_show_trace(char **args, char *payload, struct appctx *appctx, void *private)
1299
0
{
1300
0
  struct trace_source *src;
1301
0
  const struct sink *sink;
1302
0
  int i;
1303
1304
0
  args++; // make args[1] the 1st arg
1305
1306
0
  if (!*args[1]) {
1307
    /* no arg => report the list of supported sources */
1308
0
    chunk_printf(&trash,
1309
0
           "Supported trace sources and states (.=stopped, w=waiting, R=running) :\n"
1310
0
           );
1311
1312
0
    list_for_each_entry(src, &trace_sources, source_link) {
1313
0
      sink = src->sink;
1314
0
      chunk_appendf(&trash, " [%c] %-10s -> %s [drp %u]  [%s]\n",
1315
0
              trace_state_char(src->state), src->name.ptr,
1316
0
              sink ? sink->name : "none",
1317
0
              sink ? sink->ctx.dropped : 0,
1318
0
              src->desc);
1319
0
    }
1320
1321
0
    trash.area[trash.data] = 0;
1322
0
    return cli_msg(appctx, LOG_INFO, trash.area);
1323
0
  }
1324
1325
0
  if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1326
0
    return 1;
1327
1328
0
  src = trace_find_source(args[1]);
1329
0
  if (!src)
1330
0
    return cli_err(appctx, "No such trace source");
1331
1332
0
  sink = src->sink;
1333
0
  chunk_printf(&trash, "Trace status for %s:\n", src->name.ptr);
1334
0
  chunk_appendf(&trash, "  - sink: %s [%u dropped]\n",
1335
0
          sink ? sink->name : "none", sink ? sink->ctx.dropped : 0);
1336
1337
0
  chunk_appendf(&trash, "  - event name   :     report    start    stop    pause\n");
1338
0
  for (i = 0; src->known_events && src->known_events[i].mask; i++) {
1339
0
    chunk_appendf(&trash, "    %-12s :        %c        %c        %c       %c\n",
1340
0
            src->known_events[i].name,
1341
0
            trace_event_char(src->report_events, src->known_events[i].mask),
1342
0
            trace_event_char(src->start_events, src->known_events[i].mask),
1343
0
            trace_event_char(src->stop_events, src->known_events[i].mask),
1344
0
            trace_event_char(src->pause_events, src->known_events[i].mask));
1345
0
  }
1346
1347
0
  trash.area[trash.data] = 0;
1348
  return cli_msg(appctx, LOG_WARNING, trash.area);
1349
0
}
1350
1351
static struct cli_kw_list cli_kws = {{ },{
1352
  { { "trace", NULL },         "trace [<module>|0] [cmd [args...]]      : manage live tracing (empty to list, 0 to stop all)", cli_parse_trace, NULL, NULL },
1353
  { { "show", "trace", NULL }, "show trace [<module>]                   : show live tracing state",                            cli_parse_show_trace, NULL, NULL },
1354
  {{},}
1355
}};
1356
1357
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
1358
1359
static struct cfg_kw_list cfg_kws = {ILH, {
1360
  { CFG_TRACES, "trace", cfg_parse_trace },
1361
  { /* END */ },
1362
}};
1363
1364
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
1365
1366
/*
1367
 * Local variables:
1368
 *  c-indent-level: 8
1369
 *  c-basic-offset: 8
1370
 * End:
1371
 */