]> git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
lib/clippy: error out on unsupported bits
[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
41 class StringHandler(RenderHandler):
42 argtype = 'const char *'
43 decl = Template('const char *$varname = NULL;')
44 code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
45 drop_str = True
46 canfail = False
47
48 class LongHandler(RenderHandler):
49 argtype = 'long'
50 decl = Template('long $varname = 0;')
51 code = Template('''\
52 char *_end;
53 $varname = strtol(argv[_i]->arg, &_end, 10);
54 _fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
55
56 # A.B.C.D/M (prefix_ipv4) and
57 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
58 # struct prefix:
59
60 class PrefixBase(RenderHandler):
61 def combine(self, other):
62 if type(self) == type(other):
63 return other
64 if isinstance(other, PrefixBase):
65 return PrefixGenHandler(None)
66 return StringHandler(None)
67 deref = '&'
68 class Prefix4Handler(PrefixBase):
69 argtype = 'const struct prefix_ipv4 *'
70 decl = Template('struct prefix_ipv4 $varname = { };')
71 code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
72 class Prefix6Handler(PrefixBase):
73 argtype = 'const struct prefix_ipv6 *'
74 decl = Template('struct prefix_ipv6 $varname = { };')
75 code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
76 class PrefixEthHandler(PrefixBase):
77 argtype = 'struct prefix_eth *'
78 decl = Template('struct prefix_eth $varname = { };')
79 code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
80 class PrefixGenHandler(PrefixBase):
81 argtype = 'const struct prefix *'
82 decl = Template('struct prefix $varname = { };')
83 code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
84
85 # same for IP addresses. result is union sockunion.
86 class IPBase(RenderHandler):
87 def combine(self, other):
88 if type(self) == type(other):
89 return other
90 if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
91 return IPGenHandler(None)
92 return StringHandler(None)
93 class IP4Handler(IPBase):
94 argtype = 'struct in_addr'
95 decl = Template('struct in_addr $varname = { INADDR_ANY };')
96 code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
97 class IP6Handler(IPBase):
98 argtype = 'struct in6_addr'
99 decl = Template('struct in6_addr $varname = {};')
100 code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
101 class IPGenHandler(IPBase):
102 argtype = 'const union sockunion *'
103 decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
104 code = Template('''\
105 if (argv[_i]->text[0] == 'X') {
106 s__$varname.sa.sa_family = AF_INET6;
107 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
108 $varname = &s__$varname;
109 } else {
110 s__$varname.sa.sa_family = AF_INET;
111 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
112 $varname = &s__$varname;
113 }''')
114
115 def mix_handlers(handlers):
116 def combine(a, b):
117 if a is None:
118 return b
119 return a.combine(b)
120 return reduce(combine, handlers, None)
121
122 handlers = {
123 'WORD_TKN': StringHandler,
124 'VARIABLE_TKN': StringHandler,
125 'RANGE_TKN': LongHandler,
126 'IPV4_TKN': IP4Handler,
127 'IPV4_PREFIX_TKN': Prefix4Handler,
128 'IPV6_TKN': IP6Handler,
129 'IPV6_PREFIX_TKN': Prefix6Handler,
130 'MAC_TKN': PrefixEthHandler,
131 'MAC_PREFIX_TKN': PrefixEthHandler,
132 }
133
134 # core template invoked for each occurence of DEFPY.
135 #
136 # the "#if $..." bits are there to keep this template unified into one
137 # common form, without requiring a more advanced template engine (e.g.
138 # jinja2)
139 templ = Template('''/* $fnname => "$cmddef" */
140 DEFUN_CMD_FUNC_DECL($fnname)
141 #define funcdecl_$fnname static int ${fnname}_magic(\\
142 const struct cmd_element *self __attribute__ ((unused)),\\
143 struct vty *vty __attribute__ ((unused)),\\
144 int argc __attribute__ ((unused)),\\
145 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
146 funcdecl_$fnname;
147 DEFUN_CMD_FUNC_TEXT($fnname)
148 {
149 #if $nonempty /* anything to parse? */
150 int _i;
151 #if $canfail /* anything that can fail? */
152 unsigned _fail = 0, _failcnt = 0;
153 #endif
154 $argdecls
155 for (_i = 0; _i < argc; _i++) {
156 if (!argv[_i]->varname)
157 continue;
158 #if $canfail /* anything that can fail? */
159 _fail = 0;
160 #endif
161 $argblocks
162 #if $canfail /* anything that can fail? */
163 if (_fail)
164 vty_out (vty, "%% invalid input for %s: %s\\n",
165 argv[_i]->varname, argv[_i]->arg);
166 _failcnt += _fail;
167 #endif
168 }
169 #if $canfail /* anything that can fail? */
170 if (_failcnt)
171 return CMD_WARNING;
172 #endif
173 #endif
174 return ${fnname}_magic(self, vty, argc, argv$arglist);
175 }
176
177 ''')
178
179 # invoked for each named parameter
180 argblock = Template('''
181 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
182 $code
183 }''')
184
185 def process_file(fn, ofd, dumpfd, all_defun):
186 errors = 0
187 filedata = clippy.parse(fn)
188
189 for entry in filedata['data']:
190 if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')):
191 if len(entry['args'][0]) != 1:
192 sys.stderr.write('%s:%d: DEFPY function name not parseable (%r)\n' % (fn, entry['lineno'], entry['args'][0]))
193 errors += 1
194 continue
195
196 cmddef = entry['args'][2]
197 for i in cmddef:
198 if not (i.startswith('"') and i.endswith('"')):
199 sys.stderr.write('%s:%d: DEFPY command string not parseable (%r)\n' % (fn, entry['lineno'], cmddef))
200 errors += 1
201 cmddef = None
202 break
203 if cmddef is None:
204 continue
205 cmddef = ''.join([i[1:-1] for i in cmddef])
206
207 graph = clippy.Graph(cmddef)
208 args = OrderedDict()
209 for token, depth in clippy.graph_iterate(graph):
210 if token.type not in handlers:
211 continue
212 if token.varname is None:
213 continue
214 arg = args.setdefault(token.varname, [])
215 arg.append(handlers[token.type](token))
216
217 #print('-' * 76)
218 #pprint(entry)
219 #clippy.dump(graph)
220 #pprint(args)
221
222 params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
223 argdefs = []
224 argdecls = []
225 arglist = []
226 argblocks = []
227 doc = []
228 canfail = 0
229
230 def do_add(handler, varname, attr = ''):
231 argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
232 argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
233 arglist.append(', %s%s' % (handler.deref, varname))
234 if attr == '':
235 at = handler.argtype
236 if not at.startswith('const '):
237 at = '. . . ' + at
238 doc.append('\t%-26s %s' % (at, varname))
239
240 for varname in args.keys():
241 handler = mix_handlers(args[varname])
242 #print(varname, handler)
243 if handler is None: continue
244 do_add(handler, varname)
245 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
246 if handler.canfail:
247 canfail = 1
248 strblock = ''
249 if not handler.drop_str:
250 do_add(StringHandler(None), '%s_str' % (varname), ' __attribute__ ((unused))')
251 strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
252 argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
253
254 if dumpfd is not None:
255 if len(arglist) > 0:
256 dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
257 else:
258 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
259
260 params['argdefs'] = ''.join(argdefs)
261 params['argdecls'] = ''.join(argdecls)
262 params['arglist'] = ''.join(arglist)
263 params['argblocks'] = ''.join(argblocks)
264 params['canfail'] = canfail
265 params['nonempty'] = len(argblocks)
266 ofd.write(templ.substitute(params))
267
268 return errors
269
270 if __name__ == '__main__':
271 import argparse
272
273 argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
274 argp.add_argument('--all-defun', action = 'store_const', const = True,
275 help = 'process DEFUN() statements in addition to DEFPY()')
276 argp.add_argument('--show', action = 'store_const', const = True,
277 help = 'print out list of arguments and types for each definition')
278 argp.add_argument('-o', type = str, metavar = 'OUTFILE',
279 help = 'output C file name')
280 argp.add_argument('cfile', type = str)
281 args = argp.parse_args()
282
283 dumpfd = None
284 if args.o is not None:
285 ofd = StringIO()
286 if args.show:
287 dumpfd = sys.stdout
288 else:
289 ofd = sys.stdout
290 if args.show:
291 dumpfd = sys.stderr
292
293 errors = process_file(args.cfile, ofd, dumpfd, args.all_defun)
294 if errors != 0:
295 sys.exit(1)
296
297 if args.o is not None:
298 clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable])