#!/usr/bin/env python3
"""
Reads function information and generates code for the preloader library.
The code is NOT nice. It just does the trick.
"""
import sys
import re
import copy
# Function definition regular expression
func_def_re = re.compile(
r'(?P<ret_type>(?:[\w\*]+\s+\**)+)+(?P<name>\w+).*\((?P<params>.*)\).*;')
# Regular expression to extract the types and names of the parameters from a
# string containing the definition parameters (e.g. from
# "int a, const char *b" extracts [('int ', 'a'), ('const char *', 'b')]
params_info_re = \
re.compile(r"(?:(?P<type>(?:[\w\*]+\s+\**)+)+(?P<name>\w+),?\s*)+?")
class Context:
"""Represents the current context information within a module
definition file."""
def __init__(self):
self.fiu_name_base = 'UNKNOWN'
class Function:
"Represents a function to be wrapped"
def __init__(self, definition, ctx):
"Constructor, takes the C definition as a string"
self.definition = definition
self.load_from_definition(definition)
# fiu name, constructed by default from the context but can be
# overriden by info
self.fiu_name = ctx.fiu_name_base + '/' + self.name
# what to return on error, by default set to None, which means
# "take it from failinfo"
self.on_error = None
# whether to set errno or not, and the list of valid errnos;
# in any case if failinfo is set we take the errno value from
# there
self.use_errno = False
self.valid_errnos = []
# the FILE * for which a future ferror() shall fail too.
self.ferror = None
# if the given parameter should be reduced by a random amount
self.reduce = None
# describes possible variations of function, for example
# pread() and pread64().
self.variants = []
def load_from_definition(self, definition):
m = func_def_re.match(definition)
self.name = m.group("name")
self.ret_type = m.group("ret_type")
self.params = m.group("params")
self.params_info = params_info_re.findall(self.params)
def load_info(self, info):
"Loads additional information from the given string"
if ':' in info:
s = info.split(':', 1)
k, v = s[0].strip(), s[1].strip()
if k == 'fiu name':
self.fiu_name = v
elif k == 'on error':
self.on_error = v
elif k == 'valid errnos':
self.use_errno = True
self.valid_errnos = v.split()
elif k == 'ferror':
self.ferror = v
elif k == 'reduce':
self.reduce = v
elif k == 'variants':
self.variants = v.split();
else:
raise SyntaxError("Unknown information: " + k)
def __repr__(self):
s = '<F %(rt)s %(n)s ( %(p)s ) -- %(fn)s %(oe)s %(ve)s>' % \
{
'rt': self.ret_type,
'n': self.name,
'p': self.params,
'fn': self.fiu_name,
'oe': self.on_error,
've': str(self.valid_errnos),
}
return s
def generate_to(self, f):
"""Generates code to the given file. Strongly related to
codegen.h."""
f.write('/* Wrapper for %s() */\n' % self.name)
# extract params names and types
paramst = ', '.join([i[0] for i in self.params_info])
paramsn = ', '.join([i[1] for i in self.params_info])
f.write('mkwrap_top(%s, %s, (%s), (%s), (%s), (%s))\n' % \
(self.ret_type, self.name, self.params,
paramsn, paramst, self.on_error) )
if self.reduce:
f.write('mkwrap_body_reduce("%s/reduce", %s)\n' % \
(self.fiu_name, self.reduce) )
if self.use_errno:
if self.on_error is None:
desc = "%s uses errno but has no on_error" % \
self.name
raise RuntimeError(desc)
# We can't put this as a macro parameter, so it has to
# be explicit
self.write_valid_errnos(f)
if self.ferror is not None:
f.write('mkwrap_body_errno_ferror("%s", %s, %s)\n' % \
(self.fiu_name, self.on_error, self.ferror) )
else:
f.write('mkwrap_body_errno("%s", %s)\n' % \
(self.fiu_name, self.on_error) )
elif self.on_error is not None:
f.write('mkwrap_body_hardcoded("%s", %s)\n' % \
(self.fiu_name, self.on_error) )
else:
f.write('mkwrap_body_failinfo("%s", %s)\n' % \
(self.fiu_name, self.ret_type) )
f.write('mkwrap_bottom(%s, (%s))\n' % (self.name, paramsn))
f.write('\n\n')
def write_valid_errnos(self, f):
"Generates the code for the static list of valid errnos."
f.write("\tstatic const int valid_errnos[] = {\n")
for e in self.valid_errnos:
f.write("\t #ifdef %s\n" % e)
f.write("\t\t%s,\n" % e)
f.write("\t #endif\n")
f.write("\t};\n");
def fiu_names(self):
n = [self.fiu_name]
if self.reduce:
n.append(self.fiu_name + '/reduce')
return n
def apply_variant(self, v):
if v != 'off64_t':
raise SyntaxError("Unknown function variant: " + v)
f = copy.copy(self)
# NOTE: We don't modify fiu_name here to be able to enable
# both <func> and <func>64 versions of the function by
# enabling just <func>.
f.name = f.name + "64"
f.params = f.params.replace("off_t", "off64_t")
f.params = f.params.replace("fpos_t *", "fpos64_t *")
f.params = f.params.replace("const fpos_t *", "const fpos64_t *")
f.params_info = [
(x, y) if x != "off_t " else ("off64_t ", y)
for (x, y) in f.params_info]
f.params_info = [
(x, y) if x != "fpos_t *" else ("fpos64_t *", y)
for (x, y) in f.params_info]
f.params_info = [
(x, y) if x != "const fpos_t *" else ("const fpos64_t *", y)
for (x, y) in f.params_info]
f.ret_type = f.ret_type.replace("off_t", "off64_t")
# This is glibc-specific, so surround it with #ifdefs.
return [Verbatim("#ifdef __GLIBC__"), f, Verbatim("#endif")]
def get_all_variants(self):
"""Returns all variants of the given function provided via
'variants:' function directive"""
variants = [self]
for v in self.variants:
variants.extend(self.apply_variant(v))
return variants
class Include:
"Represents an include directive"
def __init__(self, path):
self.path = path
def __repr__(self):
return '<I %s>' % self.path
def generate_to(self, f):
f.write("#include %s\n" % self.path)
class Verbatim:
"Represent a verbatim directive"
def __init__(self, line):
self.line = line
def __repr__(self):
return '<V %s>' % self.line
def generate_to(self, f):
f.write(self.line + '\n')
class EmptyLine:
"Represents an empty line"
def __repr__(self):
return '<E>'
def generate_to(self, f):
f.write('\n')
class Comment:
"Represents a full-line comment"
def __init__(self, line):
self.body = line.strip()[1:].strip()
def __repr__(self):
return '<C %s>' % self.body
def generate_to(self, f):
f.write("// %s \n" % self.body)
def parse_module(path):
"Parses a module definition"
f = open(path)
directives = []
ctx = Context()
current_func = None
while True:
l = f.readline()
# handle EOF
if not l:
break
# handle \ at the end of the line
while l.endswith("\\\n"):
nl = f.readline()
l = l[:-2] + nl
if not l.strip():
directives.append(EmptyLine())
continue
if l.strip().startswith("#"):
directives.append(Comment(l))
continue
if not l.startswith(" ") and not l.startswith("\t"):
# either a new function or a directive, but in either
# case the current function is done
if current_func:
directives.extend(current_func.get_all_variants())
current_func = None
l = l.strip()
if ':' in l:
# directive
s = l.split(':', 1)
k, v = s[0].strip(), s[1].strip()
if k == 'fiu name base':
v = v.strip().strip('/')
ctx.fiu_name_base = v
elif k == 'include':
directives.append(Include(v))
elif k == 'v':
directives.append(Verbatim(v))
else:
raise SyntaxError("Unknown directive", l)
else:
current_func = Function(l, ctx)
else:
# function information
current_func.load_info(l.strip())
if current_func:
directives.extend(current_func.get_all_variants())
return directives
#
# Code generation
#
# Templates
gen_header = """
/*
* AUTOGENERATED FILE - DO NOT EDIT
*
* This file was automatically generated by libfiu, do not edit it directly,
* but see libfiu's "preload" directory.
*/
#include "codegen.h"
"""
def generate_code(directives, path):
"""Generates code to the file in the given path"""
f = open(path, 'w')
f.write(gen_header)
for directive in directives:
directive.generate_to(f)
def write_function_list(directives, path):
"Writes the function list to the given path"
f = open(path, 'a')
for d in directives:
if isinstance(d, Function):
f.write("%-32s%s\n" % (d.name, \
', '.join(d.fiu_names())) )
def usage():
print("Use: ./generate input.mod output.c file_list.fl")
def main():
if len(sys.argv) < 4:
usage()
sys.exit(1)
input_name = sys.argv[1]
output_name = sys.argv[2]
filelist_name = sys.argv[3]
directives = parse_module(input_name)
#import pprint
#pprint.pprint(directives)
generate_code(directives, output_name)
write_function_list(directives, filelist_name)
if __name__ == '__main__':
main()