Coverage Report

Created: 2025-10-23 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/frr/lib/command_graph.c
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
 * CLI graph handling
4
 *
5
 * --
6
 * Copyright (C) 2016 Cumulus Networks, Inc.
7
 * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
8
 * Copyright (C) 2013 by Open Source Routing.
9
 * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC")
10
 */
11
12
#include <zebra.h>
13
14
#include "command_graph.h"
15
16
8
DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens");
17
8
DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text");
18
8
DEFINE_MTYPE_STATIC(LIB, CMD_TEXT, "Command Token Help");
19
8
DEFINE_MTYPE(LIB, CMD_ARG, "Command Argument");
20
8
DEFINE_MTYPE_STATIC(LIB, CMD_VAR, "Command Argument Name");
21
8
22
8
struct cmd_token *cmd_token_new(enum cmd_token_type type, uint8_t attr,
23
8
        const char *text, const char *desc)
24
75
{
25
75
  struct cmd_token *token =
26
75
    XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_token));
27
75
  token->type = type;
28
75
  token->attr = attr;
29
75
  token->text = text ? XSTRDUP(MTYPE_CMD_TEXT, text) : NULL;
30
75
  token->desc = desc ? XSTRDUP(MTYPE_CMD_DESC, desc) : NULL;
31
75
  token->refcnt = 1;
32
75
  token->arg = NULL;
33
75
  token->allowrepeat = false;
34
75
  token->varname = NULL;
35
36
75
  return token;
37
75
}
38
39
void cmd_token_del(struct cmd_token *token)
40
0
{
41
0
  if (!token)
42
0
    return;
43
44
0
  XFREE(MTYPE_CMD_TEXT, token->text);
45
0
  XFREE(MTYPE_CMD_DESC, token->desc);
46
0
  XFREE(MTYPE_CMD_ARG, token->arg);
47
0
  XFREE(MTYPE_CMD_VAR, token->varname);
48
49
0
  XFREE(MTYPE_CMD_TOKENS, token);
50
0
}
51
52
struct cmd_token *cmd_token_dup(struct cmd_token *token)
53
0
{
54
0
  struct cmd_token *copy =
55
0
    cmd_token_new(token->type, token->attr, NULL, NULL);
56
0
  copy->max = token->max;
57
0
  copy->min = token->min;
58
0
  copy->text = token->text ? XSTRDUP(MTYPE_CMD_TEXT, token->text) : NULL;
59
0
  copy->desc = token->desc ? XSTRDUP(MTYPE_CMD_DESC, token->desc) : NULL;
60
0
  copy->arg = token->arg ? XSTRDUP(MTYPE_CMD_ARG, token->arg) : NULL;
61
0
  copy->varname =
62
0
    token->varname ? XSTRDUP(MTYPE_CMD_VAR, token->varname) : NULL;
63
64
0
  return copy;
65
0
}
66
67
static void cmd_token_varname_do(struct cmd_token *token, const char *varname,
68
         uint8_t varname_src)
69
0
{
70
0
  if (token->varname_src >= varname_src)
71
0
    return;
72
73
0
  XFREE(MTYPE_CMD_VAR, token->varname);
74
75
0
  size_t len = strlen(varname), i;
76
0
  token->varname = XMALLOC(MTYPE_CMD_VAR, len + 1);
77
0
  token->varname_src = varname_src;
78
79
0
  for (i = 0; i < len; i++)
80
0
    switch (varname[i]) {
81
0
    case '-':
82
0
    case '+':
83
0
    case '*':
84
0
    case ':':
85
0
      token->varname[i] = '_';
86
0
      break;
87
0
    default:
88
0
      token->varname[i] = tolower((unsigned char)varname[i]);
89
0
    }
90
0
  token->varname[len] = '\0';
91
0
}
92
93
void cmd_token_varname_set(struct cmd_token *token, const char *varname)
94
0
{
95
0
  if (varname) {
96
0
    cmd_token_varname_do(token, varname, VARNAME_EXPLICIT);
97
0
    return;
98
0
  }
99
0
  if (token->type == VARIABLE_TKN) {
100
0
    if (strcmp(token->text, "WORD") && strcmp(token->text, "NAME"))
101
0
      cmd_token_varname_do(token, token->text, VARNAME_TEXT);
102
0
  }
103
0
}
104
105
static void cmd_token_varname_fork(struct graph_node *node,
106
           struct cmd_token *prevtoken)
