]> git.proxmox.com Git - mirror_frr.git/blame - lib/command_graph.c
*: make consistent & update GPLv2 file headers
[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
29DECLARE_MTYPE(CMD_TOKEN_DATA)
30
31DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens")
32DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text")
33DEFINE_MTYPE_STATIC(LIB, CMD_TEXT, "Command Token Help")
34DEFINE_MTYPE( LIB, CMD_ARG, "Command Argument")
35DEFINE_MTYPE_STATIC(LIB, CMD_VAR, "Command Argument Name")
36
37struct cmd_token *
38cmd_token_new (enum cmd_token_type type, u_char attr,
39 const char *text, const char *desc)
40{
41 struct cmd_token *token = XCALLOC (MTYPE_CMD_TOKENS, sizeof (struct cmd_token));
42 token->type = type;
43 token->attr = attr;
44 token->text = text ? XSTRDUP (MTYPE_CMD_TEXT, text) : NULL;
45 token->desc = desc ? XSTRDUP (MTYPE_CMD_DESC, desc) : NULL;
46 token->refcnt = 1;
47 token->arg = NULL;
48 token->allowrepeat = false;
49 token->varname = NULL;
50
51 return token;
52}
53
54void
55cmd_token_del (struct cmd_token *token)
56{
57 if (!token) return;
58
59 XFREE (MTYPE_CMD_TEXT, token->text);
60 XFREE (MTYPE_CMD_DESC, token->desc);
61 XFREE (MTYPE_CMD_ARG, token->arg);
62 XFREE (MTYPE_CMD_VAR, token->varname);
63
64 XFREE (MTYPE_CMD_TOKENS, token);
65}
66
67struct cmd_token *
68cmd_token_dup (struct cmd_token *token)
69{
70 struct cmd_token *copy = cmd_token_new (token->type, token->attr, NULL, NULL);
71 copy->max = token->max;
72 copy->min = token->min;
73 copy->text = token->text ? XSTRDUP (MTYPE_CMD_TEXT, token->text) : NULL;
74 copy->desc = token->desc ? XSTRDUP (MTYPE_CMD_DESC, token->desc) : NULL;
75 copy->arg = token->arg ? XSTRDUP (MTYPE_CMD_ARG, token->arg) : NULL;
76 copy->varname = token->varname ? XSTRDUP (MTYPE_CMD_VAR, token->varname) : NULL;
77
78 return copy;
79}
80
81void cmd_token_varname_set(struct cmd_token *token, const char *varname)
82{
83 XFREE (MTYPE_CMD_VAR, token->varname);
c09c46ae
DL
84 if (!varname)
85 {
86 token->varname = NULL;
87 return;
88 }
89
90 size_t len = strlen (varname), i;
91 token->varname = XMALLOC (MTYPE_CMD_VAR, len + 1);
92
93 for (i = 0; i < len; i++)
94 switch (varname[i])
95 {
96 case '-':
97 case '+':
98 case '*':
99 case ':':
100 token->varname[i] = '_';
101 break;
102 default:
103 token->varname[i] = tolower (varname[i]);
104 }
105 token->varname[len] = '\0';
5894e76d
DL
106}
107
108static bool
109cmd_nodes_link (struct graph_node *from, struct graph_node *to)
110{
111 for (size_t i = 0; i < vector_active (from->to); i++)
112 if (vector_slot (from->to, i) == to)
113 return true;
114 return false;
115}
116
117static bool cmd_nodes_equal (struct graph_node *ga, struct graph_node *gb);
118
119/* returns a single node to be excluded as "next" from iteration
120 * - for JOIN_TKN, never continue back to the FORK_TKN
121 * - in all other cases, don't try the node itself (in case of "...")
122 */
123static inline struct graph_node *
124cmd_loopstop(struct graph_node *gn)
125{
126 struct cmd_token *tok = gn->data;
127 if (tok->type == JOIN_TKN)
128 return tok->forkjoin;
129 else
130 return gn;
131}
132
133static bool
134cmd_subgraph_equal (struct graph_node *ga, struct graph_node *gb,
135 struct graph_node *a_join)
136{
137 size_t i, j;
138 struct graph_node *a_fork, *b_fork;
139 a_fork = cmd_loopstop (ga);
140 b_fork = cmd_loopstop (gb);
141
142 if (vector_active (ga->to) != vector_active (gb->to))
143 return false;
144 for (i = 0; i < vector_active (ga->to); i++)
145 {
146 struct graph_node *cga = vector_slot (ga->to, i);
147
148 for (j = 0; j < vector_active (gb->to); j++)
149 {
150 struct graph_node *cgb = vector_slot (gb->to, i);
151
152 if (cga == a_fork && cgb != b_fork)
153 continue;
154 if (cga == a_fork && cgb == b_fork)
155 break;
156
157 if (cmd_nodes_equal (cga, cgb))
158 {
159 if (cga == a_join)
160 break;
161 if (cmd_subgraph_equal (cga, cgb, a_join))
162 break;
163 }
164 }
165 if (j == vector_active (gb->to))
166 return false;
167 }
168 return true;
169}
170
171/* deep compare -- for FORK_TKN, the entire subgraph is compared.
172 * this is what's needed since we're not currently trying to partially
173 * merge subgraphs */
174static bool
175cmd_nodes_equal (struct graph_node *ga, struct graph_node *gb)
176{
177 struct cmd_token *a = ga->data, *b = gb->data;
178
179 if (a->type != b->type || a->allowrepeat != b->allowrepeat)
180 return false;
181 if (a->type < SPECIAL_TKN && strcmp (a->text, b->text))
182 return false;
183 /* one a ..., the other not. */
184 if (cmd_nodes_link (ga, ga) != cmd_nodes_link (gb, gb))
185 return false;
c09c46ae
DL
186 if (!a->varname != !b->varname)
187 return false;
188 if (a->varname && strcmp (a->varname, b->varname))
189 return false;
5894e76d
DL
190
191 switch (a->type)
192 {
193 case RANGE_TKN:
194 return a->min == b->min && a->max == b->max;
195
196 case FORK_TKN:
197 /* one is keywords, the other just option or selector ... */
198 if (cmd_nodes_link (a->forkjoin, ga) != cmd_nodes_link (b->forkjoin, gb))
199 return false;
200 if (cmd_nodes_link (ga, a->forkjoin) != cmd_nodes_link (gb, b->forkjoin))
201 return false;
202 return cmd_subgraph_equal (ga, gb, a->forkjoin);
203
204 default:
205 return true;
206 }
207}
208
209static void
210cmd_fork_bump_attr (struct graph_node *gn, struct graph_node *join,
211 u_char attr)
212{
213 size_t i;
214 struct cmd_token *tok = gn->data;
215 struct graph_node *stop = cmd_loopstop (gn);
216
217 tok->attr = attr;
218 for (i = 0; i < vector_active (gn->to); i++)
219 {
220 struct graph_node *next = vector_slot (gn->to, i);
221 if (next == stop || next == join)
222 continue;
223 cmd_fork_bump_attr (next, join, attr);
224 }
225}
226
227/* move an entire subtree from the temporary graph resulting from
228 * parse() into the permanent graph for the command node.
229 *
230 * this touches rather deeply into the graph code unfortunately.
231 */
232static void
233cmd_reparent_tree (struct graph *fromgraph, struct graph *tograph,
234 struct graph_node *node)
235{
236 struct graph_node *stop = cmd_loopstop (node);
237 size_t i;
238
239 for (i = 0; i < vector_active (fromgraph->nodes); i++)
240 if (vector_slot (fromgraph->nodes, i) == node)
241 {
242 /* agressive iteration punching through subgraphs - may hit some
243 * nodes twice. reparent only if found on old graph */
244 vector_unset (fromgraph->nodes, i);
245 vector_set (tograph->nodes, node);
246 break;
247 }
248
249 for (i = 0; i < vector_active (node->to); i++)
250 {
251 struct graph_node *next = vector_slot (node->to, i);
252 if (next != stop)
253 cmd_reparent_tree (fromgraph, tograph, next);
254 }
255}
256
257static void
258cmd_free_recur (struct graph *graph, struct graph_node *node,
259 struct graph_node *stop)
260{
261 struct graph_node *next, *nstop;
262
263 for (size_t i = vector_active (node->to); i; i--)
264 {
265 next = vector_slot (node->to, i - 1);
266 if (next == stop)
267 continue;
268 nstop = cmd_loopstop (next);
269 if (nstop != next)
270 cmd_free_recur (graph, next, nstop);
271 cmd_free_recur (graph, nstop, stop);
272 }
273 graph_delete_node (graph, node);
274}
275
276static void
277cmd_free_node (struct graph *graph, struct graph_node *node)
278{
279 struct cmd_token *tok = node->data;
280 if (tok->type == JOIN_TKN)
281 cmd_free_recur (graph, tok->forkjoin, node);
282 graph_delete_node (graph, node);
283}
284
285/* recursive graph merge. call with
286 * old ~= new
287 * (which holds true for old == START_TKN, new == START_TKN)
288 */
289static void
290cmd_merge_nodes (struct graph *oldgraph, struct graph *newgraph,
291 struct graph_node *old, struct graph_node *new,
292 int direction)
293{
294 struct cmd_token *tok;
295 struct graph_node *old_skip, *new_skip;
296 old_skip = cmd_loopstop (old);
297 new_skip = cmd_loopstop (new);
298
299 assert (direction == 1 || direction == -1);
300
301 tok = old->data;
302 tok->refcnt += direction;
303
304 size_t j, i;
305 for (j = 0; j < vector_active (new->to); j++)
306 {
307 struct graph_node *cnew = vector_slot (new->to, j);
308 if (cnew == new_skip)
309 continue;
310
311 for (i = 0; i < vector_active (old->to); i++)
312 {
313 struct graph_node *cold = vector_slot (old->to, i);
314 if (cold == old_skip)
315 continue;
316
317 if (cmd_nodes_equal (cold, cnew))
318 {
319 struct cmd_token *told = cold->data, *tnew = cnew->data;
320
321 if (told->type == END_TKN)
322 {
323 if (direction < 0)
324 {
325 graph_delete_node (oldgraph, vector_slot (cold->to, 0));
326 graph_delete_node (oldgraph, cold);
327 }
328 else
329 /* force no-match handling to install END_TKN */
330 i = vector_active (old->to);
331 break;
332 }
333
334 /* the entire fork compared as equal, we continue after it. */
335 if (told->type == FORK_TKN)
336 {
337 if (tnew->attr < told->attr && direction > 0)
338 cmd_fork_bump_attr (cold, told->forkjoin, tnew->attr);
339 /* XXX: no reverse bump on uninstall */
340 told = (cold = told->forkjoin)->data;
341 tnew = (cnew = tnew->forkjoin)->data;
342 }
343 if (tnew->attr < told->attr)
344 told->attr = tnew->attr;
345
346 cmd_merge_nodes (oldgraph, newgraph, cold, cnew, direction);
347 break;
348 }
349 }
350 /* nothing found => add new to old */
351 if (i == vector_active (old->to) && direction > 0)
352 {
353 assert (vector_count (cnew->from) ==
354 cmd_nodes_link (cnew, cnew) ? 2 : 1);
355 graph_remove_edge (new, cnew);
356
357 cmd_reparent_tree (newgraph, oldgraph, cnew);
358
359 graph_add_edge (old, cnew);
360 }
361 }
362
363 if (!tok->refcnt)
364 cmd_free_node (oldgraph, old);
365}
366
367void
368cmd_graph_merge (struct graph *old, struct graph *new, int direction)
369{
370 assert (vector_active (old->nodes) >= 1);
371 assert (vector_active (new->nodes) >= 1);
372
373 cmd_merge_nodes (old, new,
374 vector_slot (old->nodes, 0), vector_slot (new->nodes, 0),
375 direction);
376}
c09c46ae
DL
377
378static void
379cmd_node_names (struct graph_node *gn, struct graph_node *join,
380 const char *prevname)
381{
382 size_t i;
383 struct cmd_token *tok = gn->data, *jointok;
384 struct graph_node *stop = cmd_loopstop (gn);
385
386 switch (tok->type)
387 {
388 case WORD_TKN:
389 prevname = tok->text;
390 break;
391
392 case VARIABLE_TKN:
393 if (!tok->varname
394 && strcmp (tok->text, "WORD")
395 && strcmp (tok->text, "NAME"))
396 cmd_token_varname_set (tok, tok->text);
397 /* fallthrough */
398 case RANGE_TKN:
399 case IPV4_TKN:
400 case IPV4_PREFIX_TKN:
401 case IPV6_TKN:
402 case IPV6_PREFIX_TKN:
403 if (!tok->varname && prevname)
404 cmd_token_varname_set (tok, prevname);
405 prevname = NULL;
406 break;
407
408 case START_TKN:
409 case END_TKN:
410 case JOIN_TKN:
411 /* "<foo|bar> WORD" -> word is not "bar" or "foo" */
412 prevname = NULL;
413 break;
414
415 case FORK_TKN:
416 /* apply "<A.B.C.D|X:X::X:X>$name" */
417 jointok = tok->forkjoin->data;
418 if (!jointok->varname)
419 break;
420 for (i = 0; i < vector_active (tok->forkjoin->from); i++)
421 {
422 struct graph_node *tail = vector_slot (tok->forkjoin->from, i);
423 struct cmd_token *tailtok = tail->data;
424 if (tail == gn || tailtok->varname)
425 continue;
426 cmd_token_varname_set (tailtok, jointok->varname);
427 }
428 break;
429 }
430
431 for (i = 0; i < vector_active (gn->to); i++)
432 {
433 struct graph_node *next = vector_slot (gn->to, i);
434 if (next == stop || next == join)
435 continue;
436 cmd_node_names (next, join, prevname);
437 }
438
439 if (tok->type == FORK_TKN && tok->forkjoin != join)
440 cmd_node_names (tok->forkjoin, join, NULL);
441}
442
443void
444cmd_graph_names (struct graph *graph)
445{
446 struct graph_node *start;
447
448 assert (vector_active (graph->nodes) >= 1);
449 start = vector_slot (graph->nodes, 0);
450
451 /* apply varname on initial "[no]" */
452 do
453 {
454 if (vector_active (start->to) != 1)
455 break;
456
457 struct graph_node *first = vector_slot (start->to, 0);
458 struct cmd_token *tok = first->data;
459 /* looking for an option with 2 choices, nothing or "no" */
460 if (tok->type != FORK_TKN || vector_active (first->to) != 2)
461 break;
462
463 struct graph_node *next0 = vector_slot (first->to, 0);
464 struct graph_node *next1 = vector_slot (first->to, 1);
465 /* one needs to be empty */
466 if (next0 != tok->forkjoin && next1 != tok->forkjoin)
467 break;
468
469 struct cmd_token *tok0 = next0->data;
470 struct cmd_token *tok1 = next1->data;
471 /* the other one needs to be "no" (only one will match here) */
472 if ((tok0->type == WORD_TKN && !strcmp(tok0->text, "no")))
473 cmd_token_varname_set (tok0, "no");
474 if ((tok1->type == WORD_TKN && !strcmp(tok1->text, "no")))
475 cmd_token_varname_set (tok1, "no");
476 }
477 while (0);
478
479 cmd_node_names (start, NULL, NULL);
480}