]>
git.proxmox.com Git - mirror_frr.git/blob - lib/command_graph.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
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")
14 #include "command_graph.h"
16 DEFINE_MTYPE_STATIC(LIB
, CMD_TOKENS
, "Command Tokens");
17 DEFINE_MTYPE_STATIC(LIB
, CMD_DESC
, "Command Token Text");
18 DEFINE_MTYPE_STATIC(LIB
, CMD_TEXT
, "Command Token Help");
19 DEFINE_MTYPE(LIB
, CMD_ARG
, "Command Argument");
20 DEFINE_MTYPE_STATIC(LIB
, CMD_VAR
, "Command Argument Name");
22 struct cmd_token
*cmd_token_new(enum cmd_token_type type
, uint8_t attr
,
23 const char *text
, const char *desc
)
25 struct cmd_token
*token
=
26 XCALLOC(MTYPE_CMD_TOKENS
, sizeof(struct cmd_token
));
29 token
->text
= text
? XSTRDUP(MTYPE_CMD_TEXT
, text
) : NULL
;
30 token
->desc
= desc
? XSTRDUP(MTYPE_CMD_DESC
, desc
) : NULL
;
33 token
->allowrepeat
= false;
34 token
->varname
= NULL
;
39 void cmd_token_del(struct cmd_token
*token
)
44 XFREE(MTYPE_CMD_TEXT
, token
->text
);
45 XFREE(MTYPE_CMD_DESC
, token
->desc
);
46 XFREE(MTYPE_CMD_ARG
, token
->arg
);
47 XFREE(MTYPE_CMD_VAR
, token
->varname
);
49 XFREE(MTYPE_CMD_TOKENS
, token
);
52 struct cmd_token
*cmd_token_dup(struct cmd_token
*token
)
54 struct cmd_token
*copy
=
55 cmd_token_new(token
->type
, token
->attr
, NULL
, NULL
);
56 copy
->max
= token
->max
;
57 copy
->min
= token
->min
;
58 copy
->text
= token
->text
? XSTRDUP(MTYPE_CMD_TEXT
, token
->text
) : NULL
;
59 copy
->desc
= token
->desc
? XSTRDUP(MTYPE_CMD_DESC
, token
->desc
) : NULL
;
60 copy
->arg
= token
->arg
? XSTRDUP(MTYPE_CMD_ARG
, token
->arg
) : NULL
;
62 token
->varname
? XSTRDUP(MTYPE_CMD_VAR
, token
->varname
) : NULL
;
67 static void cmd_token_varname_do(struct cmd_token
*token
, const char *varname
,
70 if (token
->varname_src
>= varname_src
)
73 XFREE(MTYPE_CMD_VAR
, token
->varname
);
75 size_t len
= strlen(varname
), i
;
76 token
->varname
= XMALLOC(MTYPE_CMD_VAR
, len
+ 1);
77 token
->varname_src
= varname_src
;
79 for (i
= 0; i
< len
; i
++)
85 token
->varname
[i
] = '_';
88 token
->varname
[i
] = tolower((unsigned char)varname
[i
]);
90 token
->varname
[len
] = '\0';
93 void cmd_token_varname_set(struct cmd_token
*token
, const char *varname
)
96 cmd_token_varname_do(token
, varname
, VARNAME_EXPLICIT
);
99 if (token
->type
== VARIABLE_TKN
) {
100 if (strcmp(token
->text
, "WORD") && strcmp(token
->text
, "NAME"))
101 cmd_token_varname_do(token
, token
->text
, VARNAME_TEXT
);
105 static void cmd_token_varname_fork(struct graph_node
*node
,
106 struct cmd_token
*prevtoken
)
108 for (size_t i
= 0; i
< vector_active(node
->to
); i
++) {
109 struct graph_node
*next
= vector_slot(node
->to
, i
);
110 struct cmd_token
*nexttoken
= next
->data
;
112 if (nexttoken
->type
== FORK_TKN
) {
113 cmd_token_varname_fork(next
, prevtoken
);
116 if (nexttoken
->varname
)
118 if (!IS_VARYING_TOKEN(nexttoken
->type
))
121 cmd_token_varname_do(nexttoken
, prevtoken
->text
, VARNAME_TEXT
);
125 void cmd_token_varname_join(struct graph_node
*join
, const char *varname
)
130 for (size_t i
= 0; i
< vector_active(join
->from
); i
++) {
131 struct graph_node
*prev
= vector_slot(join
->from
, i
);
132 struct cmd_token
*token
= prev
->data
;
134 if (token
->type
== JOIN_TKN
)
135 cmd_token_varname_join(prev
, varname
);
136 else if (token
->type
< SPECIAL_TKN
)
137 cmd_token_varname_do(token
, varname
, VARNAME_EXPLICIT
);
141 void cmd_token_varname_seqappend(struct graph_node
*node
)
143 struct graph_node
*prevnode
= node
;
144 struct cmd_token
*token
= node
->data
;
145 struct cmd_token
*prevtoken
;
147 if (token
->type
== WORD_TKN
)
151 if (vector_active(prevnode
->from
) != 1)
154 prevnode
= vector_slot(prevnode
->from
, 0);
155 prevtoken
= prevnode
->data
;
156 } while (prevtoken
->type
== FORK_TKN
);
158 if (prevtoken
->type
!= WORD_TKN
)
161 if (token
->type
== FORK_TKN
)
162 cmd_token_varname_fork(node
, prevtoken
);
164 cmd_token_varname_do(token
, prevtoken
->text
, VARNAME_TEXT
);
167 static bool cmd_nodes_link(struct graph_node
*from
, struct graph_node
*to
)
169 for (size_t i
= 0; i
< vector_active(from
->to
); i
++)
170 if (vector_slot(from
->to
, i
) == to
)
175 static bool cmd_nodes_equal(struct graph_node
*ga
, struct graph_node
*gb
);
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 "...")
181 static inline struct graph_node
*cmd_loopstop(struct graph_node
*gn
)
183 struct cmd_token
*tok
= gn
->data
;
184 if (tok
->type
== JOIN_TKN
)
185 return tok
->forkjoin
;
190 static bool cmd_subgraph_equal(struct graph_node
*ga
, struct graph_node
*gb
,
191 struct graph_node
*a_join
)
194 struct graph_node
*a_fork
, *b_fork
;
195 a_fork
= cmd_loopstop(ga
);
196 b_fork
= cmd_loopstop(gb
);
198 if (vector_active(ga
->to
) != vector_active(gb
->to
))
200 for (i
= 0; i
< vector_active(ga
->to
); i
++) {
201 struct graph_node
*cga
= vector_slot(ga
->to
, i
);
203 for (j
= 0; j
< vector_active(gb
->to
); j
++) {
204 struct graph_node
*cgb
= vector_slot(gb
->to
, i
);
206 if (cga
== a_fork
&& cgb
!= b_fork
)
208 if (cga
== a_fork
&& cgb
== b_fork
)
211 if (cmd_nodes_equal(cga
, cgb
)) {
214 if (cmd_subgraph_equal(cga
, cgb
, a_join
))
218 if (j
== vector_active(gb
->to
))
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
227 static bool cmd_nodes_equal(struct graph_node
*ga
, struct graph_node
*gb
)
229 struct cmd_token
*a
= ga
->data
, *b
= gb
->data
;
231 if (a
->type
!= b
->type
|| a
->allowrepeat
!= b
->allowrepeat
)
233 if (a
->type
< SPECIAL_TKN
&& strcmp(a
->text
, b
->text
))
235 /* one a ..., the other not. */
236 if (cmd_nodes_link(ga
, ga
) != cmd_nodes_link(gb
, gb
))
238 if (!a
->varname
!= !b
->varname
)
240 if (a
->varname
&& strcmp(a
->varname
, b
->varname
))
245 return a
->min
== b
->min
&& a
->max
== b
->max
;
248 /* one is keywords, the other just option or selector ... */
249 if (cmd_nodes_link(a
->forkjoin
, ga
)
250 != cmd_nodes_link(b
->forkjoin
, gb
))
252 if (cmd_nodes_link(ga
, a
->forkjoin
)
253 != cmd_nodes_link(gb
, b
->forkjoin
))
255 return cmd_subgraph_equal(ga
, gb
, a
->forkjoin
);
259 case IPV4_PREFIX_TKN
:
260 case IPV6_PREFIX_TKN
:
273 assert(!"Reached end of function we should never hit");
276 static void cmd_fork_bump_attr(struct graph_node
*gn
, struct graph_node
*join
,
280 struct cmd_token
*tok
= gn
->data
;
281 struct graph_node
*stop
= cmd_loopstop(gn
);
284 for (i
= 0; i
< vector_active(gn
->to
); i
++) {
285 struct graph_node
*next
= vector_slot(gn
->to
, i
);
286 if (next
== stop
|| next
== join
)
288 cmd_fork_bump_attr(next
, join
, attr
);
292 /* move an entire subtree from the temporary graph resulting from
293 * parse() into the permanent graph for the command node.
295 * this touches rather deeply into the graph code unfortunately.
297 static void cmd_reparent_tree(struct graph
*fromgraph
, struct graph
*tograph
,
298 struct graph_node
*node
)
300 struct graph_node
*stop
= cmd_loopstop(node
);
303 for (i
= 0; i
< vector_active(fromgraph
->nodes
); i
++)
304 if (vector_slot(fromgraph
->nodes
, i
) == node
) {
305 /* agressive iteration punching through subgraphs - may
307 * nodes twice. reparent only if found on old graph */
308 vector_unset(fromgraph
->nodes
, i
);
309 vector_set(tograph
->nodes
, node
);
313 for (i
= 0; i
< vector_active(node
->to
); i
++) {
314 struct graph_node
*next
= vector_slot(node
->to
, i
);
316 cmd_reparent_tree(fromgraph
, tograph
, next
);
320 static void cmd_free_recur(struct graph
*graph
, struct graph_node
*node
,
321 struct graph_node
*stop
)
323 struct graph_node
*next
, *nstop
;
325 for (size_t i
= vector_active(node
->to
); i
; i
--) {
326 next
= vector_slot(node
->to
, i
- 1);
329 nstop
= cmd_loopstop(next
);
331 cmd_free_recur(graph
, next
, nstop
);
332 cmd_free_recur(graph
, nstop
, stop
);
334 graph_delete_node(graph
, node
);
337 static void cmd_free_node(struct graph
*graph
, struct graph_node
*node
)
339 struct cmd_token
*tok
= node
->data
;
340 if (tok
->type
== JOIN_TKN
)
341 cmd_free_recur(graph
, tok
->forkjoin
, node
);
342 graph_delete_node(graph
, node
);
345 /* recursive graph merge. call with
347 * (which holds true for old == START_TKN, new == START_TKN)
349 static void cmd_merge_nodes(struct graph
*oldgraph
, struct graph
*newgraph
,
350 struct graph_node
*old
, struct graph_node
*new,
353 struct cmd_token
*tok
;
354 struct graph_node
*old_skip
, *new_skip
;
355 old_skip
= cmd_loopstop(old
);
356 new_skip
= cmd_loopstop(new);
358 assert(direction
== 1 || direction
== -1);
361 tok
->refcnt
+= direction
;
364 for (j
= 0; j
< vector_active(new->to
); j
++) {
365 struct graph_node
*cnew
= vector_slot(new->to
, j
);
366 if (cnew
== new_skip
)
369 for (i
= 0; i
< vector_active(old
->to
); i
++) {
370 struct graph_node
*cold
= vector_slot(old
->to
, i
);
371 if (cold
== old_skip
)
374 if (cmd_nodes_equal(cold
, cnew
)) {
375 struct cmd_token
*told
= cold
->data
,
378 if (told
->type
== END_TKN
) {
382 vector_slot(cold
->to
,
384 graph_delete_node(oldgraph
,
387 /* force no-match handling to
389 i
= vector_active(old
->to
);
393 /* the entire fork compared as equal, we
394 * continue after it. */
395 if (told
->type
== FORK_TKN
) {
396 if (tnew
->attr
< told
->attr
399 cold
, told
->forkjoin
,
401 /* XXX: no reverse bump on uninstall */
402 told
= (cold
= told
->forkjoin
)->data
;
403 tnew
= (cnew
= tnew
->forkjoin
)->data
;
405 if (tnew
->attr
< told
->attr
)
406 told
->attr
= tnew
->attr
;
408 cmd_merge_nodes(oldgraph
, newgraph
, cold
, cnew
,
413 /* nothing found => add new to old */
414 if (i
== vector_active(old
->to
) && direction
> 0) {
415 graph_remove_edge(new, cnew
);
417 cmd_reparent_tree(newgraph
, oldgraph
, cnew
);
419 graph_add_edge(old
, cnew
);
424 cmd_free_node(oldgraph
, old
);
427 void cmd_graph_merge(struct graph
*old
, struct graph
*new, int direction
)
429 assert(vector_active(old
->nodes
) >= 1);
430 assert(vector_active(new->nodes
) >= 1);
432 cmd_merge_nodes(old
, new, vector_slot(old
->nodes
, 0),
433 vector_slot(new->nodes
, 0), direction
);
436 void cmd_graph_names(struct graph
*graph
)
438 struct graph_node
*start
;
440 assert(vector_active(graph
->nodes
) >= 1);
441 start
= vector_slot(graph
->nodes
, 0);
443 /* apply varname on initial "[no]" */
445 if (vector_active(start
->to
) != 1)
448 struct graph_node
*first
= vector_slot(start
->to
, 0);
449 struct cmd_token
*tok
= first
->data
;
450 /* looking for an option with 2 choices, nothing or "no" */
451 if (tok
->type
!= FORK_TKN
|| vector_active(first
->to
) != 2)
454 struct graph_node
*next0
= vector_slot(first
->to
, 0);
455 struct graph_node
*next1
= vector_slot(first
->to
, 1);
456 /* one needs to be empty */
457 if (next0
!= tok
->forkjoin
&& next1
!= tok
->forkjoin
)
460 struct cmd_token
*tok0
= next0
->data
;
461 struct cmd_token
*tok1
= next1
->data
;
462 /* the other one needs to be "no" (only one will match here) */
463 if ((tok0
->type
== WORD_TKN
&& !strcmp(tok0
->text
, "no")))
464 cmd_token_varname_do(tok0
, "no", VARNAME_AUTO
);
465 if ((tok1
->type
== WORD_TKN
&& !strcmp(tok1
->text
, "no")))
466 cmd_token_varname_do(tok1
, "no", VARNAME_AUTO
);
470 #ifndef BUILDING_CLIPPY
475 void cmd_graph_node_print_cb(struct graph_node
*gn
, struct buffer
*buf
)
480 struct cmd_token
*tok
= gn
->data
;
481 const char *color
= NULL
;
488 if (tok
->type
== END_TKN
) {
493 snprintf(nbuf
, sizeof(nbuf
), " n%p [ shape=box, label=<", gn
);
494 buffer_putstr(buf
, nbuf
);
495 snprintf(nbuf
, sizeof(nbuf
), "<b>%s</b>",
496 lookup_msg(tokennames
, tok
->type
, NULL
));
497 buffer_putstr(buf
, nbuf
);
498 if (tok
->attr
& CMD_ATTR_DEPRECATED
)
499 buffer_putstr(buf
, " (d)");
500 /* DEPRECATED implies HIDDEN, don't print both */
501 else if (tok
->attr
& CMD_ATTR_HIDDEN
)
502 buffer_putstr(buf
, " (h)");
504 if (tok
->type
== WORD_TKN
)
507 "<br/>\"<font color=\"#0055ff\" point-size=\"11\"><b>%s</b></font>\"",
510 snprintf(nbuf
, sizeof(nbuf
), "<br/>%s", tok
->text
);
511 buffer_putstr(buf
, nbuf
);
532 case IPV4_PREFIX_TKN
:
534 case IPV6_PREFIX_TKN
:
545 * Some compilers have the mistaken belief that we can
546 * get here without initializing color.
548 snprintf(nbuf
, sizeof(nbuf
),
549 ">, style = filled, fillcolor = \"%s\" ];\n", color
);
550 buffer_putstr(buf
, nbuf
);
552 for (unsigned int i
= 0; i
< vector_active(gn
->to
); i
++) {
553 struct graph_node
*adj
= vector_slot(gn
->to
, i
);
555 if (((struct cmd_token
*)adj
->data
)->type
== END_TKN
) {
556 snprintf(nbuf
, sizeof(nbuf
), " n%p -> end%p;\n", gn
,
558 buffer_putstr(buf
, nbuf
);
561 " end%p [ shape=box, label=<end>, style = filled, fillcolor = \"#ffddaa\" ];\n",
564 snprintf(nbuf
, sizeof(nbuf
), " n%p -> n%p;\n", gn
,
567 buffer_putstr(buf
, nbuf
);
571 char *cmd_graph_dump_dot(struct graph
*cmdgraph
)
573 struct graph_node
*start
= vector_slot(cmdgraph
->nodes
, 0);
575 return graph_dump_dot(cmdgraph
, start
, cmd_graph_node_print_cb
);
578 #endif /* BUILDING_CLIPPY */