mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-10-31 23:29:26 +00:00 
			
		
		
		
	 c4ca3ef5e7
			
		
	
	
		c4ca3ef5e7
		
	
	
	
	
		
			
			The current directory is in the build tree while running clippy; find ourselves so we can access headers from the source tree. Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
		
			
				
	
	
		
			363 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # FRR CLI preprocessor (DEFPY)
 | |
| #
 | |
| # Copyright (C) 2017  David Lamparter for NetDEF, Inc.
 | |
| #
 | |
| # This program is free software; you can redistribute it and/or modify it
 | |
| # under the terms of the GNU General Public License as published by the Free
 | |
| # Software Foundation; either version 2 of the License, or (at your option)
 | |
| # any later version.
 | |
| #
 | |
| # This program is distributed in the hope that it will be useful, but WITHOUT
 | |
| # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | |
| # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | |
| # more details.
 | |
| #
 | |
| # You should have received a copy of the GNU General Public License along
 | |
| # with this program; see the file COPYING; if not, write to the Free Software
 | |
| # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 | |
| 
 | |
| import clippy, traceback, sys, os
 | |
| from collections import OrderedDict
 | |
| from functools import reduce
 | |
| from pprint import pprint
 | |
| from string import Template
 | |
| from io import StringIO
 | |
| 
 | |
| # the various handlers generate output C code for a particular type of
 | |
| # CLI token, choosing the most useful output C type.
 | |
| 
 | |
| class RenderHandler(object):
 | |
|     def __init__(self, token):
 | |
|         pass
 | |
|     def combine(self, other):
 | |
|         if type(self) == type(other):
 | |
|             return other
 | |
|         return StringHandler(None)
 | |
| 
 | |
|     deref = ''
 | |
|     drop_str = False
 | |
|     canfail = True
 | |
|     canassert = False
 | |
| 
 | |
| class StringHandler(RenderHandler):
 | |
|     argtype = 'const char *'
 | |
|     decl = Template('const char *$varname = NULL;')
 | |
|     code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
 | |
|     drop_str = True
 | |
|     canfail = False
 | |
|     canassert = True
 | |
| 
 | |
| class LongHandler(RenderHandler):
 | |
|     argtype = 'long'
 | |
|     decl = Template('long $varname = 0;')
 | |
|     code = Template('''\
 | |
| char *_end;
 | |
| $varname = strtol(argv[_i]->arg, &_end, 10);
 | |
| _fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
 | |
| 
 | |
| # A.B.C.D/M (prefix_ipv4) and
 | |
| # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
 | |
| # struct prefix:
 | |
| 
 | |
| class PrefixBase(RenderHandler):
 | |
|     def combine(self, other):
 | |
|         if type(self) == type(other):
 | |
|             return other
 | |
|         if isinstance(other, PrefixBase):
 | |
|             return PrefixGenHandler(None)
 | |
|         return StringHandler(None)
 | |
|     deref = '&'
 | |
| class Prefix4Handler(PrefixBase):
 | |
|     argtype = 'const struct prefix_ipv4 *'
 | |
|     decl = Template('struct prefix_ipv4 $varname = { };')
 | |
|     code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
 | |
| class Prefix6Handler(PrefixBase):
 | |
|     argtype = 'const struct prefix_ipv6 *'
 | |
|     decl = Template('struct prefix_ipv6 $varname = { };')
 | |
|     code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
 | |
| class PrefixEthHandler(PrefixBase):
 | |
|     argtype = 'struct prefix_eth *'
 | |
|     decl = Template('struct prefix_eth $varname = { };')
 | |
|     code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
 | |
| class PrefixGenHandler(PrefixBase):
 | |
|     argtype = 'const struct prefix *'
 | |
|     decl = Template('struct prefix $varname = { };')
 | |
|     code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
 | |
| 
 | |
| # same for IP addresses.  result is union sockunion.
 | |
| class IPBase(RenderHandler):
 | |
|     def combine(self, other):
 | |
|         if type(self) == type(other):
 | |
|             return other
 | |
|         if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
 | |
|             return IPGenHandler(None)
 | |
|         return StringHandler(None)
 | |
| class IP4Handler(IPBase):
 | |
|     argtype = 'struct in_addr'
 | |
|     decl = Template('struct in_addr $varname = { INADDR_ANY };')
 | |
|     code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
 | |
| class IP6Handler(IPBase):
 | |
|     argtype = 'struct in6_addr'
 | |
|     decl = Template('struct in6_addr $varname = {};')
 | |
|     code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
 | |
| class IPGenHandler(IPBase):
 | |
|     argtype = 'const union sockunion *'
 | |
|     decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
 | |
|     code = Template('''\
 | |
| if (argv[_i]->text[0] == 'X') {
 | |
| 	s__$varname.sa.sa_family = AF_INET6;
 | |
| 	_fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
 | |
| 	$varname = &s__$varname;
 | |
| } else {
 | |
| 	s__$varname.sa.sa_family = AF_INET;
 | |
| 	_fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
 | |
| 	$varname = &s__$varname;
 | |
| }''')
 | |
|     canassert = True
 | |
| 
 | |
| def mix_handlers(handlers):
 | |
|     def combine(a, b):
 | |
|         if a is None:
 | |
|             return b
 | |
|         return a.combine(b)
 | |
|     return reduce(combine, handlers, None)
 | |
| 
 | |
| handlers = {
 | |
|     'WORD_TKN':         StringHandler,
 | |
|     'VARIABLE_TKN':     StringHandler,
 | |
|     'RANGE_TKN':        LongHandler,
 | |
|     'IPV4_TKN':         IP4Handler,
 | |
|     'IPV4_PREFIX_TKN':  Prefix4Handler,
 | |
|     'IPV6_TKN':         IP6Handler,
 | |
|     'IPV6_PREFIX_TKN':  Prefix6Handler,
 | |
|     'MAC_TKN':          PrefixEthHandler,
 | |
|     'MAC_PREFIX_TKN':   PrefixEthHandler,
 | |
| }
 | |
| 
 | |
| # core template invoked for each occurence of DEFPY.
 | |
| #
 | |
| # the "#if $..." bits are there to keep this template unified into one
 | |
| # common form, without requiring a more advanced template engine (e.g.
 | |
| # jinja2)
 | |
| templ = Template('''/* $fnname => "$cmddef" */
 | |
| DEFUN_CMD_FUNC_DECL($fnname)
 | |
| #define funcdecl_$fnname static int ${fnname}_magic(\\
 | |
| 	const struct cmd_element *self __attribute__ ((unused)),\\
 | |
| 	struct vty *vty __attribute__ ((unused)),\\
 | |
| 	int argc __attribute__ ((unused)),\\
 | |
| 	struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
 | |
| funcdecl_$fnname;
 | |
| DEFUN_CMD_FUNC_TEXT($fnname)
 | |
| {
 | |
| #if $nonempty /* anything to parse? */
 | |
| 	int _i;
 | |
| #if $canfail /* anything that can fail? */
 | |
| 	unsigned _fail = 0, _failcnt = 0;
 | |
| #endif
 | |
| $argdecls
 | |
| 	for (_i = 0; _i < argc; _i++) {
 | |
| 		if (!argv[_i]->varname)
 | |
| 			continue;
 | |
| #if $canfail /* anything that can fail? */
 | |
| 		_fail = 0;
 | |
| #endif
 | |
| $argblocks
 | |
| #if $canfail /* anything that can fail? */
 | |
| 		if (_fail)
 | |
| 			vty_out (vty, "%% invalid input for %s: %s\\n",
 | |
| 				   argv[_i]->varname, argv[_i]->arg);
 | |
| 		_failcnt += _fail;
 | |
| #endif
 | |
| 	}
 | |
| #if $canfail /* anything that can fail? */
 | |
| 	if (_failcnt)
 | |
| 		return CMD_WARNING;
 | |
| #endif
 | |
| #endif
 | |
| $argassert
 | |
| 	return ${fnname}_magic(self, vty, argc, argv$arglist);
 | |
| }
 | |
| 
 | |
| ''')
 | |
| 
 | |
| # invoked for each named parameter
 | |
| argblock = Template('''
 | |
| 		if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
 | |
| 			$code
 | |
| 		}''')
 | |
| 
 | |
| def get_always_args(token, always_args, args = [], stack = []):
 | |
|     if token in stack:
 | |
|         return
 | |
|     if token.type == 'END_TKN':
 | |
|         for arg in list(always_args):
 | |
|             if arg not in args:
 | |
|                 always_args.remove(arg)
 | |
|         return
 | |
| 
 | |
|     stack = stack + [token]
 | |
|     if token.type in handlers and token.varname is not None:
 | |
|         args = args + [token.varname]
 | |
|     for nexttkn in token.next():
 | |
|         get_always_args(nexttkn, always_args, args, stack)
 | |
| 
 | |
| class Macros(dict):
 | |
|     def load(self, filename):
 | |
|         filedata = clippy.parse(filename)
 | |
|         for entry in filedata['data']:
 | |
|             if entry['type'] != 'PREPROC':
 | |
|                 continue
 | |
|             ppdir = entry['line'].lstrip().split(None, 1)
 | |
|             if ppdir[0] != 'define' or len(ppdir) != 2:
 | |
|                 continue
 | |
|             ppdef = ppdir[1].split(None, 1)
 | |
|             name = ppdef[0]
 | |
|             if '(' in name:
 | |
|                 continue
 | |
|             val = ppdef[1] if len(ppdef) == 2 else ''
 | |
| 
 | |
|             val = val.strip(' \t\n\\')
 | |
|             if name in self:
 | |
|                 sys.stderr.write('warning: macro %s redefined!\n' % (name))
 | |
|             self[name] = val
 | |
| 
 | |
| def process_file(fn, ofd, dumpfd, all_defun, macros):
 | |
|     errors = 0
 | |
|     filedata = clippy.parse(fn)
 | |
| 
 | |
|     for entry in filedata['data']:
 | |
|         if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')):
 | |
|             if len(entry['args'][0]) != 1:
 | |
|                 sys.stderr.write('%s:%d: DEFPY function name not parseable (%r)\n' % (fn, entry['lineno'], entry['args'][0]))
 | |
|                 errors += 1
 | |
|                 continue
 | |
| 
 | |
|             cmddef = entry['args'][2]
 | |
|             cmddefx = []
 | |
|             for i in cmddef:
 | |
|                 while i in macros:
 | |
|                     i = macros[i]
 | |
|                 if i.startswith('"') and i.endswith('"'):
 | |
|                     cmddefx.append(i[1:-1])
 | |
|                     continue
 | |
| 
 | |
|                 sys.stderr.write('%s:%d: DEFPY command string not parseable (%r)\n' % (fn, entry['lineno'], cmddef))
 | |
|                 errors += 1
 | |
|                 cmddefx = None
 | |
|                 break
 | |
|             if cmddefx is None:
 | |
|                 continue
 | |
|             cmddef = ''.join([i for i in cmddefx])
 | |
| 
 | |
|             graph = clippy.Graph(cmddef)
 | |
|             args = OrderedDict()
 | |
|             always_args = set()
 | |
|             for token, depth in clippy.graph_iterate(graph):
 | |
|                 if token.type not in handlers:
 | |
|                     continue
 | |
|                 if token.varname is None:
 | |
|                     continue
 | |
|                 arg = args.setdefault(token.varname, [])
 | |
|                 arg.append(handlers[token.type](token))
 | |
|                 always_args.add(token.varname)
 | |
| 
 | |
|             get_always_args(graph.first(), always_args)
 | |
| 
 | |
|             #print('-' * 76)
 | |
|             #pprint(entry)
 | |
|             #clippy.dump(graph)
 | |
|             #pprint(args)
 | |
| 
 | |
|             params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
 | |
|             argdefs = []
 | |
|             argdecls = []
 | |
|             arglist = []
 | |
|             argblocks = []
 | |
|             argassert = []
 | |
|             doc = []
 | |
|             canfail = 0
 | |
| 
 | |
|             def do_add(handler, basename, varname, attr = ''):
 | |
|                 argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
 | |
|                 argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
 | |
|                 arglist.append(', %s%s' % (handler.deref, varname))
 | |
|                 if basename in always_args and handler.canassert:
 | |
|                     argassert.append('''\tif (!%s) {
 | |
| \t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s");
 | |
| \t\treturn CMD_WARNING;
 | |
| \t}\n''' % (varname, varname))
 | |
|                 if attr == '':
 | |
|                     at = handler.argtype
 | |
|                     if not at.startswith('const '):
 | |
|                         at = '. . . ' + at
 | |
|                     doc.append('\t%-26s %s  %s' % (at, 'alw' if basename in always_args else 'opt', varname))
 | |
| 
 | |
|             for varname in args.keys():
 | |
|                 handler = mix_handlers(args[varname])
 | |
|                 #print(varname, handler)
 | |
|                 if handler is None: continue
 | |
|                 do_add(handler, varname, varname)
 | |
|                 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
 | |
|                 if handler.canfail:
 | |
|                     canfail = 1
 | |
|                 strblock = ''
 | |
|                 if not handler.drop_str:
 | |
|                     do_add(StringHandler(None), varname, '%s_str' % (varname), ' __attribute__ ((unused))')
 | |
|                     strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
 | |
|                 argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
 | |
| 
 | |
|             if dumpfd is not None:
 | |
|                 if len(arglist) > 0:
 | |
|                     dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
 | |
|                 else:
 | |
|                     dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
 | |
| 
 | |
|             params['argdefs'] = ''.join(argdefs)
 | |
|             params['argdecls'] = ''.join(argdecls)
 | |
|             params['arglist'] = ''.join(arglist)
 | |
|             params['argblocks'] = ''.join(argblocks)
 | |
|             params['canfail'] = canfail
 | |
|             params['nonempty'] = len(argblocks)
 | |
|             params['argassert'] = ''.join(argassert)
 | |
|             ofd.write(templ.substitute(params))
 | |
| 
 | |
|     return errors
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     import argparse
 | |
| 
 | |
|     argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
 | |
|     argp.add_argument('--all-defun', action = 'store_const', const = True,
 | |
|             help = 'process DEFUN() statements in addition to DEFPY()')
 | |
|     argp.add_argument('--show', action = 'store_const', const = True,
 | |
|             help = 'print out list of arguments and types for each definition')
 | |
|     argp.add_argument('-o', type = str, metavar = 'OUTFILE',
 | |
|             help = 'output C file name')
 | |
|     argp.add_argument('cfile', type = str)
 | |
|     args = argp.parse_args()
 | |
| 
 | |
|     dumpfd = None
 | |
|     if args.o is not None:
 | |
|         ofd = StringIO()
 | |
|         if args.show:
 | |
|             dumpfd = sys.stdout
 | |
|     else:
 | |
|         ofd = sys.stdout
 | |
|         if args.show:
 | |
|             dumpfd = sys.stderr
 | |
| 
 | |
|     basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 | |
| 
 | |
|     macros = Macros()
 | |
|     macros.load('lib/route_types.h')
 | |
|     macros.load(os.path.join(basepath, 'lib/command.h'))
 | |
|     # sigh :(
 | |
|     macros['PROTO_REDIST_STR'] = 'FRR_REDIST_STR_ISISD'
 | |
| 
 | |
|     errors = process_file(args.cfile, ofd, dumpfd, args.all_defun, macros)
 | |
|     if errors != 0:
 | |
|         sys.exit(1)
 | |
| 
 | |
|     if args.o is not None:
 | |
|         clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable])
 |