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