]> git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
Docker binary image on Debian Buster (#4504)
[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 filedata = clippy.parse(fn)
187
188 for entry in filedata['data']:
189 if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')):
190 cmddef = entry['args'][2]
191 cmddef = ''.join([i[1:-1] for i in cmddef])
192
193 graph = clippy.Graph(cmddef)
194 args = OrderedDict()
195 for token, depth in clippy.graph_iterate(graph):
196 if token.type not in handlers:
197 continue
198 if token.varname is None:
199 continue
200 arg = args.setdefault(token.varname, [])
201 arg.append(handlers[token.type](token))
202
203 #print('-' * 76)
204 #pprint(entry)
205 #clippy.dump(graph)
206 #pprint(args)
207
208 params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
209 argdefs = []
210 argdecls = []
211 arglist = []
212 argblocks = []
213 doc = []
214 canfail = 0
215
216 def do_add(handler, varname, attr = ''):
217 argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
218 argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
219 arglist.append(', %s%s' % (handler.deref, varname))
220 if attr == '':
221 at = handler.argtype
222 if not at.startswith('const '):
223 at = '. . . ' + at
224 doc.append('\t%-26s %s' % (at, varname))
225
226 for varname in args.keys():
227 handler = mix_handlers(args[varname])
228 #print(varname, handler)
229 if handler is None: continue
230 do_add(handler, varname)
231 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
232 if handler.canfail:
233 canfail = 1
234 strblock = ''
235 if not handler.drop_str:
236 do_add(StringHandler(None), '%s_str' % (varname), ' __attribute__ ((unused))')
237 strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
238 argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
239
240 if dumpfd is not None:
241 if len(arglist) > 0:
242 dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
243 else:
244 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
245
246 params['argdefs'] = ''.join(argdefs)
247 params['argdecls'] = ''.join(argdecls)
248 params['arglist'] = ''.join(arglist)
249 params['argblocks'] = ''.join(argblocks)
250 params['canfail'] = canfail
251 params['nonempty'] = len(argblocks)
252 ofd.write(templ.substitute(params))
253
254 if __name__ == '__main__':
255 import argparse
256
257 argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
258 argp.add_argument('--all-defun', action = 'store_const', const = True,
259 help = 'process DEFUN() statements in addition to DEFPY()')
260 argp.add_argument('--show', action = 'store_const', const = True,
261 help = 'print out list of arguments and types for each definition')
262 argp.add_argument('-o', type = str, metavar = 'OUTFILE',
263 help = 'output C file name')
264 argp.add_argument('cfile', type = str)
265 args = argp.parse_args()
266
267 dumpfd = None
268 if args.o is not None:
269 ofd = StringIO()
270 if args.show:
271 dumpfd = sys.stdout
272 else:
273 ofd = sys.stdout
274 if args.show:
275 dumpfd = sys.stderr
276
277 process_file(args.cfile, ofd, dumpfd, args.all_defun)
278
279 if args.o is not None:
280 clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable])