#!/usr/bin/env python # # Copyright (c) 2008-2010 Wind River Systems, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the Lesser GNU General Public License version 2.1 as # published by the Free Software Foundation. # # 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 Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # version 2.1 along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 'convert wrapfuncs.in to wrapper function stubs and tables' import glob import sys import re import os import string import datetime import time from string import Template class SourceFile(object): "A template for creating a source file" def __init__(self, path): # default values... # no name or file yet self.name = '' self.sections = {} self.file = None self.path = None # open a new file for each function self.file_per_func = False # empty footer if none specified: self.sections['footer'] = [] # lines appended to body by default self.sections['body'] = [] current = self.sections['body'] self.f = file(path, 'r') if not self.f: return None for line in self.f: line = line.rstrip() if line.startswith('@'): if ' ' in line: leading, trailing = line.split(' ', 1) else: leading, trailing = line, None if leading == '@name': if not trailing: raise Exception("@name requires a file name.") self.path = trailing if '$' in self.path: self.file_per_func = True else: section = leading[1:] if not section in self.sections: self.sections[section] = [] current = self.sections[section] else: current.append(line) for section, data in self.sections.items(): self.sections[section] = Template("\n".join(data)) # You need a file if this isn't a file-per-func if not self.file_per_func: self.file = file(self.path, 'w') if not self.file: raise IOError("Couldn't open %s to read a template." % self.path) def __del__(self): if self.file: self.file.close() def __repr__(self): strings = [] if self.file_per_func: strings.append("path: %s (per func)" % self.path) else: strings.append("path: %s" % self.path) for name, data in self.sections.items(): strings.append("%s:" % name) strings.append(data.safe_substitute({})) return "\n".join(strings) def emit(self, template, func = None): if self.file_per_func: if not func: # print "Must have a function to emit any part of a file-per-func template." return path = Template(self.path).safe_substitute(func) if os.path.exists(path): # print "We don't overwrite existing files." return self.file = file(path, 'w') if not self.file: print "Couldn't open '%s' (expanded from %s), not emitting '%s'." % (path, self.path, template) return if template == "copyright": # hey, at least it's not a global variable, amirite? self.file.write(SourceFile.copyright) elif template in self.sections: templ = self.sections[template] self.file.write(templ.safe_substitute(func)) self.file.write("\n") else: print "Warning: Unknown template '%s'." % template if self.file_per_func: self.file.close() self.file = None class ArgumentList: "A (possibly empty) list of arguments" def __init__(self, text): "parse a comma-separated argument list (may contain function prototypes)" self.args = [] self.variadic = False self.variadic_decl = "" self.variadic_start = "" self.variadic_end = "" self.prologue_call_real = "/* pass the call on to the underlying syscall */" # (void) is an empty list, not a list of a single argument which is void if text == "void": return depth = 0 accum = '' comma_sep = text.split(', ') # now, what if there was a comma embedded in an argument? for arg in comma_sep: lcount = arg.count('(') rcount = arg.count(')') depth = depth + lcount - rcount if (depth > 0): accum += arg + ', ' else: self.args.append(C_Argument(accum + arg)) accum = '' if depth != 0: raise Exception("mismatched ()s while parsing '%s'" % text) if self.args[-1].vararg: self.variadic = True self.variadic_arg = self.args[-1] self.last_fixed_arg = self.args[-2].name self.variadic_decl = "va_list ap;\n" self.variadic_start = "va_start(ap, %s);\n" % self.last_fixed_arg if self.variadic_arg.vararg_wraps: self.variadic_decl += "\t%s;\n" % self.variadic_arg.vararg_wraps.decl() self.variadic_start += "\t%s = va_arg(ap, %s);\n\tva_end(ap);\n" % (self.variadic_arg.name, self.variadic_arg.type) else: self.variadic_end = "va_end(ap);\n" self.prologue_call_real = '/* no way to pass a ... */\n\t\t\t\tassert(!"cannot chain to real versions of variadic functions");' def decl(self, **kw): if not self.args: return "void" list = map(lambda x: x.decl(**kw), self.args) return ', '.join(list) def call(self): if not self.args: return "" list = map(lambda x: x.call(), self.args) return ', '.join(list) def __repr__(self): if not self.args: return "no arguments" else: return '::'.join(map(lambda x:x.decl(), self.args)) class C_Argument: "A function argument such as 'char *path' or 'char (*foo)(void)'" def __init__(self, text): "get the type and name of a trivial C declaration" self.vararg = False self.function_pointer = False self.spacer = '' if text == 'void': raise Exception("Tried to declare a nameless object of type void.") if text.startswith('...'): self.vararg = True if len(text) > 3: # we're a wrapper for something else, declared as # ...{real_decl}, as in the third argument to open(2) text = text[4:-1] # stash a copy of these values without the vararg flag, so we can # declare them prettily later self.vararg_wraps = C_Argument(text) else: # nothing to do. self.vararg_wraps = None self.type, self.name = None, None return else: self.vararg = False # try for a function pointer match = re.match('(.*)\(\*([a-zA-Z0-9_]*)\)\((.*)\)', text) if match: self.function_pointer = True self.args = match.group(3) ret_type = match.group(1) args = match.group(3) self.fulltype = "$ret_type(*)($args)" self.name = match.group(2).rstrip(' ') self.type = ret_type else: # plain declaration match = re.match('(.*[ *])\(?\*?([a-zA-Z0-9_]*)\)?', text) # there may not be a match, say in the special case where an arg is '...' if match: self.type, self.name = match.group(1).rstrip(' '), match.group(2) else: self.type, self.name = None, None # spacing between type and name, needed if type ends with a character # which could be part of an identifier if re.match('[_a-zA-Z0-9]', self.type[-1]): self.spacer = ' ' def decl(self, **kw): comment = kw.get('comment', False) if self.function_pointer: decl = "%s%s(*%s)(%s)" % (self.type, self.spacer, self.name, self.args) else: decl = "%s%s%s" % (self.type, self.spacer, self.name) if self.vararg: if self.vararg_wraps: if comment: decl = "... { %s }" % decl else: decl = "... /* %s */" % decl else: decl = "..." return decl def call(self): if self.type == 'void': return '' if self.vararg and not self.vararg_wraps: return "ap" return self.name def str(self): return self.decl() def __repr__(self): return self.decl() class C_Function: "A function signature and additional data about how the function works" def __init__(self, line): # table of known default values: default_values = { 'gid_t': '0', 'uid_t': '0', 'int': '-1', 'long': '-1', 'ssize_t': '-1' } self.dirfd = 'AT_FDCWD' self.flags = '0' self.paths_to_munge = [] # used for the copyright date when creating stub functions self.date = datetime.date.today().year function, comments = line.split(';') comment = re.search('/\* *(.*) *\*/', comments) if comment: self.comments = comment.group(1) else: self.comments = None bits = re.match('([^(]*)\((.*)\)', function) x = C_Argument(bits.group(1)) self.type, self.name = x.type, x.name # it's convenient to have this declared here so we can use its .decl later if self.type != 'void': self.rc = C_Argument("%s rc" % x.type) # Some args get special treatment: # * If the arg has a name ending in 'path', we will canonicalize it. # * If the arg is named 'dirfd' or 'flags', it becomes the default # values for the dirfd and flags arguments when canonicalizing. # * Note that the "comments" field (/* ... */ after the decl) can # override the dirfd/flags values. self.args = ArgumentList(bits.group(2)) for arg in self.args.args: # ignore varargs, they never get these special treatments if arg.vararg: pass elif arg.name == 'dirfd': self.dirfd = 'dirfd' elif arg.name == 'flags': self.flags = 'flags' elif arg.name[-4:] == 'path': self.paths_to_munge.append(arg.name) # pick default values if self.type == 'void': self.default_value = '' elif self.type[-1:] == '*': self.default_value = 'NULL' else: try: self.default_value = default_values[self.type] except KeyError: raise Exception("Function %s has return type %s, for which there is no default value." % (self.name, self.type)) # handle special comments, such as flags=AT_SYMLINK_NOFOLLOW if self.comments: modifiers = self.comments.split(', ') for mod in modifiers: key, value = mod.split('=') value = value.rstrip(' ') setattr(self, key, value) def comment(self): return self.decl(comment = True) def decl(self, **kw): if self.type[-1:] == '*': spacer = '' else: spacer = ' ' return "%s%s%s(%s)" % (self.type, spacer, self.name, self.args.decl(**kw)) def decl_args(self): return self.args.decl() def wrap_args(self): return self.args.decl(wrap = True) def call_args(self): return self.args.call() def alloc_paths(self): alloc_paths = [] for p in self.paths_to_munge: alloc_paths.append("%s = pseudo_root_path(__func__, __LINE__, %s, %s, %s);" % (p, self.dirfd, p, self.flags)) return "\n\t\t\t".join(alloc_paths); def free_paths(self): free_paths = [] # the cast is here because free's argument isn't const qualified, but # the original path may have been -- but we only GET here if the path has # been overwritten. for p in self.paths_to_munge: free_paths.append("free((void *) %s);" % p) return "\n\t\t\t".join(free_paths); def rc_return(self): if self.type == 'void': return "return;" else: return "return rc;" def rc_decl(self): if self.type == 'void': return "" else: return "%s = %s;" % (self.rc.decl(), self.default_value) def rc_assign(self): if self.type == 'void': return "(void)" else: return "rc =" def def_return(self): if self.type == 'void': return "return;" else: return "return %s;" % self.default_value def __getitem__(self, key): "Make this object look like a dict for Templates to use" try: attr = getattr(self, key) except AttributeError: # There's a few attributes that are handled inside the args object, so check there # too... attr = getattr(self.args, key) if callable(attr): return attr() else: return attr def __repr__(self): pretty = "%(name)s returns %(type)s and takes " % self pretty += repr(self.args) if self.comments: pretty += ' (%s)' % self.comments return pretty files = {} def main(): funcs = [] sources = [] # error checking helpfully provided by the exception handler copyright_file = file('guts/COPYRIGHT', 'r') SourceFile.copyright = copyright_file.read() copyright_file.close() for path in glob.glob('templates/*'): try: source = SourceFile(path) source.emit('copyright') source.emit('header') sources.append(source) except IOError: print "Invalid or malformed template %s. Aborting." % path exit(1) for filename in sys.argv[1:]: "parse the list of functions" sys.stdout.write("%s: " % filename) f = file(filename, 'r') for line in f: line = line.rstrip(" \r\n") if line.startswith('#') or line == "": continue try: func = C_Function(line) funcs.append(func) sys.stdout.write(".") except Exception as e: print "Parsing failed:", e exit(1) f.close() print "" # the per-function stuff print "Writing functions...", for func in funcs: "populate various tables and files with each function" for source in sources: source.emit('body', func) print "done. Cleaning up." for source in sources: "clean up files" source.emit('footer') del source if __name__ == '__main__': main()