]> git.proxmox.com Git - mirror_frr.git/blame - python/clidef.py
Merge pull request #4920 from ddutt/bgp-summary-upd8
[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
29class RenderHandler(object):
30 def __init__(self, token):
31 pass
32 def combine(self, other):
33 if type(self) == type(other):
34 return other
35 return StringHandler(None)
36
37 deref = ''
38 drop_str = False
0ee0892b 39 canfail = True
4381a59b 40 canassert = False
5578a14d
DL
41
42class StringHandler(RenderHandler):
43 argtype = 'const char *'
44 decl = Template('const char *$varname = NULL;')
5254bb15 45 code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
5578a14d 46 drop_str = True
0ee0892b 47 canfail = False
4381a59b 48 canassert = True
5578a14d
DL
49
50class LongHandler(RenderHandler):
51 argtype = 'long'
52 decl = Template('long $varname = 0;')
53 code = Template('''\
54char *_end;
55$varname = strtol(argv[_i]->arg, &_end, 10);
56_fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
57
58# A.B.C.D/M (prefix_ipv4) and
59# X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
60# struct prefix:
61
62class PrefixBase(RenderHandler):
63 def combine(self, other):
64 if type(self) == type(other):
65 return other
473196f6 66 if isinstance(other, PrefixBase):
5578a14d
DL
67 return PrefixGenHandler(None)
68 return StringHandler(None)
69 deref = '&'
70class Prefix4Handler(PrefixBase):
71 argtype = 'const struct prefix_ipv4 *'
3e300703 72 decl = Template('struct prefix_ipv4 $varname = { };')
5578a14d
DL
73 code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
74class Prefix6Handler(PrefixBase):
75 argtype = 'const struct prefix_ipv6 *'
3e300703 76 decl = Template('struct prefix_ipv6 $varname = { };')
5578a14d 77 code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
473196f6
QY
78class PrefixEthHandler(PrefixBase):
79 argtype = 'struct prefix_eth *'
3e300703 80 decl = Template('struct prefix_eth $varname = { };')
473196f6 81 code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
5578a14d
DL
82class PrefixGenHandler(PrefixBase):
83 argtype = 'const struct prefix *'
3e300703 84 decl = Template('struct prefix $varname = { };')
5578a14d
DL
85 code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
86
87# same for IP addresses. result is union sockunion.
88class IPBase(RenderHandler):
89 def combine(self, other):
90 if type(self) == type(other):
91 return other
92 if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
93 return IPGenHandler(None)
94 return StringHandler(None)
95class IP4Handler(IPBase):
96 argtype = 'struct in_addr'
97 decl = Template('struct in_addr $varname = { INADDR_ANY };')
98 code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
99class IP6Handler(IPBase):
100 argtype = 'struct in6_addr'
3e300703 101 decl = Template('struct in6_addr $varname = {};')
5578a14d
DL
102 code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
103class IPGenHandler(IPBase):
104 argtype = 'const union sockunion *'
105 decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
106 code = Template('''\
107if (argv[_i]->text[0] == 'X') {
108 s__$varname.sa.sa_family = AF_INET6;
109 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
110 $varname = &s__$varname;
111} else {
112 s__$varname.sa.sa_family = AF_INET;
113 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
114 $varname = &s__$varname;
115}''')
4381a59b 116 canassert = True
5578a14d
DL
117
118def mix_handlers(handlers):
119 def combine(a, b):
120 if a is None:
121 return b
122 return a.combine(b)
123 return reduce(combine, handlers, None)
124
125handlers = {
126 'WORD_TKN': StringHandler,
127 'VARIABLE_TKN': StringHandler,
128 'RANGE_TKN': LongHandler,
129 'IPV4_TKN': IP4Handler,
130 'IPV4_PREFIX_TKN': Prefix4Handler,
131 'IPV6_TKN': IP6Handler,
132 'IPV6_PREFIX_TKN': Prefix6Handler,
473196f6
QY
133 'MAC_TKN': PrefixEthHandler,
134 'MAC_PREFIX_TKN': PrefixEthHandler,
5578a14d
DL
135}
136
137# core template invoked for each occurence of DEFPY.
0ee0892b
DL
138#
139# the "#if $..." bits are there to keep this template unified into one
140# common form, without requiring a more advanced template engine (e.g.
141# jinja2)
5578a14d
DL
142templ = Template('''/* $fnname => "$cmddef" */
143DEFUN_CMD_FUNC_DECL($fnname)
144#define funcdecl_$fnname static int ${fnname}_magic(\\
145 const struct cmd_element *self __attribute__ ((unused)),\\
146 struct vty *vty __attribute__ ((unused)),\\
147 int argc __attribute__ ((unused)),\\
148 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
149funcdecl_$fnname;
150DEFUN_CMD_FUNC_TEXT($fnname)
151{
0ee0892b 152#if $nonempty /* anything to parse? */
5578a14d 153 int _i;
0ee0892b 154#if $canfail /* anything that can fail? */
5578a14d 155 unsigned _fail = 0, _failcnt = 0;
0ee0892b 156#endif
5578a14d
DL
157$argdecls
158 for (_i = 0; _i < argc; _i++) {
159 if (!argv[_i]->varname)
160 continue;
0ee0892b
DL
161#if $canfail /* anything that can fail? */
162 _fail = 0;
163#endif
164$argblocks
165#if $canfail /* anything that can fail? */
5578a14d 166 if (_fail)
5c7571d4 167 vty_out (vty, "%% invalid input for %s: %s\\n",
8e25c8ce 168 argv[_i]->varname, argv[_i]->arg);
5578a14d 169 _failcnt += _fail;
0ee0892b 170#endif
5578a14d 171 }
0ee0892b 172#if $canfail /* anything that can fail? */
5578a14d
DL
173 if (_failcnt)
174 return CMD_WARNING;
0ee0892b
DL
175#endif
176#endif
4381a59b 177$argassert
5578a14d
DL
178 return ${fnname}_magic(self, vty, argc, argv$arglist);
179}
180
181''')
182
183# invoked for each named parameter
184argblock = Template('''
185 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
186 $code
187 }''')
188
4381a59b
DL
189def get_always_args(token, always_args, args = [], stack = []):
190 if token in stack:
191 return
192 if token.type == 'END_TKN':
193 for arg in list(always_args):
194 if arg not in args:
195 always_args.remove(arg)
196 return
197
198 stack = stack + [token]
199 if token.type in handlers and token.varname is not None:
200 args = args + [token.varname]
201 for nexttkn in token.next():
202 get_always_args(nexttkn, always_args, args, stack)
203
b41b3f7b
DL
204class Macros(dict):
205 def load(self, filename):
206 filedata = clippy.parse(filename)
207 for entry in filedata['data']:
208 if entry['type'] != 'PREPROC':
209 continue
210 ppdir = entry['line'].lstrip().split(None, 1)
211 if ppdir[0] != 'define' or len(ppdir) != 2:
212 continue
213 ppdef = ppdir[1].split(None, 1)
214 name = ppdef[0]
215 if '(' in name:
216 continue
217 val = ppdef[1] if len(ppdef) == 2 else ''
218
219 val = val.strip(' \t\n\\')
220 if name in self:
221 sys.stderr.write('warning: macro %s redefined!\n' % (name))
222 self[name] = val
223
224def process_file(fn, ofd, dumpfd, all_defun, macros):
3779776a 225 errors = 0
5578a14d
DL
226 filedata = clippy.parse(fn)
227
228 for entry in filedata['data']:
e31f4dbe 229 if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')):
3779776a
DL
230 if len(entry['args'][0]) != 1:
231 sys.stderr.write('%s:%d: DEFPY function name not parseable (%r)\n' % (fn, entry['lineno'], entry['args'][0]))
232 errors += 1
233 continue
234
5578a14d 235 cmddef = entry['args'][2]
b41b3f7b 236 cmddefx = []
3779776a 237 for i in cmddef:
b41b3f7b
DL
238 while i in macros:
239 i = macros[i]
240 if i.startswith('"') and i.endswith('"'):
241 cmddefx.append(i[1:-1])
242 continue
243
244 sys.stderr.write('%s:%d: DEFPY command string not parseable (%r)\n' % (fn, entry['lineno'], cmddef))
245 errors += 1
246 cmddefx = None
247 break
248 if cmddefx is None:
3779776a 249 continue
b41b3f7b 250 cmddef = ''.join([i for i in cmddefx])
5578a14d
DL
251
252 graph = clippy.Graph(cmddef)
253 args = OrderedDict()
4381a59b 254 always_args = set()
5578a14d
DL
255 for token, depth in clippy.graph_iterate(graph):
256 if token.type not in handlers:
257 continue
258 if token.varname is None:
259 continue
260 arg = args.setdefault(token.varname, [])
261 arg.append(handlers[token.type](token))
4381a59b
DL
262 always_args.add(token.varname)
263
264 get_always_args(graph.first(), always_args)
5578a14d
DL
265
266 #print('-' * 76)
267 #pprint(entry)
268 #clippy.dump(graph)
269 #pprint(args)
270
271 params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
272 argdefs = []
273 argdecls = []
274 arglist = []
275 argblocks = []
4381a59b 276 argassert = []
5578a14d 277 doc = []
0ee0892b 278 canfail = 0
5578a14d 279
4381a59b 280 def do_add(handler, basename, varname, attr = ''):
5578a14d
DL
281 argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
282 argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
283 arglist.append(', %s%s' % (handler.deref, varname))
4381a59b
DL
284 if basename in always_args and handler.canassert:
285 argassert.append('''\tif (!%s) {
286\t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
287\t\treturn CMD_WARNING;
288\t}\n''' % (varname, varname))
5578a14d
DL
289 if attr == '':
290 at = handler.argtype
291 if not at.startswith('const '):
292 at = '. . . ' + at
4381a59b 293 doc.append('\t%-26s %s %s' % (at, 'alw' if basename in always_args else 'opt', varname))
5578a14d
DL
294
295 for varname in args.keys():
296 handler = mix_handlers(args[varname])
297 #print(varname, handler)
298 if handler is None: continue
4381a59b 299 do_add(handler, varname, varname)
5578a14d 300 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
0ee0892b
DL
301 if handler.canfail:
302 canfail = 1
5578a14d
DL
303 strblock = ''
304 if not handler.drop_str:
4381a59b 305 do_add(StringHandler(None), varname, '%s_str' % (varname), ' __attribute__ ((unused))')
5578a14d
DL
306 strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
307 argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
308
309 if dumpfd is not None:
310 if len(arglist) > 0:
311 dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
312 else:
313 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
314
315 params['argdefs'] = ''.join(argdefs)
316 params['argdecls'] = ''.join(argdecls)
317 params['arglist'] = ''.join(arglist)
318 params['argblocks'] = ''.join(argblocks)
0ee0892b
DL
319 params['canfail'] = canfail
320 params['nonempty'] = len(argblocks)
4381a59b 321 params['argassert'] = ''.join(argassert)
5578a14d
DL
322 ofd.write(templ.substitute(params))
323
3779776a
DL
324 return errors
325
5578a14d
DL
326if __name__ == '__main__':
327 import argparse
328
329 argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
330 argp.add_argument('--all-defun', action = 'store_const', const = True,
331 help = 'process DEFUN() statements in addition to DEFPY()')
332 argp.add_argument('--show', action = 'store_const', const = True,
333 help = 'print out list of arguments and types for each definition')
334 argp.add_argument('-o', type = str, metavar = 'OUTFILE',
335 help = 'output C file name')
336 argp.add_argument('cfile', type = str)
337 args = argp.parse_args()
338
339 dumpfd = None
340 if args.o is not None:
341 ofd = StringIO()
342 if args.show:
343 dumpfd = sys.stdout
344 else:
345 ofd = sys.stdout
346 if args.show:
347 dumpfd = sys.stderr
348
c4ca3ef5
DL
349 basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
350
b41b3f7b
DL
351 macros = Macros()
352 macros.load('lib/route_types.h')
c4ca3ef5 353 macros.load(os.path.join(basepath, 'lib/command.h'))
ed18356f 354 macros.load(os.path.join(basepath, 'bgpd/bgp_vty.h'))
b41b3f7b
DL
355 # sigh :(
356 macros['PROTO_REDIST_STR'] = 'FRR_REDIST_STR_ISISD'
357
358 errors = process_file(args.cfile, ofd, dumpfd, args.all_defun, macros)
3779776a
DL
359 if errors != 0:
360 sys.exit(1)
5578a14d
DL
361
362 if args.o is not None:
b716e79a 363 clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable])