2017-04-04 1:52 GMT+02:00 Dawid Zamirski <dzamirski@xxxxxxxxx>: > This patch updates the code generator that outputs C headers and code > for WMI classes. It has been updated to handle multiple versions (or > namespaces) of the same class which were introduced with Hyperv 2012+ > --- > src/hyperv/hyperv_wmi_generator.py | 385 +++++++++++++++++++++++++++---------- > 1 file changed, 288 insertions(+), 97 deletions(-) > > diff --git a/src/hyperv/hyperv_wmi_generator.py b/src/hyperv/hyperv_wmi_generator.py > index 8c62882..f2c9cde 100755 > --- a/src/hyperv/hyperv_wmi_generator.py > +++ b/src/hyperv/hyperv_wmi_generator.py > @@ -24,130 +24,310 @@ import sys > import os > import os.path > > +separator = "/*" + ("*" * 50) + "*\n" > +wmi_version_separator = "/" > +wmi_classes_by_name = {} > + > +class WmiClass: > + """Represents WMI class and provides methods to generate C code. > + > + This class holds one or more instances of WmiClassVersion because with the > + Windows 2012 release, Microsoft introduced "v2" version of Msvm_* family of > + classes that need different URI for making wsman requests and also have > + some additional/changed properties (though many of the properies are the > + same as in "v1". Therefore, this class makes sure that C code is generated > + for each of them while avoiding name conflics, identifies common members, > + and defined *_WmiInfo structs holding info about each version so the driver > + code can make the right choices based on which Hyper-v host it's connected s/Hyper-v/Hyper-V/ > + to. > + """ > + > + def __init__(self, name, versions = []): > + self.name = name > + self.versions = versions > + self.common = None > > > -separator = "/* " + ("* " * 37) + "*\n" > + def prepare(self): > + """Prepares the class for code generation > > + Makes sure that "versioned" classes are sorted by version, identfies s/identfies/identifies/ > + common properies and ensures that they are aligned by name and > + type in each version > + """ > + # sort vesioned classes by version in case input file did not have them > + # in order > + self.versions = sorted(self.versions, key=lambda cls: cls.version) > > + # if there's more than one verion make sure first one has name suffixed > + # because we'll generate "common" memeber and will be the "base" name > + if len(self.versions) > 1: > + first = self.versions[0] > + if first.version == None: > + first.version = "v1" > + first.name = "%s_%s" % (first.name, first.version) > > -class Class: > - def __init__(self, name, properties): > - self.name = name > - self.properties = properties > + # finally, identify common members in all versions and make sure they > + # are in the same order - to ensure C struc member alignment s/struc/struct/ > + self._align_property_members() > > > - def generate_header(self): > + def generate_classes_header(self): > + """Generate C header code and return it as string > + > + Declares: > + <class_name>_Data - used as one of hypervObject->data members > + <class_name>_TypeInfo - used as wsman XmlSerializerInfo > + <class_name> - "inherits" hypervObject struct > + """ > + > name_upper = self.name.upper() > > header = separator > header += " * %s\n" % self.name > header += " */\n" > header += "\n" > - header += "int hypervGet%sList(hypervPrivate *priv, virBufferPtr query, %s **list);\n" \ > - % (self.name.replace("_", ""), self.name) > - header += "\n" > + header += "#define %s_CLASSNAME \\\n" % name_upper > + header += " \"%s\"\n" % self.name > header += "\n" > + header += "#define %s_WQL_SELECT \\\n" % name_upper > + header += " \"SELECT * FROM %s \"\n" % self.name > header += "\n" > + header += "extern hypervWmiClassInfoListPtr %s_WmiInfo;\n\n" % self.name > + > + header += self._declare_data_structs() > + header += self._declare_hypervObject_struct() > > return header > > > + def generate_classes_source(self): > + """Returns a C code string defining wsman data structs > + > + Defines: > + <class_name>_Data structs > + <class_name>_WmiInfo - list holding metadata (e.g. request URIs) for > + each known version of WMI class. > + """ > + > + source = separator > + source += " * %s\n" % self.name > + source += " */\n" > + > + for cls in self.versions: > + source += "SER_START_ITEMS(%s_Data)\n" % cls.name > + > + for property in cls.properties: > + source += property.generate_classes_source(cls.name) > + > + source += "SER_END_ITEMS(%s_Data);\n\n" % cls.name > + > + > + source += self._define_WmiInfo_struct() > + source += "\n\n" > + > + return source > + > + > def generate_classes_typedef(self): > - typedef = "typedef struct _%s_Data %s_Data;\n" % (self.name, self.name) > - typedef += "typedef struct _%s %s;\n" % (self.name, self.name) > + """Returns C string for typdefs""" > + > + typedef = "typedef struct _%s %s;\n" % (self.name, self.name) > + > + if self.common is not None: > + typedef += "typedef struct _%s_Data %s_Data;\n" % (self.name, self.name) > + > + for cls in self.versions: > + typedef += "typedef struct _%s_Data %s_Data;\n" % (cls.name, cls.name) > > return typedef > > > - def generate_classes_header(self): > - name_upper = self.name.upper() > > - header = separator > - header += " * %s\n" % self.name > - header += " */\n" > - header += "\n" > - header += "#define %s_RESOURCE_URI \\\n" % name_upper > + def _declare_data_structs(self): > + """Returns string C code declaring data structs. > > - if self.name.startswith("Win32_") or self.name.startswith("CIM_"): > - header += " \"http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/%s\"\n" % self.name > - else: > - header += " \"http://schemas.microsoft.com/wbem/wsman/1/wmi/root/virtualization/%s\"\n" % self.name > + The *_Data structs are members of hypervObject data union. Each one has > + corresponding *_TypeInfo that is used for wsman unserialization of > + response XML into the *_Data structs. If there's a "common" member, it > + won't have corresponding *_TypeInfo becuase this is a special case only > + used to provide a common "view" of v1, v2 etc members > + """ > > - header += "\n" > - header += "#define %s_CLASSNAME \\\n" % name_upper > - header += " \"%s\"\n" % self.name > - header += "\n" > - header += "#define %s_WQL_SELECT \\\n" % name_upper > - header += " \"select * from %s \"\n" % self.name > - header += "\n" > - header += "struct _%s_Data {\n" % self.name > + header = "" > + if self.common is not None: > + header += "struct _%s_Data {\n" % self.name > + for property in self.common: > + header += property.generate_classes_header() > + header += "};\n\n" > > - for property in self.properties: > - header += property.generate_classes_header() > + # Declare actual data struct for each versions > + for cls in self.versions: > + header += "#define %s_RESOURCE_URI \\\n" % cls.name.upper() > + header += " \"%s\"\n" % cls.uri_info.resourceUri > + header += "\n" > + header += "struct _%s_Data {\n" % cls.name > + for property in cls.properties: > + header += property.generate_classes_header() > + header += "};\n\n" > + header += "SER_DECLARE_TYPE(%s_Data);\n" % cls.name > > - header += "};\n" > - header += "\n" > - header += "SER_DECLARE_TYPE(%s_Data);\n" % self.name > - header += "\n" > + return header > + > + > + def _declare_hypervObject_struct(self): > + """Return string for C code declaring hypervObject instance""" > + > + header = "\n/* must match hypervObject */\n" > header += "struct _%s {\n" % self.name > - header += " XmlSerializerInfo *serializerInfo;\n" > - header += " %s_Data *data;\n" % self.name > + header += " union {\n" > + > + # if there's common use it as "common" else first and only version is > + # the "common" member > + if self.common is not None: > + header += " %s_Data *common;\n" % self.name > + else: > + header += " %s_Data *common;\n" % self.versions[0].name > + > + for cls in self.versions: > + header += " %s_Data *%s;\n" % (cls.name, cls.version) > + > + header += " } data;\n" > + header += " hypervWmiClassInfoPtr info;\n" > header += " %s *next;\n" % self.name > header += "};\n" > - header += "\n" > - header += "\n" > - header += "\n" > + > + header += "\n\n\n" > > return header > > > - def generate_source(self): > - name_upper = self.name.upper() > + def _define_WmiInfo_struct(self): > + """Return string for C code defining *_WmiInfo struct > > - source = separator > - source += " * %s\n" % self.name > - source += " */\n" > - source += "\n" > - source += "int\n" > - source += "hypervGet%sList(hypervPrivate *priv, virBufferPtr query, %s **list)\n" \ > - % (self.name.replace("_", ""), self.name) > - source += "{\n" > - > - if self.name.startswith("Win32_") or self.name.startswith("CIM_"): > - source += " return hypervEnumAndPull(priv, query, ROOT_CIMV2,\n" > - else: > - source += " return hypervEnumAndPull(priv, query, ROOT_VIRTUALIZATION,\n" > + Those structs hold info with meta-data needed to make wsman requests for > + each version of WMI class > + """ > + > + source = "hypervWmiClassInfoListPtr %s_WmiInfo = &(hypervWmiClassInfoList) {\n" % self.name > + source += " .count = %d,\n" % len(self.versions) > + source += " .objs = (hypervWmiClassInfoPtr []) {\n" > > - source += " %s_Data_TypeInfo,\n" % self.name > - source += " %s_RESOURCE_URI,\n" % name_upper > - source += " %s_CLASSNAME,\n" % name_upper > - source += " (hypervObject **)list);\n" > - source += "}\n" > - source += "\n" > - source += "\n" > - source += "\n" > + for cls in self.versions: > + source += " &(hypervWmiClassInfo) {\n" > + source += " .name = %s_CLASSNAME,\n" % self.name.upper() > + if cls.version is not None: > + source += " .version = \"%s\",\n" % cls.version > + else: > + source += " .version = NULL,\n" > + source += " .rootUri = %s,\n" % cls.uri_info.rootUri > + source += " .resourceUri = %s_RESOURCE_URI,\n" % cls.name.upper() > + source += " .serializerInfo = %s_Data_TypeInfo\n" % cls.name > + source += " },\n" > + > + source += " }\n" > + source += "};\n" > > return source > > > - def generate_classes_source(self): > - name_upper = self.name.upper() > + def _align_property_members(self): > + """Identifies common properties in all class versions. > > - source = separator > - source += " * %s\n" % self.name > - source += " */\n" > - source += "\n" > - source += "SER_START_ITEMS(%s_Data)\n" % self.name > + Makes sure that properties in all versions are ordered with common > + members first and that they are in the same order. This makes the > + generated C structs memory aligned and safe to access via the "common" > + struct that "shares" members with v1, v2 etc. > + """ > > - for property in self.properties: > - source += property.generate_classes_source(self.name) > + num_classes = len(self.versions) > + common = {} > + property_info = {} > > - source += "SER_END_ITEMS(%s_Data);\n" % self.name > - source += "\n" > - source += "\n" > - source += "\n" > + if num_classes < 2: > + return > + > + # count property occurences in all class versions > + for cls in self.versions: > + for prop in cls.properties: > + # consdered same if matches by name AND type > + key = "%s_%s" % (prop.name, prop.type) > + > + if key in property_info: > + property_info[key][1] += 1 > + else: > + property_info[key] = [prop, 1] > + > + # isolate those that are common for all and keep track of their postions > + pos = 0 > + for key in property_info: > + info = property_info[key] > + # exists in all class versions > + if info[1] == num_classes: > + common[info[0].name] = [info[0], pos] > + pos += 1 > + > + # alter each versions's property list so that common members are first > + # and in the same order as in the common dictionary > + total = len(common) > + for cls in self.versions: > + index = 0 > + count = len(cls.properties) > + > + while index < count: > + prop = cls.properties[index] > + > + # it's a "common" proptery s/proptery/property/ > + if prop.name in common: > + pos = common[prop.name][1] > + > + # move to the same position as in "common" dictionary > + if index != pos: > + tmp = cls.properties[pos] > + cls.properties[pos] = prop > + cls.properties[index] = tmp > + else: > + index += 1 > + else: > + index += 1 > + > + # finally, get common properties as list sorted by position in dictionary > + tmp = sorted(common.values(), key=lambda x: x[1]) > + self.common = [] > + for x in tmp: > + self.common.append(x[0]) > + > + > + > +class ClassUriInfo: > + """Prepares URI information needed for wsman requests.""" > + > + def __init__(self, wmi_name, version): > + self.rootUri = "ROOT_CIMV2" > + self.resourceUri = None > + baseUri = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2" > + > + if wmi_name.startswith("Msvm_"): > + baseUri = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/virtualization" > + self.rootUri = "ROOT_VIRTUALIZATION" > + > + if version == "v2": > + baseUri += "/v2" > + self.rootUri = "ROOT_VIRTUALIZATION_V2" > + > + self.resourceUri = "%s/%s" % (baseUri, wmi_name) > + > + > + > +class WmiClassVersion: > + """Represents specific version of WMI class.""" > + > + def __init__(self, name, version, properties, uri_info): > + self.name = name > + self.version = version > + self.properties = properties > + self.uri_info = uri_info > > - return source > > > class Property: > @@ -155,9 +335,13 @@ class Property: > "string" : "STR", > "datetime" : "STR", > "int8" : "INT8", > + "sint8" : "INT8", > "int16" : "INT16", > + "sint16" : "INT16", > "int32" : "INT32", > + "sint32" : "INT32", > "int64" : "INT64", > + "sint64" : "INT64", > "uint8" : "UINT8", > "uint16" : "UINT16", > "uint32" : "UINT32", > @@ -189,8 +373,6 @@ class Property: > return " SER_NS_%s(%s_RESOURCE_URI, \"%s\", 1),\n" \ > % (Property.typemap[self.type], class_name.upper(), self.name) > > - > - > def open_and_print(filename): > if filename.startswith("./"): > print " GEN " + filename[2:] > @@ -217,8 +399,15 @@ def parse_class(block): > assert header_items[0] == "class" > > name = header_items[1] > - > properties = [] > + version = None > + wmi_name = name > + ns_separator = name.find(wmi_version_separator) > + > + if ns_separator != -1: > + version = name[:ns_separator] > + wmi_name = name[ns_separator + 1:] > + name = "%s_%s" % (wmi_name, version) > > for line in block[1:]: > # expected format: <type> <name> > @@ -236,7 +425,13 @@ def parse_class(block): > properties.append(Property(type=items[0], name=items[1], > is_array=is_array)) > > - return Class(name=name, properties=properties) > + cls = WmiClassVersion(name=name, version=version, properties=properties, > + uri_info=ClassUriInfo(wmi_name, version)) > + > + if wmi_name in wmi_classes_by_name: > + wmi_classes_by_name[wmi_name].versions.append(cls) > + else: > + wmi_classes_by_name[wmi_name] = WmiClass(wmi_name, [cls]) > > > > @@ -248,15 +443,13 @@ def main(): > input_filename = os.path.join(os.getcwd(), "hyperv_wmi_generator.input") > output_dirname = os.getcwd() > > - header = open_and_print(os.path.join(output_dirname, "hyperv_wmi.generated.h")) > - source = open_and_print(os.path.join(output_dirname, "hyperv_wmi.generated.c")) > + Unnecessary whitespace addition. > classes_typedef = open_and_print(os.path.join(output_dirname, "hyperv_wmi_classes.generated.typedef")) > classes_header = open_and_print(os.path.join(output_dirname, "hyperv_wmi_classes.generated.h")) > classes_source = open_and_print(os.path.join(output_dirname, "hyperv_wmi_classes.generated.c")) > > - # parse input file > + Why did you drop this comment? > number = 0 > - classes_by_name = {} > block = None > > for line in file(input_filename, "rb").readlines(): > @@ -268,7 +461,7 @@ def main(): > line = line.lstrip().rstrip() > > if len(line) < 1: > - continue > + continue Unnecessary whitespace change. -- Matthias Bolte http://photron.blogspot.com -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list