[RFC 00/29] RFC: Generate object-model code based on relax-ng files

Outline ========= In libvirt world, objects(like domain, network, etc.) are described with two representations: structures in c-language and XML specified by relax-ng. Since c-language-implementation and xml restricted by relax-ng are merely the different expressions of the same object-model, we can make a tool to generate those C-language codes based on relax-ng files. RNG2C tool =========== RNG2C is a python tool which can translate hierarchy of notations in relax-ng into c-language structures. In general, <element> is converted to struct in C, struct's members derive from its <attribute>s and its sub <element>s; <choice> with multiple <value>s is converted to enum in C; <data> are converted into various builtin-type in C, such as int, char *. Also, RNG2C can generate relative conversion functions: vir[XXX]ParseXML, vir[XXX]FormatBuf and vir[XXX]Clear. These structures and functions can be used to replace hardcoded codes in current libvirt source. The tool RNG2C has three subcommands: 'list', 'show' and 'generate'. The 'list' and 'show' are used for previewing generated code, and the 'generate' is prepared to be invoked by Makefile. 1) list - list all types generated by this tool. Example: # ./rng2c/go list SHORT_ID META LOCATION -------- ---- ---------- 3df63b7a String String 9b81a5f5 UInt UInt 9d10c738 Enum /basictypes.rng/virOnOff.define/choice 5a024c44 Struct /network.rng/network.define/network.element/dns.element f6f0c927 Struct /network.rng/network.define/network.element ... ... 2) show - show details of the target type. The target is specified with SHORT_ID or LOCATION. Option '-k' specifies kinds of output information, 'scpf' stands for [s]tructure | [c]learfunc | [p]arsefunc | [f]ormatfunc. Example: # ./rng2c/go show 5a024c44 -kscpf Or # ./rng2c/go show /network.rng/network.define/network.element/dns.element -kscpf It will show four aspects of this Type: the directive of it; the declaration and implementation of its structure; code of its clearfunc; code of its parsefunc; code of its formatfunc. 3) generate - just like show, but it dosen't print information on screen. It is only for Makefile. It will generate c-language-code, such as struct/enum/ parsefunc/etc., to take the place of the relative hardcoded stuff. Problems ========= If we replace hardcoded code with generated code directly, there're still several problems. Problem1 - name convension Generated code follows specific name convension, now there're some differences between generated code and current libvirt codes. For example, generated struct's name will be like virXXXDef, parsefunc's name will be like virXXXDefParseXML; the 'XXX' is a kind of Camel-Case name which implies the struct's place in the hierarchy. But in current code, there're many variations of name. So if we replace current codes with generated codes directly, it requires a lot of modifications in the caller codes. Problem2 - error-checking codes in the parsefunc Most of current parse functions have error-checking codes after parsing XML code. These code lines validate existence and parsing results. Now RNG2C can't generate error-checking codes in the parsefunc. So we should find a way to handle it. Problem3 - hierarchy consistence The RNG2C generate a 'struct'(C code) for each 'element'(relax-ng). Most of current code follows this convention, but there're still some exceptions. For example, the element 'bridge'(in network.rng) should be translated into a struct called 'virNetworkBridgeDef'; but in fact, no this struct in the current codes, and its members (stp/delay/...) are exposed to the struct 'virNetworkDef'. Problem4 - target output files for generated code The current codes to be replaced exist in various source files. They include other source files, and are included by other source files. So we should specify proper target files for generated codes to take the place of old codes and inherit those original relationships. Directive =========== To solve these problems, the directive mechanism is introduced. stage1 stage2 relax-ng ---------> directives(json) ---------> c-language-code ^ | | | |---------------------| feedback-injection The basic process of generating codes includes two internal stages: Stage1: RNG2C parses the content of relax-ng files and generates directives. Stage2: RNG2C generates c-language code based on directives. So the code's form depends on directives directly. Injecting directives with new values into relax-ng files can override the default directives of stage1. Then in the stage2, the final c-code will be different. Directives are in the form of json and then wrapped as an special XML comment. So when common relax-ng parsers parse these relax-ng files, these directives will be ignored. Only RNG2C can recognize and handle them. For example: We want to replace the hardcoded virNetworkDNSHostDef with the generated structure. The original hardcoded structure is as below: typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { virSocketAddr ip; size_t nnames; char **names; }; Take several steps as below: 1) Execute subcommand list to find it # ./rng2c/go list | grep dns | grep host SHORT_ID META LOCATION 7892979d Struct /network.rng/network.define/network.element/dns.element/host.element 2) Execute subcommand show with SHORT-ID to check its generated structure # ./rng2c/go show 7892979d -ks ###### Directive ###### <!-- VIRT:DIRECTIVE { "id": "7892979d7e7a89882e9a324d95198d05d851d02b", "location": "/network.rng/network.define/network.element/dns.element/host.element", "name": "virNetworkDNSHostDef", "meta": "Struct", "members": [ {"id": "ip", "type": "String:b48b4186"}, {"id": "hostname", "more": true, "type": "String:7d3736b9"} ] } --> ###### Structure (Disabled: NO OUTPUT for "structure".) ###### [.h] typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { virSocketAddr ip; size_t nhostnames; char **hostnames; }; We can see, the name of the generated struct depends on root property "name" of the directive. Fortunately, it's the same and we don't need override it. There're two differences between generated structure and hardcoded stuff: In original struct, names of the second and third member are 'nnames' and 'names'; but in generated code, they are 'nhostnames' and 'hostnames'. These names are controlled by this part: "members": [{"id": "hostname", ...}]. 3) Override properties of the directive and reinject it into the relax-ng Based on "location" of the directive, we find the injection point and only need to fill changed parts, just as below: <!-- VIRT:DIRECTIVE { "members": [{"id": "hostname", "name": "name"}] } --> <element name="host"> ... ... We need to specify which member will be altered by property "id". 4) Re-execute subcommand 'show' to confirm the sameness After we bridge the gap between the generated structure and the hardcoded stuff, we can perform the substitution. Directive properties ===================== The schema of directive are defined in rng2c/schema.json. Directive consists of properties. Properties retain or derive from the notations in the relax-ng. 1) root properties id - string, READ-ONLY. Unique identity of the type. The id is generated by SHA-1. Its leftmost 8 digits are taken out as SHORT-ID. location - string, READ-ONLY. The location where the type derive from. When injecting a directive into relax-ng file, the injection point is based on this location. tag - string, READ-ONLY. The type's source tag. It can be 'element', 'data' or 'choice'. name - string. The type's name. For struct and enum, name is generated automatically. For struct, its default form is vir[XXX]Def; for enum, its default form is vir[XXX]Type. The main part 'XXX' represents its position in the hierarchy. If name is specified, it will override the default name. For example, inject directive as below: <!-- VIRT:DIRECTIVE {"name": "virNetDevIPRoute"} --> <element name="route"> ... ... Then the name of the generated struct will be 'virNetDevIPRoute'. This root property 'name' can be used to solve Problem1. meta - string. The type's meta. For struct, it is 'Struct'; for enum, it is 'Enum'; for builtins, it is the builtin's meta. Meta can be overrided. For example, inject directive as below: <define name="UUID"> <!-- VIRT:DIRECTIVE { "meta": "UChars", "structure": {"array.size": "VIR_UUID_BUFLEN"} } --> <data type="string"> ... ... Then this builtin will change into UChars from String(i.e. char *). Note: RNG2C don't generate implementation for builtin, they need to be hardcoded in libvirt source. unpack - boolean. Only for struct. Only valid for struct. On setting it true, this type will expose its members to its upper-level struct rather than act as a standalone struct. For example, inject directive on element 'bridge' as below: <!-- VIRT:DIRECTIVE { "unpack": true, "members": [ ... ... ] } --> <element name="bridge"> So struct virNetworkBridgeDef will not appear. All of its members, such as bridgeName/bridgeZone/stp/delay, are exposed to its parent struct virNetworkDef. The 'unpack' and 'pack' can be used to solve Problem3. pack - boolean. It can only work on <define> in the relax-ng. Setting it true, children of <define> are converted into members; and then these members are packed into a struct for reuse. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "name": "virPCIDeviceAddress", "pack": true, ... ... } --> <define name="pciaddress"> ... ... The <define name="pciaddress"> will be packed as a struct virPCIDeviceAddress which name is specified by the property "name". union - string. It can only work on <choice> in the relax-ng. By default, all children of <choice> will act as members and be exposed to the upper-level struct. When setting 'union' a non-null string which is called union-id, those members will be packed into a struct; then, a new member defined by this struct will be exposed to the upper-level struct. The new member's id and name are specified by the union-id. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "union": "if", "name": "virNetworkForwardIfDef" } --> <choice> <group> <zeroOrMore> <!-- VIRT:DIRECTIVE {"unpack": true} --> <element name='interface'> ... </group> <group> <zeroOrMore> <!-- VIRT:DIRECTIVE {"unpack": true} --> <element name='address'> ... </group> </choice> A new struct virNetworkForwardIfDef will be created; then a new member named 'if' is exposed to the parent struct. namespace - boolean. Only for struct. By default, there's no stuff about namespace in the struct. When setting it true, additional namespace members will be inserted into the struct. And the struct's clearfunc, parsefunc and formatfunc will include relative codes to handle these namespace stuff. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "namespace": true, ... ... } --> <element name="network"> Then in the struct virNetworkDef, there're two additional members: typedef virNetworkDef *virNetworkDefPtr; struct _virNetworkDef { ... ... void *namespaceData; virXMLNamespace ns; } In the virNetworkDefClear, there're the codes to invoke ns.free; in the virNetworkDefParseXML, there're the codes to invoke ns.parse; in the virNetworkDefFormatBuf, there're the codes to invoke ns.format. values - array. Only for enum. All values of an enum except 'none' and 'last'. It can be overrided. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "name": "virNetworkForwardType", "values": ["nat", "route", "open", "bridge", "private", "vepa", "passthrough", "hostdev"] } --> <choice> ... ... <value>passthrough</value> <value>private</value> <value>vepa</value> <value>hostdev</value> </choice> Generally the property 'values' doesn't need to be overrided, only if the order of values in the enum need to be adjusted. members - array. Only for struct. Property 'members' is used to display all the members of the struct. But when altering a member, only fill in this member's 'id' and properties to be changed. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "localPtr", "name": "localPTR"}, {"id": "tftp", "name": "tftproot"} ] } --> <element name="ip"> Specify the member "localPtr" and "tftp" by their id, then only these two members will be altered. In this case, their names are changed. For more member properties, refer to 6) PRESERVE and APPLY - string. By default, directives take effect on the injection point. But sometimes, we need preserve a directive at first and then apply it somewhere else. APPLY corresponds to PRESERVE through their value called preserve-id. This preserve-id can be any form of string. These two properties can be used on two notations, <choice> and <ref>. Here's an example for using them on <choice>, inject directive as below: <define name="virtualPortProfile"> <!-- VIRT:DIRECTIVE { "PRESERVE": "virtualport.element", "name": "virNetDevVPortProfile", ... ... } --> <choice> <group> <!-- VIRT:DIRECTIVE {"APPLY": "virtualport.element"} --> <element name="virtualport"> </group> <group> <!-- VIRT:DIRECTIVE {"APPLY": "virtualport.element"} --> <element name="virtualport"> </group> ... ... In this case, there're many <element name="virtualport"> notation under <choice>. We can define a preserve directive and apply it to all the <element name="virtualport">. Here's an example for using them on <ref>, inject PRESERVE as below: <!-- VIRT:DIRECTIVE { "PRESERVE": "port.element", "unpack": true, ... ... "members": [{"id": "isolated", "name": "isolatedPort"}] } --> <ref name="portOptions"/> ... ... And in <define>, inject APPLY as below: <define name="portOptions"> <!-- VIRT:DIRECTIVE {"APPLY": "port.element"} --> <element name="port"> ... ... It is useful in a case: a <define> is referenced by several <ref>s. We can define different directives on different <ref>s and expect different effects when they are applied in the <define>. TOUCH - boolean. Request to handle the target <define> explicitly. By default, the process of RNG2C starts at <start>, and handles a <define> only when a <ref> references to it. If a <define> isn't touched, RNG2C cannot find it. Inject a directive with TOUCH on a <define> to touch it. For example, the <define> 'zpciaddress' hasn't been referenced in the process of parsing network.rng (also networkcommon.rng and basictypes.rng), but we hope that this notation will be handle. Just inject the directive as below: <!-- VIRT:DIRECTIVE {"TOUCH": true} --> <define name="zpciaddress"> <element name="zpci"> ... ... Then it can be discovered when executing subcommand 'list'. 2) structure properties output - string. Only for struct and enum. Control whether to output the structure code and where to output those code. Refer to 7) enum.default - string. Only for enum. By default, for enum, first item's name is 'none'. Override it with other name. For example, inject directive as below: <define name="macTableManager"> <!-- VIRT:DIRECTIVE { "name": "virNetworkBridgeMACTableManagerType", "structure": {"enum.default": "default"} } --> <choice> <value>kernel</value> ... ... Then the declaration of enum virNetworkBridgeMACTableManagerType will be like: typedef enum { VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_DEFAULT = 0, VIR_NETWORK_BRIDGE_MAC_TABLE_MANAGER_KERNEL, ... ... } virNetworkBridgeMACTableManagerType; The first item is suffixed with '_DEFAULT' rather than '_NONE'. array.size - string. Only for char array or unsigned char array. It specifies length of array. The value can be a macro or a literal number. For example, inject directive as below: <define name="UUID"> <choice> <!-- VIRT:DIRECTIVE { "meta": "UChars", "structure": {"array.size": "VIR_UUID_BUFLEN"} } --> <data type="string"> ... ... Then it will generate an array type: unsigned char XXX[VIR_UUID_BUFLEN]; 3) clearfunc properties output - string. Only for struct. Control whether to output the clearfunc code and where to output those code. Refer to 7) name - string. The clearfunc's name. By default, its name is based type's name and suffixed 'Clear'. It can be overrided with any other name. args - array. Extra formal args for clearfunc. But now it hasn't been used. 4) parsefunc properties The parsefunc's declaration and implementation are controlled by properties. The basic form of parsefunc's declaration is as below: int ${parsefunc.name}(xmlNodePtr curnode, ${type.name}Ptr def, xmlXPathContextPtr ctxt, /* omit if args.noctxt is true */ const char *instanceName, /* exist if args.instname is true */ ${parent.name}Ptr parent, /* exist if args.parent is true */ ${args}); /* exist if args is NOT none */ output - string. Only for struct. Control whether to output the parsefunc code and where to output those code. Refer to 7) name - string. The parsefunc's name. By default, its name is based type's name and suffixed 'ParseXML'. It can be overrided with any other name. args - array. Extra formal args for parsefunc. Each arg can have four properties: name, type, pointer and ctype. The first three are according to the usage of namesake properties in member. And ctype is an alternate to type when type can't express the argument's type. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "parsefunc": { "args": [{"name": "partialOkay", "type": "Bool"}] } } --> <element name="txt"> ... ... Then the args of parsefunc will be appended by a arg, as below: int virNetworkDNSTxtDefParseXML(xmlNodePtr curnode, virNetworkDNSTxtDefPtr def, xmlXPathContextPtr ctxt, bool partialOkay); args.noctxt - boolean. By default, there's a arg 'ctxt' in the function arguments. When 'args.noctxt' is set to true, omit ctxt from the arguments. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "parsefunc": { "args.noctxt": true, } } --> <element name="txt"> ... ... Then the arg 'ctxt' will be removed from the default args. int virNetworkDNSTxtDefParseXML(xmlNodePtr curnode, virNetworkDNSTxtDefPtr def); At the same time, the generated implementation of virNetworkDNSTxtDefParseXML will be different according to this property. args.instname - boolean. By default, there's no instance-name in the function arguments. When it is set to true, add instname into the arguments. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "parsefunc": { "args.instname": true, } } --> <element name="txt"> ... ... Then the arg 'instname' will be added into the args. int virNetworkDNSTxtDefParseXML(xmlNodePtr curnode, virNetworkDNSTxtDefPtr def, xmlXPathContextPtr ctxt, const char *instanceName); args.parent - boolean. By default, there's no parent struct pointer name in the function arguments. When it is set to true, add parent struct pointer into the arguments. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "parsefunc": { "args.parent": true } } --> <element name="dhcp"> Then the arg 'parent' will be added into the args. int virNetworkIPDHCPDefParseXML(xmlNodePtr curnode, virNetworkIPDHCPDefPtr def, xmlXPathContextPtr ctxt, virNetworkIPDefPtr parentdef); post - boolean. By default, there's no post hook in the parse function. This property enables a post hook for parsefunc. The hook is invoked after all members have been parsed. The post hook function's name is based on parsefunc's name, and suffxied with 'Post'. To solve the foregoing Problem2, extract error-checking code from the current parsefunc and move them to the post hook function, then current parsefunc can be replaced with generated parsefunc. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "parsefunc": {"post": true} } --> <element name="dns"> ... ... The declaration of the post hook will be generated, as below: int virNetworkDNSDefParseXMLPost(xmlNodePtr curnode, virNetworkDNSDefPtr def, xmlXPathContextPtr ctxt, const char *enableStr, const char *forwardPlainNamesStr, int nForwarderNodes, int nTxtNodes, int nSrvNodes, int nHostNodes); Implement it. It will be invoked by generated parsefunc automatically. post.notmpvars - boolean. It is valid only when post is set. By default, all the temporary variables in the parsefunc will be passed to the post hook function. When it is set to true, there's no those temporary variables as arguments in the post hook functions. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "parsefunc": {"post": true, "post.notmpvars": true} } --> <element name="dns"> ... ... The declaration of the post hook will be generated, as below: int virNetworkDNSDefParseXMLPost(xmlNodePtr curnode, virNetworkDNSDefPtr def, xmlXPathContextPtr ctxt); Now all tmpvars are removed from the args of parse hook function. 5) formatfunc properties output - string. Only for struct. Control whether to output the formatfunc code and where to output those code. Refer to 7) name - string. The formatfunc's name. By default, its name is based type's name and suffixed 'FormatBuf'. It can be overrided with any other name. args - array. Extra formal args for formatfunc. Just like the args of parsefunc. shorthand.ignore - boolean. By default, formatfunc adopts shorthand closing tag notation when there're no elements to output. If shorthand.ignore is set to true, this default optimization will be prevented. For example, virNetworkIPDefFormat does output <ip ...></ip> rather than <ip .../> when there're no child element 'tftp' and 'dhcp'. So this property is used to meet the need. order - array. It is a array which item is member's id. In formatfunc, the relative order of formatting members is sensitive. If the order is not correct, setting this property to re-arrange the order. And when the array includes only part of ids, other members will follow and keep their original relative order. For example, virPortGroupDefFormat has a special order to format its members. <!-- VIRT:DIRECTIVE { "formatfunc": { "order": ["name", "default", "trustGuestRxFilters", "vlan"] } } --> Then format order will be altered according to this. 6) member properties Member properties can only appear in the members' array. Use these properties to override the struct members' default settings. id - string, READ-ONLY. When id is set, this property specify the member to be changed. When id is ignored, it means this member is a new member and needed to append to the struct's members. For example, inject a directive as below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "dev", "name": "devname"}, {"name": "connections", "type": "Int"} ] } --> <element name='pf'> ... ... The the member 'dev' will be altered; and then, a new member 'connections' will be added. For a new member, its type must be specified. name - string. Member's name. By default, name is the same with id. It can be overrided with any name. opt - boolean. Whether optional or not. In the parsefunc, non-existence for optional member is legal; otherwise, there's a error-checking to check its existence and report error. For example, inject a directive as below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "value", "opt": true} ] } --> <element name="txt"> ... ... Then in the virNetworkDNSTxtDefParseXML, it's ok when the notation 'value' is missing. more - boolean. When it is set, the member is a list. For example, inject a directive as below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "pf", "more": true} ] } --> <element name="forward"> ... ... Then the member 'pf' will be declared as below: size_t npfs; virNetworkForwardPfDefPtr pfs; tag - string. Display the source's tag. When a new member is to add, it can be specified. type - string. Member's type. Its form is "meta[:short_id]", meta is the type's meta, short_id is the leftmost 8 digits of type's id. For builtins, ignore colon and short_id. This property can be overrided with other type. pointer - boolean. By default, member is an instance defined by its type. When this property is set to true, member is a pointer of its type. For example, inject directive as below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "bandwidth", "pointer": true} ] } --> Then type of the member 'bandwidth' will be changed to pointer, as below: virBandwidthDefPtr bandwidth; specified - boolean. When this property is set to true, member will be attached with a new member which specifies the existence of its master. The new additional member's form is "bool XXX_specified;". For example, inject directive as below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "managerid", "name": "managerID", "specified": true}, ... ... ] } --> <element name="parameters"> ... ... An addional member 'managerID_specified' will follow the 'managerID'. uint8_t managerID; bool managerID_specified; declare.comment - string. Specify a comment behind member's declaration in struct. For example, inject a directive like below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "uid", "declare.comment": "exempt from syntax-check"} ] } --> <element name="zpci"> ... ... Then the declaration of the member 'uid' is like below: int uid; /* exempt from syntax-check */ parse.disable - boolean. When this property is set to true, member will not appear in parsefunc. parse.args - array. Only for struct-type member. Specify the extra actual arguments when member is parsed by calling parsefunc. If member's type is struct and the struct's parsefunc has extra formal argument, passing actual arguments by this property. For example, type of the member 'host' is virNetworkDHCPHostDef, and the parsefunc related to the type has an extra arg partialOkey(specified by "args" in "parsefunc"). Here this extra arg needs to be specified a value. Directive is like below: <!-- VIRT:DIRECTIVE { "members": [ {"id": "host", "parse.args": [{"name": "partialOkay", "value": "false"}]} ] } --> <element name="dhcp"> ... ... Then the parsing code for 'host' in the parsefunc is like below: if (virNetworkIPDHCPHostDefParseXML(node, &def->hosts[i], ctxt, false) < 0) goto error; A specific value is passed into the function as an actual arg. parse.instname - string. Only for struct-type member. When member's type is struct and the struct's parsefunc needs a instance-name argument, passing instance name through this property. For example: <!-- VIRT:DIRECTIVE { "members": [ {"id": "ip", "parse.instname": "def->name"} ] } --> <element name="network"> ... ... Then parsing code for 'ip' in the parsefunc will be: if (virNetworkIPDefParseXML(node, &def->ips[i], ctxt, def->name) < 0) goto error; The 'def->name' is passed in as instance-name. parse.default - string. Specify a value for member when it doesn't get value from XML. For example: <!-- VIRT:DIRECTIVE { "members": [ {"id": "mode", "parse.default": "VIR_NETWORK_FORWARD_NAT"} ] } --> Then when the member 'mode' dosen't get value in parsefunc, it will be assigned the default value 'VIR_NETWORK_FORWARD_NAT'. format.disable - boolean. When this property is set to true, member will not be skipped in formatfunc. format.nocheck - boolean. Format and dump member to XML despite of the conditions. By default, a member needs to be check whether its value is empty before handling it in formatfunc. When this property is set to true, ignore these checks and always output. For example: <!-- VIRT:DIRECTIVE { "members": [ {"id": "domain", "format.nocheck": true} ] } --> <define name="pciaddress"> ... ... Then whether or not the member 'domain' is zero, it will be formatted and output. format.precheck - string. Only valid when format.nocheck isn't set. In formatfunc, invoke a function as condition-check rather than default checks. The function should be hardcoded in advance and its name is specified by this property. For example, in virNetworkDefFormatBuf, the member 'connections' will be formatted and output only when some conditions evaluate to true. So set this property to enable a check function named 'checkXMLInactive', and put logic of checking conditions into the function. For example: <!-- VIRT:DIRECTIVE { "members": [ {"id": "connections", "format.precheck": "checkXMLInactive"} ] } --> <define name="network"> ... ... Then RNG2C generates the declaration of checkXMLInactive. bool checkXMLInactive(const unsigned int def G_GNUC_UNUSED, const virNetworkDef *parent G_GNUC_UNUSED, unsigned int flags); Implement this function in libvirt source. format.fmt - string. For every builtin, there's a default format when they're formatting. This property can be overrided to change the default format. For example: <!-- VIRT:DIRECTIVE { "members": [ {"id": "slot", "format.fmt": "0x%02x"} ] } --> <define name="pciaddress"> ... ... Then the member 'slot' will be formatting based on format "0x%02x". format.args - array. Only for struct-type member. This property is just like parse.args, except that it is for formatfunc. Now it hasn't been used. hint - string. READ-ONLY. Display special cases for member. The 'unpack' indicates member's type is unpack; 'pack' indicates member's type is pack; 'union' indicates member's type is union; 'new' means this member is added. 7) output property This property is for structure, clearfunc, parsefunc and formatfunc. It has two effects: First, it controls whether or not to output the code to the files. Second, it controls what file to output. Value of the property indicates a base path, and it is suffixed with ".generated.[c|h]" to form two generated files. The base path should be according to the file in which the code is about to be replaced with generated code. So the relationship between original files and generated files is as below: original.c ---> original.h --------- ^ | | v original.generated.c original.generated.h The original.generated.c includes original.h automatically, and the original.h needs to be inserted with a line '#include "original.generated.h"' manually. This method can solve the Problem4. Steps of each patch ===================== Replacing hardcoded code with generated stuff, we need a series of patches. Each patch just replaces a target, i.e. a structure or a function. The steps are as below: 1) Execute subcommand 'list' to query the target's id and location. 2) Execute subcommand 'show' to preview the target's directive and generated code. 3) Modify the directive and re-inject it into the relax-ng file to bridge the gap between current code and generated code. 4) Execute subcommand 'show' again to check the new directive and new generated code, until generated code can take the place of hardcoded stuff. 5) Remove the hardcoded code. 6) Compile & Test the libvirt source. About the series of patches ============================ Now I select network as a test target and make a try to generate virNetworkDef, virNetworkDefClear, virNetworkDefParseXML and virNetworkDefFormatBuf and their subordinate stuff automatically. But this needs around 130 patches. That's too many! So this series just include about 30 patches for evaluation. Thanks! Shi Lei (29): rng2c: Add tool RNG2C to translate relax-ng into c-language-code. maint: Call RNG2C automatically when relax-ng files change util: Add two helper functions virXMLChildNode and virXMLChildNodeSet util: Move VIR_ENUM_IMPL and VIR_ENUM_DECL from virenum.h to internal.h util: Replace virTristateBool|virTristateSwitch(hardcoded) with namesakes(generated) util: Add a helper function virStringParseOnOff util: Add some helper functions for virSocketAddr util: Add a helper function virStrToLong_u8p conf: Replace virNetworkDNSTxtDef(hardcoded) with namesake(generated) conf: Replace virNetworkDNSSrvDef(hardcoded) with namesake(generated) conf: Replace virNetworkDNSHostDef(hardcoded) with namesake(generated) conf: Replace virNetworkDNSForwarder(hardcoded) with namesake(generated) conf: Replace virNetworkDNSDef(hardcoded) with namesake(generated) conf: Replace virNetworkDNSDefClear(hardcoded) with namesake(generated) conf: Extract error-checking code from virNetworkDNSHostDefParseXML conf: Replace virNetworkDNSHostDefParseXML(hardcoded) with namesake(generated) conf: Extract error-checking code from virNetworkDNSSrvDefParseXML conf: Replace virNetworkDNSSrvDefParseXML(hardcoded) with namesake(generated) conf: Extract error-checking code from virNetworkDNSTxtDefParseXML conf: Replace virNetworkDNSTxtDefParseXML(hardcoded) with namesake(generated) conf: Add virNetworkDNSForwarderParseXML and virNetworkDNSForwarderParseXMLPost conf: Replace virNetworkDNSForwarderParseXML(hardcoded) with namesake(generated) conf: Extract error-checking code from virNetworkDNSDefParseXML conf: Replace virNetworkDNSDefParseXML(hardcoded) with namesake(generated) conf: Apply virNetworkDNSForwarderFormatBuf(generated) in virNetworkDNSDefFormat conf: Apply virNetworkDNSTxtDefFormatBuf(generated) in virNetworkDNSDefFormat conf: Apply virNetworkDNSSrvDefFormatBuf(generated) in virNetworkDNSDefFormat conf: Apply virNetworkDNSHostDefFormatBuf(generated) in virNetworkDNSDefFormat conf: Replace virNetworkDNSDefFormat(hardcoded) with virNetworkDNSDefFormatBuf(generated) docs/schemas/basictypes.rng | 15 + docs/schemas/network.rng | 76 ++ docs/schemas/networkcommon.rng | 2 +- po/POTFILES.in | 2 + rng2c/directive.py | 1693 +++++++++++++++++++++++++++++++ rng2c/generator.py | 504 +++++++++ rng2c/go | 8 + rng2c/schema.json | 113 +++ rng2c/utils.py | 163 +++ src/Makefile.am | 16 +- src/access/Makefile.inc.am | 2 +- src/conf/Makefile.inc.am | 12 +- src/conf/network_conf.c | 739 ++++---------- src/conf/network_conf.h | 51 +- src/esx/Makefile.inc.am | 2 +- src/interface/Makefile.inc.am | 2 +- src/internal.h | 19 + src/lxc/Makefile.inc.am | 1 + src/network/Makefile.inc.am | 2 +- src/network/bridge_driver.c | 2 +- src/node_device/Makefile.inc.am | 2 +- src/nwfilter/Makefile.inc.am | 2 +- src/qemu/Makefile.inc.am | 1 + src/remote/Makefile.inc.am | 2 +- src/secret/Makefile.inc.am | 2 +- src/storage/Makefile.inc.am | 2 +- src/test/Makefile.inc.am | 2 +- src/util/Makefile.inc.am | 13 +- src/util/virenum.c | 15 - src/util/virenum.h | 39 +- src/util/virsocketaddr.c | 27 + src/util/virsocketaddr.h | 10 + src/util/virstring.c | 37 + src/util/virstring.h | 10 + src/util/virxml.c | 57 ++ src/util/virxml.h | 3 + src/vbox/Makefile.inc.am | 2 +- tests/Makefile.am | 2 + tools/Makefile.am | 2 + 39 files changed, 3000 insertions(+), 654 deletions(-) create mode 100644 rng2c/directive.py create mode 100755 rng2c/generator.py create mode 100755 rng2c/go create mode 100644 rng2c/schema.json create mode 100644 rng2c/utils.py -- 2.17.1