107
0
{
108
0
  for (size_t i = 0; i < vector_active(node->to); i++) {
109
0
    struct graph_node *next = vector_slot(node->to, i);
110
0
    struct cmd_token *nexttoken = next->data;
111
112
0
    if (nexttoken->type == FORK_TKN) {
113
0
      cmd_token_varname_fork(next, prevtoken);
114
0
      continue;
115
0
    }
116
0
    if (nexttoken->varname)
117
0
      continue;
118
0
    if (!IS_VARYING_TOKEN(nexttoken->type))
119
0
      continue;
120
121
0
    cmd_token_varname_do(nexttoken, prevtoken->text, VARNAME_TEXT);
122
0
  }
123
0
}
124
125
void cmd_token_varname_join(struct graph_node *join, const char *varname)
126
0
{
127
0
  if (!varname)
128
0
    return;
129
130
0
  for (size_t i = 0; i < vector_active(join->from); i++) {
131
0
    struct graph_node *prev = vector_slot(join->from, i);
132
0
    struct cmd_token *token = prev->data;
133
134
0
    if (token->type == JOIN_TKN)
135
0
      cmd_token_varname_join(prev, varname);
136
0
    else if (token->type < SPECIAL_TKN)
137
0
      cmd_token_varname_do(token, varname, VARNAME_EXPLICIT);
138
0
  }
139
0
}
140
141
void cmd_token_varname_seqappend(struct graph_node *node)
142
0
{
143
0
  struct graph_node *prevnode = node;
144
0
  struct cmd_token *token = node->data;
145
0
  struct cmd_token *prevtoken;
146
147
0
  if (token->type == WORD_TKN)
148
0
    return;
149
150
0
  do {
151
0
    if (vector_active(prevnode->from) != 1)
152
0
      return;
153
154
0
    prevnode = vector_slot(prevnode->from, 0);
155
0
    prevtoken = prevnode->data;
156
0
  } while (prevtoken->type == FORK_TKN);
157
158
0
  if (prevtoken->type != WORD_TKN)
159
0
    return;
160
161
0
  if (token->type == FORK_TKN)
162
0
    cmd_token_varname_fork(node, prevtoken);
163
0
  else
164
0
    cmd_token_varname_do(token, prevtoken->text, VARNAME_TEXT);
165
0
}
166
167
static bool cmd_nodes_link(struct graph_node *from, struct graph_node *to)
168
0
{
169
0
  for (size_t i = 0; i < vector_active(from->to); i++)
170
0
    if (vector_slot(from->to, i) == to)
171
0
      return true;
172
0
  return false;
173
0
}
174
175
static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb);
176
177
/* returns a single node to be excluded as "next" from iteration
178
 * - for JOIN_TKN, never continue back to the FORK_TKN
179
 * - in all other cases, don't try the node itself (in case of "...")
180
 */
181
static inline struct graph_node *cmd_loopstop(struct graph_node *gn)
182
0
{
183
0
  struct cmd_token *tok = gn->data;
184
0
  if (tok->type == JOIN_TKN)
185
0
    return tok->forkjoin;
186
0
  else
187
0
    return gn;
188
0
}
189
190
static bool cmd_subgraph_equal(struct graph_node *ga, struct graph_node *gb,
191
             struct graph_node *a_join)
192
0
{
193
0
  size_t i, j;
194
0
  struct graph_node *a_fork, *b_fork;
195
0
  a_fork = cmd_loopstop(ga);
196
0
  b_fork = cmd_loopstop(gb);
197
198
0
  if (vector_active(ga->to) != vector_active(gb->to))
199
0
    return false;
200
0
  for (i = 0; i < vector_active(ga->to); i++) {
201
0
    struct graph_node *cga = vector_slot(ga->to, i);
202
203
0
    for (j = 0; j < vector_active(gb->to); j++) {
204
0
      struct graph_node *cgb = vector_slot(gb->to, i);
205
206
0
      if (cga == a_fork && cgb != b_fork)
207
0
        continue;
208
0
      if (cga == a_fork && cgb == b_fork)
209
0
        break;
210
211
0
      if (cmd_nodes_equal(cga, cgb)) {
212
0
        if (cga == a_join)
213
0
          break;
214
0
        if (cmd_subgraph_equal(cga, cgb, a_join))
215
0
          break;
216
0
      }
217
0
    }
218
0
    if (j == vector_active(gb->to))
219
0
      return false;
220
0
  }
221
0
  return true;
222
0
}
223
224
/* deep compare -- for FORK_TKN, the entire subgraph is compared.
225
 * this is what's needed since we're not currently trying to partially
226
 * merge subgraphs */
227
static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb)
228
0
{
229
0
  struct cmd_token *a = ga->data, *b = gb->data;
230
231
0
  if (a->type != b->type || a->allowrepeat != b->allowrepeat)
232
0
    return false;
233
0
  if (a->type < SPECIAL_TKN && strcmp(a->text, b->text))
234
0
    return false;
235
  /* one a ..., the other not. */
236
0
  if (cmd_nodes_link(ga, ga) != cmd_nodes_link(gb, gb))
237
0
    return false;
238
0
  if (!a->varname != !b->varname)
239
0
    return false;
240
0
  if (a->varname && strcmp(a->varname, b->varname))
241
0
    return false;
242
243
0
  switch (a->type) {
244
0
  case RANGE_TKN:
245
0
    return a->min == b->min && a->max == b->max;
246
247
0
  case FORK_TKN:
248
    /* one is keywords, the other just option or selector ... */
249
0
    if (cmd_nodes_link(a->forkjoin, ga)
250
0
        != cmd_nodes_link(b->forkjoin, gb))
251
0
      return false;
252
0
    if (cmd_nodes_link(ga, a->forkjoin)
253
0
        != cmd_nodes_link(gb, b->forkjoin))
254
0
      return false;
255
0
    return cmd_subgraph_equal(ga, gb, a->forkjoin);
256
257
0
  case VARIABLE_TKN:
258
0
  case IPV4_TKN:
259
0
  case IPV4_PREFIX_TKN:
260
0
  case IPV6_PREFIX_TKN:
261
0
  case IPV6_TKN:
262
0
  case MAC_TKN:
263
0
  case MAC_PREFIX_TKN:
264
0
  case JOIN_TKN:
265
0
  case START_TKN:
266
0
  case END_TKN:
267
0
  case NEG_ONLY_TKN:
268
0
  case WORD_TKN:
269
0
  case ASNUM_TKN:
270
0
    return true;
271
0
  }
272
273
0
  assert(!"Reached end of function we should never hit");
274
0
}
275
276
static void cmd_fork_bump_attr(struct graph_node *gn, struct graph_node *join,
277
             uint8_t attr)
278
0
{
279
0
  size_t i;
280
0
  struct cmd_token *tok = gn->data;
281
0
  struct graph_node *stop = cmd_loopstop(gn);
282
283
0
  tok->attr = attr;
284
0
  for (i = 0; i < vector_active(gn->to); i++) {
285
0
    struct graph_node *next = vector_slot(gn->to, i);
286
0
    if (next == stop || next == join)
287
0
      continue;
288
0
    cmd_fork_bump_attr(next, join, attr);
289
0
  }
290
0
}
291
292
/* move an entire subtree from the temporary graph resulting from
293
 * parse() into the permanent graph for the command node.
294
 *
295
 * this touches rather deeply into the graph code unfortunately.
296
 */
297
static void cmd_reparent_tree(struct graph *fromgraph, struct graph *tograph,
298
            struct graph_node *node)
299
0
{
300
0
  struct graph_node *stop = cmd_loopstop(node);
301
0
  size_t i;
302
303
0
  for (i = 0; i < vector_active(fromgraph->nodes); i++)
304
0
    if (vector_slot(fromgraph->nodes, i) == node) {
305
      /* agressive iteration punching through subgraphs - may
306
       * hit some
307
       * nodes twice.  reparent only if found on old graph */
308
0
      vector_unset(fromgraph->nodes, i);
309
0
      vector_set(tograph->nodes, node);
310
0
      break;
311
0
    }
312
313
0
  for (i = 0; i < vector_active(node->to); i++) {
314
0
    struct graph_node *next = vector_slot(node->to, i);
315
0
    if (next != stop)
316
0
      cmd_reparent_tree(fromgraph, tograph, next);
317
0
  }
318
0
}
319
320
static void cmd_free_recur(struct graph *graph, struct graph_node *node,
321
         struct graph_node *stop)
322
0
{
323
0
  struct graph_node *next, *nstop;
324
325
0
  for (size_t i = vector_active(node->to); i; i--) {
326
0
    next = vector_slot(node->to, i - 1);
327
0
    if (next == stop)
328
0
      continue;
329
0
    nstop = cmd_loopstop(next);
330
0
    if (nstop != next)
331
0
      cmd_free_recur(graph, next, nstop);
332
0
    cmd_free_recur(graph, nstop, stop);
333
0
  }
334
0
  graph_delete_node(graph, node);
335
0
}
336
337
static void cmd_free_node(struct graph *graph, struct graph_node *node)
338
0
{
339
0
  struct cmd_token *tok = node->data;
340
0
  if (tok->type == JOIN_TKN)
341
0
    cmd_free_recur(graph, tok->forkjoin, node);
342
0
  graph_delete_node(graph, node);
343
0
}
344
345
/* recursive graph merge.  call with
346
 *   old ~= new
347
 * (which holds true for old == START_TKN, new == START_TKN)
348
 */
