]> git.proxmox.com Git - mirror_frr.git/blame - python/clidef.py
bgpd: store the bgp as identifier in the configured as-notation
[mirror_frr.git] / python / clidef.py
CommitLineData
5578a14d
DL
1# FRR CLI preprocessor (DEFPY)
2#
3# Copyright (C) 2017 David Lamparter for NetDEF, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; either version 2 of the License, or (at your option)
8# any later version.
9#
10# This program is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; see the file COPYING; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19import clippy, traceback, sys, os
20from collections import OrderedDict
21from functools import reduce
22from pprint import pprint
23from string import Template
24from io import StringIO
25
26# the various handlers generate output C code for a particular type of
27# CLI token, choosing the most useful output C type.
28
701a0192 29
5578a14d
DL
30class RenderHandler(object):
31 def __init__(self, token):
32 pass
701a0192 33
5578a14d
DL
34 def combine(self, other):
35 if type(self) == type(other):
36 return other
37 return StringHandler(None)
38
701a0192 39 deref = ""
5578a14d 40 drop_str = False
0ee0892b 41 canfail = True
4381a59b 42 canassert = False
5578a14d 43
701a0192 44
5578a14d 45class StringHandler(RenderHandler):
701a0192 46 argtype = "const char *"
47 decl = Template("const char *$varname = NULL;")
48 code = Template(
49 "$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;"
50 )
5578a14d 51 drop_str = True
0ee0892b 52 canfail = False
4381a59b 53 canassert = True
5578a14d 54
701a0192 55
5578a14d 56class LongHandler(RenderHandler):
701a0192 57 argtype = "long"
58 decl = Template("long $varname = 0;")
59 code = Template(
60 """\
5578a14d
DL
61char *_end;
62$varname = strtol(argv[_i]->arg, &_end, 10);
701a0192 63_fail = (_end == argv[_i]->arg) || (*_end != '\\0');"""
64 )
65
5578a14d
DL
66
67# A.B.C.D/M (prefix_ipv4) and
68# X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
69# struct prefix:
70
701a0192 71
5578a14d
DL
72class PrefixBase(RenderHandler):
73 def combine(self, other):
74 if type(self) == type(other):
75 return other
473196f6 76 if isinstance(other, PrefixBase):
5578a14d
DL
77 return PrefixGenHandler(None)
78 return StringHandler(None)
701a0192 79
80 deref = "&"
81
82
5578a14d 83class Prefix4Handler(PrefixBase):
701a0192 84 argtype = "const struct prefix_ipv4 *"
85 decl = Template("struct prefix_ipv4 $varname = { };")
86 code = Template("_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);")
87
88
5578a14d 89class Prefix6Handler(PrefixBase):
701a0192 90 argtype = "const struct prefix_ipv6 *"
91 decl = Template("struct prefix_ipv6 $varname = { };")
92 code = Template("_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);")
93
94
473196f6 95class PrefixEthHandler(PrefixBase):
701a0192 96 argtype = "struct prefix_eth *"
97 decl = Template("struct prefix_eth $varname = { };")
98 code = Template("_fail = !str2prefix_eth(argv[_i]->arg, &$varname);")
99
100
5578a14d 101class PrefixGenHandler(PrefixBase):
701a0192 102 argtype = "const struct prefix *"
103 decl = Template("struct prefix $varname = { };")
104 code = Template("_fail = !str2prefix(argv[_i]->arg, &$varname);")
105
5578a14d
DL
106
107# same for IP addresses. result is union sockunion.
108class IPBase(RenderHandler):
109 def combine(self, other):
110 if type(self) == type(other):
111 return other
112 if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
113 return IPGenHandler(None)
114 return StringHandler(None)
701a0192 115
116
5578a14d 117class IP4Handler(IPBase):
701a0192 118 argtype = "struct in_addr"
119 decl = Template("struct in_addr $varname = { INADDR_ANY };")
120 code = Template("_fail = !inet_aton(argv[_i]->arg, &$varname);")
121
122
5578a14d 123class IP6Handler(IPBase):
701a0192 124 argtype = "struct in6_addr"
125 decl = Template("struct in6_addr $varname = {};")
126 code = Template("_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);")
127
128
5578a14d 129class IPGenHandler(IPBase):
701a0192 130 argtype = "const union sockunion *"
131 decl = Template(
132 """union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;"""
133 )
134 code = Template(
135 """\
5578a14d
DL
136if (argv[_i]->text[0] == 'X') {
137 s__$varname.sa.sa_family = AF_INET6;
138 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
139 $varname = &s__$varname;
140} else {
141 s__$varname.sa.sa_family = AF_INET;
142 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
143 $varname = &s__$varname;
701a0192 144}"""
145 )
4381a59b 146 canassert = True
5578a14d 147
701a0192 148
5578a14d
DL
149def mix_handlers(handlers):
150 def combine(a, b):
151 if a is None:
152 return b
153 return a.combine(b)
701a0192 154
5578a14d
DL
155 return reduce(combine, handlers, None)
156
701a0192 157
5578a14d 158handlers = {
701a0192 159 "WORD_TKN": StringHandler,
160 "VARIABLE_TKN": StringHandler,
161 "RANGE_TKN": LongHandler,
162 "IPV4_TKN": IP4Handler,
163 "IPV4_PREFIX_TKN": Prefix4Handler,
164 "IPV6_TKN": IP6Handler,
165 "IPV6_PREFIX_TKN": Prefix6Handler,
166 "MAC_TKN": PrefixEthHandler,
167 "MAC_PREFIX_TKN": PrefixEthHandler,
5578a14d
DL
168}
169
170# core template invoked for each occurence of DEFPY.
0ee0892b
DL
171#
172# the "#if $..." bits are there to keep this template unified into one
173# common form, without requiring a more advanced template engine (e.g.
174# jinja2)
701a0192 175templ = Template(
d11238bb 176 """$cond_begin/* $fnname => "$cmddef" */
5578a14d
DL
177DEFUN_CMD_FUNC_DECL($fnname)
178#define funcdecl_$fnname static int ${fnname}_magic(\\
179 const struct cmd_element *self __attribute__ ((unused)),\\
180 struct vty *vty __attribute__ ((unused)),\\
181 int argc __attribute__ ((unused)),\\
182 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
183funcdecl_$fnname;
184DEFUN_CMD_FUNC_TEXT($fnname)
185{
0ee0892b 186#if $nonempty /* anything to parse? */
5578a14d 187 int _i;
0ee0892b 188#if $canfail /* anything that can fail? */
5578a14d 189 unsigned _fail = 0, _failcnt = 0;
0ee0892b 190#endif
5578a14d
DL
191$argdecls
192 for (_i = 0; _i < argc; _i++) {
193 if (!argv[_i]->varname)
194 continue;
0ee0892b
DL
195#if $canfail /* anything that can fail? */
196 _fail = 0;
197#endif
198$argblocks
199#if $canfail /* anything that can fail? */
5578a14d 200 if (_fail)
5c7571d4 201 vty_out (vty, "%% invalid input for %s: %s\\n",
8e25c8ce 202 argv[_i]->varname, argv[_i]->arg);
5578a14d 203 _failcnt += _fail;
0ee0892b 204#endif
5578a14d 205 }
0ee0892b 206#if $canfail /* anything that can fail? */
5578a14d
DL
207 if (_failcnt)
208 return CMD_WARNING;
0ee0892b
DL
209#endif
210#endif
4381a59b 211$argassert
5578a14d
DL
212 return ${fnname}_magic(self, vty, argc, argv$arglist);
213}
d11238bb 214$cond_end
701a0192 215"""
216)
5578a14d
DL
217
218# invoked for each named parameter
701a0192 219argblock = Template(
220 """
5578a14d
DL
221 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
222 $code
701a0192 223 }"""
224)
5578a14d 225
701a0192 226
227def get_always_args(token, always_args, args=[], stack=[]):
4381a59b
DL
228 if token in stack:
229 return
701a0192 230 if token.type == "END_TKN":
4381a59b
DL
231 for arg in list(always_args):
232 if arg not in args:
233 always_args.remove(arg)
234 return
235
236 stack = stack + [token]
237 if token.type in handlers and token.varname is not None:
238 args = args + [token.varname]
239 for nexttkn in token.next():
240 get_always_args(nexttkn, always_args, args, stack)
241
701a0192 242
b41b3f7b 243class Macros(dict):
e960eedd
DL
244 def __init__(self):
245 super().__init__()
246 self._loc = {}
247
b41b3f7b
DL
248 def load(self, filename):
249 filedata = clippy.parse(filename)
701a0192 250 for entry in filedata["data"]:
251 if entry["type"] != "PREPROC":
b41b3f7b 252 continue
e960eedd
DL
253 self.load_preproc(filename, entry)
254
255 def setup(self, key, val, where="built-in"):
256 self[key] = val
257 self._loc[key] = (where, 0)
258
259 def load_preproc(self, filename, entry):
260 ppdir = entry["line"].lstrip().split(None, 1)
261 if ppdir[0] != "define" or len(ppdir) != 2:
262 return
263 ppdef = ppdir[1].split(None, 1)
264 name = ppdef[0]
265 if "(" in name:
266 return
267 val = ppdef[1] if len(ppdef) == 2 else ""
268
269 val = val.strip(" \t\n\\")
270 if self.get(name, val) != val:
271 sys.stderr.write(
272 "%s:%d: warning: macro %s redefined!\n"
273 % (
274 filename,
275 entry["lineno"],
276 name,
277 )
278 )
279 sys.stderr.write(
280 "%s:%d: note: previously defined here\n"
281 % (
282 self._loc[name][0],
283 self._loc[name][1],
284 )
285 )
286 else:
b41b3f7b 287 self[name] = val
e960eedd 288 self._loc[name] = (filename, entry["lineno"])
b41b3f7b 289
701a0192 290
b41b3f7b 291def process_file(fn, ofd, dumpfd, all_defun, macros):
3779776a 292 errors = 0
5578a14d
DL
293 filedata = clippy.parse(fn)
294
d11238bb
DL
295 cond_stack = []
296
701a0192 297 for entry in filedata["data"]:
d11238bb
DL
298 if entry["type"] == "PREPROC":
299 line = entry["line"].lstrip()
300 tokens = line.split(maxsplit=1)
301 line = "#" + line + "\n"
302
303 if not tokens:
304 continue
305
306 if tokens[0] in ["if", "ifdef", "ifndef"]:
307 cond_stack.append(line)
308 elif tokens[0] in ["elif", "else"]:
309 prev_line = cond_stack.pop(-1)
310 cond_stack.append(prev_line + line)
311 elif tokens[0] in ["endif"]:
312 cond_stack.pop(-1)
e960eedd
DL
313 elif tokens[0] in ["define"]:
314 if not cond_stack:
315 macros.load_preproc(fn, entry)
316 elif len(cond_stack) == 1 and cond_stack[0] == "#ifdef CLIPPY\n":
317 macros.load_preproc(fn, entry)
d11238bb 318 continue
701a0192 319 if entry["type"].startswith("DEFPY") or (
320 all_defun and entry["type"].startswith("DEFUN")
321 ):
322 if len(entry["args"][0]) != 1:
323 sys.stderr.write(
324 "%s:%d: DEFPY function name not parseable (%r)\n"
325 % (fn, entry["lineno"], entry["args"][0])
326 )
3779776a
DL
327 errors += 1
328 continue
329
701a0192 330 cmddef = entry["args"][2]
b41b3f7b 331 cmddefx = []
3779776a 332 for i in cmddef:
b41b3f7b
DL
333 while i in macros:
334 i = macros[i]
335 if i.startswith('"') and i.endswith('"'):
336 cmddefx.append(i[1:-1])
337 continue
338
701a0192 339 sys.stderr.write(
340 "%s:%d: DEFPY command string not parseable (%r)\n"
341 % (fn, entry["lineno"], cmddef)
342 )
b41b3f7b
DL
343 errors += 1
344 cmddefx = None
345 break
346 if cmddefx is None:
3779776a 347 continue
701a0192 348 cmddef = "".join([i for i in cmddefx])
5578a14d
DL
349
350 graph = clippy.Graph(cmddef)
351 args = OrderedDict()
4381a59b 352 always_args = set()
5578a14d
DL
353 for token, depth in clippy.graph_iterate(graph):
354 if token.type not in handlers:
355 continue
356 if token.varname is None:
357 continue
358 arg = args.setdefault(token.varname, [])
359 arg.append(handlers[token.type](token))
4381a59b
DL
360 always_args.add(token.varname)
361
362 get_always_args(graph.first(), always_args)
5578a14d 363
701a0192 364 # print('-' * 76)
365 # pprint(entry)
366 # clippy.dump(graph)
367 # pprint(args)
5578a14d 368
701a0192 369 params = {"cmddef": cmddef, "fnname": entry["args"][0][0]}
5578a14d
DL
370 argdefs = []
371 argdecls = []
372 arglist = []
373 argblocks = []
4381a59b 374 argassert = []
5578a14d 375 doc = []
0ee0892b 376 canfail = 0
5578a14d 377
701a0192 378 def do_add(handler, basename, varname, attr=""):
379 argdefs.append(",\\\n\t%s %s%s" % (handler.argtype, varname, attr))
380 argdecls.append(
381 "\t%s\n"
382 % (
383 handler.decl.substitute({"varname": varname}).replace(
384 "\n", "\n\t"
385 )
386 )
387 )
388 arglist.append(", %s%s" % (handler.deref, varname))
4381a59b 389 if basename in always_args and handler.canassert:
701a0192 390 argassert.append(
391 """\tif (!%s) {
4381a59b
DL
392\t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
393\t\treturn CMD_WARNING;
701a0192 394\t}\n"""
395 % (varname, varname)
396 )
397 if attr == "":
5578a14d 398 at = handler.argtype
701a0192 399 if not at.startswith("const "):
400 at = ". . . " + at
401 doc.append(
402 "\t%-26s %s %s"
403 % (at, "alw" if basename in always_args else "opt", varname)
404 )
5578a14d
DL
405
406 for varname in args.keys():
407 handler = mix_handlers(args[varname])
701a0192 408 # print(varname, handler)
409 if handler is None:
410 continue
4381a59b 411 do_add(handler, varname, varname)
701a0192 412 code = handler.code.substitute({"varname": varname}).replace(
413 "\n", "\n\t\t\t"
414 )
0ee0892b
DL
415 if handler.canfail:
416 canfail = 1
701a0192 417 strblock = ""
5578a14d 418 if not handler.drop_str:
701a0192 419 do_add(
420 StringHandler(None),
421 varname,
422 "%s_str" % (varname),
423 " __attribute__ ((unused))",
424 )
425 strblock = "\n\t\t\t%s_str = argv[_i]->arg;" % (varname)
426 argblocks.append(
427 argblock.substitute(
428 {"varname": varname, "strblock": strblock, "code": code}
429 )
430 )
5578a14d
DL
431
432 if dumpfd is not None:
433 if len(arglist) > 0:
701a0192 434 dumpfd.write('"%s":\n%s\n\n' % (cmddef, "\n".join(doc)))
5578a14d
DL
435 else:
436 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
437
d11238bb
DL
438 params["cond_begin"] = "".join(cond_stack)
439 params["cond_end"] = "".join(["#endif\n"] * len(cond_stack))
701a0192 440 params["argdefs"] = "".join(argdefs)
441 params["argdecls"] = "".join(argdecls)
442 params["arglist"] = "".join(arglist)
443 params["argblocks"] = "".join(argblocks)
444 params["canfail"] = canfail
445 params["nonempty"] = len(argblocks)
446 params["argassert"] = "".join(argassert)
5578a14d
DL
447 ofd.write(templ.substitute(params))
448
3779776a
DL
449 return errors
450
701a0192 451
452if __name__ == "__main__":
5578a14d
DL
453 import argparse
454
701a0192 455 argp = argparse.ArgumentParser(description="FRR CLI preprocessor in Python")
456 argp.add_argument(
457 "--all-defun",
458 action="store_const",
459 const=True,
460 help="process DEFUN() statements in addition to DEFPY()",
461 )
462 argp.add_argument(
463 "--show",
464 action="store_const",
465 const=True,
466 help="print out list of arguments and types for each definition",
467 )
468 argp.add_argument("-o", type=str, metavar="OUTFILE", help="output C file name")
469 argp.add_argument("cfile", type=str)
5578a14d
DL
470 args = argp.parse_args()
471
472 dumpfd = None
473 if args.o is not None:
474 ofd = StringIO()
475 if args.show:
476 dumpfd = sys.stdout
477 else:
478 ofd = sys.stdout
479 if args.show:
480 dumpfd = sys.stderr
481
c4ca3ef5
DL
482 basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
483
b41b3f7b 484 macros = Macros()
701a0192 485 macros.load("lib/route_types.h")
486 macros.load(os.path.join(basepath, "lib/command.h"))
487 macros.load(os.path.join(basepath, "bgpd/bgp_vty.h"))
b41b3f7b 488 # sigh :(
e960eedd
DL
489 macros.setup("PROTO_REDIST_STR", "FRR_REDIST_STR_ISISD")
490 macros.setup("PROTO_IP_REDIST_STR", "FRR_IP_REDIST_STR_ISISD")
491 macros.setup("PROTO_IP6_REDIST_STR", "FRR_IP6_REDIST_STR_ISISD")
b41b3f7b
DL
492
493 errors = process_file(args.cfile, ofd, dumpfd, args.all_defun, macros)
3779776a
DL
494 if errors != 0:
495 sys.exit(1)
5578a14d
DL
496
497 if args.o is not None:
701a0192 498 clippy.wrdiff(
499 args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable]
500 )