RNG2C has three subcommands: 'list'/'show' for previewing directives and generated codes; 'generate' for Makefile to generate and output codes. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- po/POTFILES.in | 1 + rng2c/directive.py | 1693 ++++++++++++++++++++++++++++++++++++++++++++ rng2c/generator.py | 504 +++++++++++++ rng2c/go | 8 + rng2c/schema.json | 113 +++ rng2c/utils.py | 163 +++++ 6 files changed, 2482 insertions(+) create mode 100644 rng2c/directive.py create mode 100755 rng2c/generator.py create mode 100755 rng2c/go create mode 100644 rng2c/schema.json create mode 100644 rng2c/utils.py diff --git a/po/POTFILES.in b/po/POTFILES.in index 6103d4c..2358b01 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -5,6 +5,7 @@ @BUILDDIR@/src/admin/admin_server_dispatch_stubs.h @BUILDDIR@/src/remote/remote_client_bodies.h @BUILDDIR@/src/remote/remote_daemon_dispatch_stubs.h +@SRCDIR@/rng2c/directive.py @SRCDIR@/src/access/viraccessdriverpolkit.c @SRCDIR@/src/access/viraccessmanager.c @SRCDIR@/src/admin/admin_server.c diff --git a/rng2c/directive.py b/rng2c/directive.py new file mode 100644 index 0000000..ab52488 --- /dev/null +++ b/rng2c/directive.py @@ -0,0 +1,1693 @@ +# +# Copyright (C) 2020 Shandong Massclouds Co.,Ltd. +# +# 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.1 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, see +# <http://www.gnu.org/licenses/>. +# +# References: +# https://www.w3.org/TR/xmlschema-2/#decimal +# https://json-schema.org/understanding-json-schema +# + +import sys +import json +from collections import OrderedDict +from utils import singleton, assertOnlyOne, deepupdate +from utils import dedup, counterName +from utils import BlockAssembler, sha1ID +from utils import Terms, singleline, indent, render, renderByDict + +g_schema = None + +JSON_TYPE_MAP = { + 'null': (['NoneType'], None), + 'string': (['str', 'unicode'], ''), + 'array': (['list'], []), + 'object': (['dict'], {}), + 'boolean': (['bool'], False), + 'integer': (['int'], 0), +} + + +def initDirectiveSchema(path): + def _resolveRef(schema, definitions): + if '$ref' in schema: + link = schema.pop('$ref') + link = link[len('#/definitions/'):] + definition = _resolveRef(definitions[link], definitions) + schema.update(definition) + + for key in schema: + if isinstance(schema[key], dict): + _resolveRef(schema[key], definitions) + + return schema + + global g_schema + if not g_schema: + with open(path) as f: + schema = json.load(f) + g_schema = _resolveRef(schema, schema['definitions']) + + return g_schema + + +def _createDirective(kvs, schema, innerkeys): + def _createDefault(schema): + if 'const' in schema: + return schema['const'] + assert 'type' in schema, schema + return JSON_TYPE_MAP[schema['type']][1] + + ret = {} + for key in schema: + if key in kvs: + value = kvs[key] + else: + value = _createDefault(schema[key]) + ret[key] = value.copy() if isinstance(value, dict) else value + + for key in innerkeys: + if key in kvs: + value = kvs[key] + ret[key] = value.copy() if isinstance(value, dict) else value + + return ret + + +def createMember(typeid, kvs): + assert 'id' in kvs, kvs + kvs['meta'] = 'Member' + kvs['name'] = kvs['id'] + kvs['_typeid'] = typeid + + global g_schema + schema = g_schema['definitions']['member']['properties'] + inner = ['_typeid', 'meta'] + return _createDirective(kvs, schema, inner) + + +def createTypeLocation(kvs): + ids = BlockAssembler() + ids.append(kvs['_env']['rng']) + ids.append(kvs['_env']['define'] + '.define') + if kvs['_nodepath']: + nodepath = [n[0] + n[1] for n in kvs['_nodepath']] + anchor = kvs.get('_anchor', -1) + if anchor >= 0: + nodepath[anchor] = '[%s]' % nodepath[anchor] + ids.extend(nodepath) + return '/' + ids.output('/') + + +def createFullname(kvs): + if not kvs['_nodepath']: + ret = kvs['_env']['define'] + else: + ret = ''.join([Terms.upperInitial(n[0]) for n in kvs['_nodepath']]) + if not ret.startswith('vir'): + ret = 'vir' + Terms.upperInitial(ret) + return ret + + +def createType(meta, kvs, children=None): + kvs['meta'] = meta + if 'location' not in kvs: + kvs['location'] = createTypeLocation(kvs) + + if meta in BUILTIN_TYPES: + if 'name' in kvs and kvs['meta'] == 'String': + kvs['gap'] = ' ' # Fix gap for hardcoded 'String' + elif meta in ['Struct']: + if 'name' not in kvs: + kvs['name'] = createFullname(kvs) + 'Def' + + for kind in ['structure', 'clearfunc', 'parsefunc', 'formatfunc']: + if kind not in kvs: + kvs[kind] = {} + + for member in kvs.pop('members', {}): + assert verifyMember(member), "Invalid member: %s" % member + if 'type' in member: + typeid = TypeTable().getByLocation(member['type'])['id'] + member['_typeid'] = typeid + + if 'id' not in member: + member['hint'] = 'new' + member['id'] = member['name'] + member['_env'] = kvs['_env'] + assert member['_typeid'] + children.append(createMember(member['_typeid'], member)) + continue + + child = findMember(member['id'], children) + if child: + deepupdate(child, member) + mtype = TypeTable().get(child['_typeid']) + if not child['hint']: + if mtype['unpack']: + child['hint'] = 'unpack' + elif mtype['pack']: + child['hint'] = 'pack' + + kvs['members'] = children + elif meta in ['Enum']: + if not kvs.get('name', None): + fullname = createFullname(kvs) + if not fullname.endswith('Type'): + fullname += 'Type' + kvs['name'] = fullname + + if 'structure' not in kvs: + if 'structure' not in kvs: + kvs['structure'] = {} + + if not kvs.get('values', None): + kvs['values'] = children + elif meta in ['Constant']: + pass + else: + assert False, "Unsupported meta '%s'." % meta + + global g_schema + schema = g_schema['properties'] + inner = ['_anchor', '_nodepath', '_env'] + return _createDirective(kvs, schema, inner) + + +def _verifyDirective(kvs, schema): + def _verifyType(obj, schema): + if 'const' in schema: + return obj == schema['const'] + + target = schema['type'] + if isinstance(target, list): + for t in target: + if type(obj).__name__ in JSON_TYPE_MAP[t][0]: + return True + return False + + return type(obj).__name__ in JSON_TYPE_MAP[target][0] + + for key, value in kvs.items(): + if key.startswith('_'): + continue + if key not in schema: + print("fatal: undefined directive '%s'" % key) + return False + if not _verifyType(value, schema[key]): + print("fatal: directive '%s:%s' type error" % (key, value)) + return False + if isinstance(value, dict): + if not _verifyDirective(value, schema[key]['properties']): + return False + + return True + + +def verifyMember(kvs): + global g_schema + schema = g_schema['definitions']['member']['properties'] + return _verifyDirective(kvs, schema) + + +def verifyType(kvs): + global g_schema + return _verifyDirective(kvs, g_schema['properties']) + + +BUILTIN_TYPES = { + 'PVoid': {'ctype': 'void *', 'gap': ''}, + 'String': {'ctype': 'char *', 'gap': ''}, + 'Bool': {'ctype': 'bool'}, + 'Bool.yes_no': {'ctype': 'bool', 'values': ['yes', 'no']}, + 'Bool.on_off': {'ctype': 'bool', 'values': ['yes', 'no']}, + 'Chars': { + 'ctype': 'char', 'conv': 'virStrcpyStatic(def->${name}, ${name}Str)' + }, + 'UChars': { + 'ctype': 'unsigned char', + 'conv': 'virStrcpyStatic((char *)def->${name}, ${mdvar})' + }, + 'Int': { + 'ctype': 'int', 'fmt': '%d', + 'conv': 'virStrToLong_i(${mdvar}, NULL, 0, &def->${name})' + }, + 'UInt': { + 'ctype': 'unsigned int', 'fmt': '%u', + 'conv': 'virStrToLong_uip(${mdvar}, NULL, 0, &def->${name})' + }, + 'ULongLegacy': { + 'ctype': 'unsigned long', 'fmt': '%lu', + 'conv': 'virStrToLong_ulp(${mdvar}, NULL, 0, &def->${name})' + }, + 'ULong': { + 'ctype': 'unsigned long long', 'fmt': '%llu', + 'conv': 'virStrToLong_ullp(${mdvar}, NULL, 0, &def->${name})' + }, + 'U8': { + 'ctype': 'uint8_t', 'fmt': '%u', + 'conv': 'virStrToLong_u8p(${mdvar}, NULL, 0, &def->${name})' + }, + 'U32': { + 'ctype': 'uint32_t', 'fmt': '%u', + 'conv': 'virStrToLong_uip(${mdvar}, NULL, 0, &def->${name})' + }, + 'ConstString': {'ctype': 'const char *', 'gap': ''}, + 'Constant': {'ctype': 'bool'}, +} +BUILTIN_TYPES['Integer'] = BUILTIN_TYPES['Int'] +BUILTIN_TYPES['UnsignedInt'] = BUILTIN_TYPES['UInt'] +BUILTIN_TYPES['PositiveInteger'] = BUILTIN_TYPES['UInt'] +BUILTIN_TYPES['UnsignedLong'] = BUILTIN_TYPES['ULong'] + + +def isBuiltin(meta): + return meta in BUILTIN_TYPES + + +class NodeList(list): + def __init__(self, first=None): + if first: + self.append(first) + + def _getUniform(self, node): + return 'Builtin' if isBuiltin(node['meta']) else node['meta'] + + def uniform(self): + return self._getUniform(self[0]) if len(self) else None + + def append(self, node): + if len(self): + assert self.uniform() == self._getUniform(node) + + if self.uniform() == 'Member': + for cur in self: + if cur['id'] == node['id'] and \ + cur.get('more') == node.get('more'): + if cur['name'] == node['name']: + cur['opt'] = cur['opt'] or node['opt'] + return + elif self.uniform() == 'Builtin': + cur = assertOnlyOne(self) + if node['id'] == cur['id']: + return + + # String is always swallowed by other builtin-types. + if cur['meta'] == 'String' and node['meta'] != 'String': + TypeTable().pop(cur['id']) + self[0] = node + else: + TypeTable().pop(node['id']) + return + + super(NodeList, self).append(node) + + def extend(self, nodes): + if nodes: + for node in nodes: + self.append(node) + + +@singleton +class TypeTable(OrderedDict): + def __init__(self): + OrderedDict.__init__(self) + for meta, kvs in BUILTIN_TYPES.items(): + tid = sha1ID(meta) + kvs['id'] = tid + kvs['location'] = meta + self[tid] = createType(meta, kvs) + + def _merge(self, tid, newkvs): + kvs = self[tid] + if kvs['meta'] == 'Constant' and newkvs['meta'] == 'Constant': + kvs['meta'] = 'Enum' # Reset meta explicitly + assert 'values' in kvs, kvs + values = kvs.pop('values') + values.extend(newkvs['values']) + self[tid] = createType('Enum', kvs, values) + elif kvs['meta'] == 'Enum' and newkvs['meta'] == 'Constant': + kvs['values'].extend(newkvs['values']) + elif kvs['meta'] == 'Struct' and newkvs['meta'] == 'Struct': + kvs['members'].extend(newkvs['members']) + else: + assert isBuiltin(kvs['meta']) and isBuiltin(newkvs['meta']), \ + '%s:%s, %s' % (kvs['meta'], newkvs['meta'], tid) + + def register(self, meta, kvs, children=None): + kvs = createType(meta, kvs, children) + tid = sha1ID(kvs['location']) + kvs['id'] = tid + + if tid in self: + self._merge(tid, kvs) + else: + # Verify uniqueness of leftmost 8 digits of 'id'. + assert not self._getByPartialID(tid[:8]) + self[tid] = kvs + + return tid + + def getByLocation(self, location): + for _, atype in self.items(): + if atype.get('location', None) == location: + return atype + print("fatal: bad type location '%s'." % location) + return None + + def _getByPartialID(self, pid): + ret = [] + for key, atype in self.items(): + if key.startswith(pid): + ret.append(atype) + return ret + + def getByPartialID(self, pid): + ret = self._getByPartialID(pid) + if not ret: + print("fatal: bad type id '%s'." % pid) + return None + elif len(ret) != 1: + ids = ', '.join([item['id'][:8] for item in ret]) + print("notice: several candidates[%s] for id '%s'." % (ids, pid)) + return None + + return ret[0] + + +T_STRUCT_STRUCTURE = ''' +typedef struct _${fullname} ${fullname}; +typedef ${fullname} *${fullname}Ptr; +struct _${fullname} { + ${members} +}; +''' + +T_ENUM_STRUCTURE_DECL = ''' +typedef enum { + ${caps_shortname}_${default} = 0, + ${values} + ${caps_shortname}_LAST, +} ${fullname}; + +VIR_ENUM_DECL(${shortname}); +''' + +T_ENUM_STRUCTURE_IMPL = ''' +VIR_ENUM_IMPL(${shortname}, +${indentation}${caps_shortname}_LAST, +${indentation}${array}, +); +''' + +T_MEMBER_DECL = ''' +${type_decl}${gap}${asterisk}${name}${suffix};${comment} +''' + +T_NAMESPACE_PARSE = ''' +if (xmlopt) + def->ns = xmlopt->ns; +if (def->ns.parse) { + if (virXMLNamespaceRegister(ctxt, &def->ns) < 0) + goto error; + if ((def->ns.parse)(ctxt, &def->namespaceData) < 0) + goto error; +} +''' + +T_NAMESPACE_FORMAT_BEGIN = ''' +if (def->namespaceData && def->ns.format) + virXMLNamespaceFormatNS(buf, &def->ns); +''' + +T_NAMESPACE_FORMAT_END = ''' +if (def->namespaceData && def->ns.format) { + if ((def->ns.format)(buf, def->namespaceData) < 0) + return -1; +} +''' + + +def pointer(atype): + if isBuiltin(atype['meta']) and not atype.get('name', None): + return BUILTIN_TYPES.get(atype['meta'])['ctype'] + '*' + return atype['name'] + 'Ptr' + + +def proto(atype, pointer): + if isBuiltin(atype['meta']) and not atype.get('name', None): + return BUILTIN_TYPES.get(atype['meta'])['ctype'] + elif atype['meta'] == 'Struct' and pointer: + return atype['name'] + 'Ptr' + return atype['name'] + + +def gapOf(atype): + if isBuiltin(atype['meta']) and not atype.get('name', None): + return BUILTIN_TYPES.get(atype['meta']).get('gap', ' ') + return ' ' + + +def declareMember(member): + mtype = TypeTable().get(member['_typeid']) + + # + # Helper functions + # + def _declare(type_decl, asterisk, gap, name): + asterisk = '*' if asterisk else '' + if mtype['meta'] in ['Chars', 'UChars']: + suffix = '[%s]' % mtype['structure']['array.size'] + else: + suffix = '' + if member['declare.comment']: + comment = ' /* %s */' % member['declare.comment'] + else: + comment = '' + return render(T_MEMBER_DECL, type_decl=type_decl, + gap=gap, asterisk=asterisk, + name=name, suffix=suffix, comment=comment) + + # + # Main routine + # + code = '' + if member['more']: + code += 'size_t %s;\n' % counterName(member['name']) + code += _declare(pointer(mtype), member['pointer'], gapOf(mtype), + Terms.pluralize(member['name'])) + else: + code += _declare(proto(mtype, member.get('pointer', False)), + False, gapOf(mtype), member['name']) + if member['specified']: + code += '\nbool %s_specified;' % member['name'] + + return code + + +def flattenMembers(members): + ret = NodeList() + for member in members: + mtype = TypeTable().get(member['_typeid']) + if mtype['meta'] == 'Struct' and mtype['unpack']: + ret.extend(flattenMembers(mtype['members'])) + else: + ret.append(member) + + return ret + + +def makeStructure(writer, atype): + def _makeMembers(): + blocks = BlockAssembler() + for member in flattenMembers(atype['members']): + blocks.append(declareMember(member)) + + if atype.get('namespace', False): + blocks.append('void *namespaceData;') + blocks.append('virXMLNamespace ns;') + + return blocks.output() + + if atype.get('unpack', False): + writer.write(atype, 'structure', '.h', _makeMembers()) + return + + if atype['meta'] == 'Struct': + members = indent(_makeMembers(), 1) + decl = render(T_STRUCT_STRUCTURE, + fullname=atype['name'], members=members) + + writer.write(atype, 'structure', '.h', decl) + elif atype['meta'] == 'Enum': + assert atype['name'], atype + shortname = atype['name'][:-4] + caps_shortname = Terms.allcaps(shortname) + default = atype['structure'].get('enum.default', 'none') + atype['values'].insert(0, default) + + atype['_values_map'] = OrderedDict() + for value in atype['values']: + caps_value = Terms.allcaps(value).replace('.', '') + item = '%s_%s' % (caps_shortname, caps_value) + atype['_values_map'][value] = item + + values = ',\n'.join(list(atype['_values_map'].values())[1:]) + ',' + decl = render(T_ENUM_STRUCTURE_DECL, + shortname=shortname, + caps_shortname=Terms.allcaps(shortname), + fullname=atype['name'], + default=Terms.allcaps(default), + values=indent(values, 1)) + writer.write(atype, 'structure', '.h', decl) + + array = ', '.join(['"%s"' % v for v in atype['values']]) + impl = render(T_ENUM_STRUCTURE_IMPL, + shortname=shortname, + caps_shortname=Terms.allcaps(shortname), + indentation=align('VIR_ENUM_IMPL'), + default=default, array=array) + writer.write(atype, 'structure', '.c', impl) + + +T_CLEAR_FUNC_IMPL = ''' +void +${funcname}(${typename}Ptr def) +{ + if (!def) + return; + + ${body} +} +''' + +T_CLEAR_FUNC_DECL = ''' +void +${funcname}(${typename}Ptr def); +''' + + +def clearMember(member): + mtype = TypeTable().get(member['_typeid']) + if member['more']: + name = 'def->%s[i]' % Terms.pluralize(member['name']) + else: + name = 'def->%s' % member['name'] + + funcname = mtype['clearfunc'].get('name', None) + if not funcname and mtype['name']: + funcname = mtype['name'] + 'Clear' + + code = '' + if funcname and mtype['meta'] != 'Enum': + amp = '' if member['pointer'] else '&' + code = '%s(%s%s);' % (funcname, amp, name) + if member['pointer']: + code += '\nVIR_FREE(%s);' % name + elif mtype['meta'] == 'String': + code = 'VIR_FREE(%s);' % name + elif mtype['meta'] in ['Chars', 'UChars']: + code = 'memset(%s, 0, sizeof(%s));' % (name, name) + elif not member['more']: + code = '%s = 0;' % name + + if member['more']: + if code: + name = Terms.pluralize(member['name']) + counter = counterName(member['name']) + if singleline(code): + code = render(T_LOOP_SINGLE, counter=counter, body=code) + else: + code = render(T_LOOP_MULTI, + counter=counter, body=indent(code, 2)) + code += '\nVIR_FREE(def->%s);\ndef->%s = 0;' % (name, counter) + else: + if member['specified']: + code += '\n%s_specified = false;' % name + + return code + + +T_CLEAR_NAMESPACE = ''' +if (def->namespaceData && def->ns.free) + (def->ns.free)(def->namespaceData); +''' + + +def makeClearFunc(writer, atype): + clearfunc = atype['clearfunc'] + + if atype['unpack']: + return + + blocks = BlockAssembler() + for member in flattenMembers(atype['members']): + blocks.append(clearMember(member)) + + funcname = clearfunc.get('name', None) + if not funcname: + funcname = atype['name'] + 'Clear' + + if atype.get('namespace', False): + blocks.append(T_CLEAR_NAMESPACE.strip()) + + impl = render(T_CLEAR_FUNC_IMPL, funcname=funcname, typename=atype['name'], + body=indent(blocks.output('\n\n'), 1)) + + decl = render(T_CLEAR_FUNC_DECL, funcname=funcname, typename=atype['name']) + + writer.write(atype, 'clearfunc', '.h', decl) + writer.write(atype, 'clearfunc', '.c', impl) + + +# +# Templates for parsing member block +# +T_SET_DEFAULT_VALUE = 'def->${name} = ${default};' +T_READ_XML_BY_XPATH = '${mdvar} = ${xfuncname}(${xpath}, ctxt);' +T_READ_ATTR_BY_PROP = '${mdvar} = virXMLPropString(curnode, "${oname}");' +T_READ_ELEM_BY_PROP = '${mdvar} = virXMLChildNode(curnode, "${oname}");' + +T_READ_NODES = '${number} = virXMLChildNodeSet(curnode, "${oname}", &nodes);' +T_READ_NODES_CTXT = '${number} = virXPathNodeSet("./${oname}", ctxt, &nodes);' + +T_PARSE_MEMBER_MORE = ''' +if (${number} > 0) { + size_t i; + xmlNodePtr node; + + if (VIR_ALLOC_N(def->${name}, ${number}) < 0) + goto error; + + for (i = 0; i < ${number}; i++) { + node = nodes[i]; + ${item} + } + def->${counter} = ${number}; + VIR_FREE(nodes); +} else if (${number} < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Invalid ${oname} element found.")); + goto error; +}${report_missing} +''' + +T_GENERATE_ON_MISSING = ''' +if (${funcname}(def->${name}) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot generate a random uuid for ${name}")); + goto error; +} +''' + +T_REPORT_INVALID_WITH_INSTANCE = ''' + virReportError(VIR_ERR_XML_ERROR, + _("Invalid '${oname}' setting '%s' in '%s'"), + ${mdvar}, instanceName); +''' + +T_REPORT_INVALID_WITHOUT_INSTANCE = ''' + virReportError(VIR_ERR_XML_ERROR, + _("Invalid '${oname}' setting '%s'"), + ${mdvar}); +''' + +T_CHECK_INVALID_ERROR = ''' +if (${tmpl}) { + ${report_err} + goto error; +} +''' + +T_REPORT_MISSING_WITH_INSTANCE = ''' + virReportError(VIR_ERR_XML_ERROR, + _("Missing '${oname}' setting in '%s'"), + instanceName); +''' + +T_REPORT_MISSING_WITHOUT_INSTANCE = ''' + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Missing '${oname}' setting")); +''' + +T_MISSING_ERROR = ''' +{ + ${report_err} + goto error; +} +''' + +T_CHECK_MISSING_ERROR = 'if (${mdvar} == NULL) ' + T_MISSING_ERROR.strip() + +T_ALLOC_MEMORY = ''' +if (VIR_ALLOC(def->${name}) < 0) + goto error; +''' + +T_STRUCT_ASSIGNMENT_TEMPLATE = ''' +if (${funcname}(${mdvar}, ${amp}${refname}${args}) < 0) + goto error; +''' + +T_POST_PARSE_MEMBER = ''' +if (${funcname}Post(name, ${amp}def->${name}, NULL) < 0) + goto error; +''' + + +def makeActualArgs(formal_args, actual_args): + def _findValue(name): + if actual_args: + for arg in actual_args: + if arg['name'] == name: + return arg['value'] + return None + + if not formal_args: + return [] + + args = [] + for arg in formal_args: + value = _findValue(arg['name']) + if value: + args.append(value) + elif arg.get('pointer'): + args.append(arg['name']) + else: + assert arg.get('type'), arg + argtype = TypeTable().getByLocation(arg['type']) + if argtype['meta'].startswith('Bool'): + args.append('false') + else: + args.append('0') + + return args + + +def parseMember(member, atype, tmpvars, pack=None): + if member['parse.disable']: + return None + + mtype = TypeTable().get(member['_typeid']) + + if mtype['pack'] or mtype['union']: + block = BlockAssembler() + for child in mtype['members']: + block.append(parseMember(child, atype, tmpvars, member)) + return block.output('\n\n') + + # + # Helper functions + # + def _makeXPath(): + if member['tag'] == 'attribute': + return '"string(./@%s)"' % member['id'] + + if mtype['meta'] == 'Struct': + return '"./%s"' % member['id'] + return '"string(./%s[1])"' % member['id'] + + def _reportInvalid(mdvar): + if atype['parsefunc'] and atype['parsefunc'].get('args.instname'): + return render(T_REPORT_INVALID_WITH_INSTANCE, + oname=member['id'], mdvar=mdvar) + return render(T_REPORT_INVALID_WITHOUT_INSTANCE, + oname=member['id'], mdvar=mdvar) + + def _reportMissing(): + if atype['parsefunc'] and atype['parsefunc'].get('args.instname'): + return render(T_REPORT_MISSING_WITH_INSTANCE, + oname=member['id']) + return render(T_REPORT_MISSING_WITHOUT_INSTANCE, + oname=member['id']) + + def _setDefaultValue(name): + if not member['parse.default']: + return None + return render(T_SET_DEFAULT_VALUE, + name=name, default=member['parse.default']) + + def _readXMLByXPath(mdvar, xleaf): + tag = member['tag'] + if not tag: + return 'node = ctxt->node;' + + if tag == 'attribute': + return render(T_READ_ATTR_BY_PROP, + mdvar=mdvar, oname=member['id']) + + if tag == 'element' and mtype['meta'] == 'Struct': + if mtype['parsefunc'].get('args.noctxt', False): + return render(T_READ_ELEM_BY_PROP, + mdvar=mdvar, oname=member['id']) + + if mtype['meta'] in ['Struct']: + xfuncname = 'virXPathNode' + else: + xfuncname = 'virXPathString' + return render(T_READ_XML_BY_XPATH, + mdvar=mdvar, xfuncname=xfuncname, xpath=xleaf) + + def _assignValue(name, mdvar): + refname = 'def->' + name + if mtype['unpack'] and not pack: + refname = 'def' + + if mtype['meta'] in ['Struct']: + funcname = mtype['parsefunc'].get('name', None) + if not funcname: + funcname = mtype['name'] + 'ParseXML' + + tmpl = '' + if member['pointer']: + tmpl += T_ALLOC_MEMORY + tmpl += T_STRUCT_ASSIGNMENT_TEMPLATE + if member.get('parse.post', False): + tmpl += T_POST_PARSE_MEMBER + + args = [] + if not mtype['parsefunc'].get('args.noctxt'): + args.append('ctxt') + if mtype['parsefunc'].get('args.instname'): + if member['parse.instname']: + args.append(member['parse.instname']) + else: + args.append('instanceName') + if mtype['parsefunc'].get('args.parent'): + args.append('def') + + args.extend(makeActualArgs(mtype['parsefunc'].get('args'), + member['parse.args'])) + args = ', '.join(args) + if args: + args = ', ' + args + + if refname == 'def' or member['pointer']: + amp = '' + else: + amp = '&' + + tmpl = render(tmpl, funcname=funcname, args=args, + alignment=align('if (' + funcname), + amp=amp, mdvar=mdvar) + elif mtype['meta'] == 'Enum': + tmpl = '(def->${name} = %sFromString(%s)) <= 0' \ + % (mtype['name'], mdvar) + elif mtype['meta'] == 'Constant' and mtype['values'][0] == 'yes' \ + or mtype['meta'] == 'Bool.yes_no': + tmpl = 'virStringParseYesNo(${mdvar}, &def->${name}) < 0' + elif mtype['meta'] == 'Constant' and mtype['values'][0] == 'on' \ + or mtype['meta'] == 'Bool.on_off': + tmpl = 'virStringParseOnOff(${mdvar}, &def->${name}) < 0' + elif mtype['meta'] == 'Constant' or mtype['meta'] == 'Bool': + tmpl = 'virStrToBool(${mdvar}, "%s", &def->${name}) < 0' \ + % mtype['values'][0] + elif mtype['name'] or mtype['parsefunc'].get('name'): + funcname = mtype['parsefunc'].get('name', None) + if not funcname: + funcname = mtype['name'] + 'ParseXML' + if mtype['meta'] in ['UChars', 'Chars']: + aof = '' + else: + aof = '&' + tmpl = '%s(${mdvar}, %sdef->${name}) < 0' % (funcname, aof) + elif mtype['meta'] == 'String': + tmpl = 'def->${name} = g_strdup(${mdvar});' + else: + tmpl = None + builtin = BUILTIN_TYPES.get(mtype['meta'], None) + if builtin: + tmpl = builtin.get('conv', None) + if tmpl: + tmpl += ' < 0' + + if not tmpl: + return None + + if mdvar.endswith('Str') and \ + (mtype['meta'] != 'String' or mtype['name']): + tmpl = render(T_CHECK_INVALID_ERROR, + tmpl=tmpl, report_err=_reportInvalid(mdvar)) + + ret = render(tmpl, refname=refname, name=name, + oname=member['id'], mdvar=mdvar) + + if member['specified'] and not member['more']: + ret += '\ndef->%s_specified = true;' % name + return ret + + def _assignValueOnCondition(name, mdvar): + block = _assignValue(name, mdvar) + if not block: + return None + + ret = None + if member['opt']: + if singleline(block): + ret = render(T_IF_CONDITION_SINGLE, condition=mdvar, + body=block) + else: + ret = render(T_IF_CONDITION_MULTI, condition=mdvar, + body=indent(block, 1)) + else: + ret = render(T_CHECK_MISSING_ERROR, + mdvar=mdvar, report_err=_reportMissing()) + if block: + ret += '\n\n' + block + return ret + + # + # Main routine + # + if not member['tag'] or member['id'] == '_Any_': + return None + + # For sequence-type member + if member['more']: + assert member['tag'] == 'element' + node_num = 'n%sNodes' % Terms.upperInitial(member['id']) + tmpvars.append(node_num) + tmpvars.append('nodes') + + if pack: + seqname = Terms.pluralize(pack['name']) + counter = counterName(pack['name']) + else: + seqname = Terms.pluralize(member['name']) + counter = counterName(member['name']) + + name = seqname + '[i]' + report_missing = '' + if not member['opt']: + report_missing = ' else ' + render(T_MISSING_ERROR, + report_err=_reportMissing()) + + if mtype['meta'] != 'Struct': + item = 'def->%s = virXMLNodeContentString(node);' % name + else: + item = _assignValue(name, 'node') + + if atype['parsefunc'] and atype['parsefunc'].get('args.noctxt'): + tmpl = T_READ_NODES + else: + tmpl = T_READ_NODES_CTXT + tmpl += T_PARSE_MEMBER_MORE + return render(tmpl, name=seqname, counter=counter, + number=node_num, item=indent(item, 2), + report_missing=report_missing, oname=member['id']) + + # For ordinary member + if pack: + arrow = '->' if pack['pointer'] else '.' + name = pack['name'] + arrow + member['name'] + else: + name = member['name'] + + blocks = BlockAssembler() + mdvar = member['name'] + mdvar += 'Node' if mtype['meta'] in ['Struct'] else 'Str' + tmpvars.append(mdvar) + xpath = _makeXPath() + + blocks.append(_setDefaultValue(name)) + blocks.append(_readXMLByXPath(mdvar, xpath)) + blocks.append(_assignValueOnCondition(name, mdvar)) + return blocks.output() + + +def align(funcname): + return ' ' * (len(funcname) + 1) + + +T_PARSE_FUNC_DECL = ''' +int +${funcname}(${formal_args}); +''' + +T_PARSE_FUNC_IMPL = ''' +int +${funcname}(${formal_args}) +{ + ${declare_vars} + + ${body} + + ${end} + + error: + ${cleanup_vars} + ${typename}Clear(def); + return -1; +} +''' + +T_PARSE_FUNC_POST_INVOKE = ''' +if (${funcname}Post(${actual_args}) < 0) + goto error; +''' + +T_FUNC_EXTRA_ARGS = ''' +${alignment}${ctype}${gap}${name} +''' + + +def _handleTmpVars(tmpvars, noctxt): + heads, tails = [], [] + tmpvars = dedup(tmpvars) + for var in tmpvars: + if var == 'nodes': + heads.append('xmlNodePtr *nodes = NULL;') + tails.append('VIR_FREE(nodes);') + elif var.endswith('Str'): + heads.append('g_autofree char *%s = NULL;' % var) + elif var.endswith('Node'): + heads.append('xmlNodePtr %s = NULL;' % var) + else: + assert var.endswith('Nodes') and var.startswith('n') + heads.append('int %s = 0;' % var) + + if not noctxt: + heads.append('xmlNodePtr save = ctxt->node;') + heads.append('ctxt->node = curnode;') + tails.insert(0, 'ctxt->node = save;') + return '\n'.join(heads), '\n'.join(tails) + + +def findMember(mid, members): + members = list(filter(lambda m: m['id'] == mid, members)) + if not members: + return None + return assertOnlyOne(members) + + +def makeParseFunc(writer, atype): + if atype['pack'] or atype['union']: + return + + parsefunc = atype['parsefunc'] + funcname = parsefunc.get('name', None) + if not funcname: + funcname = atype['name'] + 'ParseXML' + + alignment = align(funcname) + + if atype['unpack']: + if not atype.get('_parent'): + print("fatal: unpack is set on direct child(%s) of <define>." + % atype['location']) + sys.exit(-1) + typename = atype['_parent']['name'] + else: + typename = atype['name'] + + formal_args = ['xmlNodePtr curnode', typename + 'Ptr def'] + actual_args = ['curnode', 'def'] + + if not parsefunc.get('args.noctxt', False): + formal_args.append('xmlXPathContextPtr ctxt') + actual_args.append('ctxt') + + if parsefunc.get('args.instname'): + formal_args.append('const char *instanceName') + actual_args.append('instanceName') + + if parsefunc.get('args.parent'): + assert atype.get('_parent') + formal_args.append('%sPtr parentdef' % atype['_parent']['name']) + actual_args.append('parentdef') + + if atype.get('namespace'): + formal_args.append('virNetworkXMLOptionPtr xmlopt') + actual_args.append('xmlopt') + + formal_args.extend(createFormalArgs(parsefunc.get('args'), alignment)) + actual_args.extend([arg['name'] for arg in parsefunc.get('args', [])]) + + kwargs = {'funcname': funcname, 'typename': typename, + 'formal_args': (',\n%s' % alignment).join(formal_args)} + + tmpvars = [] + blocks = BlockAssembler() + for member in atype['members']: + blocks.append(parseMember(member, atype, tmpvars)) + + decl = renderByDict(T_PARSE_FUNC_DECL, kwargs) + + if parsefunc.get('post', False): + if not parsefunc.get('post.notmpvars', False): + for var in tmpvars: + if var.endswith('Str') or var.endswith('Node') or \ + var.endswith('Nodes') and var.startswith('n'): + actual_args.append(var) + + actual_args = ', '.join(actual_args) if actual_args else '' + post = render(T_PARSE_FUNC_POST_INVOKE, funcname=funcname, + actual_args=actual_args) + blocks.append(post) + + if not parsefunc.get('post.notmpvars', False): + for var in tmpvars: + line = None + if var.endswith('Str'): + line = 'const char *' + var + elif var.endswith('Node'): + line = 'xmlNodePtr ' + var + elif var.endswith('Nodes') and var.startswith('n'): + line = 'int ' + var + + if line: + formal_args.append(line) + + connector = ',\n' + alignment + 4 * ' ' + decl += '\n' + render(T_PARSE_FUNC_DECL, funcname=funcname + 'Post', + formal_args=connector.join(formal_args)) + + writer.write(atype, 'parsefunc', '.h', decl) + + if atype['namespace']: + blocks.append(T_NAMESPACE_PARSE.strip()) + + kwargs['body'] = indent(blocks.output('\n\n'), 1) + + declare_vars, cleanup_vars = _handleTmpVars(tmpvars, + parsefunc.get('args.noctxt')) + kwargs['declare_vars'] = indent(declare_vars, 1) + kwargs['cleanup_vars'] = indent(cleanup_vars, 1) + + end = '' + if not parsefunc.get('args.noctxt', False): + end = 'ctxt->node = save;\n' + end += 'return 0;' + + kwargs['end'] = indent(end, 1) + + impl = renderByDict(T_PARSE_FUNC_IMPL, kwargs) + writer.write(atype, 'parsefunc', '.c', impl) + + +T_FORMAT_FUNC_DECL = ''' +int +${funcname}(${formal_args}); +''' + +T_FORMAT_FUNC_IMPL = ''' +int +${funcname}(${formal_args}) +{ + if (!def) + return 0; + + ${format_members} + + return 0; +} +''' + +T_FORMAT_ELEMENTS = ''' +virBufferAddLit(buf, ">\\n"); + +virBufferAdjustIndent(buf, 2); + +${elements} + +virBufferAdjustIndent(buf, -2); +virBufferAsprintf(buf, "</%s>\\n", name); +''' + +T_FORMAT_SHORTHAND = ''' +if (!(${checks})) { + virBufferAddLit(buf, "/>\\n"); + return 0; +} +''' + +T_IF_CONDITION_SINGLE = ''' +if (${condition}) + ${body} +''' + +T_IF_CONDITION_MULTI = ''' +if (${condition}) { + ${body} +} +''' + +T_LOOP_SINGLE = ''' +if (def->${counter} > 0) { + size_t i; + for (i = 0; i < def->${counter}; i++) + ${body} +} +''' + +T_LOOP_MULTI = ''' +if (def->${counter} > 0) { + size_t i; + for (i = 0; i < def->${counter}; i++) { + ${body} + } +} +''' + +T_FORMAT_MEMBER_OF_ENUM = ''' +const char *str = ${fullname}ToString(${var}); +if (!str) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown ${oname} type %d"), + ${var}); + return -1; +} +virBufferAsprintf(buf, "${layout}", str); +''' + +T_FORMAT_PRECHECK_DECLARE = ''' +bool +${funcname}(${formal_args}); +''' + + +def createFormalArgs(args, alignment): + if not args: + return [] + + lines = [] + for arg in args: + gap = ' ' + ctype = arg.get('ctype', None) + if not ctype: + argtype = TypeTable().getByLocation(arg['type']) + if isBuiltin(argtype['meta']): + ctype = BUILTIN_TYPES.get(argtype['meta'])['ctype'] + gap = gapOf(argtype) + else: + assert argtype['meta'] in ['Struct'] + ctype = argtype['name'] + + name = arg['name'] + if arg.get('pointer', False): + name = '*' + name + + line = render(T_FUNC_EXTRA_ARGS, alignment=alignment, + ctype=ctype, name=name, gap=gap) + lines.append(line) + + return lines + + +def formatMember(member, require, ret_checks, ret_decls, atype, pack=None): + if member['format.disable']: + return None + + mtype = TypeTable().get(member['_typeid']) + + if mtype['pack'] or mtype['union']: + checks = [] + block = BlockAssembler() + for child in mtype['members']: + block.append(formatMember(child, require, checks, ret_decls, + atype, member)) + checks = dedup(checks) + ret_checks.append(' || '.join(checks)) + return block.output('\n\n') + + # + # Helper functions. + # + def _checkForMember(m, var): + t = TypeTable().get(m['_typeid']) + + ret = None + if m['pointer']: + ret = var + elif m['specified']: + ret = var + '_specified' + if ret.startswith('&'): + ret = ret[1:] + elif t['meta'] in ['Chars', 'UChars']: + ret = var + '[0]' + elif t['meta'] == 'Enum': + ret = var + elif isBuiltin(t['meta']): + ret = var + if t['name']: + ret = '%sCheck(%s)' % (t['name'], var) + + return ret + + def _makeVar(): + if mtype['unpack'] and not pack: + return 'def' + + curname = member['name'] + if member['more']: + curname = Terms.pluralize(member['name']) + '[i]' + + if pack: + packname = pack['name'] + if pack['more']: + packname = Terms.pluralize(pack['name']) + '[i]' + if pack['hint'] == 'union': + var = packname + else: + arrow = '->' if pack['pointer'] else '.' + var = packname + arrow + curname + else: + var = curname + var = 'def->' + var + + if mtype['meta'] == 'Struct': + if not member['pointer']: + var = '&' + var + elif mtype['meta'] != 'Enum' and mtype['name']: + var = '&' + var + + return var + + def _checkOnCondition(var): + if member['format.nocheck']: + return None + + if member.get('format.precheck'): + alist = [a['name'] for a in atype['formatfunc'].get('args', [])] + args = ', '.join(alist) + if args: + args = ', ' + args + + precheck = member['format.precheck'] + checks = render('${funcname}(${var}, def${args})', + funcname=precheck, var=var, args=args) + + ret_decls.append(_makePrecheckDeclare(precheck, var)) + return checks + + if mtype['unpack']: + return mtype.get('check_all_members', None) + + if member['more']: + return None + + checks = _checkForMember(member, var) + return checks + + def _handleMore(code): + code = indent(code, 2) + if pack: + counter = counterName(pack['name']) + else: + counter = counterName(member['name']) + ret_checks.append('def->' + counter) + if singleline(code): + return render(T_LOOP_SINGLE, counter=counter, body=code) + + return render(T_LOOP_MULTI, counter=counter, body=code) + + def _makePrecheckDeclare(funcname, var): + assert funcname + if mtype['unpack']: + typename = mtype['_parent']['name'] + else: + typename = proto(mtype, False) + + if atype['unpack']: + parentname = atype['_parent']['name'] + else: + parentname = proto(atype, False) + + asterisk = '*' if mtype['unpack'] or var.startswith('&') else '' + args = ['const %s %sdef' % (typename, asterisk)] + args.append('const %s *parent' % parentname) + args.extend(createFormalArgs(atype['formatfunc'].get('args'), + align(funcname))) + fargs = (',\n%s' % align(funcname)).join(args) + return render(T_FORMAT_PRECHECK_DECLARE, + funcname=funcname, formal_args=fargs) + + def _format(layout, var): + tmpl = '${funcname}(buf, "${layout}", ${var}${args})' + + args = [] + funcname = 'virBufferAsprintf' + has_return = False + if mtype['meta'] == 'Struct': + if mtype['formatfunc'].get('name', None): + funcname = mtype['formatfunc']['name'] + else: + funcname = mtype['name'] + 'FormatBuf' + + args.extend(makeActualArgs(mtype['formatfunc'].get('args'), + member['format.args'])) + has_return = True + elif mtype['meta'] == 'Enum': + tmpl = render(T_FORMAT_MEMBER_OF_ENUM, + fullname=mtype['name'], + oname=member['id']) + elif mtype['name'] or mtype['formatfunc'].get('name', None): + if mtype['formatfunc'].get('name', None): + funcname = mtype['formatfunc']['name'] + else: + funcname = mtype['name'] + 'FormatBuf' + has_return = True + elif mtype['meta'] in ['String', 'Chars', 'UChars']: + funcname = 'virBufferEscapeString' + elif mtype['meta'] == 'Bool.yes_no': + var = '%s ? "yes" : "no"' % var + elif mtype['meta'] == 'Bool.on_off': + var = '%s ? "on" : "off"' % var + elif mtype['meta'] == 'Bool': + pass + elif mtype['meta'] == 'Constant': + tmpl = 'virBufferAddLit(buf, "${layout}")' + layout = " %s='%s'" % (member['id'], mtype['values'][0]) + + args = ', '.join(args) + if args: + args = ', ' + args + + code = render(tmpl, funcname=funcname, layout=layout, + var=var, args=args) + if has_return: + code += ' < 0' + code = render(T_IF_CONDITION_SINGLE, + condition=code, body='return -1;') + elif mtype['meta'] not in ['Enum']: + code += ';' + + return code + + def _handleAttr(tagname, var): + if member['tag'] != 'attribute': + return None + + fmt = '%s' + if member['format.fmt']: + fmt = member['format.fmt'] + elif isBuiltin(mtype['meta']): + fmt = BUILTIN_TYPES[mtype['meta']].get('fmt', '%s') + + layout = " %s='%s'" % (tagname, fmt) + return _format(layout, var) + + def _handleElem(tagname, var): + if member['tag'] == 'attribute': + return None + + if mtype['meta'] != 'Struct': + layout = '<%s>%%s</%s>\\n' % (tagname, tagname) + else: + layout = tagname + + code = _format(layout, var) + return code + + # + # Main routine + # + assert require in ['attribute', 'element'] + if not member.get('tag', None): + return None + + var = _makeVar() + + ret = None + tagname = member['id'] + if require == 'attribute': + ret = _handleAttr(tagname, var) + else: + ret = _handleElem(tagname, var) + + if not ret: + return None + + checks = _checkOnCondition(var) + if checks: + ret = indent(ret, 1) + if singleline(ret): + ret = render(T_IF_CONDITION_SINGLE, + condition=checks, body=ret) + else: + ret = render(T_IF_CONDITION_MULTI, + condition=checks, body=ret) + + if member['more']: + return _handleMore(ret) + + if checks: + if '&&' in checks or '||' in checks: + checks = '(%s)' % checks + ret_checks.append(checks) + + return ret + + +def makeFormatFunc(writer, atype): + formatfunc = atype['formatfunc'] + + # + # Helper functions. + # + def _reorder(children, order): + if not order: + return children + + ret = NodeList() + for mid in order: + ret.append(findMember(mid, children)) + + if len(order) < len(children): + for child in children: + if child['id'] not in order: + ret.append(child) + + return ret + + def _formatMembers(prechecks): + attrs = [] + elems = [] + check_attrs = [] + check_elems = [] + members = _reorder(atype['members'], formatfunc.get('order', None)) + + for member in members: + attr = formatMember(member, 'attribute', + check_attrs, prechecks, atype) + if attr: + attrs.append(attr) + + elem = formatMember(member, 'element', + check_elems, prechecks, atype) + if elem: + elems.append(elem) + + ret = BlockAssembler() + if len(check_attrs) == len(attrs) \ + and len(check_elems) == len(elems): + checks = ' || '.join(check_attrs + check_elems) + atype['check_all_members'] = checks + ret.append(render(T_IF_CONDITION_SINGLE, + condition='!(%s)' % checks, + body='return 0;')) + + ret.append('virBufferAsprintf(buf, "<%s", name);') + + if atype['namespace']: + ret.append(T_NAMESPACE_FORMAT_BEGIN.strip()) + + ret.extend(attrs) + + if elems: + if not formatfunc.get('shorthand.ignore'): + if attrs and len(check_elems) == len(elems): + checks = ' || '.join(check_elems) + ret.append(render(T_FORMAT_SHORTHAND, checks=checks)) + + elements = '\n\n'.join(elems) + if atype['namespace']: + elements += '\n\n' + T_NAMESPACE_FORMAT_END.strip() + + ret.append(render(T_FORMAT_ELEMENTS, elements=elements)) + else: + ret.append('virBufferAddLit(buf, "/>\\n");') + + return ret.output('\n\n') + + # + # Main routine of formating. + # + if atype['pack'] or atype['union']: + return + + if formatfunc.get('name', None): + funcname = formatfunc['name'] + else: + funcname = atype['name'] + 'FormatBuf' + + if atype['unpack']: + typename = atype['_parent']['name'] + else: + typename = atype['name'] + + alignment = align(funcname) + + args = [{'name': 'buf', 'ctype': 'virBufferPtr'}, + {'name': 'name', 'ctype': 'const char', 'pointer': True}, + {'name': 'def', 'ctype': 'const ' + typename, 'pointer': True}] + + args.extend(formatfunc.get('args', [])) + + formal_args = createFormalArgs(args, alignment) + formal_args = (',\n%s' % alignment).join(formal_args) + + kwargs = {'funcname': funcname, 'formal_args': formal_args} + + prechecks = [] + format_members = _formatMembers(prechecks) + + decl = renderByDict(T_FORMAT_FUNC_DECL, kwargs) + if prechecks: + prechecks = dedup(prechecks) + decl += '\n\n' + '\n\n'.join(prechecks) + writer.write(atype, 'formatfunc', '.h', decl) + + kwargs['format_members'] = indent(format_members, 1) + + impl = renderByDict(T_FORMAT_FUNC_IMPL, kwargs) + writer.write(atype, 'formatfunc', '.c', impl) + + +T_DIRECTIVE_JSON = ''' +<!-- VIRT:DIRECTIVE { + ${items} +} --> +''' + + +def dumpJson(atype): + def _dumpJson(obj, compact=False): + lines = BlockAssembler() + for key, value in obj.items(): + if key.startswith('_') or key in ['tag', 'output']: + continue + if not value: + continue + if key == 'name' and value == obj['id']: + continue + if key == 'meta' and value == 'Member': + mtype = TypeTable().get(obj['_typeid']) + desc = '%s:%s' % (mtype['meta'], mtype['id'][:8]) + lines.append('"type": "%s"' % desc) + continue + if key == 'members': + block = BlockAssembler() + for member in value: + block.append(' ' + _dumpJson(member, True)) + lines.append('"members": [\n%s\n]' % block.output(',\n')) + continue + if key in ['_env']: + value = _dumpJson(value, True) + else: + value = json.dumps(value) + lines.append('"%s": %s' % (key, value)) + + if compact: + return '{' + lines.output(', ') + '}' + return lines.output(',\n') + + return _dumpJson(atype) + + +def showDirective(atype): + print('\n###### Directive ######\n') + items = indent(dumpJson(atype), 1, 2) + print(render(T_DIRECTIVE_JSON, items=items)) diff --git a/rng2c/generator.py b/rng2c/generator.py new file mode 100755 index 0000000..d3ab64d --- /dev/null +++ b/rng2c/generator.py @@ -0,0 +1,504 @@ +#!/usr/bin/env python +# +# Copyright (C) 2020 Shandong Massclouds Co.,Ltd. +# +# 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.1 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, see +# <http://www.gnu.org/licenses/>. +# +# References: +# http://relaxng.org/spec-20011203.html +# https://www.w3.org/TR/xmlschema-2/#decimal +# + +import os +import sys +import json +import argparse +from copy import deepcopy +from datetime import datetime +from xml.dom import minidom + +from utils import assertOnlyOne, assertObj, Terms, deepupdate +from directive import TypeTable, NodeList, createMember +from directive import initDirectiveSchema, makeStructure +from directive import makeClearFunc, makeParseFunc, makeFormatFunc +from directive import verifyType, showDirective, isBuiltin, BUILTIN_TYPES + +g_rngs = [] +g_defines = {} +g_touches = [] + +VIRT_DIRECTIVE_HEAD = 'VIRT:DIRECTIVE' + +TOOL_DESC = ''' +Generate c-language code based on relax-ng files. + +Subcommand:\n + list: List all types discovered by this tool. Display three fields + SHORTID/MEAT/LOCATION for each type, SHORTID is the type's + sha1-id, only reserve the leftmost 8 digits; META is the + type's meta, includes 'Struct', 'Enum', 'String', 'UInt', + 'Bool', etc.; LOCATION indicates where the type derives from.\n + show: Show the target type's directives and its code for preview. + Specify target type by its SHORTID or LOCATION. The option + '-k' indicates the kinds of code for preview.\n + generate: Generate code. To be called by Makefile. + It needs option -k to filter output. + +Option:\n + -k: Specify kinds to filter code output. More than one kind can be + specified, 's' for structure; 'c' for clearfunc; 'p' for parsefunc; + 'f' for formatfunc.\n + The option '-k' is only valid for show and generate. +''' + + +def _getText(xnode): + return assertOnlyOne(xnode.childNodes).data + + +def _resetParentNames(kvs, name): + kvs['id'] = name + kvs['_nodepath'].append((name, '.' + kvs['tag'])) + + +def collectJson(target, line, parent): + kvs = json.loads(line) + if 'PRESERVE' in kvs: + key = kvs.pop('PRESERVE') + kvs['_anchor'] = len(parent.get('_nodepath', [])) + target.setdefault('_preserve_table', {}) + target['_preserve_table'][key] = kvs + target['_preserve'] = key + elif 'APPLY' in kvs: + key = kvs.pop('APPLY') + kvs = parent['_preserve_table'][key] + target.update(deepcopy(kvs)) + else: + target.update(kvs) + + +def _makekvs(xnode, directives, parentkvs): + name = xnode.getAttribute('name') + kvs = {'id': name, 'tag': xnode.tagName, + '_env': {}, '_nodepath': [], '_preserve_table': {}} + kvs['_env'].update(parentkvs['_env']) + kvs['_preserve_table'].update(parentkvs.get('_preserve_table', {})) + kvs['_nodepath'].extend(parentkvs.get('_nodepath', [])) + if xnode.tagName == 'choice': + kvs['_nodepath'].append(('', 'choice')) + elif xnode.tagName == 'data': + kvs['_nodepath'].append((xnode.getAttribute('type'), '.data')) + elif name: + kvs['_nodepath'].append((name, '.' + xnode.tagName)) + return deepupdate(kvs, directives) + + +def _traverse(xchildren, parentkvs): + directive = {} + nodes = NodeList() + for xchild in xchildren: + if xchild.nodeType is xchild.COMMENT_NODE: + line = xchild.data.strip() + if line.startswith(VIRT_DIRECTIVE_HEAD): + collectJson(directive, line[len(VIRT_DIRECTIVE_HEAD):], + parentkvs) + elif xchild.nodeType is xchild.ELEMENT_NODE: + if xchild.getAttribute('ns'): + continue + if not verifyType(directive): + sys.exit(-1) + childkvs = _makekvs(xchild, directive, parentkvs) + directive = {} + opFunc = globals().get('op%s' % Terms.camelize(xchild.tagName)) + assert opFunc, "Unsupported tag '%s'" % xchild.tagName + nodes.extend(opFunc(childkvs, parentkvs, xchild)) + return nodes + + +def opGrammar(kvs, parentkvs, _): + global g_touches + global g_rngs + g_rngs.append(kvs['_env']['rng']) + path = kvs['_env']['topdir'] + '/docs/schemas/' + kvs['_env']['rng'] + doc = minidom.parse(path) + grammar = doc.getElementsByTagName('grammar')[0] + _traverse(grammar.childNodes, kvs) + if 'start.xnode' in kvs: + for touch in g_touches: + opRef({'id': touch}, {}, None) + g_touches = [] + _traverse(kvs['start.xnode'].childNodes, kvs['start.kvs']) + return None + + +def opStart(kvs, parentkvs, xnode): + parentkvs['start.xnode'] = xnode + parentkvs['start.kvs'] = kvs + return None + + +def opInclude(kvs, parentkvs, xnode): + global g_rngs + rng = xnode.getAttribute('href') + if rng not in g_rngs: + kvs['_env']['rng'] = rng + opGrammar(kvs, {}, None) + return None + + +def opDefine(kvs, parentkvs, xnode): + global g_defines + global g_touches + name = assertObj(kvs['id']) + if kvs.pop('TOUCH', False): + g_touches.append(name) + + kvs['_env']['define'] = name + kvs['_xnode'] = xnode + kvs['_nodepath'] = [] + g_defines[name] = kvs + return None + + +def opRef(kvs, parentkvs, _): + global g_defines + ref = kvs['id'] + assert ref in g_defines, "Can't find <define> '%s'." % ref + if not kvs.get('_preserve') and isinstance(g_defines[ref], NodeList): + nodes = g_defines[ref] + if nodes.uniform() == 'Member': + nodes = deepcopy(nodes) + return nodes + + xnode = g_defines[ref].pop('_xnode') + if kvs.get('_preserve'): + kvs['_nodepath'].pop() + kvs = deepupdate(g_defines[ref], kvs) + else: + deepupdate(kvs, g_defines[ref]) + + # Preset it to avoid recursion + save = g_defines[ref] + g_defines[ref] = NodeList() + nodes = _traverse(xnode.childNodes, kvs) + if kvs.get('pack', False): + # Pack all members into a pseudo Struct. + assert nodes.uniform() is 'Member', kvs + typeid = TypeTable().register('Struct', kvs, nodes) + nodes = NodeList(createMember(typeid, kvs)) + + # Rewrite it with NodeList to indicate *PARSED*. + if kvs.get('_preserve'): + g_defines[ref] = save + else: + g_defines[ref] = nodes + + if nodes.uniform() == 'Member': + nodes = deepcopy(nodes) + return nodes + + +def opElement(kvs, parentkvs, xnode): + typeid = TypeTable().getByLocation('String')['id'] + nodes = _traverse(xnode.childNodes, kvs) + if nodes: + if nodes.uniform() == 'Member': + typeid = TypeTable().register('Struct', kvs, nodes) + else: + assert nodes.uniform() == 'Builtin', nodes.uniform() + typeid = assertOnlyOne(nodes)['id'] + return NodeList(createMember(typeid, kvs)) + + +def opOptional(kvs, parentkvs, xnode): + nodes = _traverse(xnode.childNodes, kvs) + assert not nodes or nodes.uniform() == 'Member' + for node in nodes: + node['opt'] = True + return nodes + + +def opAttribute(kvs, parentkvs, xnode): + typeid = TypeTable().getByLocation('String')['id'] + nodes = _traverse(xnode.childNodes, kvs) + if nodes: + node = assertOnlyOne(nodes) + if node['meta'] == 'Value': + kvs['values'] = [node['value']] + meta = kvs.get('meta') + if not meta: + meta = 'Constant' + typeid = TypeTable().register(meta, kvs) + else: + assert nodes.uniform() in ['Builtin', 'Enum'] + typeid = node['id'] + + return NodeList(createMember(typeid, kvs)) + + +def opData(kvs, parentkvs, xnode): + if 'meta' in kvs: + meta = kvs['meta'] + else: + meta = Terms.camelize(xnode.getAttribute('type')) + + typeid = TypeTable().register(meta, kvs) + return NodeList(TypeTable().get(typeid)) + + +def opParam(kvs, parentkvs, xnode): + return None + + +def opChoice(kvs, parentkvs, xnode): + nodes = _traverse(xnode.childNodes, kvs) + if nodes.uniform() == 'Value': + children = [child['value'] for child in nodes] + typeid = TypeTable().register('Enum', kvs, children) + return NodeList(TypeTable().get(typeid)) + + if kvs.get('union'): + # Pack all members into a pseudo Struct. + assert nodes.uniform() is 'Member', kvs + typeid = TypeTable().register('Struct', kvs, nodes) + kvs['id'] = kvs['union'] + kvs['hint'] = 'union' + nodes = NodeList(createMember(typeid, kvs)) + + return nodes + + +def opValue(kvs, parentkvs, xnode): + return NodeList({'meta': 'Value', 'value': _getText(xnode)}) + + +def opInterleave(kvs, parentkvs, xnode): + return _traverse(xnode.childNodes, kvs) + + +def opText(kvs, parentkvs, xnode): + return None + + +def opEmpty(kvs, parentkvs, xnode): + return None + + +def opZeroOrMore(kvs, parentkvs, xnode): + nodes = _traverse(xnode.childNodes, kvs) + for node in nodes: + node['more'] = True + node['opt'] = True + return nodes + + +def opOneOrMore(kvs, parentkvs, xnode): + nodes = _traverse(xnode.childNodes, kvs) + for node in nodes: + node['more'] = True + return nodes + + +def opGroup(kvs, parentkvs, xnode): + nodes = _traverse(xnode.childNodes, kvs) + assert nodes.uniform() == 'Member' + for node in nodes: + node['opt'] = True + return nodes + + +def opAnyName(kvs, parentkvs, xnode): + _resetParentNames(parentkvs, '_Any_') + return None + + +def opName(kvs, parentkvs, xnode): + _resetParentNames(parentkvs, _getText(kvs['xnode'])) + return None + + +def mendParent(member, parent): + mtype = TypeTable().get(member['_typeid']) + assert mtype, member + if mtype['meta'] == 'Struct': + if mtype['_env']['define'] != parent['_env']['define']: + parent = None + + mtype['_parent'] = parent + nextp = parent if mtype['unpack'] else mtype + for child in mtype['members']: + mendParent(child, nextp) + + +class CodeWriter(object): + def __init__(self, args): + self._cmd = args.cmd + self._files = {} + self._filters = {} + self._filters['structure'] = args.kinds and 's' in args.kinds + self._filters['clearfunc'] = args.kinds and 'c' in args.kinds + self._filters['parsefunc'] = args.kinds and 'p' in args.kinds + self._filters['formatfunc'] = args.kinds and 'f' in args.kinds + if args.cmd == 'show': + self._filters['target'] = args.target + + def _getFile(self, path, ext): + assert ext in ['.h', '.c'] + _, basename = os.path.split(path) + path = '%s.generated%s' % (path, ext) + f = self._files.get(path) + if f is None: + f = open(path, 'w') + f.write('/* Generated by rng2c/generator.py */\n\n') + if ext in ['.c']: + f.write('#include <config.h>\n') + f.write('#include "%s.h"\n' % basename) + f.write('#include "viralloc.h"\n') + f.write('#include "virerror.h"\n') + f.write('#include "virstring.h"\n\n') + f.write('#define VIR_FROM_THIS VIR_FROM_NONE\n') + else: + f.write('#pragma once\n\n') + f.write('#include "internal.h"\n') + f.write('#include "virxml.h"\n') + self._files[path] = f + return f + + def write(self, atype, kind, extname, content): + if not self._filters[kind]: + return + + if self._cmd == 'show': + target = self._filters['target'] + if not target or target == atype['id']: + if extname == '.h': + info = Terms.upperInitial(kind) + if atype['unpack']: + parent = atype['_parent']['name'] + info += ' (Unpack: expose to "%s".)' % parent + elif not atype[kind].get('output'): + info += ' (Disabled: NO OUTPUT for "%s".)' % kind + print('\n###### %s ######' % info) + print('\n[.h]') + else: + print('\n[.c]') + print('\n' + content) + return + + assert self._cmd == 'generate' + + if atype[kind].get('output'): + lfs = '\n' if extname == '.h' else '\n\n' + path = atype['_env']['builddir'] + '/' + atype[kind]['output'] + f = self._getFile(path, extname) + f.write(lfs + content + '\n') + + def complete(self): + for name in self._files: + self._files[name].close() + self._files.clear() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=TOOL_DESC) + subparsers = parser.add_subparsers(dest='cmd') + parser_list = subparsers.add_parser('list', help='list all types') + parser_show = subparsers.add_parser('show', help='show target code') + parser_show.add_argument('target', help='target for being previewed') + parser_show.add_argument('-k', dest='kinds', + help='kinds of code to be previewed') + parser_generate = subparsers.add_parser('generate', help='generate code') + parser_generate.add_argument('-k', dest='kinds', + help='kinds of code to be generated') + args = parser.parse_args() + + if not args.cmd: + parser.print_help() + sys.exit(1) + + if args.cmd == 'generate': + print('###### RNG2C: start ... ######') + if not args.kinds: + print("[dry run]: no kinds specified for 'generate'") + + timestamp = datetime.now() + topdir = assertObj(os.environ.get('topdir', None)) + builddir = assertObj(os.environ.get('builddir', None)) + entries = assertObj(os.environ.get('entries', None)) + initDirectiveSchema(topdir + '/rng2c/schema.json') + + for entry in entries.split(): + env = {'_env': {'rng': entry, 'topdir': topdir, 'builddir': builddir}} + opGrammar(env, {}, None) + + for atype in TypeTable().values(): + if atype['meta'] == 'Struct' and not atype['unpack']: + for member in atype['members']: + mendParent(member, atype) + + if args.cmd == 'list': + print('%s %-16s %s' % ('SHORT_ID', 'META', 'LOCATION')) + for atype in TypeTable().values(): + assert 'id' in atype, atype + print('%-8s %-16s %s' % (atype['id'][:8], atype['meta'], + atype['location'])) + sys.exit(0) + elif args.cmd == 'show': + assert args.target, args + if '/' in args.target or isBuiltin(args.target): + atype = TypeTable().getByLocation(args.target) + elif len(args.target) == 40: + atype = TypeTable().get(args.target) + else: + atype = TypeTable().getByPartialID(args.target) + if not atype: + sys.exit(0) + + args.target = atype['id'] + showDirective(atype) + if isBuiltin(atype['meta']): + print('\n###### Builtin details ######\n') + if atype.get('name', None): + ctype = atype['name'] + else: + ctype = BUILTIN_TYPES.get(atype['meta'])['ctype'] + print("ctype: %s\n" % ctype) + + writer = CodeWriter(args) + for atype in TypeTable().values(): + if atype['meta'] in ['Struct', 'Enum']: + makeStructure(writer, atype) + + for atype in TypeTable().values(): + if atype['meta'] == 'Struct': + makeClearFunc(writer, atype) + + for atype in TypeTable().values(): + if atype['meta'] == 'Struct': + makeParseFunc(writer, atype) + + for atype in TypeTable().values(): + if atype['meta'] == 'Struct': + makeFormatFunc(writer, atype) + + writer.complete() + + if args.cmd == 'generate': + elapse = (datetime.now() - timestamp).microseconds + print('\n###### RNG2C: elapse %d(us) ######\n' % elapse) + + sys.exit(0) diff --git a/rng2c/go b/rng2c/go new file mode 100755 index 0000000..0bf6b14 --- /dev/null +++ b/rng2c/go @@ -0,0 +1,8 @@ +# This is a command-line tool + +WORK_DIR=$(cd $(dirname $0); pwd) +export PYTHONDONTWRITEBYTECODE=1 +export topdir="${WORK_DIR}/.." +export builddir="${WORK_DIR}/../build" +export entries="network.rng" +${WORK_DIR}/generator.py $@ diff --git a/rng2c/schema.json b/rng2c/schema.json new file mode 100644 index 0000000..5417989 --- /dev/null +++ b/rng2c/schema.json @@ -0,0 +1,113 @@ +{ + "definitions": { + "formal_arg": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "type": {"type": "string"}, + "pointer": {"type": "boolean"}, + "ctype": {"type": "string"} + } + }, + "function": { + "output": {"type": ["string", "null"]}, + "name": {"type": "string"}, + "args": { + "type": "array", + "items": {"$ref": "#/definitions/formal_arg"} + } + }, + "actual_arg": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "value": {"type": "string"} + } + }, + "member": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "name": {"type": "string"}, + "opt": {"type": "boolean"}, + "more": {"type": "boolean"}, + "tag": {"type": "string"}, + "type": {"type": "string"}, + "pointer": {"type": "boolean"}, + "specified": {"type": "boolean"}, + "declare.comment": {"type": "string"}, + "parse.disable": {"type": "boolean"}, + "parse.args": { + "type": "array", + "items": {"$ref": "#/definitions/actual_arg"} + }, + "parse.instname": {"type": "string"}, + "parse.default": {"type": "string"}, + "format.disable": {"type": "boolean"}, + "format.nocheck": {"type": "boolean"}, + "format.precheck": {"type": "string"}, + "format.fmt": {"type": "string"}, + "format.args": { + "type": "array", + "items": {"$ref": "#/definitions/actual_arg"} + }, + "hint": {"type": "string"} + } + } + }, + + "type": "object", + "properties": { + "id": {"type": "string"}, + "location": {"type": "string"}, + "tag": {"type": "string"}, + "name": {"type": "string"}, + "meta": {"type": "string"}, + "unpack": {"type": "boolean"}, + "pack": {"type": "boolean"}, + "union": {"type": "string"}, + "structure": { + "type": "object", + "properties": { + "output": {"type": ["string", "null"]}, + "enum.default": {"type": "string"}, + "array.size": {"type": "string"} + } + }, + "clearfunc": { + "type": "object", + "properties": {"$ref": "#/definitions/function"} + }, + "parsefunc": { + "type": "object", + "properties": { + "$ref": "#/definitions/function", + "args.noctxt": {"type": "boolean"}, + "args.instname": {"type": "boolean"}, + "args.parent": {"type": "boolean"}, + "post": {"type": "boolean"}, + "post.notmpvars": {"type": "boolean"} + } + }, + "formatfunc": { + "type": "object", + "properties": { + "$ref": "#/definitions/function", + "shorthand.ignore": {"type": "boolean"}, + "order": {"type": "array", "items": {"type": "string"}} + } + }, + "namespace": {"type": "boolean"}, + "values": { + "type": "array", + "items": {"type": "string"} + }, + "members": { + "type": "array", + "items": {"$ref": "#/definitions/member"} + }, + "PRESERVE": {"type": "string"}, + "APPLY": {"type": "string"}, + "TOUCH": {"type": "boolean"} + } +} diff --git a/rng2c/utils.py b/rng2c/utils.py new file mode 100644 index 0000000..98d6f55 --- /dev/null +++ b/rng2c/utils.py @@ -0,0 +1,163 @@ +# +# Copyright (C) 2020 Shandong Massclouds Co.,Ltd. +# +# 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.1 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, see +# <http://www.gnu.org/licenses/>. +# + +import re +import string +import hashlib + + +def singleton(cls): + _instances = {} + + def inner(): + if cls not in _instances: + _instances[cls] = cls() + return _instances[cls] + return inner + + +class Terms(object): + abbrs = ['uuid', 'pci', 'zpci', 'ptr', 'mac', 'mtu', 'dns', 'ip', 'dhcp'] + plurals = {'address': 'addresses'} + caps = {'NET_DEV': 'NETDEV', 'MACTABLE': 'MAC_TABLE'} + + @classmethod + def _split(cls, word): + ret = [] + if not word: + return ret + head = 0 + for pos in range(1, len(word)): + if word[pos].isupper() and not word[pos - 1].isupper(): + ret.append(word[head:pos]) + head = pos + ret.append(word[head:]) + return ret + + @classmethod + def pluralize(cls, word): + ret = cls.plurals.get(word, None) + return ret if ret else word + 's' + + # Don't use str.capitalize() which force other letters to be lowercase. + @classmethod + def upperInitial(cls, word): + if not word: + return '' + if word in cls.abbrs: + return word.upper() + if len(word) > 0 and word[0].isupper(): + return word + return word[0].upper() + word[1:] + + @classmethod + def camelize(cls, word): + if not word: + return '' + parts = cls._split(word) + parts = map(cls.upperInitial, parts) + return ''.join(parts) + + @classmethod + def allcaps(cls, word): + if len(word) == 0: + return word + parts = cls._split(word) + ret = '_'.join([part.upper() for part in parts]) + for key, value in cls.caps.items(): + ret = ret.replace('_%s_' % key, '_%s_' % value) + return ret + + +def assertOnlyOne(objs): + assert len(objs) == 1 and objs[0], len(objs) + return objs[0] + + +def assertObj(obj, msg=''): + assert obj, msg + return obj + + +def singleline(code): + return len(re.findall(r'\n', code.strip())) == 0 + + +def indent(block, count, unit=4): + if not block: + return '' + lines = [] + for line in block.strip().split('\n'): + lines.append(' ' * unit * count + line if line else '') + return '\n'.join(lines).strip() + + +def render(template, **kwargs): + return string.Template(template).safe_substitute(kwargs).strip() + + +def renderByDict(template, dictionary): + return string.Template(template).safe_substitute(**dictionary).strip() + + +def deepupdate(target, source): + assert isinstance(target, dict) + assert isinstance(source, dict) + for key, value in source.items(): + if key not in target: + target[key] = value + continue + + if isinstance(value, dict): + deepupdate(target[key], value) + else: + target[key] = value + + return target + + +class BlockAssembler(list): + def append(self, block): + if block: + super(BlockAssembler, self).append(block) + + def output(self, connector='\n'): + return connector.join(self) + + +def sha1ID(unitext): + sha1 = hashlib.sha1() + sha1.update(unitext.encode()) + return sha1.hexdigest() + + +def dedup(alist): + assert isinstance(alist, list) + ret = [] + for e in alist: + if e not in ret: + ret.append(e) + + return ret + + +def counterName(name): + name = Terms.pluralize(name) + if not name.islower(): + name = Terms.upperInitial(name) + return 'n' + name -- 2.17.1

Let makefiles call RNG2C for generating codes automatically. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/Makefile.am | 16 +++++++++++++++- src/access/Makefile.inc.am | 2 +- src/conf/Makefile.inc.am | 10 +++++++++- src/esx/Makefile.inc.am | 2 +- src/interface/Makefile.inc.am | 2 +- src/lxc/Makefile.inc.am | 1 + src/network/Makefile.inc.am | 2 +- src/node_device/Makefile.inc.am | 2 +- src/nwfilter/Makefile.inc.am | 2 +- src/qemu/Makefile.inc.am | 1 + src/remote/Makefile.inc.am | 2 +- src/secret/Makefile.inc.am | 2 +- src/storage/Makefile.inc.am | 2 +- src/test/Makefile.inc.am | 2 +- src/util/Makefile.inc.am | 11 ++++++++++- src/vbox/Makefile.inc.am | 2 +- tests/Makefile.am | 2 ++ tools/Makefile.am | 2 ++ 18 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 12dd6b8..dc2adb0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,7 @@ AM_CPPFLAGS = -I$(top_srcdir) \ -I../include \ -I$(top_srcdir)/include \ -I$(srcdir)/util \ - -I./util \ + -I./util -I./conf \ -DIN_LIBVIRT \ -Dabs_top_builddir="\"$(abs_top_builddir)\"" \ -Dabs_top_srcdir="\"$(abs_top_srcdir)\"" \ @@ -87,6 +87,20 @@ sbin_PROGRAMS = bin_PROGRAMS = DRIVER_SOURCES = +RNG2C_GENERATED_FILES_STAMP = .rng2c_generator.stamp +RNG_FILES = $(wildcard $(top_srcdir)/docs/schemas/*.rng) +RNG2C_SOURCE = $(wildcard $(top_srcdir)/rng2c/*.py) +RNG2C_ENTRIES = network.rng + +$(RNG2C_GENERATED_FILES_STAMP): $(RNG_FILES) $(RNG2C_SOURCE) + $(AM_V_GEN)topdir=$(top_srcdir) builddir=$(top_builddir) \ + entries=$(RNG2C_ENTRIES) \ + $(PYTHON) -B $(top_srcdir)/rng2c/generator.py generate -k scpf \ + && touch $@ + +MAINTAINERCLEANFILES += $(RNG2C_GENERATED_FILES_STAMP) +CLEANFILES += $(RNG2C_GENERATED_FILES_STAMP) + COMMON_UNIT_VARS = \ -e 's|[@]runstatedir[@]|$(runstatedir)|g' \ -e 's|[@]sbindir[@]|$(sbindir)|g' \ diff --git a/src/access/Makefile.inc.am b/src/access/Makefile.inc.am index 11f87c6..7832558 100644 --- a/src/access/Makefile.inc.am +++ b/src/access/Makefile.inc.am @@ -55,7 +55,7 @@ nodist_libvirt_driver_access_la_SOURCES = \ noinst_LTLIBRARIES += libvirt_driver_access.la libvirt_la_BUILT_LIBADD += libvirt_driver_access.la libvirt_driver_access_la_CFLAGS = \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(NULL) libvirt_driver_access_la_LDFLAGS = $(AM_LDFLAGS) diff --git a/src/conf/Makefile.inc.am b/src/conf/Makefile.inc.am index debc6f4..1fbde38 100644 --- a/src/conf/Makefile.inc.am +++ b/src/conf/Makefile.inc.am @@ -160,7 +160,11 @@ DEVICE_CONF_SOURCES = \ conf/device_conf.h \ $(NULL) +CONF_GENERATED_SOURCES = \ + $(NULL) + CONF_SOURCES = \ + $(CONF_GENERATED_SOURCES) \ $(NETDEV_CONF_SOURCES) \ $(DOMAIN_CONF_SOURCES) \ $(OBJECT_EVENT_SOURCES) \ @@ -180,11 +184,15 @@ CONF_SOURCES = \ $(DEVICE_CONF_SOURCES) \ $(NULL) +$(CONF_GENERATED_SOURCES): $(RNG2C_GENERATED_FILES_STAMP) +MAINTAINERCLEANFILES += $(CONF_GENERATED_SOURCES) +CLEANFILES += $(CONF_GENERATED_SOURCES) + noinst_LTLIBRARIES += libvirt_conf.la libvirt_la_BUILT_LIBADD += libvirt_conf.la libvirt_conf_la_SOURCES = $(CONF_SOURCES) libvirt_conf_la_CFLAGS = \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(NULL) libvirt_conf_la_LDFLAGS = $(AM_LDFLAGS) diff --git a/src/esx/Makefile.inc.am b/src/esx/Makefile.inc.am index d53cef1..1df9a54 100644 --- a/src/esx/Makefile.inc.am +++ b/src/esx/Makefile.inc.am @@ -78,7 +78,7 @@ noinst_LTLIBRARIES += libvirt_driver_esx.la libvirt_la_BUILT_LIBADD += libvirt_driver_esx.la libvirt_driver_esx_la_CFLAGS = \ $(CURL_CFLAGS) \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ -I$(builddir)/esx \ -I$(srcdir)/vmx \ $(AM_CFLAGS) \ diff --git a/src/interface/Makefile.inc.am b/src/interface/Makefile.inc.am index 39157c0..e12c73a 100644 --- a/src/interface/Makefile.inc.am +++ b/src/interface/Makefile.inc.am @@ -23,7 +23,7 @@ mod_LTLIBRARIES += libvirt_driver_interface.la libvirt_driver_interface_la_CFLAGS = \ -I$(srcdir)/access \ -I$(builddir)/access \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(LIBNL_CFLAGS) \ $(NULL) diff --git a/src/lxc/Makefile.inc.am b/src/lxc/Makefile.inc.am index 2fee607..2e4c107 100644 --- a/src/lxc/Makefile.inc.am +++ b/src/lxc/Makefile.inc.am @@ -95,6 +95,7 @@ libvirt_driver_lxc_impl_la_CFLAGS = \ -I$(srcdir)/access \ -I$(builddir)/access \ -I$(srcdir)/conf \ + -I./conf \ -I$(builddir)/lxc \ -I$(builddir)/rpc \ -I$(srcdir)/hypervisor \ diff --git a/src/network/Makefile.inc.am b/src/network/Makefile.inc.am index bc05b01..5184908 100644 --- a/src/network/Makefile.inc.am +++ b/src/network/Makefile.inc.am @@ -46,7 +46,7 @@ libvirt_driver_network_impl_la_CFLAGS = \ $(DBUS_CFLAGS) \ -I$(srcdir)/access \ -I$(builddir)/access \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(NULL) libvirt_driver_network_impl_la_SOURCES = $(NETWORK_DRIVER_SOURCES) diff --git a/src/node_device/Makefile.inc.am b/src/node_device/Makefile.inc.am index 0b28718..9e9d16d 100644 --- a/src/node_device/Makefile.inc.am +++ b/src/node_device/Makefile.inc.am @@ -40,7 +40,7 @@ libvirt_driver_nodedev_la_SOURCES = $(NODE_DEVICE_DRIVER_SOURCES) libvirt_driver_nodedev_la_CFLAGS = \ -I$(srcdir)/access \ -I$(builddir)/access \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(LIBNL_CFLAGS) \ $(NULL) diff --git a/src/nwfilter/Makefile.inc.am b/src/nwfilter/Makefile.inc.am index 9a68fd8..772bf17 100644 --- a/src/nwfilter/Makefile.inc.am +++ b/src/nwfilter/Makefile.inc.am @@ -40,7 +40,7 @@ libvirt_driver_nwfilter_impl_la_CFLAGS = \ $(DBUS_CFLAGS) \ -I$(srcdir)/access \ -I$(builddir)/access \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(NULL) libvirt_driver_nwfilter_impl_la_LDFLAGS = $(AM_LDFLAGS) diff --git a/src/qemu/Makefile.inc.am b/src/qemu/Makefile.inc.am index 51cd798..a54a498 100644 --- a/src/qemu/Makefile.inc.am +++ b/src/qemu/Makefile.inc.am @@ -99,6 +99,7 @@ libvirt_driver_qemu_impl_la_CFLAGS = \ -I$(srcdir)/access \ -I$(builddir)/access \ -I$(srcdir)/conf \ + -I./conf \ -I$(srcdir)/secret \ -I$(srcdir)/hypervisor \ $(AM_CFLAGS) \ diff --git a/src/remote/Makefile.inc.am b/src/remote/Makefile.inc.am index 958bd18..e6eec31 100644 --- a/src/remote/Makefile.inc.am +++ b/src/remote/Makefile.inc.am @@ -47,7 +47,7 @@ REMOTE_DAEMON_CFLAGS = \ $(COVERAGE_CFLAGS) \ -I$(srcdir)/access \ -I$(builddir)/access \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ -I$(srcdir)/rpc \ -I$(builddir)/rpc \ -I$(builddir)/remote \ diff --git a/src/secret/Makefile.inc.am b/src/secret/Makefile.inc.am index 63c8bc6..e5b608a 100644 --- a/src/secret/Makefile.inc.am +++ b/src/secret/Makefile.inc.am @@ -20,7 +20,7 @@ mod_LTLIBRARIES += libvirt_driver_secret.la libvirt_driver_secret_la_CFLAGS = \ -I$(srcdir)/access \ -I$(builddir)/access \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(NULL) libvirt_driver_secret_la_LIBADD = \ diff --git a/src/storage/Makefile.inc.am b/src/storage/Makefile.inc.am index 3655b8a..c792583 100644 --- a/src/storage/Makefile.inc.am +++ b/src/storage/Makefile.inc.am @@ -122,7 +122,7 @@ libvirt_driver_storage_impl_la_SOURCES = libvirt_driver_storage_impl_la_CFLAGS = \ -I$(srcdir)/access \ -I$(builddir)/access \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ -I$(srcdir)/secret \ $(AM_CFLAGS) \ $(NULL) diff --git a/src/test/Makefile.inc.am b/src/test/Makefile.inc.am index b84ab52..ce1e635 100644 --- a/src/test/Makefile.inc.am +++ b/src/test/Makefile.inc.am @@ -21,7 +21,7 @@ driver_test_assetdir = $(pkgdatadir) noinst_LTLIBRARIES += libvirt_driver_test.la libvirt_la_BUILT_LIBADD += libvirt_driver_test.la libvirt_driver_test_la_CFLAGS = \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ $(NULL) libvirt_driver_test_la_SOURCES = $(TEST_DRIVER_SOURCES) diff --git a/src/util/Makefile.inc.am b/src/util/Makefile.inc.am index 718b11a..ada1bb2 100644 --- a/src/util/Makefile.inc.am +++ b/src/util/Makefile.inc.am @@ -237,6 +237,10 @@ UTIL_SOURCES = \ $(NULL) +UTIL_GENERATED_SOURCES = \ + $(NULL) + + EXTRA_DIST += \ $(srcdir)/keycodemapdb/data/keymaps.csv \ $(srcdir)/keycodemapdb/tools/keymap-gen \ @@ -251,8 +255,11 @@ KEYTABLES = \ $(KEYNAMES:%=util/virkeynametable_%.h) \ $(NULL) -BUILT_SOURCES += $(KEYTABLES) +UTIL_SOURCES += $(KEYTABLES) $(UTIL_GENERATED_SOURCES) +BUILT_SOURCES += $(KEYTABLES) $(UTIL_GENERATED_SOURCES) CLEANFILES += $(KEYTABLES) +CLEANFILES += $(KEYMANS) $(KEYPODS) $(UTIL_GENERATED_SOURCES) +MAINTAINERCLEANFILES += $(KEYTABLES) $(UTIL_GENERATED_SOURCES) UTIL_IO_HELPER_SOURCES = util/iohelper.c @@ -298,6 +305,8 @@ libvirt_util_la_LIBADD = \ $(NULL) +$(UTIL_GENERATED_SOURCES): $(RNG2C_GENERATED_FILES_STAMP) + util/virkeycodetable_%.h: $(srcdir)/keycodemapdb/data/keymaps.csv \ $(srcdir)/keycodemapdb/tools/keymap-gen Makefile.am $(AM_V_GEN)export NAME=`echo $@ | sed -e 's,util/virkeycodetable_,,' \ diff --git a/src/vbox/Makefile.inc.am b/src/vbox/Makefile.inc.am index 72a15c6..00ad02a 100644 --- a/src/vbox/Makefile.inc.am +++ b/src/vbox/Makefile.inc.am @@ -52,7 +52,7 @@ mod_LTLIBRARIES += libvirt_driver_vbox.la libvirt_driver_vbox_la_LDFLAGS = $(AM_LDFLAGS_MOD_NOUNDEF) libvirt_driver_vbox_impl_la_CFLAGS = \ - -I$(srcdir)/conf \ + -I$(srcdir)/conf -I./conf \ $(AM_CFLAGS) \ -DVBOX_DRIVER \ $(NULL) diff --git a/tests/Makefile.am b/tests/Makefile.am index ada5b8f..46d2523 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -21,7 +21,9 @@ AM_CPPFLAGS = \ -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_builddir)/src -I$(top_srcdir)/src \ -I$(top_srcdir)/src/util \ + -I$(top_builddir)/src/util \ -I$(top_srcdir)/src/conf \ + -I$(top_builddir)/src/conf \ -I$(top_srcdir)/src/hypervisor \ -I$(top_builddir)/src/rpc \ $(NULL) diff --git a/tools/Makefile.am b/tools/Makefile.am index 53df930..05a219e 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -19,6 +19,8 @@ AM_CPPFLAGS = \ -I$(top_builddir)/include -I$(top_srcdir)/include \ -I$(top_builddir)/src -I$(top_srcdir)/src \ -I$(top_srcdir)/src/util \ + -I$(top_builddir)/src/util \ + -I$(top_builddir)/src/conf \ -I$(top_srcdir) \ $(NULL) -- 2.17.1

When directive property 'args.noctxt" is set, there's no 'ctxt' argument for the parse function. So RNG2C uses virXMLChildNode, virXMLChildNodeSet and virXMLPropString to construct implementation of the parse function. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/util/virxml.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/util/virxml.h | 3 +++ 2 files changed, 60 insertions(+) diff --git a/src/util/virxml.c b/src/util/virxml.c index 9ea7b99..bfaed58 100644 --- a/src/util/virxml.c +++ b/src/util/virxml.c @@ -502,6 +502,63 @@ virXMLCheckIllegalChars(const char *nodeName, } +/** + * virXMLChildNode: + * @node: Parent XML dom node pointer + * @name: Name of the child element + * + * Convenience function to return the child element of a XML node. + * + * Returns the pointer of child element node or NULL in case of failure. + * If there are many nodes match condition, it only returns the first node. + */ +xmlNodePtr +virXMLChildNode(xmlNodePtr node, const char *name) +{ + xmlNodePtr cur = node->children; + while (cur) { + if (cur->type == XML_ELEMENT_NODE && virXMLNodeNameEqual(cur, name)) + return cur; + cur = cur->next; + } + return NULL; +} + + +/** + * virXMLChildNodeSet: + * @node: Parent XML dom node pointer + * @name: Name of the children element + * @list: the returned list of nodes (or NULL if only count matters) + * + * Convenience function to evaluate a set of children elements + * + * Returns the number of nodes found in which case @list is set (and + * must be freed) or -1 if the evaluation failed. + */ +int +virXMLChildNodeSet(xmlNodePtr node, const char *name, xmlNodePtr **list) +{ + size_t count = 0; + if (list != NULL) + *list = NULL; + + xmlNodePtr cur = node->children; + while (cur) { + if (cur->type == XML_ELEMENT_NODE && virXMLNodeNameEqual(cur, name)) { + if (list != NULL) { + if (VIR_APPEND_ELEMENT_COPY(*list, count, cur) < 0) + return -1; + } else { + count++; + } + } + cur = cur->next; + } + return count; +} + + /** * virXMLPropString: * @node: XML dom node pointer diff --git a/src/util/virxml.h b/src/util/virxml.h index 26ab9f9..0abde44 100644 --- a/src/util/virxml.h +++ b/src/util/virxml.h @@ -79,6 +79,9 @@ char * virXMLPropStringLimit(xmlNodePtr node, char * virXMLNodeContentString(xmlNodePtr node); long virXMLChildElementCount(xmlNodePtr node); +xmlNodePtr virXMLChildNode(xmlNodePtr node, const char *name); +int virXMLChildNodeSet(xmlNodePtr node, const char *name, xmlNodePtr **list); + /* Internal function; prefer the macros below. */ xmlDocPtr virXMLParseHelper(int domcode, const char *filename, -- 2.17.1

Generated codes by RNG2C need to invoke VIR_ENUM_IMPL and VIR_ENUM_DECL. Move them from virenum.h to internal.h for convenience. This patch also prepares for next patch: replacing virTristateBool/virTristateSwitch(hardcoded) with namesakes. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/internal.h | 19 +++++++++++++++++++ src/util/virenum.h | 18 ------------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/internal.h b/src/internal.h index e181218..27e56ac 100644 --- a/src/internal.h +++ b/src/internal.h @@ -502,3 +502,22 @@ enum { # define fprintf(fh, ...) g_fprintf(fh, __VA_ARGS__) #endif /* VIR_NO_GLIB_STDIO */ + + +#define VIR_ENUM_IMPL(name, lastVal, ...) \ + static const char *const name ## TypeList[] = { __VA_ARGS__ }; \ + const char *name ## TypeToString(int type) { \ + return virEnumToString(name ## TypeList, \ + G_N_ELEMENTS(name ## TypeList), \ + type); \ + } \ + int name ## TypeFromString(const char *type) { \ + return virEnumFromString(name ## TypeList, \ + G_N_ELEMENTS(name ## TypeList), \ + type); \ + } \ + G_STATIC_ASSERT(G_N_ELEMENTS(name ## TypeList) == lastVal) + +#define VIR_ENUM_DECL(name) \ + const char *name ## TypeToString(int type); \ + int name ## TypeFromString(const char*type) diff --git a/src/util/virenum.h b/src/util/virenum.h index d74af35..24a69bd 100644 --- a/src/util/virenum.h +++ b/src/util/virenum.h @@ -30,24 +30,6 @@ virEnumToString(const char * const *types, unsigned int ntypes, int type); -#define VIR_ENUM_IMPL(name, lastVal, ...) \ - static const char *const name ## TypeList[] = { __VA_ARGS__ }; \ - const char *name ## TypeToString(int type) { \ - return virEnumToString(name ## TypeList, \ - G_N_ELEMENTS(name ## TypeList), \ - type); \ - } \ - int name ## TypeFromString(const char *type) { \ - return virEnumFromString(name ## TypeList, \ - G_N_ELEMENTS(name ## TypeList), \ - type); \ - } \ - G_STATIC_ASSERT(G_N_ELEMENTS(name ## TypeList) == lastVal) - -#define VIR_ENUM_DECL(name) \ - const char *name ## TypeToString(int type); \ - int name ## TypeFromString(const char*type) - typedef enum { VIR_TRISTATE_BOOL_ABSENT = 0, VIR_TRISTATE_BOOL_YES, -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/basictypes.rng | 14 ++++++++++++++ src/util/Makefile.inc.am | 2 ++ src/util/virenum.c | 15 --------------- src/util/virenum.h | 21 +++------------------ 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng index 8146527..e602715 100644 --- a/docs/schemas/basictypes.rng +++ b/docs/schemas/basictypes.rng @@ -571,6 +571,13 @@ </define> <define name="virYesNo"> + <!-- VIRT:DIRECTIVE { + "name": "virTristateBoolType", + "structure": { + "output": "src/util/virenum", + "enum.default": "absent" + } + } --> <choice> <value>yes</value> <value>no</value> @@ -578,6 +585,13 @@ </define> <define name="virOnOff"> + <!-- VIRT:DIRECTIVE { + "name": "virTristateSwitchType", + "structure": { + "output": "src/util/virenum", + "enum.default": "absent" + } + } --> <choice> <value>on</value> <value>off</value> diff --git a/src/util/Makefile.inc.am b/src/util/Makefile.inc.am index ada1bb2..fcf0a0d 100644 --- a/src/util/Makefile.inc.am +++ b/src/util/Makefile.inc.am @@ -238,6 +238,8 @@ UTIL_SOURCES = \ UTIL_GENERATED_SOURCES = \ + util/virenum.generated.h \ + util/virenum.generated.c \ $(NULL) diff --git a/src/util/virenum.c b/src/util/virenum.c index 26093bd..8236924 100644 --- a/src/util/virenum.c +++ b/src/util/virenum.c @@ -22,21 +22,6 @@ #define VIR_FROM_THIS VIR_FROM_NONE -VIR_ENUM_IMPL(virTristateBool, - VIR_TRISTATE_BOOL_LAST, - "default", - "yes", - "no", -); - -VIR_ENUM_IMPL(virTristateSwitch, - VIR_TRISTATE_SWITCH_LAST, - "default", - "on", - "off", -); - - virTristateBool virTristateBoolFromBool(bool val) { diff --git a/src/util/virenum.h b/src/util/virenum.h index 24a69bd..add02ee 100644 --- a/src/util/virenum.h +++ b/src/util/virenum.h @@ -19,6 +19,7 @@ #pragma once #include "internal.h" +#include "virenum.generated.h" int virEnumFromString(const char * const *types, @@ -30,24 +31,8 @@ virEnumToString(const char * const *types, unsigned int ntypes, int type); -typedef enum { - VIR_TRISTATE_BOOL_ABSENT = 0, - VIR_TRISTATE_BOOL_YES, - VIR_TRISTATE_BOOL_NO, - - VIR_TRISTATE_BOOL_LAST -} virTristateBool; - -typedef enum { - VIR_TRISTATE_SWITCH_ABSENT = 0, - VIR_TRISTATE_SWITCH_ON, - VIR_TRISTATE_SWITCH_OFF, - - VIR_TRISTATE_SWITCH_LAST -} virTristateSwitch; - -VIR_ENUM_DECL(virTristateBool); -VIR_ENUM_DECL(virTristateSwitch); +typedef virTristateBoolType virTristateBool; +typedef virTristateSwitchType virTristateSwitch; virTristateBool virTristateBoolFromBool(bool val); virTristateSwitch virTristateSwitchFromBool(bool val); -- 2.17.1

When RNG2C generates codes for builtin 'Bool.on_off', it includes virStringParseOnOff in those codes. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/util/virstring.c | 22 ++++++++++++++++++++++ src/util/virstring.h | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/src/util/virstring.c b/src/util/virstring.c index e9e792f..2d0bb23 100644 --- a/src/util/virstring.c +++ b/src/util/virstring.c @@ -1404,3 +1404,25 @@ int virStringParseYesNo(const char *str, bool *result) return 0; } + + +/** + * virStringParseOnOff: + * @str: "on|off" to parse, must not be NULL. + * @result: pointer to the boolean result of @str conversion + * + * Parses a "on|off" string and converts it into a boolean. + * + * Returns 0 on success and -1 on error. + */ +int virStringParseOnOff(const char *str, bool *result) +{ + if (STREQ(str, "on")) + *result = true; + else if (STREQ(str, "off")) + *result = false; + else + return -1; + + return 0; +} diff --git a/src/util/virstring.h b/src/util/virstring.h index 360c683..c528e44 100644 --- a/src/util/virstring.h +++ b/src/util/virstring.h @@ -185,6 +185,11 @@ int virStringParsePort(const char *str, int virStringParseYesNo(const char *str, bool *result) G_GNUC_WARN_UNUSED_RESULT; + +int virStringParseOnOff(const char *str, + bool *result) + G_GNUC_WARN_UNUSED_RESULT; + /** * VIR_AUTOSTRINGLIST: * -- 2.17.1

Add virSocketAddrParse, virSocketAddrFormat, virSocketAddrClear and virSocketAddrCheck. In generated codes by RNG2C, these functions are called for handling virSocketAddr. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/basictypes.rng | 1 + src/util/virsocketaddr.c | 27 +++++++++++++++++++++++++++ src/util/virsocketaddr.h | 10 ++++++++++ 3 files changed, 38 insertions(+) diff --git a/docs/schemas/basictypes.rng b/docs/schemas/basictypes.rng index e602715..a6c3fc8 100644 --- a/docs/schemas/basictypes.rng +++ b/docs/schemas/basictypes.rng @@ -222,6 +222,7 @@ <!-- An ipv4 "dotted quad" address --> <define name="ipv4Addr"> + <!-- VIRT:DIRECTIVE {"name": "virSocketAddr"} --> <data type="string"> <param name="pattern">(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([1-9][0-9])|([0-9]))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([1-9][0-9])|([0-9]))</param> </data> diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c index 4cad329..f502978 100644 --- a/src/util/virsocketaddr.c +++ b/src/util/virsocketaddr.c @@ -157,6 +157,11 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) return len; } +int virSocketAddrParseXML(const char *val, virSocketAddrPtr addr) +{ + return virSocketAddrParse(addr, val, AF_UNSPEC); +} + /** * virSocketAddrParseAny: * @addr: where to store the return value, optional. @@ -444,6 +449,22 @@ virSocketAddrFormat(const virSocketAddr *addr) return virSocketAddrFormatFull(addr, false, NULL); } +int +virSocketAddrFormatBuf(virBufferPtr buf, + const char *fmt, + const virSocketAddr *addr) +{ + g_autofree char *str = NULL; + if (!VIR_SOCKET_ADDR_VALID(addr)) + return 0; + + str = virSocketAddrFormatFull(addr, false, NULL); + if (!str) + return -1; + + virBufferAsprintf(buf, fmt, str); + return 0; +} /* * virSocketAddrFormatFull: @@ -1319,3 +1340,9 @@ virSocketAddrFree(virSocketAddrPtr addr) { VIR_FREE(addr); } + +void +virSocketAddrClear(virSocketAddrPtr addr) +{ + memset(addr, 0, sizeof(virSocketAddr)); +} diff --git a/src/util/virsocketaddr.h b/src/util/virsocketaddr.h index d06e751..f255107 100644 --- a/src/util/virsocketaddr.h +++ b/src/util/virsocketaddr.h @@ -18,6 +18,7 @@ #pragma once +#include "virbuffer.h" #include "virsocket.h" #define VIR_LOOPBACK_IPV4_ADDR "127.0.0.1" @@ -44,6 +45,8 @@ typedef struct { #define VIR_SOCKET_ADDR_FAMILY(s) \ ((s)->data.sa.sa_family) +#define virSocketAddrCheck VIR_SOCKET_ADDR_VALID + #define VIR_SOCKET_ADDR_IPV4_ALL "0.0.0.0" #define VIR_SOCKET_ADDR_IPV6_ALL "::" @@ -70,6 +73,8 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family); +int virSocketAddrParseXML(const char *val, virSocketAddrPtr addr); + int virSocketAddrParseAny(virSocketAddrPtr addr, const char *val, int family, @@ -93,6 +98,10 @@ char *virSocketAddrFormatFull(const virSocketAddr *addr, bool withService, const char *separator); +int virSocketAddrFormatBuf(virBufferPtr buf, + const char *fmt, + const virSocketAddr *addr); + char *virSocketAddrGetPath(virSocketAddrPtr addr); int virSocketAddrSetPort(virSocketAddrPtr addr, int port); @@ -145,5 +154,6 @@ int virSocketAddrPTRDomain(const virSocketAddr *addr, ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); void virSocketAddrFree(virSocketAddrPtr addr); +void virSocketAddrClear(virSocketAddrPtr addr); G_DEFINE_AUTOPTR_CLEANUP_FUNC(virSocketAddr, virSocketAddrFree); -- 2.17.1

Meta 'U8' corresponds to 'uint8_t'. Add virStrToLong_u8p when parsing uint8_t. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/util/virstring.c | 15 +++++++++++++++ src/util/virstring.h | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/util/virstring.c b/src/util/virstring.c index 2d0bb23..83d13ff 100644 --- a/src/util/virstring.c +++ b/src/util/virstring.c @@ -460,6 +460,21 @@ virStrToLong_uip(char const *s, char **end_ptr, int base, unsigned int *result) return 0; } +/* Just like virStrToLong_uip, above, but produce an "uint8_t" value. + * This version rejects any negative signs. */ +int +virStrToLong_u8p(char const *s, char **end_ptr, int base, uint8_t *result) +{ + unsigned int val; + int ret; + ret = virStrToLong_uip(s, end_ptr, base, &val); + if (ret < 0 || val > 0xff) + return -1; + + *result = (uint8_t) val; + return 0; +} + /* Just like virStrToLong_i, above, but produce a "long" value. */ int virStrToLong_l(char const *s, char **end_ptr, int base, long *result) diff --git a/src/util/virstring.h b/src/util/virstring.h index c528e44..da73f86 100644 --- a/src/util/virstring.h +++ b/src/util/virstring.h @@ -77,6 +77,11 @@ int virStrToLong_uip(char const *s, int base, unsigned int *result) G_GNUC_WARN_UNUSED_RESULT; +int virStrToLong_u8p(char const *s, + char **end_ptr, + int base, + uint8_t *result) + G_GNUC_WARN_UNUSED_RESULT; int virStrToLong_l(char const *s, char **end_ptr, int base, -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 3 +++ src/conf/network_conf.h | 8 +------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 6045322..4d23c8f 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -281,6 +281,9 @@ </element> </zeroOrMore> <zeroOrMore> + <!-- VIRT:DIRECTIVE { + "structure": {"output": "src/conf/network_conf"} + } --> <element name="txt"> <attribute name="name"><ref name="dnsName"/></attribute> <attribute name="value"><text/></attribute> diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index db7243e..4e6cf72 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -37,6 +37,7 @@ #include "virmacmap.h" #include "virenum.h" #include "virxml.h" +#include "network_conf.generated.h" struct _virNetworkXMLOption { virObject parent; @@ -103,13 +104,6 @@ struct _virNetworkDHCPHostDef { virSocketAddr ip; }; -typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; -typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; -struct _virNetworkDNSTxtDef { - char *name; - char *value; -}; - typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; struct _virNetworkDNSSrvDef { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 3 +++ docs/schemas/networkcommon.rng | 2 +- src/conf/network_conf.h | 12 ------------ 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 4d23c8f..1747fc2 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -290,6 +290,9 @@ </element> </zeroOrMore> <zeroOrMore> + <!-- VIRT:DIRECTIVE { + "structure": {"output": "src/conf/network_conf"} + } --> <element name="srv"> <attribute name="service"><text/></attribute> <attribute name="protocol"> diff --git a/docs/schemas/networkcommon.rng b/docs/schemas/networkcommon.rng index ad3f590..35bcbd8 100644 --- a/docs/schemas/networkcommon.rng +++ b/docs/schemas/networkcommon.rng @@ -193,7 +193,7 @@ </define> <define name='unsignedShort'> - <data type='integer'> + <data type='unsignedInt'> <param name="minInclusive">0</param> <param name="maxInclusive">65535</param> </data> diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 4e6cf72..25a6991 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -104,18 +104,6 @@ struct _virNetworkDHCPHostDef { virSocketAddr ip; }; -typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; -typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; -struct _virNetworkDNSSrvDef { - char *domain; - char *service; - char *protocol; - char *target; - unsigned int port; - unsigned int priority; - unsigned int weight; -}; - typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 4 ++++ src/conf/network_conf.h | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 1747fc2..c83a796 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -322,6 +322,10 @@ </element> </zeroOrMore> <zeroOrMore> + <!-- VIRT:DIRECTIVE { + "structure": {"output": "src/conf/network_conf"}, + "members": [{"id": "hostname", "name": "name"}] + } --> <element name="host"> <attribute name="ip"><ref name="ipAddr"/></attribute> <oneOrMore> diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 25a6991..d4d3c01 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -104,15 +104,6 @@ struct _virNetworkDHCPHostDef { virSocketAddr ip; }; -typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; -typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; -struct _virNetworkDNSHostDef { - virSocketAddr ip; - size_t nnames; - char **names; -}; - - typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; struct _virNetworkDNSForwarder { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 4 ++++ src/conf/network_conf.h | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index c83a796..0034c4f 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -270,6 +270,10 @@ </optional> <interleave> <zeroOrMore> + <!-- VIRT:DIRECTIVE { + "name": "virNetworkDNSForwarder", + "structure": {"output": "src/conf/network_conf"} + } --> <element name="forwarder"> <optional> <attribute name="addr"><ref name="ipAddr"/></attribute> diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index d4d3c01..1f96999 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -104,13 +104,6 @@ struct _virNetworkDHCPHostDef { virSocketAddr ip; }; -typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; -typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; -struct _virNetworkDNSForwarder { - virSocketAddr addr; - char *domain; -}; - typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; struct _virNetworkDNSDef { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 3 +++ src/conf/network_conf.c | 12 ++++++------ src/conf/network_conf.h | 15 --------------- src/network/bridge_driver.c | 2 +- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 0034c4f..6127dae 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -257,6 +257,9 @@ <!-- Define the DNS related elements like TXT records and other features in the <dns> element --> <optional> + <!-- VIRT:DIRECTIVE { + "structure": {"output": "src/conf/network_conf"} + } --> <element name="dns"> <optional> <attribute name="enable"> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 819b645..84b4887 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -194,8 +194,8 @@ static void virNetworkDNSDefClear(virNetworkDNSDefPtr def) { if (def->forwarders) { - while (def->nfwds) - virNetworkDNSForwarderClear(&def->forwarders[--def->nfwds]); + while (def->nforwarders) + virNetworkDNSForwarderClear(&def->forwarders[--def->nforwarders]); VIR_FREE(def->forwarders); } if (def->txts) { @@ -931,7 +931,7 @@ virNetworkDNSDefParseXML(const char *networkName, goto cleanup; } VIR_FREE(addr); - def->nfwds++; + def->nforwarders++; } } @@ -2156,7 +2156,7 @@ virNetworkDNSDefFormat(virBufferPtr buf, { size_t i, j; - if (!(def->enable || def->forwardPlainNames || def->nfwds || def->nhosts || + if (!(def->enable || def->forwardPlainNames || def->nforwarders || def->nhosts || def->nsrvs || def->ntxts)) return 0; @@ -2183,7 +2183,7 @@ virNetworkDNSDefFormat(virBufferPtr buf, } virBufferAsprintf(buf, " forwardPlainNames='%s'", fwd); } - if (!(def->nfwds || def->nhosts || def->nsrvs || def->ntxts)) { + if (!(def->nforwarders || def->nhosts || def->nsrvs || def->ntxts)) { virBufferAddLit(buf, "/>\n"); return 0; } @@ -2191,7 +2191,7 @@ virNetworkDNSDefFormat(virBufferPtr buf, virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); - for (i = 0; i < def->nfwds; i++) { + for (i = 0; i < def->nforwarders; i++) { virBufferAddLit(buf, "<forwarder"); if (def->forwarders[i].domain) { diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 1f96999..db0119d 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -104,21 +104,6 @@ struct _virNetworkDHCPHostDef { virSocketAddr ip; }; -typedef struct _virNetworkDNSDef virNetworkDNSDef; -typedef virNetworkDNSDef *virNetworkDNSDefPtr; -struct _virNetworkDNSDef { - int enable; /* enum virTristateBool */ - int forwardPlainNames; /* enum virTristateBool */ - size_t ntxts; - virNetworkDNSTxtDefPtr txts; - size_t nhosts; - virNetworkDNSHostDefPtr hosts; - size_t nsrvs; - virNetworkDNSSrvDefPtr srvs; - size_t nfwds; - virNetworkDNSForwarderPtr forwarders; -}; - typedef struct _virNetworkIPDef virNetworkIPDef; typedef virNetworkIPDef *virNetworkIPDefPtr; struct _virNetworkIPDef { diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index f060992..3cccbe5 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -1112,7 +1112,7 @@ networkDnsmasqConfContents(virNetworkObjPtr obj, */ bool addNoResolv = false; - for (i = 0; i < def->dns.nfwds; i++) { + for (i = 0; i < def->dns.nforwarders; i++) { virNetworkDNSForwarderPtr fwd = &def->dns.forwarders[i]; virBufferAddLit(&configbuf, "server="); -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 13 ++++++--- src/conf/Makefile.inc.am | 2 ++ src/conf/network_conf.c | 60 ---------------------------------------- 3 files changed, 11 insertions(+), 64 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 6127dae..919464d 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -258,7 +258,8 @@ and other features in the <dns> element --> <optional> <!-- VIRT:DIRECTIVE { - "structure": {"output": "src/conf/network_conf"} + "structure": {"output": "src/conf/network_conf"}, + "clearfunc": {"output": "src/conf/network_conf"} } --> <element name="dns"> <optional> @@ -275,7 +276,8 @@ <zeroOrMore> <!-- VIRT:DIRECTIVE { "name": "virNetworkDNSForwarder", - "structure": {"output": "src/conf/network_conf"} + "structure": {"output": "src/conf/network_conf"}, + "clearfunc": {"output": "src/conf/network_conf"} } --> <element name="forwarder"> <optional> @@ -289,7 +291,8 @@ </zeroOrMore> <zeroOrMore> <!-- VIRT:DIRECTIVE { - "structure": {"output": "src/conf/network_conf"} + "structure": {"output": "src/conf/network_conf"}, + "clearfunc": {"output": "src/conf/network_conf"} } --> <element name="txt"> <attribute name="name"><ref name="dnsName"/></attribute> @@ -298,7 +301,8 @@ </zeroOrMore> <zeroOrMore> <!-- VIRT:DIRECTIVE { - "structure": {"output": "src/conf/network_conf"} + "structure": {"output": "src/conf/network_conf"}, + "clearfunc": {"output": "src/conf/network_conf"} } --> <element name="srv"> <attribute name="service"><text/></attribute> @@ -331,6 +335,7 @@ <zeroOrMore> <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, + "clearfunc": {"output": "src/conf/network_conf"}, "members": [{"id": "hostname", "name": "name"}] } --> <element name="host"> diff --git a/src/conf/Makefile.inc.am b/src/conf/Makefile.inc.am index 1fbde38..b9ab535 100644 --- a/src/conf/Makefile.inc.am +++ b/src/conf/Makefile.inc.am @@ -161,6 +161,8 @@ DEVICE_CONF_SOURCES = \ $(NULL) CONF_GENERATED_SOURCES = \ + conf/network_conf.generated.c \ + conf/network_conf.generated.h \ $(NULL) CONF_SOURCES = \ diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 84b4887..060d0e3 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -156,66 +156,6 @@ virNetworkIPDefClear(virNetworkIPDefPtr def) } -static void -virNetworkDNSTxtDefClear(virNetworkDNSTxtDefPtr def) -{ - VIR_FREE(def->name); - VIR_FREE(def->value); -} - - -static void -virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def) -{ - while (def->nnames) - VIR_FREE(def->names[--def->nnames]); - VIR_FREE(def->names); -} - - -static void -virNetworkDNSSrvDefClear(virNetworkDNSSrvDefPtr def) -{ - VIR_FREE(def->domain); - VIR_FREE(def->service); - VIR_FREE(def->protocol); - VIR_FREE(def->target); -} - - -static void -virNetworkDNSForwarderClear(virNetworkDNSForwarderPtr def) -{ - VIR_FREE(def->domain); -} - - -static void -virNetworkDNSDefClear(virNetworkDNSDefPtr def) -{ - if (def->forwarders) { - while (def->nforwarders) - virNetworkDNSForwarderClear(&def->forwarders[--def->nforwarders]); - VIR_FREE(def->forwarders); - } - if (def->txts) { - while (def->ntxts) - virNetworkDNSTxtDefClear(&def->txts[--def->ntxts]); - VIR_FREE(def->txts); - } - if (def->hosts) { - while (def->nhosts) - virNetworkDNSHostDefClear(&def->hosts[--def->nhosts]); - VIR_FREE(def->hosts); - } - if (def->srvs) { - while (def->nsrvs) - virNetworkDNSSrvDefClear(&def->srvs[--def->nsrvs]); - VIR_FREE(def->srvs); - } -} - - static void virNetworkForwardDefClear(virNetworkForwardDefPtr def) { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 8 ++++++ src/conf/network_conf.c | 58 +++++++++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 919464d..ee4487e 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -336,6 +336,14 @@ <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, + "parsefunc": { + "args.noctxt": true, + "args.instname": true, + "post": true, + "args": [ + {"name": "partialOkay", "type": "Bool"} + ] + }, "members": [{"id": "hostname", "name": "name"}] } --> <element name="host"> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 060d0e3..d9f2252 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -560,6 +560,40 @@ virNetworkDHCPDefParseXML(const char *networkName, } +static int +virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, + virNetworkDNSHostDefPtr def, + const char *networkName, + bool partialOkay, + const char *ipStr, + int nHostnameNodes G_GNUC_UNUSED) +{ + if (!ipStr && !partialOkay) { + virReportError(VIR_ERR_XML_DETAIL, + _("Missing IP address in network '%s' DNS HOST record"), + networkName); + return -1; + } + + if (def->nnames == 0 && !partialOkay) { + virReportError(VIR_ERR_XML_DETAIL, + _("Missing hostname in network '%s' DNS HOST record"), + networkName); + return -1; + } + + if (!VIR_SOCKET_ADDR_VALID(&def->ip) && def->nnames == 0) { + virReportError(VIR_ERR_XML_DETAIL, + _("Missing ip and hostname in network '%s' DNS HOST record"), + networkName); + return -1; + } + + return 0; +} + + +/* virNetworkDNSHostDefParseXML will be replaced by generated namesake */ static int virNetworkDNSHostDefParseXML(const char *networkName, xmlNodePtr node, @@ -569,13 +603,7 @@ virNetworkDNSHostDefParseXML(const char *networkName, xmlNodePtr cur; char *ip; - if (!(ip = virXMLPropString(node, "ip")) && !partialOkay) { - virReportError(VIR_ERR_XML_DETAIL, - _("Missing IP address in network '%s' DNS HOST record"), - networkName); - goto error; - } - + ip = virXMLPropString(node, "ip"); if (ip && (virSocketAddrParse(&def->ip, ip, AF_UNSPEC) < 0)) { virReportError(VIR_ERR_XML_DETAIL, _("Invalid IP address in network '%s' DNS HOST record"), @@ -583,7 +611,6 @@ virNetworkDNSHostDefParseXML(const char *networkName, VIR_FREE(ip); goto error; } - VIR_FREE(ip); cur = node->children; while (cur != NULL) { @@ -606,23 +633,16 @@ virNetworkDNSHostDefParseXML(const char *networkName, } cur = cur->next; } - if (def->nnames == 0 && !partialOkay) { - virReportError(VIR_ERR_XML_DETAIL, - _("Missing hostname in network '%s' DNS HOST record"), - networkName); - goto error; - } - if (!VIR_SOCKET_ADDR_VALID(&def->ip) && def->nnames == 0) { - virReportError(VIR_ERR_XML_DETAIL, - _("Missing ip and hostname in network '%s' DNS HOST record"), - networkName); + if (virNetworkDNSHostDefParseXMLPost(node, def, networkName, + partialOkay, ip, def->nnames) < 0) goto error; - } + VIR_FREE(ip); return 0; error: + VIR_FREE(ip); virNetworkDNSHostDefClear(def); return -1; } -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 7 +++-- po/POTFILES.in | 1 + src/conf/network_conf.c | 66 +++++----------------------------------- 3 files changed, 13 insertions(+), 61 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index ee4487e..c902f7e 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -337,14 +337,17 @@ "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { - "args.noctxt": true, + "output": "src/conf/network_conf", "args.instname": true, "post": true, "args": [ {"name": "partialOkay", "type": "Bool"} ] }, - "members": [{"id": "hostname", "name": "name"}] + "members": [ + {"id": "ip", "opt": true}, + {"id": "hostname", "name": "name", "opt": true} + ] } --> <element name="host"> <attribute name="ip"><ref name="ipAddr"/></attribute> diff --git a/po/POTFILES.in b/po/POTFILES.in index 2358b01..5e2985a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -3,6 +3,7 @@ @BUILDDIR@/src/access/viraccessapicheckqemu.c @BUILDDIR@/src/admin/admin_client.h @BUILDDIR@/src/admin/admin_server_dispatch_stubs.h +@BUILDDIR@/src/conf/network_conf.generated.c @BUILDDIR@/src/remote/remote_client_bodies.h @BUILDDIR@/src/remote/remote_daemon_dispatch_stubs.h @SRCDIR@/rng2c/directive.py diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index d9f2252..6f0722a 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -560,9 +560,10 @@ virNetworkDHCPDefParseXML(const char *networkName, } -static int +int virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, virNetworkDNSHostDefPtr def, + xmlXPathContextPtr ctxt G_GNUC_UNUSED, const char *networkName, bool partialOkay, const char *ipStr, @@ -593,61 +594,6 @@ virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } -/* virNetworkDNSHostDefParseXML will be replaced by generated namesake */ -static int -virNetworkDNSHostDefParseXML(const char *networkName, - xmlNodePtr node, - virNetworkDNSHostDefPtr def, - bool partialOkay) -{ - xmlNodePtr cur; - char *ip; - - ip = virXMLPropString(node, "ip"); - if (ip && (virSocketAddrParse(&def->ip, ip, AF_UNSPEC) < 0)) { - virReportError(VIR_ERR_XML_DETAIL, - _("Invalid IP address in network '%s' DNS HOST record"), - networkName); - VIR_FREE(ip); - goto error; - } - - cur = node->children; - while (cur != NULL) { - if (cur->type == XML_ELEMENT_NODE && - virXMLNodeNameEqual(cur, "hostname")) { - if (cur->children != NULL) { - char *name = (char *) xmlNodeGetContent(cur); - - if (!name) { - virReportError(VIR_ERR_XML_DETAIL, - _("Missing hostname in network '%s' DNS HOST record"), - networkName); - goto error; - } - if (VIR_APPEND_ELEMENT(def->names, def->nnames, name) < 0) { - VIR_FREE(name); - goto error; - } - } - } - cur = cur->next; - } - - if (virNetworkDNSHostDefParseXMLPost(node, def, networkName, - partialOkay, ip, def->nnames) < 0) - goto error; - - VIR_FREE(ip); - return 0; - - error: - VIR_FREE(ip); - virNetworkDNSHostDefClear(def); - return -1; -} - - /* This includes all characters used in the names of current * /etc/services and /etc/protocols files (on Fedora 20), except ".", * which we can't allow because it would conflict with the use of "." @@ -907,8 +853,10 @@ virNetworkDNSDefParseXML(const char *networkName, goto cleanup; for (i = 0; i < nhosts; i++) { - if (virNetworkDNSHostDefParseXML(networkName, hostNodes[i], - &def->hosts[def->nhosts], false) < 0) { + if (virNetworkDNSHostDefParseXML(hostNodes[i], + &def->hosts[def->nhosts], + ctxt, + networkName, false) < 0) { goto cleanup; } def->nhosts++; @@ -3373,7 +3321,7 @@ virNetworkDefUpdateDNSHost(virNetworkDefPtr def, if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "host") < 0) goto cleanup; - if (virNetworkDNSHostDefParseXML(def->name, ctxt->node, &host, !isAdd) < 0) + if (virNetworkDNSHostDefParseXML(ctxt->node, &host, ctxt, def->name, !isAdd) < 0) goto cleanup; for (i = 0; i < dns->nhosts; i++) { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 13 ++- src/conf/network_conf.c | 218 +++++++++++++++++++++++++-------------- 2 files changed, 152 insertions(+), 79 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index c902f7e..14561d9 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -302,7 +302,18 @@ <zeroOrMore> <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, - "clearfunc": {"output": "src/conf/network_conf"} + "clearfunc": {"output": "src/conf/network_conf"}, + "parsefunc": { + "post": true, + "args.instname": true, + "args": [ + {"name": "partialOkay", "type": "Bool"} + ] + }, + "members": [ + {"id": "service", "opt": true}, + {"id": "protocol", "opt": true} + ] } --> <element name="srv"> <attribute name="service"><text/></attribute> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 6f0722a..3913cb4 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -214,6 +214,123 @@ virNetworkDefFree(virNetworkDefPtr def) } +/* This includes all characters used in the names of current + * /etc/services and /etc/protocols files (on Fedora 20), except ".", + * which we can't allow because it would conflict with the use of "." + * as a field separator in the SRV record, there appears to be no way + * to escape it in, and the protocols/services that use "." in the + * name are obscure and unlikely to be used anyway. + */ +#define PROTOCOL_CHARS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ + "-+/" + +#define SERVICE_CHARS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ + "_-+/*" + +static int +virNetworkDNSSrvDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, + virNetworkDNSSrvDefPtr def, + xmlXPathContextPtr ctxt G_GNUC_UNUSED, + const char *networkName, + bool partialOkay, + const char *serviceStr G_GNUC_UNUSED, + const char *protocolStr G_GNUC_UNUSED, + const char *domainStr G_GNUC_UNUSED, + const char *targetStr G_GNUC_UNUSED, + bool has_port, + bool has_priority, + bool has_weight) +{ + if (!def->service && !partialOkay) { + virReportError(VIR_ERR_XML_DETAIL, + _("missing required service attribute in DNS SRV record " + "of network '%s'"), networkName); + return -1; + } + if (def->service) { + if (strlen(def->service) > DNS_RECORD_LENGTH_SRV) { + virReportError(VIR_ERR_XML_DETAIL, + _("service attribute '%s' in network '%s' is too long, " + "limit is %d bytes"), + def->service, networkName, DNS_RECORD_LENGTH_SRV); + return -1; + } + if (strspn(def->service, SERVICE_CHARS) < strlen(def->service)) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid character in service attribute '%s' " + "in DNS SRV record of network '%s'"), + def->service, networkName); + return -1; + } + } + + if (!def->protocol && !partialOkay) { + virReportError(VIR_ERR_XML_DETAIL, + _("missing required protocol attribute " + "in DNS SRV record '%s' of network '%s'"), + def->service, networkName); + return -1; + } + if (def->protocol && + strspn(def->protocol, PROTOCOL_CHARS) < strlen(def->protocol)) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid character in protocol attribute '%s' " + "in DNS SRV record of network '%s'"), + def->protocol, networkName); + return -1; + } + + if (has_port && !def->target) { + virReportError(VIR_ERR_XML_DETAIL, + _("DNS SRV port attribute not permitted without " + "target for service '%s' in network '%s'"), + def->service, networkName); + return -1; + } + if (has_port && (def->port < 1 || def->port > 65535)) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid DNS SRV port attribute " + "for service '%s' in network '%s'"), + def->service, networkName); + return -1; + } + + if (has_priority && !def->target) { + virReportError(VIR_ERR_XML_DETAIL, + _("DNS SRV priority attribute not permitted without " + "target for service '%s' in network '%s'"), + def->service, networkName); + return -1; + } + if (has_priority && def->priority > 65535) { + virReportError(VIR_ERR_XML_DETAIL, + _("Invalid DNS SRV priority attribute " + "for service '%s' in network '%s'"), + def->service, networkName); + return -1; + } + + if (has_weight && !def->target) { + virReportError(VIR_ERR_XML_DETAIL, + _("DNS SRV weight attribute not permitted without " + "target for service '%s' in network '%s'"), + def->service, networkName); + return -1; + } + if (has_weight && def->weight > 65535) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid DNS SRV weight attribute " + "for service '%s' in network '%s'"), + def->service, networkName); + return -1; + } + + return 0; +} + + /* * virNetworkDefCopy: * @def: NetworkDef to copy @@ -594,21 +711,7 @@ virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } -/* This includes all characters used in the names of current - * /etc/services and /etc/protocols files (on Fedora 20), except ".", - * which we can't allow because it would conflict with the use of "." - * as a field separator in the SRV record, there appears to be no way - * to escape it in, and the protocols/services that use "." in the - * name are obscure and unlikely to be used anyway. - */ -#define PROTOCOL_CHARS \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ - "-+/" - -#define SERVICE_CHARS \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ - "_-+/*" - +/* virNetworkDNSSrvDefParseXML will be replaced by generated namesake */ static int virNetworkDNSSrvDefParseXML(const char *networkName, xmlNodePtr node, @@ -617,62 +720,23 @@ virNetworkDNSSrvDefParseXML(const char *networkName, bool partialOkay) { int ret; + bool has_port = false; + bool has_priority = false; + bool has_weight = false; xmlNodePtr save_ctxt = ctxt->node; - ctxt->node = node; - if (!(def->service = virXMLPropString(node, "service")) && !partialOkay) { - virReportError(VIR_ERR_XML_DETAIL, - _("missing required service attribute in DNS SRV record " - "of network '%s'"), networkName); - goto error; - } - if (def->service) { - if (strlen(def->service) > DNS_RECORD_LENGTH_SRV) { - virReportError(VIR_ERR_XML_DETAIL, - _("service attribute '%s' in network '%s' is too long, " - "limit is %d bytes"), - def->service, networkName, DNS_RECORD_LENGTH_SRV); - goto error; - } - if (strspn(def->service, SERVICE_CHARS) < strlen(def->service)) { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid character in service attribute '%s' " - "in DNS SRV record of network '%s'"), - def->service, networkName); - goto error; - } - } - - if (!(def->protocol = virXMLPropString(node, "protocol")) && !partialOkay) { - virReportError(VIR_ERR_XML_DETAIL, - _("missing required protocol attribute " - "in DNS SRV record '%s' of network '%s'"), - def->service, networkName); - goto error; - } - if (def->protocol && - strspn(def->protocol, PROTOCOL_CHARS) < strlen(def->protocol)) { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid character in protocol attribute '%s' " - "in DNS SRV record of network '%s'"), - def->protocol, networkName); - goto error; - } + def->service = virXMLPropString(node, "service"); + def->protocol = virXMLPropString(node, "protocol"); /* Following attributes are optional */ def->domain = virXMLPropString(node, "domain"); def->target = virXMLPropString(node, "target"); ret = virXPathUInt("string(./@port)", ctxt, &def->port); - if (ret >= 0 && !def->target) { - virReportError(VIR_ERR_XML_DETAIL, - _("DNS SRV port attribute not permitted without " - "target for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - if (ret == -2 || (ret >= 0 && (def->port < 1 || def->port > 65535))) { + if (ret >= 0) { + has_port = true; + } else if (ret == -2) { virReportError(VIR_ERR_XML_DETAIL, _("invalid DNS SRV port attribute " "for service '%s' in network '%s'"), @@ -681,14 +745,9 @@ virNetworkDNSSrvDefParseXML(const char *networkName, } ret = virXPathUInt("string(./@priority)", ctxt, &def->priority); - if (ret >= 0 && !def->target) { - virReportError(VIR_ERR_XML_DETAIL, - _("DNS SRV priority attribute not permitted without " - "target for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - if (ret == -2 || (ret >= 0 && def->priority > 65535)) { + if (ret >= 0) { + has_priority = true; + } else if (ret == -2) { virReportError(VIR_ERR_XML_DETAIL, _("Invalid DNS SRV priority attribute " "for service '%s' in network '%s'"), @@ -697,14 +756,9 @@ virNetworkDNSSrvDefParseXML(const char *networkName, } ret = virXPathUInt("string(./@weight)", ctxt, &def->weight); - if (ret >= 0 && !def->target) { - virReportError(VIR_ERR_XML_DETAIL, - _("DNS SRV weight attribute not permitted without " - "target for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - if (ret == -2 || (ret >= 0 && def->weight > 65535)) { + if (ret >= 0) { + has_weight = true; + } else if (ret == -2) { virReportError(VIR_ERR_XML_DETAIL, _("invalid DNS SRV weight attribute " "for service '%s' in network '%s'"), @@ -712,6 +766,14 @@ virNetworkDNSSrvDefParseXML(const char *networkName, goto error; } + if (virNetworkDNSSrvDefParseXMLPost(node, def, ctxt, + networkName, partialOkay, + def->service, def->protocol, + def->domain, def->target, + has_port, has_priority, + has_weight) < 0) + goto error; + ctxt->node = save_ctxt; return 0; -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 1 + src/conf/network_conf.c | 100 ++++++--------------------------------- 2 files changed, 15 insertions(+), 86 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 14561d9..4caea02 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -304,6 +304,7 @@ "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { + "output": "src/conf/network_conf", "post": true, "args.instname": true, "args": [ diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 3913cb4..0469f03 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -229,7 +229,7 @@ virNetworkDefFree(virNetworkDefPtr def) "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ "_-+/*" -static int +int virNetworkDNSSrvDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, virNetworkDNSSrvDefPtr def, xmlXPathContextPtr ctxt G_GNUC_UNUSED, @@ -239,9 +239,9 @@ virNetworkDNSSrvDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, const char *protocolStr G_GNUC_UNUSED, const char *domainStr G_GNUC_UNUSED, const char *targetStr G_GNUC_UNUSED, - bool has_port, - bool has_priority, - bool has_weight) + const char *portStr, + const char *priorityStr, + const char *weightStr) { if (!def->service && !partialOkay) { virReportError(VIR_ERR_XML_DETAIL, @@ -282,14 +282,14 @@ virNetworkDNSSrvDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, return -1; } - if (has_port && !def->target) { + if (portStr && !def->target) { virReportError(VIR_ERR_XML_DETAIL, _("DNS SRV port attribute not permitted without " "target for service '%s' in network '%s'"), def->service, networkName); return -1; } - if (has_port && (def->port < 1 || def->port > 65535)) { + if (portStr && (def->port < 1 || def->port > 65535)) { virReportError(VIR_ERR_XML_DETAIL, _("invalid DNS SRV port attribute " "for service '%s' in network '%s'"), @@ -297,14 +297,14 @@ virNetworkDNSSrvDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, return -1; } - if (has_priority && !def->target) { + if (priorityStr && !def->target) { virReportError(VIR_ERR_XML_DETAIL, _("DNS SRV priority attribute not permitted without " "target for service '%s' in network '%s'"), def->service, networkName); return -1; } - if (has_priority && def->priority > 65535) { + if (priorityStr && def->priority > 65535) { virReportError(VIR_ERR_XML_DETAIL, _("Invalid DNS SRV priority attribute " "for service '%s' in network '%s'"), @@ -312,14 +312,14 @@ virNetworkDNSSrvDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, return -1; } - if (has_weight && !def->target) { + if (weightStr && !def->target) { virReportError(VIR_ERR_XML_DETAIL, _("DNS SRV weight attribute not permitted without " "target for service '%s' in network '%s'"), def->service, networkName); return -1; } - if (has_weight && def->weight > 65535) { + if (weightStr && def->weight > 65535) { virReportError(VIR_ERR_XML_DETAIL, _("invalid DNS SRV weight attribute " "for service '%s' in network '%s'"), @@ -711,79 +711,6 @@ virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } -/* virNetworkDNSSrvDefParseXML will be replaced by generated namesake */ -static int -virNetworkDNSSrvDefParseXML(const char *networkName, - xmlNodePtr node, - xmlXPathContextPtr ctxt, - virNetworkDNSSrvDefPtr def, - bool partialOkay) -{ - int ret; - bool has_port = false; - bool has_priority = false; - bool has_weight = false; - xmlNodePtr save_ctxt = ctxt->node; - ctxt->node = node; - - def->service = virXMLPropString(node, "service"); - def->protocol = virXMLPropString(node, "protocol"); - - /* Following attributes are optional */ - def->domain = virXMLPropString(node, "domain"); - def->target = virXMLPropString(node, "target"); - - ret = virXPathUInt("string(./@port)", ctxt, &def->port); - if (ret >= 0) { - has_port = true; - } else if (ret == -2) { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid DNS SRV port attribute " - "for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - - ret = virXPathUInt("string(./@priority)", ctxt, &def->priority); - if (ret >= 0) { - has_priority = true; - } else if (ret == -2) { - virReportError(VIR_ERR_XML_DETAIL, - _("Invalid DNS SRV priority attribute " - "for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - - ret = virXPathUInt("string(./@weight)", ctxt, &def->weight); - if (ret >= 0) { - has_weight = true; - } else if (ret == -2) { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid DNS SRV weight attribute " - "for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - - if (virNetworkDNSSrvDefParseXMLPost(node, def, ctxt, - networkName, partialOkay, - def->service, def->protocol, - def->domain, def->target, - has_port, has_priority, - has_weight) < 0) - goto error; - - ctxt->node = save_ctxt; - return 0; - - error: - virNetworkDNSSrvDefClear(def); - ctxt->node = save_ctxt; - return -1; -} - - static int virNetworkDNSTxtDefParseXML(const char *networkName, xmlNodePtr node, @@ -937,8 +864,9 @@ virNetworkDNSDefParseXML(const char *networkName, goto cleanup; for (i = 0; i < nsrvs; i++) { - if (virNetworkDNSSrvDefParseXML(networkName, srvNodes[i], ctxt, - &def->srvs[def->nsrvs], false) < 0) { + if (virNetworkDNSSrvDefParseXML(srvNodes[i], + &def->srvs[def->nsrvs], ctxt, + networkName, false) < 0) { goto cleanup; } def->nsrvs++; @@ -3478,7 +3406,7 @@ virNetworkDefUpdateDNSSrv(virNetworkDefPtr def, if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "srv") < 0) goto cleanup; - if (virNetworkDNSSrvDefParseXML(def->name, ctxt->node, ctxt, &srv, !isAdd) < 0) + if (virNetworkDNSSrvDefParseXML(ctxt->node, &srv, ctxt, def->name, !isAdd) < 0) goto cleanup; for (i = 0; i < dns->nsrvs; i++) { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 13 ++++++++- src/conf/network_conf.c | 60 +++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 4caea02..b0f4a64 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -292,7 +292,18 @@ <zeroOrMore> <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, - "clearfunc": {"output": "src/conf/network_conf"} + "clearfunc": {"output": "src/conf/network_conf"}, + "parsefunc": { + "post": true, + "args.noctxt": true, + "args.instname": true, + "args": [ + {"name": "partialOkay", "type": "Bool"} + ] + }, + "members": [ + {"id": "value", "opt": true} + ] } --> <element name="txt"> <attribute name="name"><ref name="dnsName"/></attribute> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 0469f03..f83799f 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -488,6 +488,41 @@ virSocketAddrRangeParseXML(const char *networkName, } +static int +virNetworkDNSTxtDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, + virNetworkDNSTxtDefPtr def, + const char *networkName, + bool partialOkay, + const char *nameStr G_GNUC_UNUSED, + const char *valueStr G_GNUC_UNUSED) +{ + const char *bad = " ,"; + + if (strcspn(def->name, bad) != strlen(def->name)) { + virReportError(VIR_ERR_XML_DETAIL, + _("prohibited character in DNS TXT record " + "name '%s' of network %s"), def->name, networkName); + return -1; + } + + if (!def->value && !partialOkay) { + virReportError(VIR_ERR_XML_DETAIL, + _("missing required value attribute in DNS TXT record " + "named '%s' of network %s"), def->name, networkName); + return -1; + } + + if (!(def->name || def->value)) { + virReportError(VIR_ERR_XML_DETAIL, + _("Missing required name or value " + "in DNS TXT record of network %s"), networkName); + return -1; + } + + return 0; +} + + static int virNetworkDHCPHostDefParseXML(const char *networkName, virNetworkIPDefPtr def, @@ -717,33 +752,20 @@ virNetworkDNSTxtDefParseXML(const char *networkName, virNetworkDNSTxtDefPtr def, bool partialOkay) { - const char *bad = " ,"; - if (!(def->name = virXMLPropString(node, "name"))) { virReportError(VIR_ERR_XML_DETAIL, _("missing required name attribute in DNS TXT record " "of network %s"), networkName); goto error; } - if (strcspn(def->name, bad) != strlen(def->name)) { - virReportError(VIR_ERR_XML_DETAIL, - _("prohibited character in DNS TXT record " - "name '%s' of network %s"), def->name, networkName); - goto error; - } - if (!(def->value = virXMLPropString(node, "value")) && !partialOkay) { - virReportError(VIR_ERR_XML_DETAIL, - _("missing required value attribute in DNS TXT record " - "named '%s' of network %s"), def->name, networkName); - goto error; - } - if (!(def->name || def->value)) { - virReportError(VIR_ERR_XML_DETAIL, - _("Missing required name or value " - "in DNS TXT record of network %s"), networkName); + def->value = virXMLPropString(node, "value"); + + if (virNetworkDNSTxtDefParseXMLPost(node, def, + networkName, partialOkay, + def->name, def->value) < 0) goto error; - } + return 0; error: -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 1 + src/conf/network_conf.c | 37 +++++-------------------------------- 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index b0f4a64..07fbd5b 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -294,6 +294,7 @@ "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { + "output": "src/conf/network_conf", "post": true, "args.noctxt": true, "args.instname": true, diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index f83799f..5180508 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -488,7 +488,7 @@ virSocketAddrRangeParseXML(const char *networkName, } -static int +int virNetworkDNSTxtDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, virNetworkDNSTxtDefPtr def, const char *networkName, @@ -746,34 +746,6 @@ virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } -static int -virNetworkDNSTxtDefParseXML(const char *networkName, - xmlNodePtr node, - virNetworkDNSTxtDefPtr def, - bool partialOkay) -{ - if (!(def->name = virXMLPropString(node, "name"))) { - virReportError(VIR_ERR_XML_DETAIL, - _("missing required name attribute in DNS TXT record " - "of network %s"), networkName); - goto error; - } - - def->value = virXMLPropString(node, "value"); - - if (virNetworkDNSTxtDefParseXMLPost(node, def, - networkName, partialOkay, - def->name, def->value) < 0) - goto error; - - return 0; - - error: - virNetworkDNSTxtDefClear(def); - return -1; -} - - static int virNetworkDNSDefParseXML(const char *networkName, xmlNodePtr node, @@ -907,8 +879,9 @@ virNetworkDNSDefParseXML(const char *networkName, goto cleanup; for (i = 0; i < ntxts; i++) { - if (virNetworkDNSTxtDefParseXML(networkName, txtNodes[i], - &def->txts[def->ntxts], false) < 0) { + if (virNetworkDNSTxtDefParseXML(txtNodes[i], + &def->txts[def->ntxts], + networkName, false) < 0) { goto cleanup; } def->ntxts++; @@ -3513,7 +3486,7 @@ virNetworkDefUpdateDNSTxt(virNetworkDefPtr def, if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "txt") < 0) goto cleanup; - if (virNetworkDNSTxtDefParseXML(def->name, ctxt->node, &txt, !isAdd) < 0) + if (virNetworkDNSTxtDefParseXML(ctxt->node, &txt, def->name, !isAdd) < 0) goto cleanup; for (foundIdx = 0; foundIdx < dns->ntxts; foundIdx++) { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 7 +++- src/conf/network_conf.c | 69 +++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 07fbd5b..20878a8 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -277,7 +277,12 @@ <!-- VIRT:DIRECTIVE { "name": "virNetworkDNSForwarder", "structure": {"output": "src/conf/network_conf"}, - "clearfunc": {"output": "src/conf/network_conf"} + "clearfunc": {"output": "src/conf/network_conf"}, + "parsefunc": { + "post": true, + "args.noctxt": true, + "args.instname": true + } } --> <element name="forwarder"> <optional> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 5180508..91f08e9 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -214,6 +214,53 @@ virNetworkDefFree(virNetworkDefPtr def) } +static int +virNetworkDNSForwarderParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, + virNetworkDNSForwarderPtr def, + const char *networkName G_GNUC_UNUSED, + const char *addrStr, + const char *domainStr G_GNUC_UNUSED) +{ + if (!(addrStr || def->domain)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Invalid forwarder element, must contain " + "at least one of addr or domain")); + return -1; + } + + return 0; +} + + +/* virNetworkDNSForwarderParseXML will be replaced by generated namesake */ +static int +virNetworkDNSForwarderParseXML(xmlNodePtr curnode, + virNetworkDNSForwarderPtr def, + const char *networkName) +{ + char *addr = virXMLPropString(curnode, "addr"); + if (addr && virSocketAddrParse(&def->addr, addr, AF_UNSPEC) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid forwarder IP address '%s' " + "in network '%s'"), + addr, networkName); + VIR_FREE(addr); + return -1; + } + + def->domain = virXMLPropString(curnode, "domain"); + + if (virNetworkDNSForwarderParseXMLPost(curnode, def, networkName, + addr, def->domain) < 0) { + VIR_FREE(addr); + return -1; + } + + VIR_FREE(addr); + return 0; +} + + /* This includes all characters used in the names of current * /etc/services and /etc/protocols files (on Fedora 20), except ".", * which we can't allow because it would conflict with the use of "." @@ -801,25 +848,11 @@ virNetworkDNSDefParseXML(const char *networkName, goto cleanup; for (i = 0; i < nfwds; i++) { - char *addr = virXMLPropString(fwdNodes[i], "addr"); - - if (addr && virSocketAddrParse(&def->forwarders[i].addr, - addr, AF_UNSPEC) < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("Invalid forwarder IP address '%s' " - "in network '%s'"), - addr, networkName); - VIR_FREE(addr); + if (virNetworkDNSForwarderParseXML(fwdNodes[i], + &def->forwarders[i], + networkName) < 0) goto cleanup; - } - def->forwarders[i].domain = virXMLPropString(fwdNodes[i], "domain"); - if (!(addr || def->forwarders[i].domain)) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("Invalid forwarder element, must contain " - "at least one of addr or domain")); - goto cleanup; - } - VIR_FREE(addr); + def->nforwarders++; } } -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 1 + src/conf/network_conf.c | 31 +------------------------------ 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 20878a8..702018b 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -279,6 +279,7 @@ "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { + "output": "src/conf/network_conf", "post": true, "args.noctxt": true, "args.instname": true diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 91f08e9..3a43e40 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -214,7 +214,7 @@ virNetworkDefFree(virNetworkDefPtr def) } -static int +int virNetworkDNSForwarderParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, virNetworkDNSForwarderPtr def, const char *networkName G_GNUC_UNUSED, @@ -232,35 +232,6 @@ virNetworkDNSForwarderParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } -/* virNetworkDNSForwarderParseXML will be replaced by generated namesake */ -static int -virNetworkDNSForwarderParseXML(xmlNodePtr curnode, - virNetworkDNSForwarderPtr def, - const char *networkName) -{ - char *addr = virXMLPropString(curnode, "addr"); - if (addr && virSocketAddrParse(&def->addr, addr, AF_UNSPEC) < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("Invalid forwarder IP address '%s' " - "in network '%s'"), - addr, networkName); - VIR_FREE(addr); - return -1; - } - - def->domain = virXMLPropString(curnode, "domain"); - - if (virNetworkDNSForwarderParseXMLPost(curnode, def, networkName, - addr, def->domain) < 0) { - VIR_FREE(addr); - return -1; - } - - VIR_FREE(addr); - return 0; -} - - /* This includes all characters used in the names of current * /etc/services and /etc/protocols files (on Fedora 20), except ".", * which we can't allow because it would conflict with the use of "." -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 6 +++++- src/conf/network_conf.c | 33 +++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 702018b..884cd64 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -259,7 +259,11 @@ <optional> <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, - "clearfunc": {"output": "src/conf/network_conf"} + "clearfunc": {"output": "src/conf/network_conf"}, + "parsefunc": { + "post": true, + "args.instname": true + } } --> <element name="dns"> <optional> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 3a43e40..d231fea 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -764,6 +764,30 @@ virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } +static int +virNetworkDNSDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, + virNetworkDNSDefPtr def, + xmlXPathContextPtr ctxt G_GNUC_UNUSED, + const char *networkName, + const char *enableStr G_GNUC_UNUSED, + const char *forwardPlainNamesStr G_GNUC_UNUSED, + int nForwarderNodes, + int nTxtNodes, + int nSrvNodes, + int nHostNodes) +{ + if (def->enable == VIR_TRISTATE_BOOL_NO && + (nForwarderNodes || nHostNodes || nSrvNodes || nTxtNodes)) { + virReportError(VIR_ERR_XML_ERROR, + _("Extra data in disabled network '%s'"), + networkName); + return -1; + } + + return 0; +} + + static int virNetworkDNSDefParseXML(const char *networkName, xmlNodePtr node, @@ -892,13 +916,10 @@ virNetworkDNSDefParseXML(const char *networkName, } } - if (def->enable == VIR_TRISTATE_BOOL_NO && - (nfwds || nhosts || nsrvs || ntxts)) { - virReportError(VIR_ERR_XML_ERROR, - _("Extra data in disabled network '%s'"), - networkName); + if (virNetworkDNSDefParseXMLPost(node, def, ctxt, networkName, enable, + forwardPlainNames, nfwds, ntxts, + nsrvs, nhosts) < 0) goto cleanup; - } ret = 0; cleanup: -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 1 + src/conf/network_conf.c | 150 +-------------------------------------- 2 files changed, 3 insertions(+), 148 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 884cd64..7084248 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -261,6 +261,7 @@ "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { + "output": "src/conf/network_conf", "post": true, "args.instname": true } diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index d231fea..905c01f 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -764,7 +764,7 @@ virNetworkDNSHostDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } -static int +int virNetworkDNSDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, virNetworkDNSDefPtr def, xmlXPathContextPtr ctxt G_GNUC_UNUSED, @@ -788,152 +788,6 @@ virNetworkDNSDefParseXMLPost(xmlNodePtr curnode G_GNUC_UNUSED, } -static int -virNetworkDNSDefParseXML(const char *networkName, - xmlNodePtr node, - xmlXPathContextPtr ctxt, - virNetworkDNSDefPtr def) -{ - xmlNodePtr *hostNodes = NULL; - xmlNodePtr *srvNodes = NULL; - xmlNodePtr *txtNodes = NULL; - xmlNodePtr *fwdNodes = NULL; - char *forwardPlainNames = NULL; - char *enable = NULL; - int nhosts, nsrvs, ntxts, nfwds; - size_t i; - int ret = -1; - xmlNodePtr save = ctxt->node; - - ctxt->node = node; - - enable = virXPathString("string(./@enable)", ctxt); - if (enable) { - def->enable = virTristateBoolTypeFromString(enable); - if (def->enable <= 0) { - virReportError(VIR_ERR_XML_ERROR, - _("Invalid dns enable setting '%s' " - "in network '%s'"), - enable, networkName); - goto cleanup; - } - } - - forwardPlainNames = virXPathString("string(./@forwardPlainNames)", ctxt); - if (forwardPlainNames) { - def->forwardPlainNames = virTristateBoolTypeFromString(forwardPlainNames); - if (def->forwardPlainNames <= 0) { - virReportError(VIR_ERR_XML_ERROR, - _("Invalid dns forwardPlainNames setting '%s' " - "in network '%s'"), - forwardPlainNames, networkName); - goto cleanup; - } - } - - nfwds = virXPathNodeSet("./forwarder", ctxt, &fwdNodes); - if (nfwds < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("invalid <forwarder> element found in <dns> of network %s"), - networkName); - goto cleanup; - } - if (nfwds > 0) { - if (VIR_ALLOC_N(def->forwarders, nfwds) < 0) - goto cleanup; - - for (i = 0; i < nfwds; i++) { - if (virNetworkDNSForwarderParseXML(fwdNodes[i], - &def->forwarders[i], - networkName) < 0) - goto cleanup; - - def->nforwarders++; - } - } - - nhosts = virXPathNodeSet("./host", ctxt, &hostNodes); - if (nhosts < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("invalid <host> element found in <dns> of network %s"), - networkName); - goto cleanup; - } - if (nhosts > 0) { - if (VIR_ALLOC_N(def->hosts, nhosts) < 0) - goto cleanup; - - for (i = 0; i < nhosts; i++) { - if (virNetworkDNSHostDefParseXML(hostNodes[i], - &def->hosts[def->nhosts], - ctxt, - networkName, false) < 0) { - goto cleanup; - } - def->nhosts++; - } - } - - nsrvs = virXPathNodeSet("./srv", ctxt, &srvNodes); - if (nsrvs < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("invalid <srv> element found in <dns> of network %s"), - networkName); - goto cleanup; - } - if (nsrvs > 0) { - if (VIR_ALLOC_N(def->srvs, nsrvs) < 0) - goto cleanup; - - for (i = 0; i < nsrvs; i++) { - if (virNetworkDNSSrvDefParseXML(srvNodes[i], - &def->srvs[def->nsrvs], ctxt, - networkName, false) < 0) { - goto cleanup; - } - def->nsrvs++; - } - } - - ntxts = virXPathNodeSet("./txt", ctxt, &txtNodes); - if (ntxts < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("invalid <txt> element found in <dns> of network %s"), - networkName); - goto cleanup; - } - if (ntxts > 0) { - if (VIR_ALLOC_N(def->txts, ntxts) < 0) - goto cleanup; - - for (i = 0; i < ntxts; i++) { - if (virNetworkDNSTxtDefParseXML(txtNodes[i], - &def->txts[def->ntxts], - networkName, false) < 0) { - goto cleanup; - } - def->ntxts++; - } - } - - if (virNetworkDNSDefParseXMLPost(node, def, ctxt, networkName, enable, - forwardPlainNames, nfwds, ntxts, - nsrvs, nhosts) < 0) - goto cleanup; - - ret = 0; - cleanup: - VIR_FREE(enable); - VIR_FREE(forwardPlainNames); - VIR_FREE(fwdNodes); - VIR_FREE(hostNodes); - VIR_FREE(srvNodes); - VIR_FREE(txtNodes); - ctxt->node = save; - return ret; -} - - static int virNetworkIPDefParseXML(const char *networkName, xmlNodePtr node, @@ -1726,7 +1580,7 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt, dnsNode = virXPathNode("./dns", ctxt); if (dnsNode != NULL && - virNetworkDNSDefParseXML(def->name, dnsNode, ctxt, &def->dns) < 0) { + virNetworkDNSDefParseXML(dnsNode, &def->dns, ctxt, def->name) < 0) { goto error; } -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 4 ++++ src/conf/network_conf.c | 17 ++--------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 7084248..cd5be22 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -288,6 +288,10 @@ "post": true, "args.noctxt": true, "args.instname": true + }, + "formatfunc": { + "output": "src/conf/network_conf", + "order": ["domain", "addr"] } } --> <element name="forwarder"> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 905c01f..19444d6 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1964,22 +1964,9 @@ virNetworkDNSDefFormat(virBufferPtr buf, virBufferAdjustIndent(buf, 2); for (i = 0; i < def->nforwarders; i++) { - - virBufferAddLit(buf, "<forwarder"); - if (def->forwarders[i].domain) { - virBufferEscapeString(buf, " domain='%s'", - def->forwarders[i].domain); - } - if (VIR_SOCKET_ADDR_VALID(&def->forwarders[i].addr)) { - char *addr = virSocketAddrFormat(&def->forwarders[i].addr); - - if (!addr) + if (virNetworkDNSForwarderFormatBuf(buf, "forwarder", + &def->forwarders[i]) < 0) return -1; - - virBufferAsprintf(buf, " addr='%s'", addr); - VIR_FREE(addr); - } - virBufferAddLit(buf, "/>\n"); } for (i = 0; i < def->ntxts; i++) { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 1 + src/conf/network_conf.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index cd5be22..1d7b854 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -317,6 +317,7 @@ {"name": "partialOkay", "type": "Bool"} ] }, + "formatfunc": {"output": "src/conf/network_conf"}, "members": [ {"id": "value", "opt": true} ] diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 19444d6..b40ab4d 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1970,8 +1970,8 @@ virNetworkDNSDefFormat(virBufferPtr buf, } for (i = 0; i < def->ntxts; i++) { - virBufferEscapeString(buf, "<txt name='%s' ", def->txts[i].name); - virBufferEscapeString(buf, "value='%s'/>\n", def->txts[i].value); + if (virNetworkDNSTxtDefFormatBuf(buf, "txt", &def->txts[i]) < 0) + return -1; } for (i = 0; i < def->nsrvs; i++) { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 1 + src/conf/network_conf.c | 20 ++------------------ 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index 1d7b854..b97510f 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -339,6 +339,7 @@ {"name": "partialOkay", "type": "Bool"} ] }, + "formatfunc": {"output": "src/conf/network_conf"}, "members": [ {"id": "service", "opt": true}, {"id": "protocol", "opt": true} diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index b40ab4d..1e8bfbc 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1975,24 +1975,8 @@ virNetworkDNSDefFormat(virBufferPtr buf, } for (i = 0; i < def->nsrvs; i++) { - if (def->srvs[i].service && def->srvs[i].protocol) { - virBufferEscapeString(buf, "<srv service='%s' ", - def->srvs[i].service); - virBufferEscapeString(buf, "protocol='%s'", def->srvs[i].protocol); - - if (def->srvs[i].domain) - virBufferEscapeString(buf, " domain='%s'", def->srvs[i].domain); - if (def->srvs[i].target) - virBufferEscapeString(buf, " target='%s'", def->srvs[i].target); - if (def->srvs[i].port) - virBufferAsprintf(buf, " port='%d'", def->srvs[i].port); - if (def->srvs[i].priority) - virBufferAsprintf(buf, " priority='%d'", def->srvs[i].priority); - if (def->srvs[i].weight) - virBufferAsprintf(buf, " weight='%d'", def->srvs[i].weight); - - virBufferAddLit(buf, "/>\n"); - } + if (virNetworkDNSSrvDefFormatBuf(buf, "srv", &def->srvs[i]) < 0) + return -1; } if (def->nhosts) { -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 1 + src/conf/network_conf.c | 15 +++------------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index b97510f..ba5db0b 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -385,6 +385,7 @@ {"name": "partialOkay", "type": "Bool"} ] }, + "formatfunc": {"output": "src/conf/network_conf"}, "members": [ {"id": "ip", "opt": true}, {"id": "hostname", "name": "name", "opt": true} diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 1e8bfbc..e5d6e49 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1926,7 +1926,7 @@ static int virNetworkDNSDefFormat(virBufferPtr buf, const virNetworkDNSDef *def) { - size_t i, j; + size_t i; if (!(def->enable || def->forwardPlainNames || def->nforwarders || def->nhosts || def->nsrvs || def->ntxts)) @@ -1981,17 +1981,8 @@ virNetworkDNSDefFormat(virBufferPtr buf, if (def->nhosts) { for (i = 0; i < def->nhosts; i++) { - char *ip = virSocketAddrFormat(&def->hosts[i].ip); - - virBufferAsprintf(buf, "<host ip='%s'>\n", ip); - virBufferAdjustIndent(buf, 2); - for (j = 0; j < def->hosts[i].nnames; j++) - virBufferEscapeString(buf, "<hostname>%s</hostname>\n", - def->hosts[i].names[j]); - - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</host>\n"); - VIR_FREE(ip); + if (virNetworkDNSHostDefFormatBuf(buf, "host", &def->hosts[i]) < 0) + return -1; } } virBufferAdjustIndent(buf, -2); -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- docs/schemas/network.rng | 3 +- src/conf/network_conf.c | 71 +--------------------------------------- 2 files changed, 3 insertions(+), 71 deletions(-) diff --git a/docs/schemas/network.rng b/docs/schemas/network.rng index ba5db0b..db04b7c 100644 --- a/docs/schemas/network.rng +++ b/docs/schemas/network.rng @@ -264,7 +264,8 @@ "output": "src/conf/network_conf", "post": true, "args.instname": true - } + }, + "formatfunc": {"output": "src/conf/network_conf"} } --> <element name="dns"> <optional> diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index e5d6e49..e46c208 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -1922,75 +1922,6 @@ virNetworkDefParseNode(xmlDocPtr xml, } -static int -virNetworkDNSDefFormat(virBufferPtr buf, - const virNetworkDNSDef *def) -{ - size_t i; - - if (!(def->enable || def->forwardPlainNames || def->nforwarders || def->nhosts || - def->nsrvs || def->ntxts)) - return 0; - - virBufferAddLit(buf, "<dns"); - if (def->enable) { - const char *fwd = virTristateBoolTypeToString(def->enable); - - if (!fwd) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unknown enable type %d in network"), - def->enable); - return -1; - } - virBufferAsprintf(buf, " enable='%s'", fwd); - } - if (def->forwardPlainNames) { - const char *fwd = virTristateBoolTypeToString(def->forwardPlainNames); - - if (!fwd) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unknown forwardPlainNames type %d in network"), - def->forwardPlainNames); - return -1; - } - virBufferAsprintf(buf, " forwardPlainNames='%s'", fwd); - } - if (!(def->nforwarders || def->nhosts || def->nsrvs || def->ntxts)) { - virBufferAddLit(buf, "/>\n"); - return 0; - } - - virBufferAddLit(buf, ">\n"); - virBufferAdjustIndent(buf, 2); - - for (i = 0; i < def->nforwarders; i++) { - if (virNetworkDNSForwarderFormatBuf(buf, "forwarder", - &def->forwarders[i]) < 0) - return -1; - } - - for (i = 0; i < def->ntxts; i++) { - if (virNetworkDNSTxtDefFormatBuf(buf, "txt", &def->txts[i]) < 0) - return -1; - } - - for (i = 0; i < def->nsrvs; i++) { - if (virNetworkDNSSrvDefFormatBuf(buf, "srv", &def->srvs[i]) < 0) - return -1; - } - - if (def->nhosts) { - for (i = 0; i < def->nhosts; i++) { - if (virNetworkDNSHostDefFormatBuf(buf, "host", &def->hosts[i]) < 0) - return -1; - } - } - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</dns>\n"); - return 0; -} - - static int virNetworkIPDefFormat(virBufferPtr buf, const virNetworkIPDef *def) @@ -2366,7 +2297,7 @@ virNetworkDefFormatBuf(virBufferPtr buf, virBufferAddLit(buf, "/>\n"); } - if (virNetworkDNSDefFormat(buf, &def->dns) < 0) + if (virNetworkDNSDefFormatBuf(buf, "dns", &def->dns) < 0) return -1; if (virNetDevVlanFormat(&def->vlan, buf) < 0) -- 2.17.1

On Wed, Mar 25, 2020 at 03:11:40PM +0800, Shi Lei wrote:
Outline =========
In libvirt world, objects(like domain, network, etc.) are described with two representations: structures in c-language and XML specified by relax-ng. Since c-language-implementation and xml restricted by relax-ng are merely the different expressions of the same object-model, we can make a tool to generate those C-language codes based on relax-ng files.
RNG2C tool ===========
RNG2C is a python tool which can translate hierarchy of notations in relax-ng into c-language structures. In general, <element> is converted to struct in C, struct's members derive from its <attribute>s and its sub <element>s; <choice> with multiple <value>s is converted to enum in C; <data> are converted into various builtin-type in C, such as int, char *.
Also, RNG2C can generate relative conversion functions: vir[XXX]ParseXML, vir[XXX]FormatBuf and vir[XXX]Clear.
These structures and functions can be used to replace hardcoded codes in current libvirt source.
The tool RNG2C has three subcommands: 'list', 'show' and 'generate'. The 'list' and 'show' are used for previewing generated code, and the 'generate' is prepared to be invoked by Makefile.
[snip] Wow, this is a very impressive and ambitious bit of work, so thanks for proposing this idea ! The idea of generating the XML parsers is pretty appealing and the kind of thing I wish we had done right back at the very start of libvirt development. I really came to like the idea of a metadata driven XML parser when I created the libvirt-go-xml project structs. As you already no doubt found, doing the auto-generation for integrating into our existing code is even more tricky. I expect the hardest parts are probably going to be bits of the schema which are discriminated enums. These were what caused me most pain in the Go XML bindings. eg with disks where the "type=xxx" attribute on <disk> in turn controls which child elements we accept in the parser. In the RNG schema we have often been lazy and not represented the restrictions which are imposed by the actual parser. Similarly in the actual structs, we often don;t represent the discriminated enums conceptually. IOW if we go down the route of generating everything from the RNG schema, we're quite likely to need to do work to make the RNG schema more accurate. We'll probably aso need to change a fair number of the struts we use. The domain XML schema is the one most affected by this. All the other XML schemas are fairly simple in general. None of this is a bad thing, but it is potentially a lot of work. This is a big & complex series / topic, so it will take a little time for me to properly review this idea and give some more detailed feedback. So it will probably be next week before i reply again. Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Thu, Mar 26, 2020 at 18:19:20 +0000, Daniel Berrange wrote:
On Wed, Mar 25, 2020 at 03:11:40PM +0800, Shi Lei wrote:
[...]
IOW if we go down the route of generating everything from the RNG schema, we're quite likely to need to do work to make the RNG schema more accurate. We'll probably aso need to change a fair number of the struts we use. The domain XML schema is the one most affected by this. All the other XML schemas are fairly simple in general. None of this is a bad thing, but it is potentially a lot of work.
What I'm afraid of is making the internal structs closer to the RNG schema in many cases. I spent some time sanitizing the storage of data in the internal structs which don't necessarily match the RNG schema. Making them look more like the schema would be a pretty big regression in understandibility of the code and I'd like to avoid that at all costs. Specifically since we can't change the schema. Most notable is the ultra-messy NUMA topology definition, where some things are split accross multiple not-very appropriate elements and fixing that will be unpleasant. Specifically NUMA topology is a child of <cpu>, properties of numa nodes are under <numatune> etc ... These must not be changed otherwise it will be a mess. One other thing to worry about is, that schema validation is not mandatory. Coupled with guarantee of backwards compatibility, we must preserve the quirks of the parser, including parsing of stuff which is not in the schema and this will be extremely hard to preserve. The other extreme is when the schema is too generic and there's specific checking in the parser. We've got plenty of that too. As you've pointed out in terms of the disk schema, there's plenty of the above going on, including under (we were accepting seclabels even for disks which don't use them, it came handy once) and overspecified schema and also plenty of non-conformat storage. Specifically the <disk> element contains the type, which actually belongs to the storage source. The storage source also has 'backingStore' but in the XML it's not a child but a sibling. Changing this to the format in the schema would make the code, messy and more unmaintainable than it is now. In this regards, the best thing that we could do, is to generate the parser and then hand-write transformation from the XML schema into what we use internally, but that is not really better than the current state.

On Wed, Mar 25, 2020 at 03:11:40PM +0800, Shi Lei wrote:
Outline =========
In libvirt world, objects(like domain, network, etc.) are described with two representations: structures in c-language and XML specified by relax-ng. Since c-language-implementation and xml restricted by relax-ng are merely the different expressions of the same object-model, we can make a tool to generate those C-language codes based on relax-ng files.
Problems =========
If we replace hardcoded code with generated code directly, there're still several problems.
Problem1 - name convension Generated code follows specific name convension, now there're some differences between generated code and current libvirt codes. For example, generated struct's name will be like virXXXDef, parsefunc's name will be like virXXXDefParseXML; the 'XXX' is a kind of Camel-Case name which implies the struct's place in the hierarchy. But in current code, there're many variations of name. So if we replace current codes with generated codes directly, it requires a lot of modifications in the caller codes.
Regardless of whether or how we auto-generate code, cleaning up current naming conventions to be more consistent is a benefit. So as a general rule, instead of having a code generator which knows how to generate every wierd name, we should just fix the wierd names to follow a standard convention.
Problem2 - error-checking codes in the parsefunc Most of current parse functions have error-checking codes after parsing XML code. These code lines validate existence and parsing results. Now RNG2C can't generate error-checking codes in the parsefunc. So we should find a way to handle it.
In the virDomainDef XML code, we've been slowly splitting out the code for doing error validation into separate methods. These two bugs illustrate the core ideas: https://gitlab.com/libvirt/libvirt/-/issues/7 https://gitlab.com/libvirt/libvirt/-/issues/8 the same concept would apply to other XML parsers such as the network parser, but it hasn't been a high priority for non-domain XML schemas.
Problem3 - hierarchy consistence The RNG2C generate a 'struct'(C code) for each 'element'(relax-ng). Most of current code follows this convention, but there're still some exceptions. For example, the element 'bridge'(in network.rng) should be translated into a struct called 'virNetworkBridgeDef'; but in fact, no this struct in the current codes, and its members (stp/delay/...) are exposed to the struct 'virNetworkDef'.
Yep, this is the really hard problem I think. The idea of generating the structs from the RNG has a fundamental assumption that the way we express & modularize the RNG matches the way we want to express and modularize the C structs. To "fix" this, either we have to change the C structs in to something that may be worse for the code, or we have to change the RNG schema into something that may be worse for the schema validation. There's a case in the domain XML which illustrates how painfull his can be: <define name="os"> <choice> <ref name="osxen"/> <ref name="oshvm"/> <ref name="osexe"/> </choice> </define> here, the osxen/oshvm share many attributes, and thus at the C level we want them all in the same struct, but at the RNG level we wanted them separate to more strictly validate the XML documents. ...cut all the technical details about the rng2c DIRECTIVE lang.... What I'm really interested in above all is what the end result looks like for both maintainers, and casual contributors. As a general goal we want to simplify libvirt to make it easier to contribute to. Eliminating the need to write custom XML parsing code certainly fits with that goal at a conceptual level. The code generator itself though needs some input so that it knows what to generate, and we have to be mindful of what this input data looks like and how easy it will be for maintainers/contributors to understand. In last 6 months, we've had a general aim to reduce the number of languages we expose contributors to. We've eliminated alot of Perl usage. We've started to replace HTML with RST, and also want to replace our XML/XSL based website generator with something simpler and non-XML based. There are some places where we can't avoid another language, as XDR for the RPC protocol. We'll never eliminate XML from libvirt entirely, since it is a fundamental part of our public API, but I am interested in how we can minimize the visibility of XML to our contributors. Being able to auto-generate XML parsers is thus interesting, but if the source input for the auto-generator is another XML document, that is kind of failing. This is where I'm finding the rng2c tool quite unappealing. The libvirt code is primarily C based, with a little bit of magic in places where we auto-generate code. For example the RPC code generator has magic comments in the XDR protocol definition, that is used to generate client and server dispatch code. I think of the XDR language as "psuedo-C" - it is conceptually close enough to C that C programmers can understand the XDR definitions fairly easily. The RNG schemas are a very different beast. Since they're XML based they are obviously completely unlike any of the C code, and I find that people have quite a strong dislike of use of XML in general. With this in mind I'm not enthusiastic about the idea of auto-generating the XML parsers from the RNG schemas, as it means everyone needs to know more about both the RNG schema language, and also learn this custom DIRECTIVE language used by the rng2c tool. If we consider this series, we've deleted 530 lines from network_conf.c and added 200 new lines for post-parse validation logic. This is is good overall improvement. No matter what approach or tool we use for XML parser auto-generation we'll get the similar result. Now consider the network_conf.h file, we have deleted typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; struct _virNetworkDNSTxtDef { char *name; char *value; }; typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; struct _virNetworkDNSSrvDef { char *domain; char *service; char *protocol; char *target; unsigned int port; unsigned int priority; unsigned int weight; }; typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { virSocketAddr ip; size_t nnames; char **names; }; typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; struct _virNetworkDNSForwarder { virSocketAddr addr; char *domain; }; typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; struct _virNetworkDNSDef { int enable; /* enum virTristateBool */ int forwardPlainNames; /* enum virTristateBool */ size_t ntxts; virNetworkDNSTxtDefPtr txts; size_t nhosts; virNetworkDNSHostDefPtr hosts; size_t nsrvs; virNetworkDNSSrvDefPtr srvs; size_t nfwds; virNetworkDNSForwarderPtr forwarders; }; and instead of this, we now have have these extra rules in the RNG schemas: <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { "output": "src/conf/network_conf", "post": true, "args.instname": true }, "formatfunc": {"output": "src/conf/network_conf"} } --> <!-- VIRT:DIRECTIVE { "name": "virNetworkDNSForwarder", "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { "output": "src/conf/network_conf", "post": true, "args.noctxt": true, "args.instname": true }, "formatfunc": { "output": "src/conf/network_conf", "order": ["domain", "addr"] } } --> <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { "output": "src/conf/network_conf", "post": true, "args.noctxt": true, "args.instname": true, "args": [ {"name": "partialOkay", "type": "Bool"} ] }, "formatfunc": {"output": "src/conf/network_conf"}, "members": [ {"id": "value", "opt": true} ] } --> <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { "output": "src/conf/network_conf", "post": true, "args.instname": true, "args": [ {"name": "partialOkay", "type": "Bool"} ] }, "formatfunc": {"output": "src/conf/network_conf"}, "members": [ {"id": "service", "opt": true}, {"id": "protocol", "opt": true} ] } --> <!-- VIRT:DIRECTIVE { "structure": {"output": "src/conf/network_conf"}, "clearfunc": {"output": "src/conf/network_conf"}, "parsefunc": { "output": "src/conf/network_conf", "args.instname": true, "post": true, "args": [ {"name": "partialOkay", "type": "Bool"} ] }, "formatfunc": {"output": "src/conf/network_conf"}, "members": [ {"id": "ip", "opt": true}, {"id": "hostname", "name": "name", "opt": true} ] } --> If I come to libvirt as a contributor with C language skils, but little experiance of RNG, I think this is a significant step backwards in the ability to understand libvirt code. It is way easier to understand what's going on from the C structs, than from the RNG schema and the VIRT:DIRECTIVE IMHO. Even as a maintainer, and having read the cover letter here, I find the VIR:DIRECTIVE metadata to be way too verbose and hard to understand compared to the structs. So I think that although the RNG based code generator elimintes alot of C code, it has the cost of forcing people to know more about the RNG code. Overall I don't think that's a clear win. This doesn't mean auto-generating code is a bad idea. I think it just means that the RNG schema is not the right place to drive auto-generation from. I'm wondering if you've ever done any programming with Golang and used its XML parsing capabilities ? Golang has a very clever approach to XML/JSON/YAML parsing which is based on directives recorded against the native Go structs. In the libvirt-go-xml.git repository, we've used this to map all the libvirt XML schemas into Go structs. IME, this has been the most pleasant way I've come across for parsing XML. If we consider just the DNS structs that you used to illustrate rng2c in this patch series. To add support for Go XML parsing and formatting, required the following comments against the Golang structs: type NetworkDNSTXT struct { XMLName xml.Name `xml:"txt"` Name string `xml:"name,attr"` Value string `xml:"value,attr"` } type NetworkDNSSRV struct { XMLName xml.Name `xml:"srv"` Service string `xml:"service,attr,omitempty"` Protocol string `xml:"protocol,attr,omitempty"` Target string `xml:"target,attr,omitempty"` Port uint `xml:"port,attr,omitempty"` Priority uint `xml:"priority,attr,omitempty"` Weight uint `xml:"weight,attr,omitempty"` Domain string `xml:"domain,attr,omitempty"` } type NetworkDNSHostHostname struct { Hostname string `xml:",chardata"` } type NetworkDNSHost struct { XMLName xml.Name `xml:"host"` IP string `xml:"ip,attr"` Hostnames []NetworkDNSHostHostname `xml:"hostname"` } type NetworkDNSForwarder struct { Domain string `xml:"domain,attr,omitempty"` Addr string `xml:"addr,attr,omitempty"` } type NetworkDNS struct { Enable string `xml:"enable,attr,omitempty"` ForwardPlainNames string `xml:"forwardPlainNames,attr,omitempty"` Forwarders []NetworkDNSForwarder `xml:"forwarder"` TXTs []NetworkDNSTXT `xml:"txt"` Host []NetworkDNSHost `xml:"host"` SRVs []NetworkDNSSRV `xml:"srv"` } The key important thing here is that the programmer is still fundamentally working with their normal Golang struct types / fields. All that is required to handle XML parsing & formatting is to add some magic comments which serve as directives to the XML parser, telling it attribute/element names, whether things are optional or not, etc. The attribute/element names are only needed if the struct field name is different from the XML name. My gut feeling is that if we want to go ahead with auto-generating C code for XML parsing/formatting, we should ignore the RNG schemas, and instead try to do something similar to the Golang approach to XML. The hard thing is that this would require us to write something which can parse the C header files. Generally in our XML parser header files we don't try to do anything too fancy - it is quite boring C code, so we would not have to parse the full C language. We can cope with a fairly simplified parser, that assumes the C header is following certain conventions. So we would have to add some magic comment directives to each struct we have typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; struct _virNetworkDNSTxtDef { char *name; /* xmlattr */ char *value; /* xmlattr */ }; typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; struct _virNetworkDNSSrvDef { char *domain; /* xmlattr,omitempty */ char *service; /* xmlattr,omitempty */ char *protocol; /* xmlattr,omitempty */ char *target; /* xmlattr,omitempty */ unsigned int port; /* xmlattr,omitempty */ unsigned int priority; /* xmlattr,omitempty */ unsigned int weight; /* xmlattr,omitempty */ }; typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { virSocketAddr ip; /* xmlcallback */ size_t nnames; char **names; /* xmlchardata:hostname,array */ }; typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; struct _virNetworkDNSForwarder { virSocketAddr addr; /* xmlcallback */ char *domain; /* xmlattr */ }; typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; struct _virNetworkDNSDef { int enable; /* xmlattr */ int forwardPlainNames; /* xmlattr */ size_t ntxts; virNetworkDNSTxtDefPtr txts; /* xmlelement:txt,array */ size_t nhosts; virNetworkDNSHostDefPtr hosts; /* xmlelement:host,array */ size_t nsrvs; virNetworkDNSSrvDefPtr srvs; /* xmlelement:srv,array */ size_t nfwds; virNetworkDNSForwarderPtr forwarders; /* xmlelement:forwarder,array */ }; Some explanation: - xmlattr Parse the field as an XML attribute with the same name as the struct field - xmlattr:thename Parse the field as an XML attribute called "thenanme" - xmlelement Parse the field as a child struct, populating from the XML element with same name. - xmlelement:thename Parse the field as a child struct, populating from the XML element called 'thename' - xmlcallback Call out to custom written XML handler methods to handle converting the data to/from the custom data type. eg for a field virSocketAddr, we'd call virSocketAddrXMLParse and virSocketAddrXMLFormat. - ,omitempty Don't format the attribute/element/chardata is the struct field is a NULL pointer, or an integer with value 0. BTW, when looking at libvirt-go-xml.git, you'll see there are still a bunch of places where we have to hand-write parsing/formatting code. There are essentially two reasons for this - Golang has no support for unions. As a result you have to fake unions by having a struct, with a bunch of optional fields each corresponding to a new struct. Rely on convention that only one field can be non-NULL. This requires custom parse functions since the Golang XML parser has no support for this code pattern. - The XML parser has no built-in directive to control whether it parses/formats in decimal vs hex. Both of these are things we won't suffer from if we did this in C, so potentially the XML parsing annotations needed for our C struct would result in something even simpler than the libvirt-go-xml.git code. The key question is just how difficult will it be to write a tool that can parse the C header files, and magic comments, to output suitable XML parser/formatter functions ? There's no easy way to answer that without someone trying it. Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

... ...
The key question is just how difficult will it be to write a tool that can parse the C header files, and magic comments, to output suitable XML parser/formatter functions ? There's no easy way to answer that without someone trying it.
Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
Thanks for your reply in detail. I get your point. My intention of doing this is to test an idea: only using one language or notation to depict object-models in libvirt; and then calling a series of tools to translate it into various representations, such as c-language structures/parse/format, rng-files, other language bindings, part of test-framwork, part of document, etc. I knew this idea is too ideal and too big, so I just tried to generate some c-code based on rng-files as a beginning point. During the process, I found that the descriptive power of relax-ng was weak. Of cause, it is not designed for this aim. I had to introduce extra DIRECTIVES to enhance it, which made it not concise. I have also considered of using json-schema rather than relax-ng. Json-schema has more descriptive power than relax-ng. And json-schema has combining schemas and conditional schemas, which can be used to define data constrains and reduce error-checking code in parsefunctions. But using json-schema cannot solve the problems that you have mentioned and rewriting rng file with json-schema is another big job. As you suggested, parsing C-structs plus some magic comments may be a good starting point. But parsing C is much more difficult than parsing xml or json. I think this job should base on some present tools or other basis. We can first refer to Clang or other light compiler front-end. Maybe we can utilize some output of the middle stage of these compilers. Regards, Shi Lei

On Wed, Apr 22, 2020 at 01:51:10PM +0800, Shi Lei wrote:
As you suggested, parsing C-structs plus some magic comments may be a good starting point. But parsing C is much more difficult than parsing xml or json. I think this job should base on some present tools or other basis. We can first refer to Clang or other light compiler front-end. Maybe we can utilize some output of the middle stage of these compilers.
FWIW, we already have a tool in libvirt that parsers C header files. The scripts/apibuild.py file extracts info about our enums, structs, APIs and uses it to build the public API documentation. I wonder if we can refactor that tool to extract the code for parsing into a module, so that we can more reasily re-use it for both the API docs and for a new XML generator Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Wed, Apr 22, 2020 at 01:51:10PM +0800, Shi Lei wrote:
As you suggested, parsing C-structs plus some magic comments may be a good starting point. But parsing C is much more difficult than parsing xml or json. I think this job should base on some present tools or other basis. We can first refer to Clang or other light compiler front-end. Maybe we can utilize some output of the middle stage of these compilers.
FWIW, we already have a tool in libvirt that parsers C header files. The scripts/apibuild.py file extracts info about our enums, structs, APIs and uses it to build the public API documentation.
Okay. I'll examine it.
I wonder if we can refactor that tool to extract the code for parsing into a module, so that we can more reasily re-use it for both the API docs and for a new XML generator
It makes sense if this tool is suitable. Regards, Shi Lei
Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Wed, 2020-04-22 at 09:48 +0100, Daniel P. Berrangé wrote:
On Wed, Apr 22, 2020 at 01:51:10PM +0800, Shi Lei wrote:
As you suggested, parsing C-structs plus some magic comments may be a good starting point. But parsing C is much more difficult than parsing xml or json. I think this job should base on some present tools or other basis. We can first refer to Clang or other light compiler front-end. Maybe we can utilize some output of the middle stage of these compilers.
FWIW, we already have a tool in libvirt that parsers C header files. The scripts/apibuild.py file extracts info about our enums, structs, APIs and uses it to build the public API documentation.
I wonder if we can refactor that tool to extract the code for parsing into a module, so that we can more reasily re-use it for both the API docs and for a new XML generator
Or we could replace that C parsing code with something based on libclang. Either way, if this ever becomes usable I think it should not live in the libvirt repository but be a standalone tool instead, as I can see many projects potentially benefiting from it. Which begs the question: are we absolutely certain something like this doesn't exist already? We should make sure that's really the case before we invest time on it... -- Andrea Bolognani / Red Hat / Virtualization

On Wed, Apr 22, 2020 at 11:37:23AM +0200, Andrea Bolognani wrote:
On Wed, 2020-04-22 at 09:48 +0100, Daniel P. Berrangé wrote:
On Wed, Apr 22, 2020 at 01:51:10PM +0800, Shi Lei wrote:
As you suggested, parsing C-structs plus some magic comments may be a good starting point. But parsing C is much more difficult than parsing xml or json. I think this job should base on some present tools or other basis. We can first refer to Clang or other light compiler front-end. Maybe we can utilize some output of the middle stage of these compilers.
FWIW, we already have a tool in libvirt that parsers C header files. The scripts/apibuild.py file extracts info about our enums, structs, APIs and uses it to build the public API documentation.
I wonder if we can refactor that tool to extract the code for parsing into a module, so that we can more reasily re-use it for both the API docs and for a new XML generator
Or we could replace that C parsing code with something based on libclang.
I think that's a double edged sword. While it gives you the full coverage of the C language, you then have to deal with the full range of the C language. I think the simplified parser we have for the docs build will be easier to work with by being more constrained in what it tries to support.
Either way, if this ever becomes usable I think it should not live in the libvirt repository but be a standalone tool instead, as I can see many projects potentially benefiting from it.
Yes, but we can worry about that at a later date IMHO.
Which begs the question: are we absolutely certain something like this doesn't exist already? We should make sure that's really the case before we invest time on it...
I've not found anything equivalent to Golang's XML parser for C Regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|

On Wed, 2020-04-22 at 10:45 +0100, Daniel P. Berrangé wrote:
On Wed, Apr 22, 2020 at 11:37:23AM +0200, Andrea Bolognani wrote:
On Wed, 2020-04-22 at 09:48 +0100, Daniel P. Berrangé wrote:
I wonder if we can refactor that tool to extract the code for parsing into a module, so that we can more reasily re-use it for both the API docs and for a new XML generator
Or we could replace that C parsing code with something based on libclang.
I think that's a double edged sword. While it gives you the full coverage of the C language, you then have to deal with the full range of the C language. I think the simplified parser we have for the docs build will be easier to work with by being more constrained in what it tries to support.
Glancing at the ~2000 lines of Python used to parse C doesn't necessarily give me the same impression :) But then again, in all fairness I have no idea how complicated an equivalent tool that uses libclang would be.
Either way, if this ever becomes usable I think it should not live in the libvirt repository but be a standalone tool instead, as I can see many projects potentially benefiting from it.
Yes, but we can worry about that at a later date IMHO.
Absolutely! Let's just keep this goal in mind, and try to make it reasonably generic instead of hardcoding libvirt-concepts in it.
Which begs the question: are we absolutely certain something like this doesn't exist already? We should make sure that's really the case before we invest time on it...
I've not found anything equivalent to Golang's XML parser for C
Hopefully the tool we're thinking of is not just hidden in some random person's GitHub account :) -- Andrea Bolognani / Red Hat / Virtualization
participants (4)
-
Andrea Bolognani
-
Daniel P. Berrangé
-
Peter Krempa
-
Shi Lei