git » libfiu » next » tree

[next] / preload / posix / generate

#!/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.
        # Also only apply it if we can define the 64-bit functions on the same
        # file (detected at build-time).
		return [
                Verbatim("#ifdef __GLIBC__"),
                Verbatim("#if LIBFIU_CAN_DEFINE_64BIT_FUNCTIONS"),
                f,
                Verbatim("#endif  // LIBFIU_CAN_DEFINE_64BIT_FUNCTIONS"),
                Verbatim("#endif  // __GLIBC__"),
                ]

	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()