]> git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
Merge pull request #4874 from manuhalo/fix_isis_mtu
[mirror_frr.git] / python / clidef.py
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
19 import clippy, traceback, sys, os
20 from collections import OrderedDict
21 from functools import reduce
22 from pprint import pprint
23 from string import Template
24 from 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
29 class 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
39 canfail = True
40 canassert = False
41
42 class StringHandler(RenderHandler):
43 argtype = 'const char *'
44 decl = Template('const char *$varname = NULL;')
45 code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
46 drop_str = True
47 canfail = False
48 canassert = True
49
50 class LongHandler(RenderHandler):
51 argtype = 'long'
52 decl = Template('long $varname = 0;')
53 code = Template('''\
54 char *_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
62 class PrefixBase(RenderHandler):
63 def combine(self, other):
64 if type(self) == type(other):
65 return other
66 if isinstance(other, PrefixBase):
67 return PrefixGenHandler(None)
68 return StringHandler(None)
69 deref = '&'
70 class Prefix4Handler(PrefixBase):
71 argtype = 'const struct prefix_ipv4 *'
72 decl = Template('struct prefix_ipv4 $varname = { };')
73 code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
74 class Prefix6Handler(PrefixBase):
75 argtype = 'const struct prefix_ipv6 *'
76 decl = Template('struct prefix_ipv6 $varname = { };')
77 code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
78 class PrefixEthHandler(PrefixBase):
79 argtype = 'struct prefix_eth *'
80 decl = Template('struct prefix_eth $varname = { };')
81 code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
82 class PrefixGenHandler(PrefixBase):
83 argtype = 'const struct prefix *'
84 decl = Template('struct prefix $varname = { };')
85 code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
86
87 # same for IP addresses. result is union sockunion.
88 class 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)
95 class 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);')
99 class IP6Handler(IPBase):
100 argtype = 'struct in6_addr'
101 decl = Template('struct in6_addr $varname = {};')
102 code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
103 class IPGenHandler(IPBase):
104 argtype = 'const union sockunion *'
105 decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
106 code = Template('''\
107 if (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 }''')
116 canassert = True
117
118 def 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
125 handlers = {
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,
133 'MAC_TKN': PrefixEthHandler,
134 'MAC_PREFIX_TKN': PrefixEthHandler,
135 }
136
137 # core template invoked for each occurence of DEFPY.
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)
142 templ = Template('''/* $fnname => "$cmddef" */
143 DEFUN_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)
149 funcdecl_$fnname;
150 DEFUN_CMD_FUNC_TEXT($fnname)
151 {
152 #if $nonempty /* anything to parse? */
153 int _i;
154 #if $canfail /* anything that can fail? */
155 unsigned _fail = 0, _failcnt = 0;
156 #endif
157 $argdecls
158 for (_i = 0; _i < argc; _i++) {
159 if (!argv[_i]->varname)
160 continue;
161 #if $canfail /* anything that can fail? */
162 _fail = 0;
163 #endif
164 $argblocks
165 #if $canfail /* anything that can fail? */
166 if (_fail)
167 vty_out (vty, "%% invalid input for %s: %s\\n",
168 argv[_i]->varname, argv[_i]->arg);
169 _failcnt += _fail;
170 #endif
171 }
172 #if $canfail /* anything that can fail? */
173 if (_failcnt)
174 return CMD_WARNING;
175 #endif
176 #endif
177 $argassert
178 return ${fnname}_magic(self, vty, argc, argv$arglist);
179 }
180
181 ''')
182
183 # invoked for each named parameter
184 argblock = Template('''
185 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
186 $code
187 }''')
188
189 def 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
204 class 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
224 def process_file(fn, ofd, dumpfd, all_defun, macros):
225 errors = 0
226 filedata = clippy.parse(fn)
227
228 for entry in filedata['data']:
229 if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')):
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
235 cmddef = entry['args'][2]
236 cmddefx = []
237 for i in cmddef:
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:
249 continue
250 cmddef = ''.join([i for i in cmddefx])
251
252 graph = clippy.Graph(cmddef)
253 args = OrderedDict()
254 always_args = set()
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))
262 always_args.add(token.varname)
263
264 get_always_args(graph.first(), always_args)
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 = []
276 argassert = []
277 doc = []
278 canfail = 0
279
280 def do_add(handler, basename, varname, attr = ''):
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))
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))
289 if attr == '':
290 at = handler.argtype
291 if not at.startswith('const '):
292 at = '. . . ' + at
293 doc.append('\t%-26s %s %s' % (at, 'alw' if basename in always_args else 'opt', varname))
294
295 for varname in args.keys():
296 handler = mix_handlers(args[varname])
297 #print(varname, handler)
298 if handler is None: continue
299 do_add(handler, varname, varname)
300 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
301 if handler.canfail:
302 canfail = 1
303 strblock = ''
304 if not handler.drop_str:
305 do_add(StringHandler(None), varname, '%s_str' % (varname), ' __attribute__ ((unused))')
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)
319 params['canfail'] = canfail
320 params['nonempty'] = len(argblocks)
321 params['argassert'] = ''.join(argassert)
322 ofd.write(templ.substitute(params))
323
324 return errors
325
326 if __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
349 basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
350
351 macros = Macros()
352 macros.load('lib/route_types.h')
353 macros.load(os.path.join(basepath, 'lib/command.h'))
354 macros.load(os.path.join(basepath, 'bgpd/bgp_vty.h'))
355 # sigh :(
356 macros['PROTO_REDIST_STR'] = 'FRR_REDIST_STR_ISISD'
357
358 errors = process_file(args.cfile, ofd, dumpfd, args.all_defun, macros)
359 if errors != 0:
360 sys.exit(1)
361
362 if args.o is not None:
363 clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable])