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