349
static void cmd_merge_nodes(struct graph *oldgraph, struct graph *newgraph,
350
          struct graph_node *old, struct graph_node *new,
351
          int direction)
352
0
{
353
0
  struct cmd_token *tok;
354
0
  struct graph_node *old_skip, *new_skip;
355
0
  old_skip = cmd_loopstop(old);
356
0
  new_skip = cmd_loopstop(new);
357
358
0
  assert(direction == 1 || direction == -1);
359
360
0
  tok = old->data;
361
0
  tok->refcnt += direction;
362
363
0
  size_t j, i;
364
0
  for (j = 0; j < vector_active(new->to); j++) {
365
0
    struct graph_node *cnew = vector_slot(new->to, j);
366
0
    if (cnew == new_skip)
367
0
      continue;
368
369
0
    for (i = 0; i < vector_active(old->to); i++) {
370
0
      struct graph_node *cold = vector_slot(old->to, i);
371
0
      if (cold == old_skip)
372
0
        continue;
373
374
0
      if (cmd_nodes_equal(cold, cnew)) {
375
0
        struct cmd_token *told = cold->data,
376
0
             *tnew = cnew->data;
377
378
0
        if (told->type == END_TKN) {
379
0
          if (direction < 0) {
380
0
            graph_delete_node(
381
0
              oldgraph,
382
0
              vector_slot(cold->to,
383
0
                    0));
384
0
            graph_delete_node(oldgraph,
385
0
                  cold);
386
0
          } else
387
            /* force no-match handling to
388
             * install END_TKN */
389
0
            i = vector_active(old->to);
390
0
          break;
391
0
        }
392
393
        /* the entire fork compared as equal, we
394
         * continue after it. */
395
0
        if (told->type == FORK_TKN) {
396
0
          if (tnew->attr < told->attr
397
0
              && direction > 0)
398
0
            cmd_fork_bump_attr(
399
0
              cold, told->forkjoin,
400
0
              tnew->attr);
401
          /* XXX: no reverse bump on uninstall */
402
0
          told = (cold = told->forkjoin)->data;
403
0
          tnew = (cnew = tnew->forkjoin)->data;
404
0
        }
405
0
        if (tnew->attr < told->attr)
406
0
          told->attr = tnew->attr;
407
408
0
        cmd_merge_nodes(oldgraph, newgraph, cold, cnew,
409
0
            direction);
410
0
        break;
411
0
      }
412
0
    }
413
    /* nothing found => add new to old */
414
0
    if (i == vector_active(old->to) && direction > 0) {
415
0
      graph_remove_edge(new, cnew);
416
417
0
      cmd_reparent_tree(newgraph, oldgraph, cnew);
418
419
0
      graph_add_edge(old, cnew);
420
0
    }
421
0
  }
422
423
0
  if (!tok->refcnt)
424
0
    cmd_free_node(oldgraph, old);
425
0
}
426
427
void cmd_graph_merge(struct graph *old, struct graph *new, int direction)
428
0
{
429
0
  assert(vector_active(old->nodes) >= 1);
430
0
  assert(vector_active(new->nodes) >= 1);
431
432
0
  cmd_merge_nodes(old, new, vector_slot(old->nodes, 0),
433
0
      vector_slot(new->nodes, 0), direction);
434
0
}
435
436
void cmd_graph_names(struct graph *graph)
437
0
{
438
0
  struct graph_node *start;
439
440
0
  assert(vector_active(graph->nodes) >= 1);
441
0
  start = vector_slot(graph->nodes, 0);
442
443
  /* apply varname on initial "[no]" */
444
0
  do {
445
0
    if (vector_active(start->to) != 1)
446
0
      break;
447
448
0
    struct graph_node *first = vector_slot(start->to, 0);
449
0
    struct cmd_token *tok = first->data;
450
    /* looking for an option with 2 choices, nothing or "no" */
451
0
    if (tok->type != FORK_TKN || vector_active(first->to) != 2)
452
0
      break;
453
454
0
    struct graph_node *next0 = vector_slot(first->to, 0);
455
0
    struct graph_node *next1 = vector_slot(first->to, 1);
456
    /* one needs to be empty */
457
0
    if (next0 != tok->forkjoin && next1 != tok->forkjoin)
458
0
      break;
459
460
0
    struct cmd_token *tok0 = next0->data;
461
0
    struct cmd_token *tok1 = next1->data;
462
    /* the other one needs to be "no" (only one will match here) */
463
0
    if ((tok0->type == WORD_TKN && !strcmp(tok0->text, "no")))
464
0
      cmd_token_varname_do(tok0, "no", VARNAME_AUTO);
465
0
    if ((tok1->type == WORD_TKN && !strcmp(tok1->text, "no")))
466
0
      cmd_token_varname_do(tok1, "no", VARNAME_AUTO);
467
0
  } while (0);
468
0
}
469
470
#ifndef BUILDING_CLIPPY
471
472
#include "command.h"
473
#include "log.h"
474
475
void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf)
476
0
{
477
0
  static bool wasend;
478
479
0
  char nbuf[512];
480
0
  struct cmd_token *tok = gn->data;
481
0
  const char *color = NULL;
482
483
0
  if (wasend) {
484
0
    wasend = false;
485
0
    return;
486
0
  }
487
488
0
  if (tok->type == END_TKN) {
489
0
    wasend = true;
490
0
    return;
491
0
  }
492
493
0
  snprintf(nbuf, sizeof(nbuf), "  n%p [ shape=box, label=<", gn);
494
0
  buffer_putstr(buf, nbuf);
495
0
  snprintf(nbuf, sizeof(nbuf), "<b>%s</b>",
496
0
     lookup_msg(tokennames, tok->type, NULL));
497
0
  buffer_putstr(buf, nbuf);
498
0
  if (tok->attr & CMD_ATTR_DEPRECATED)
499
0
    buffer_putstr(buf, " (d)");
500
  /* DEPRECATED implies HIDDEN, don't print both */
501
0
  else if (tok->attr & CMD_ATTR_HIDDEN)
502
0
    buffer_putstr(buf, " (h)");
503
0
  if (tok->text) {
504
0
    if (tok->type == WORD_TKN)
505
0
      snprintf(
506
0
        nbuf, sizeof(nbuf),
507
0
        "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
508
0
        tok->text);
509
0
    else
510
0
      snprintf(nbuf, sizeof(nbuf), "<br/>%s", tok->text);
511
0
    buffer_putstr(buf, nbuf);
512
0
  }
513
514
0
  switch (tok->type) {
515
0
  case START_TKN:
516
0
    color = "#ccffcc";
517
0
    break;
518
0
  case FORK_TKN:
519
0
    color = "#aaddff";
520
0
    break;
521
0
  case JOIN_TKN:
522
0
    color = "#ddaaff";
523
0
    break;
524
0
  case NEG_ONLY_TKN:
525
0
    color = "#ffddaa";
526
0
    break;
527
0
  case WORD_TKN:
528
0
    color = "#ffffff";
529
0
    break;
530
0
  case RANGE_TKN:
531
0
  case IPV4_TKN:
532
0
  case IPV4_PREFIX_TKN:
533
0
  case IPV6_TKN:
534
0
  case IPV6_PREFIX_TKN:
535
0
  case MAC_TKN:
536
0
  case MAC_PREFIX_TKN:
537
0
  case END_TKN:
538
0
  case VARIABLE_TKN:
539
0
  case ASNUM_TKN:
540
0
    color = "#ffffff";
541
0
    break;
542
0
  }
543
544
  /*
545
   * Some compilers have the mistaken belief that we can
546
   * get here without initializing color.
547
   */
548
0
  snprintf(nbuf, sizeof(nbuf),
549
0
     ">, style = filled, fillcolor = \"%s\" ];\n", color);
550
0
  buffer_putstr(buf, nbuf);
551
552
0
  for (unsigned int i = 0; i < vector_active(gn->to); i++) {
553
0
    struct graph_node *adj = vector_slot(gn->to, i);
554
555
0
    if (((struct cmd_token *)adj->data)->type == END_TKN) {
556
0
      snprintf(nbuf, sizeof(nbuf), "  n%p -> end%p;\n", gn,
557
0
         adj);
558
0
      buffer_putstr(buf, nbuf);
559
0
      snprintf(
560
0
        nbuf, sizeof(nbuf),
561
0
        "  end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
562
0
        adj);
563
0
    } else
564
0
      snprintf(nbuf, sizeof(nbuf), "  n%p -> n%p;\n", gn,
565
0
         adj);
566
567
0
    buffer_putstr(buf, nbuf);
568
0
  }
569
0
}
570
571
char *cmd_graph_dump_dot(struct graph *cmdgraph)
572
0
{
573
0
  struct graph_node *start = vector_slot(cmdgraph->nodes, 0);
574
575
0
  return graph_dump_dot(cmdgraph, start, cmd_graph_node_print_cb);
576
0
}
577
578
#endif /* BUILDING_CLIPPY */