]>
Commit | Line | Data |
---|---|---|
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 | ||
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 | |
0ee0892b | 39 | canfail = True |
5578a14d DL |
40 | |
41 | class StringHandler(RenderHandler): | |
42 | argtype = 'const char *' | |
43 | decl = Template('const char *$varname = NULL;') | |
44 | code = Template('$varname = argv[_i]->arg;') | |
45 | drop_str = True | |
0ee0892b | 46 | canfail = False |
5578a14d DL |
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 | |
473196f6 | 64 | if isinstance(other, PrefixBase): |
5578a14d DL |
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);') | |
473196f6 QY |
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);') | |
5578a14d DL |
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 = IN6ADDR_ANY_INIT;') | |
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, | |
473196f6 QY |
130 | 'MAC_TKN': PrefixEthHandler, |
131 | 'MAC_PREFIX_TKN': PrefixEthHandler, | |
5578a14d DL |
132 | } |
133 | ||
134 | # core template invoked for each occurence of DEFPY. | |
0ee0892b DL |
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) | |
5578a14d DL |
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 | { | |
0ee0892b | 149 | #if $nonempty /* anything to parse? */ |
5578a14d | 150 | int _i; |
0ee0892b | 151 | #if $canfail /* anything that can fail? */ |
5578a14d | 152 | unsigned _fail = 0, _failcnt = 0; |
0ee0892b | 153 | #endif |
5578a14d DL |
154 | $argdecls |
155 | for (_i = 0; _i < argc; _i++) { | |
156 | if (!argv[_i]->varname) | |
157 | continue; | |
0ee0892b DL |
158 | #if $canfail /* anything that can fail? */ |
159 | _fail = 0; | |
160 | #endif | |
161 | $argblocks | |
162 | #if $canfail /* anything that can fail? */ | |
5578a14d | 163 | if (_fail) |
5c7571d4 | 164 | vty_out (vty, "%% invalid input for %s: %s\\n", |
8e25c8ce | 165 | argv[_i]->varname, argv[_i]->arg); |
5578a14d | 166 | _failcnt += _fail; |
0ee0892b | 167 | #endif |
5578a14d | 168 | } |
0ee0892b | 169 | #if $canfail /* anything that can fail? */ |
5578a14d DL |
170 | if (_failcnt) |
171 | return CMD_WARNING; | |
0ee0892b DL |
172 | #endif |
173 | #endif | |
5578a14d DL |
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']: | |
e31f4dbe | 189 | if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')): |
5578a14d | 190 | cmddef = entry['args'][2] |
5578a14d DL |
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 = [] | |
0ee0892b | 214 | canfail = 0 |
5578a14d DL |
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') | |
0ee0892b DL |
232 | if handler.canfail: |
233 | canfail = 1 | |
5578a14d DL |
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) | |
0ee0892b DL |
250 | params['canfail'] = canfail |
251 | params['nonempty'] = len(argblocks) | |
5578a14d DL |
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: | |
67eb7a6c | 280 | clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__)]) |