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