2017-04-04 1:52 GMT+02:00 Dawid Zamirski <dzamirski(a)datto.com>:
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