#!/usr/bin/env python # # Authors: # Stef Walter # # Copyright (C) 2014 Red Hat # # 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 3 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. If not, see . # # # Some parser code from GLib # # Copyright (C) 2008-2011 Red Hat, Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General # Public License along with this library; if not, write to the # Free Software Foundation, Inc., 59 Temple Place, Suite 330, # Boston, MA 02111-1307, USA. # # Portions by: David Zeuthen # # # DBus interfaces are defined here: # # http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format # # The introspection data format has become the standard way to represent a # DBus interface. For many examples see /usr/share/dbus-1/interfaces/ on a # typical linux machine. # # A word about annotations. These are extra flags or values that can be # assigned to anything. So far, the codegen supports this annotation: # # org.freedesktop.DBus.GLib.CSymbol # - An annotation specified in the specification that tells us what C symbol # to generate for a given interface or method. By default the codegen will # build up a symbol name from the DBus name. # import optparse import os import re import StringIO import sys import xml.parsers.expat # ----------------------------------------------------------------------------- # Objects class DBusXmlException(Exception): line = 0 file = None # Lets us print problems like a compiler would def __str__(self): message = Exception.__str__(self) if self.file and self.line: return "%s:%d: %s" % (self.file, self.line, message) elif self.file: return "%s: %s" % (self.file, message) else: return message class Base: def __init__(self, name): if not name: raise DBusXmlException('No name on element') self.name = name self.annotations = { } def validate(self): pass def c_name(self): return self.annotations.get("org.freedesktop.DBus.GLib.CSymbol", self.name) # The basic types that we support marshalling right now. These # are the ones we can pass as basic arguments to libdbus directly. # If the dbus and sssd types are identical we pass things directly. # otherwise some copying is necessary. BASIC_TYPES = { 'y': ( "DBUS_TYPE_BYTE", "uint8_t", "uint8_t" ), 'b': ( "DBUS_TYPE_BOOLEAN", "dbus_bool_t", "bool" ), 'n': ( "DBUS_TYPE_INT16", "int16_t", "int16_t" ), 'q': ( "DBUS_TYPE_UINT16", "uint16_t", "uint16_t" ), 'i': ( "DBUS_TYPE_INT32", "int32_t", "int32_t" ), 'u': ( "DBUS_TYPE_UINT32", "uint32_t", "uint32_t" ), 'x': ( "DBUS_TYPE_INT64", "int64_t", "int64_t" ), 't': ( "DBUS_TYPE_UINT64", "uint64_t", "uint64_t" ), 'd': ( "DBUS_TYPE_DOUBLE", "double", "double" ), 's': ( "DBUS_TYPE_STRING", "const char *", "const char *" ), 'o': ( "DBUS_TYPE_OBJECT_PATH", "const char *", "const char *" ), } class Typed(Base): def __init__(self, name, type): Base.__init__(self, name) self.type = type self.is_basic = False self.is_array = False self.dbus_constant = None self.dbus_type = None self.sssd_type = None if type[0] == 'a': type = type[1:] self.is_array = True if type in BASIC_TYPES: (self.dbus_constant, self.dbus_type, self.sssd_type) = BASIC_TYPES[type] # If types are not identical, we can't do array (yet) if self.is_array: self.is_basic = (self.dbus_type == self.sssd_type) else: self.is_basic = True class Arg(Typed): def __init__(self, method, name, type): Typed.__init__(self, name, type) self.method = method class Method(Base): def __init__(self, iface, name): Base.__init__(self, name) self.iface = iface self.in_args = [] self.out_args = [] def validate(self): if not self.only_basic_args() and not self.use_raw_handler(): raise DBusXmlException("Method has complex arguments and requires " + "the 'org.freedesktop.sssd.RawHandler' annotation") def fq_c_name(self): return "%s_%s" % (self.iface.c_name(), self.c_name()) def use_raw_handler(self): anno = 'org.freedesktop.sssd.RawHandler' return self.annotations.get(anno, self.iface.annotations.get(anno)) == 'true' def in_signature(self): return "".join([arg.type for arg in self.in_args]) def only_basic_args(self): for arg in self.in_args + self.out_args: if not arg.is_basic: return False return True class Signal(Base): def __init__(self, iface, name): Base.__init__(self, name) self.iface = iface self.args = [] def fq_c_name(self): return "%s_%s" % (self.iface.c_name(), self.c_name()) class Property(Typed): def __init__(self, iface, name, type, access): Typed.__init__(self, name, type) self.iface = iface self.readable = False self.writable = False if access == 'readwrite': self.readable = True self.writable = True elif access == 'read': self.readable = True elif access == 'write': self.writable = True else: raise DBusXmlException('Invalid access type %s'%self.access) def fq_c_name(self): return "%s_%s" % (self.iface.c_name(), self.c_name()) def getter_name(self): return "get_%s" % self.c_name() def getter_invoker_name(self): return "sbus_invoke_get_%s" % self.type def getter_signature(self, name): sig = "void (*%s)(struct sbus_request *, void *data, %s *" % (name, self.sssd_type) if self.is_array: sig += " *, int *" sig += ")" return sig class Interface(Base): def __init__(self, name): Base.__init__(self, name) self.methods = [] self.signals = [] self.properties = [] def c_name(self): return self.annotations.get("org.freedesktop.DBus.GLib.CSymbol", self.name.replace(".", "_")) # ----------------------------------------------------------------------------- # Code Generation def out(format, *args, **kwargs): str = format % args sys.stdout.write(str) # NOTE: Would like to use the following syntax for this function # but need to wait until python3 until it is supported: # def out(format, *args, new_line=True) if kwargs.pop("new_line", True): sys.stdout.write("\n") assert not kwargs, "unknown keyword argument(s): %s" % str(kwargs) def method_arg_types(args, with_names=False): str = "" for arg in args: str += ", " str += arg.sssd_type if with_names: if str[-1] != '*': str += " " str += "arg_" str += arg.c_name() if arg.is_array: str += "[], int" if with_names: str += " len_" str += arg.c_name() return str def method_function_pointer(meth, name, with_names=False): if meth.use_raw_handler(): return "sbus_msg_handler_fn " + name else: return "int (*%s)(struct sbus_request *%s, void *%s%s)" % \ (name, with_names and "req" or "", with_names and "data" or "", method_arg_types(meth.in_args, with_names)) def property_handlers(prop): return prop.getter_signature(prop.getter_name()) def forward_method_invoker(signature, args): out("") out("/* invokes a handler with a '%s' DBus signature */", signature) out("static int invoke_%s_method(struct sbus_request *dbus_req, void *function_ptr);", signature) def source_method_invoker(signature, args): out("") out("/* invokes a handler with a '%s' DBus signature */", signature) out("static int invoke_%s_method(struct sbus_request *dbus_req, void *function_ptr)", signature) out("{") for i in range(0, len(args)): arg = args[i] if arg.is_array: out(" %s *arg_%d;", arg.dbus_type, i) out(" int len_%d;", i) else: out(" %s arg_%d;", arg.dbus_type, i) out(" int (*handler)(struct sbus_request *, void *%s) = function_ptr;", method_arg_types(args)) out("") out(" if (!sbus_request_parse_or_finish(dbus_req,") for i in range(0, len(args)): arg = args[i] if arg.is_array: out(" DBUS_TYPE_ARRAY, %s, &arg_%d, &len_%d,", arg.dbus_constant, i, i) else: out(" %s, &arg_%d,", arg.dbus_constant, i) out(" DBUS_TYPE_INVALID)) {") out(" return EOK; /* request handled */") out(" }") out("") out(" return (handler)(dbus_req, dbus_req->intf->handler_data", new_line=False) for i in range(0, len(args)): arg = args[i] out(",\n arg_%d", i, new_line=False) if arg.is_array: out(",\n len_%d", i, new_line=False) out(");") out("}") def source_prop_types(prop, type_prefix=False): prefix = "%s_" % prop.type if type_prefix else "" if prop.is_array: out(" %s *%sprop_val;", prop.sssd_type, prefix) out(" int %sprop_len;", prefix) out(" %s *%sout_val;", prop.dbus_type, prefix) else: out(" %s %sprop_val;", prop.sssd_type, prefix) out(" %s %sout_val;", prop.dbus_type, prefix) def source_prop_handler(prop, type_prefix=False): prefix = "%s_" % prop.type if type_prefix else "" out(" %s", prop.getter_signature("%shandler" % prefix), new_line=False) out(";") def forward_method_invokers(ifaces): invokers = { } for iface in ifaces: for meth in iface.methods: if meth.use_raw_handler() or not meth.in_args: continue signature = meth.in_signature() if signature in invokers: continue forward_method_invoker(signature, meth.in_args) invokers[signature] = meth return invokers def source_method_invokers(invokers): for (signature, meth) in invokers.items(): source_method_invoker(signature, meth.in_args) def source_finisher(meth): out("") out("int %s_finish(struct sbus_request *req%s)", meth.fq_c_name(), method_arg_types(meth.out_args, with_names=True)) out("{") for arg in meth.out_args: if arg.dbus_type != arg.sssd_type: out(" %s cast_%s = arg_%s;", arg.dbus_type, arg.c_name(), arg.c_name()) out(" return sbus_request_return_and_finish(req,") for arg in meth.out_args: out(" ", new_line=False) if arg.is_array: out("DBUS_TYPE_ARRAY, %s, &arg_%s, len_%s,", arg.dbus_constant, arg.c_name(), arg.c_name()) elif arg.dbus_type != arg.sssd_type: out("%s, &cast_%s,", arg.dbus_constant, arg.c_name()) else: out("%s, &arg_%s,", arg.dbus_constant, arg.c_name()) out(" DBUS_TYPE_INVALID);") out("}") def header_reply(meth): for arg in meth.out_args: if arg.is_array: out(" %s *%s", arg.dbus_type, arg.c_name()) out(" int %s__len", arg.c_name()) else: out(" %s %s;", arg.dbus_type, arg.c_name()) types = [arg.sssd_type for arg in meth.in_args] def source_args(parent, args, suffix): out("") out("/* arguments for %s.%s */", parent.iface.name, parent.name) out("const struct sbus_arg_meta %s%s[] = {", parent.fq_c_name(), suffix) for arg in args: out(" { \"%s\", \"%s\" },", arg.name, arg.type) out(" { NULL, }") out("};") def source_methods(iface, methods): for meth in methods: if meth.in_args: source_args(meth, meth.in_args, "__in") if meth.out_args: source_args(meth, meth.out_args, "__out") if not meth.use_raw_handler(): source_finisher(meth) out("") out("/* methods for %s */", iface.name) out("const struct sbus_method_meta %s__methods[] = {", iface.c_name()) for meth in methods: out(" {") out(" \"%s\", /* name */", meth.name) if meth.in_args: out(" %s__in,", meth.fq_c_name()) else: out(" NULL, /* no in_args */") if meth.out_args: out(" %s__out,", meth.fq_c_name()) else: out(" NULL, /* no out_args */") out(" offsetof(struct %s, %s),", iface.c_name(), meth.c_name()) if meth.use_raw_handler() or not meth.in_args: out(" NULL, /* no invoker */") else: out(" invoke_%s_method,", meth.in_signature()) out(" },") out(" { NULL, }") out("};") def source_signals(iface, signals): for sig in iface.signals: if sig.args: source_args(sig, sig.args, "__args") out("") out("/* signals for %s */", iface.name) out("const struct sbus_signal_meta %s__signals[] = {", iface.c_name()) for sig in signals: out(" {") out(" \"%s\", /* name */", sig.name) if sig.args: out(" %s__args", sig.fq_c_name()) else: out(" NULL, /* no args */") out(" },") out(" { NULL, }") out("};") def source_properties(iface, properties): out("") out("/* property info for %s */", iface.name) out("const struct sbus_property_meta %s__properties[] = {", iface.c_name()) for prop in properties: out(" {") out(" \"%s\", /* name */", prop.name) out(" \"%s\", /* type */", prop.type) if prop.readable and prop.writable: out(" SBUS_PROPERTY_READABLE | SBUS_PROPERTY_WRITABLE,") elif prop.readable: out(" SBUS_PROPERTY_READABLE,") elif prop.writable: out(" SBUS_PROPERTY_WRITABLE,") else: assert False, "should not be reached" if prop.readable: out(" offsetof(struct %s, %s),", iface.c_name(), prop.getter_name()) out(" %s,", prop.getter_invoker_name()) else: out(" 0, /* not readable */") out(" NULL, /* no invoker */") out(" 0, /* not writable */") out(" NULL, /* no invoker */") out(" },") out(" { NULL, }") out("};") def header_interface(iface): out("") out("/* interface info for %s */", iface.name) out("extern const struct sbus_interface_meta %s_meta;", iface.c_name()) def source_interface(iface): out("") out("/* interface info for %s */", iface.name) out("const struct sbus_interface_meta %s_meta = {", iface.c_name()) out(" \"%s\", /* name */", iface.name) if iface.methods: out(" %s__methods,", iface.c_name()) else: out(" NULL, /* no methods */") if iface.signals: out(" %s__signals,", iface.c_name()) else: out(" NULL, /* no signals */") if iface.properties: out(" %s__properties,", iface.c_name()) else: out(" NULL, /* no properties */") out(" sbus_invoke_get_all, /* GetAll invoker */") out("};") def generate_source(ifaces, filename, include_header=None): basename = os.path.basename(filename) out("/* The following definitions are auto-generated from %s */", basename) out("") out("#include \"util/util.h\"") out("#include \"sbus/sssd_dbus.h\"") out("#include \"sbus/sssd_dbus_meta.h\"") out("#include \"sbus/sssd_dbus_invokers.h\"") if include_header: out("#include \"%s\"", os.path.basename(include_header)) meth_invokers = forward_method_invokers(ifaces) for iface in ifaces: # The methods if iface.methods: source_methods(iface, iface.methods) # The signals array if iface.signals: source_signals(iface, iface.signals) # The properties array if iface.properties: source_properties(iface, iface.properties) # The sbus_interface structure source_interface(iface) source_method_invokers(meth_invokers) def header_finisher(iface, meth): if meth.use_raw_handler(): return out("") out("/* finish function for %s */", meth.name) out("int %s_finish(struct sbus_request *req%s);", meth.fq_c_name(), method_arg_types(meth.out_args, with_names=True)) def header_vtable(iface, methods): out("") out("/* vtable for %s */", iface.name) out("struct %s {", iface.c_name()) out(" struct sbus_vtable vtable; /* derive from sbus_vtable */") # All methods for meth in iface.methods: out(" %s;", method_function_pointer(meth, meth.c_name(), with_names=True)) for prop in iface.properties: out(" %s;", property_handlers(prop)) out("};") def header_constants(iface): out("") out("/* constants for %s */", iface.name) out("#define %s \"%s\"", iface.c_name().upper(), iface.name) for meth in iface.methods: out("#define %s \"%s\"", meth.fq_c_name().upper(), meth.name) for sig in iface.signals: out("#define %s \"%s\"", sig.fq_c_name().upper(), sig.name) for prop in iface.properties: out("#define %s \"%s\"", prop.fq_c_name().upper(), prop.name) def generate_header(ifaces, filename): basename = os.path.basename(filename) guard = "__%s__" % re.sub(r'([^_A-Z0-9])', "_", basename.upper()) out("/* The following declarations are auto-generated from %s */", basename) out("") out("#ifndef %s", guard) out("#define %s", guard) out("") out("#include \"sbus/sssd_dbus.h\"") out("") out("/* ------------------------------------------------------------------------") out(" * DBus Constants") out(" *") out(" * Various constants of interface and method names mostly for use by clients") out(" */") for iface in ifaces: header_constants(iface) out("") out("/* ------------------------------------------------------------------------") out(" * DBus handlers") out(" *") out(" * These structures are filled in by implementors of the different") out(" * dbus interfaces to handle method calls.") out(" *") out(" * Handler functions of type sbus_msg_handler_fn accept raw messages,") out(" * other handlers are typed appropriately. If a handler that is") out(" * set to NULL is invoked it will result in a") out(" * org.freedesktop.DBus.Error.NotSupported error for the caller.") out(" *") out(" * Handlers have a matching xxx_finish() function (unless the method has") out(" * accepts raw messages). These finish functions the") out(" * sbus_request_return_and_finish() with the appropriate arguments to") out(" * construct a valid reply. Once a finish function has been called, the") out(" * @dbus_req it was called with is freed and no longer valid.") out(" */") for iface in ifaces: if iface.methods or iface.properties: header_vtable(iface, iface.methods) for meth in iface.methods: header_finisher(iface, meth) out("") out("/* ------------------------------------------------------------------------") out(" * DBus Interface Metadata") out(" *") out(" * These structure definitions are filled in with the information about") out(" * the interfaces, methods, properties and so on.") out(" *") out(" * The actual definitions are found in the accompanying C file next") out(" * to this header.") out(" */") for iface in ifaces: header_interface(iface) out("") out("#endif /* %s */", guard) # ----------------------------------------------------------------------------- # XML Interface Parsing STATE_TOP = 'top' STATE_NODE = 'node' STATE_INTERFACE = 'interface' STATE_METHOD = 'method' STATE_SIGNAL = 'signal' STATE_PROPERTY = 'property' STATE_ARG = 'arg' STATE_ANNOTATION = 'annotation' STATE_IGNORED = 'ignored' def expect_attr(attrs, name): if name not in attrs: raise DBusXmlException("Missing attribute '%s'" % name) if attrs[name] == "": raise DBusXmlException("Empty attribute '%s'" % name) return attrs[name] class DBusXMLParser: def __init__(self, filename): parser = xml.parsers.expat.ParserCreate() parser.CommentHandler = self.handle_comment parser.CharacterDataHandler = self.handle_char_data parser.StartElementHandler = self.handle_start_element parser.EndElementHandler = self.handle_end_element self.parsed_interfaces = [] self.cur_object = None self.state = STATE_TOP self.state_stack = [] self.cur_object = None self.cur_object_stack = [] self.arg_count = 0 try: with open(filename, "r") as f: parser.ParseFile(f) except DBusXmlException, ex: ex.line = parser.CurrentLineNumber ex.file = filename raise except xml.parsers.expat.ExpatError, ex: exc = DBusXmlException(str(ex)) exc.line = ex.lineno exc.file = filename raise exc def handle_comment(self, data): pass def handle_char_data(self, data): pass def handle_start_element(self, name, attrs): old_state = self.state old_cur_object = self.cur_object if self.state == STATE_IGNORED: self.state = STATE_IGNORED elif self.cur_object and name == STATE_ANNOTATION: val = attrs.get('value', '') self.cur_object.annotations[expect_attr(attrs, 'name')] = val self.state = STATE_IGNORED elif self.state == STATE_TOP: if name == STATE_NODE: self.state = STATE_NODE else: self.state = STATE_IGNORED elif self.state == STATE_NODE: if name == STATE_INTERFACE: self.state = STATE_INTERFACE iface = Interface(expect_attr(attrs, 'name')) self.cur_object = iface self.parsed_interfaces.append(iface) else: self.state = STATE_IGNORED elif self.state == STATE_INTERFACE: if name == STATE_METHOD: self.state = STATE_METHOD method = Method(self.cur_object, expect_attr(attrs, 'name')) self.cur_object.methods.append(method) self.cur_object = method self.arg_count = 0 elif name == STATE_SIGNAL: self.state = STATE_SIGNAL signal = Signal(self.cur_object, expect_attr(attrs, 'name')) self.cur_object.signals.append(signal) self.cur_object = signal self.arg_count = 0 elif name == STATE_PROPERTY: self.state = STATE_PROPERTY prop = Property(self.cur_object, expect_attr(attrs, 'name'), expect_attr(attrs, 'type'), expect_attr(attrs, 'access')) self.cur_object.properties.append(prop) self.cur_object = prop else: self.state = STATE_IGNORED elif self.state == STATE_METHOD: if name == STATE_ARG: self.state = STATE_ARG arg = Arg(self.cur_object, expect_attr(attrs, 'name'), expect_attr(attrs, 'type')) direction = attrs.get('direction', 'in') if direction == 'in': self.cur_object.in_args.append(arg) elif direction == 'out': self.cur_object.out_args.append(arg) else: raise DBusXmlException('Invalid direction "%s"' % direction) self.cur_object = arg else: self.state = STATE_IGNORED elif self.state == STATE_SIGNAL: if name == STATE_ARG: self.state = STATE_ARG arg = Arg(self.cur_object, expect_attr(attrs, 'name'), expect_attr(attrs, 'type')) self.cur_object.args.append(arg) self.cur_object = arg else: self.state = STATE_IGNORED elif self.state == STATE_PROPERTY: self.state = STATE_IGNORED elif self.state == STATE_ARG: self.state = STATE_IGNORED else: assert False, 'Unhandled state "%s" while entering element with name "%s"' % (self.state, name) self.state_stack.append(old_state) self.cur_object_stack.append(old_cur_object) def handle_end_element(self, name): if self.cur_object: self.cur_object.validate() self.state = self.state_stack.pop() self.cur_object = self.cur_object_stack.pop() def parse_options(): parser = optparse.OptionParser("usage: %prog [options] introspect.xml ...") parser.set_description("sbus_codegen generates sbus interface structures \ from standard XML Introspect data.") parser.add_option("--mode", dest="mode", default="header", help="'header' or 'source' (default: header)", metavar="MODE") parser.add_option("--output", dest="output", default=None, help="Set output file name (default: stdout)", metavar="FILE") parser.add_option("--include", dest="include", default=None, help="name of a header to #include", metavar="HEADER") (options, args) = parser.parse_args() if not args: print >> sys.stderr, "sbus_codegen: no input file specified" sys.exit(2) if options.mode not in ["header", "source"]: print >> sys.stderr, "sbus_codegen: specify --mode=header or --mode=source" return options, args def main(): options, args = parse_options() if options.output: sys.stdout = buf = StringIO.StringIO() for filename in args: parser = DBusXMLParser(filename) if options.mode == "header": generate_header(parser.parsed_interfaces, filename) elif options.mode == "source": generate_source(parser.parsed_interfaces, filename, options.include) else: assert False, "should not be reached" # Write output at end to be nice to 'make' if options.output: output = open(options.output, "w") output.write(buf.getvalue()) output.close() if __name__ == "__main__": try: main() except DBusXmlException, ex: print >> sys.stderr, str(ex) sys.exit(1)