[RFC 00/21] RFC: Generate parsexml/formatbuf functions based on directives

Last RFC: [https://www.redhat.com/archives/libvir-list/2020-April/msg00970.html] In last RFC, I suggested we can generate object-model code based on relax-ng files and Daniel gave it some comments. Follow the suggestion from Daniel, I make another try to generate parsexml/formatbuf functions automatically. ============ Directives ============ Still, we need several directives that can direct a tool to generate functions. These directives work on the declarations of structs. For example: typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ char *name; /* xmlattr, required */ char *value; /* xmlattr */ }; typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ char *service; /* xmlattr */ char *protocol; /* xmlattr */ char *domain; /* xmlattr */ char *target; /* xmlattr */ unsigned int port; /* xmlattr */ unsigned int priority; /* xmlattr */ unsigned int weight; /* xmlattr */ }; typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { /* genparse:withhook, genformat */ virSocketAddr ip; /* xmlattr */ size_t nnames; char **names; /* xmlelem:hostname, array */ }; typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; struct _virNetworkDNSForwarder { /* genparse:withhook, genformat */ char *domain; /* xmlattr */ virSocketAddr addr; /* xmlattr */ }; typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; struct _virNetworkDNSDef { /* genparse:withhook, genformat */ virTristateBool enable; /* xmlattr */ virTristateBool forwardPlainNames; /* xmlattr */ size_t nforwarders; virNetworkDNSForwarderPtr forwarders; /* xmlelem, array */ size_t ntxts; virNetworkDNSTxtDefPtr txts; /* xmlelem, array */ size_t nsrvs; virNetworkDNSSrvDefPtr srvs; /* xmlelem, array */ size_t nhosts; virNetworkDNSHostDefPtr hosts; /* xmlelem, array */ }; Explanation for these directives: - genparse[:withhook|concisehook] Only work on a struct. Generate parsexml function for this struct only if 'genparse' is specified. The function name is based on the struct-name and suffixed with 'ParseXML'. E.g., for struct virNetworkDNSDef, its parsexml function is virNetworkDNSDefParseXML. If a parsexml function includes some error-checking code, it needs a post-process hook to hold them. Specify 'withhook' or 'concisehook' to setup a hook point in the parsexml function. Executing the tool manually can show the declaration of hook function. E.g. check declaration of 'virNetworkDNSDefParseXMLHook'. # ./build-aux/generator/go show virNetworkDNSDef -kp int virNetworkDNSDefParseXMLHook(xmlNodePtr node, virNetworkDNSDefPtr def, const char *instname, void *opaque, const char *enableStr, const char *forwardPlainNamesStr, int nForwarderNodes, int nTxtNodes, int nSrvNodes, int nHostNodes); Some helper arguments (such as 'enableStr', 'nTxtNodes', etc.) are passed in to indicate the existence of fields. If these arguments are useless, specify 'concisehook' to omit them. When 'genparse' is specified, clear function for this struct is also generated implicitly, it is because the generated parsexml function needs to call the clear function. - genformat Only work on a struct. Generate formatbuf function for this struct only if 'genformat' is specified. The function name is based on struct-name and suffixed with 'FormatBuf'. - xmlattr[:thename] Parse/Format the field as an XML attribute. If 'thename' is specified, use it as the XML attribute name; or use the filed name. - xmlelem[:thename] Parse/Format the field as a child struct. If 'thename' is specified, use it as the XML element name; or use the filed name. - array Parse/Format the field as an array. Its related field is a counter, which name should follow the pattern: n + 'field_name'. - required The field must have a corresponding item defined in XML. Note: 1. If a field isn't specified with 'xmlattr' or 'xmlelem', it will be ignored in the parsexml/formatbuf functions. 2. For enum, use its name rather than int. E.g., the type of the field 'enable' in virNetworkDef should be 'virTristateBool' rather than 'int'. ======= Tool ======= The Tool is based on libclang and its python-binding. It has three subcommands: 'list', 'show' and 'generate'. The 'list' and 'show' are used for previewing generated code; the 'generate' is prepared to be invoked by Makefile whenever the c header files under 'src/conf' and 'src/util' have changed. =================== About this series =================== To generate all parsexml/formatbuf functions for virNetworkDef and all its subordinate structs, it needs around 95 patches. In this RFC, I just post 21 patches for evaluation. Thanks! Shi Lei (21): build-aux: Add a tool to generate xml parse/format functions maint: Check libclang and its python3 binding maint: Call generator automatically when c-head-files change maint: Add helper macro VIR_USED util: Add two helper functions virXMLChildNode and virXMLChildNodeSet conf: Extract error-checking code from virNetworkDNSTxtDefParseXML conf: Replace virNetworkDNSTxtDefParseXML(hardcoded) with namesake(generated) conf: Generate virNetworkDNSTxtDefFormatBuf conf: Extract error-checking code from virNetworkDNSSrvDefParseXML conf: Replace virNetworkDNSSrvDefParseXML(hardcoded) with namesake(generated) conf: Generate virNetworkDNSSrvDefFormatBuf util: Add parsexml/formatbuf helper functions for virSocketAddr conf: Extract error-checking code from virNetworkDNSHostDefParseXML conf: Replace virNetworkDNSHostDefParseXML(hardcoded) with namesake(generated) conf: Generate virNetworkDNSHostDefFormatBuf conf: Extract virNetworkDNSForwarderParseXML from virNetworkDNSParseXML conf: Replace virNetworkDNSForwarderParseXML(hardcoded) with namesake(generated) conf: Generate virNetworkDNSForwarderFormatBuf conf: Extract error-checking code from virNetworkDNSDefParseXML conf: Replace virNetworkDNSDefParseXML(hardcoded) with namesake(generated) conf: Generate virNetworkDNSDefFormatBuf build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++ configure.ac | 12 + po/POTFILES.in | 2 + src/Makefile.am | 15 + src/access/Makefile.inc.am | 2 +- src/conf/Makefile.inc.am | 13 +- src/conf/network_conf.c | 487 ++++-------------- src/conf/network_conf.h | 54 +- src/esx/Makefile.inc.am | 2 +- src/interface/Makefile.inc.am | 2 +- src/internal.h | 2 + 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 | 12 +- src/util/virsocketaddr.c | 38 ++ src/util/virsocketaddr.h | 22 +- src/util/virxml.c | 57 +++ src/util/virxml.h | 3 + src/vbox/Makefile.inc.am | 2 +- tests/Makefile.am | 2 + tools/Makefile.am | 2 + 32 files changed, 1674 insertions(+), 442 deletions(-) create mode 100644 build-aux/generator/directive.py create mode 100755 build-aux/generator/go create mode 100755 build-aux/generator/main.py create mode 100644 build-aux/generator/utils.py -- 2.17.1

This tool is used to generate parsexml/formatbuf functions for structs. It is based on libclang and its python-binding. Some directives (such as genparse, xmlattr, etc.) need to be added on the declarations of structs to direct the tool. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++ po/POTFILES.in | 1 + 5 files changed, 1370 insertions(+) create mode 100644 build-aux/generator/directive.py create mode 100755 build-aux/generator/go create mode 100755 build-aux/generator/main.py create mode 100644 build-aux/generator/utils.py diff --git a/build-aux/generator/directive.py b/build-aux/generator/directive.py new file mode 100644 index 0000000..c0d3c61 --- /dev/null +++ b/build-aux/generator/directive.py @@ -0,0 +1,839 @@ +# +# 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 json +from collections import OrderedDict +from utils import singleton +from utils import dedup, counterName +from utils import BlockAssembler +from utils import Terms, singleline, indent, render, renderByDict + +BUILTIN_TYPES = { + 'Bool': {}, + 'String': {}, + 'Chars': { + 'conv': 'virStrcpyStatic(def->${name}, ${name}Str)' + }, + 'UChars': { + 'conv': 'virStrcpyStatic((char *)def->${name}, ${mdvar})' + }, + 'Int': { + 'fmt': '%d', + 'conv': 'virStrToLong_i(${mdvar}, NULL, 0, &def->${name})' + }, + 'UInt': { + 'fmt': '%u', + 'conv': 'virStrToLong_uip(${mdvar}, NULL, 0, &def->${name})' + }, + 'ULong': { + 'fmt': '%lu', + 'conv': 'virStrToLong_ulp(${mdvar}, NULL, 0, &def->${name})' + }, + 'ULongLong': { + 'fmt': '%llu', + 'conv': 'virStrToLong_ullp(${mdvar}, NULL, 0, &def->${name})' + }, + 'U8': { + 'fmt': '%u', + 'conv': 'virStrToLong_u8p(${mdvar}, NULL, 0, &def->${name})' + }, + 'U32': { + 'fmt': '%u', + 'conv': 'virStrToLong_uip(${mdvar}, NULL, 0, &def->${name})' + }, +} + + +@singleton +class TypeTable(OrderedDict): + def __init__(self): + OrderedDict.__init__(self) + for name, kvs in BUILTIN_TYPES.items(): + kvs['name'] = name + kvs['meta'] = 'Builtin' + self[name] = kvs + + def register(self, kvs): + name = kvs['name'] + if name not in self: + self[name] = kvs + return name + + def get(self, name): + if name in self: + return self[name] + return {'meta': 'Struct', 'name': name, 'external': True} + + +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; +} +''' + +T_CLEAR_FUNC_IMPL = ''' +void +${typename}Clear(${typename}Ptr def) +{ + if (!def) + return; + + ${body} +} +''' + +T_CLEAR_FUNC_DECL = ''' +void +${typename}Clear(${typename}Ptr def); +''' + + +def clearMember(member): + mtype = TypeTable().get(member['type']) + + refname = 'def->%s' % member['name'] + if member.get('array'): + refname += '[i]' + + code = '' + if mtype['meta'] == 'Struct': + if member['pointer'] and not mtype.get('external'): + code = '%sClear(%s);' % (mtype['name'], refname) + code += '\nVIR_FREE(%s);' % refname + else: + code = '%sClear(&%s);' % (mtype['name'], refname) + elif mtype['name'] == 'String': + code = 'VIR_FREE(%s);' % refname + elif mtype['name'] in ['Chars', 'UChars']: + code = 'memset(%s, 0, sizeof(%s));' % (refname, refname) + elif not member.get('array'): + code = '%s = 0;' % refname + + if member.get('specified'): + assert not member.get('array'), "'specified' can't come with 'array'." + code += '\n%s_specified = false;' % refname + + if member.get('array') and code: + 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);' % member['name'] + code += '\ndef->%s = 0;' % counter + + return code + + +T_CLEAR_NAMESPACE = ''' +if (def->namespaceData && def->ns.free) + (def->ns.free)(def->namespaceData); +''' + + +def makeClearFunc(writer, atype): + if 'genparse' not in atype: + return + + blocks = BlockAssembler() + for member in atype['members']: + blocks.append(clearMember(member)) + + if 'namespace' in atype: + blocks.append(T_CLEAR_NAMESPACE.strip()) + + body = indent(blocks.output('\n\n'), 1) + + impl = render(T_CLEAR_FUNC_IMPL, typename=atype['name'], body=body) + writer.write(atype, 'clearfunc', '.c', impl) + + decl = render(T_CLEAR_FUNC_DECL, typename=atype['name']) + writer.write(atype, 'clearfunc', '.h', decl) + + +# +# Templates for parsing member block +# +T_READ_ATTR_BY_PROP = '${mdvar} = virXMLPropString(node, "${tname}");' +T_READ_ELEM_BY_PROP = '${mdvar} = virXMLChildNode(node, "${tname}");' +T_READ_ELEM_CONTENT = '${mdvar} = virXMLChildNodeContent(node, "${tname}");' + +T_PARSE_MEMBER_MORE = ''' +${number} = virXMLChildNodeSet(node, "${tname}", &nodes); +if (${number} > 0) { + size_t i; + + if (VIR_ALLOC_N(def->${name}, ${number}) < 0) + goto error; + + for (i = 0; i < ${number}; i++) { + xmlNodePtr tnode = nodes[i]; + ${item} + } + def->${counter} = ${number}; + VIR_FREE(nodes); +} else if (${number} < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Invalid ${tname} element found.")); + goto error; +}${report_missing} +''' + +T_CHECK_INVALID_ERROR = ''' +if (${tmpl}) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid '${tname}' setting '%s' in '%s'"), + ${mdvar}, instname); + goto error; +} +''' + +T_MISSING_ERROR = ''' +{ + virReportError(VIR_ERR_XML_ERROR, + _("Missing '${tname}' setting in '%s'"), + instname); + 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}, instname, NULL) < 0) + goto error; +''' + + +def parseMember(member, atype, tmpvars): + if not member.get('xmlattr') and not member.get('xmlelem'): + return None + + tname = member['xmlattr'] if member.get('xmlattr') else member['xmlelem'] + mtype = TypeTable().get(member['type']) + + # + # Helper functions + # + def _readXMLByXPath(mdvar): + if member.get('xmlattr'): + tmpl = T_READ_ATTR_BY_PROP + elif mtype['meta'] == 'Struct': + tmpl = T_READ_ELEM_BY_PROP + else: + tmpl = T_READ_ELEM_CONTENT + return render(tmpl, mdvar=mdvar, tname=tname) + + def _assignValue(name, mdvar): + refname = 'def->' + name + if member.get('callback') or mtype['meta'] == 'Struct': + if member.get('callback'): + funcname = member['callback'] + 'ParseXML' + else: + funcname = mtype['name'] + 'ParseXML' + + tmpl = '' + if member['pointer'] and not mtype.get('external'): + tmpl += T_ALLOC_MEMORY + tmpl += T_STRUCT_ASSIGNMENT_TEMPLATE + + if (member['pointer'] and not mtype.get('external')) or \ + mtype['name'] in ['Chars', 'UChars']: + amp = '' + else: + amp = '&' + + tmpl = render(tmpl, funcname=funcname, amp=amp, mdvar=mdvar) + elif mtype['meta'] == 'Enum': + formalname = mtype['name'] + if not formalname.endswith('Type'): + formalname += 'Type' + tmpl = '(def->${name} = %sFromString(%s)) <= 0' \ + % (formalname, mdvar) + elif mtype['name'] == 'Bool': + tmpl = 'virStrToBool(${mdvar}, "%s", &def->${name}) < 0' \ + % member.get('truevalue', 'yes') + elif mtype['name'] == 'String': + tmpl = 'def->${name} = g_strdup(${mdvar});' + else: + tmpl = None + builtin = BUILTIN_TYPES.get(mtype['name']) + if builtin: + tmpl = builtin.get('conv', None) + if tmpl: + tmpl += ' < 0' + + if not tmpl: + return None + + if not member.get('callback') and mtype['meta'] != 'Struct' and \ + mdvar.endswith('Str') and (mtype['name'] != 'String'): + tmpl = render(T_CHECK_INVALID_ERROR, + tmpl=tmpl, tname=tname, mdvar=mdvar) + + ret = render(tmpl, refname=refname, name=name, + tname=tname, mdvar=mdvar) + + if member.get('specified') and not member.get('array'): + ret += '\ndef->%s_specified = true;' % name + return ret + + def _assignValueOnCondition(name, mdvar): + block = _assignValue(name, mdvar) + if not block: + return None + + if member.get('required'): + ret = render(T_CHECK_MISSING_ERROR, mdvar=mdvar, tname=tname) + ret += '\n\n' + block + return ret + + if singleline(block): + return render(T_IF_SINGLE, condition=mdvar, body=block) + else: + return render(T_IF_MULTI, condition=mdvar, body=indent(block, 1)) + + # + # Main routine + # + name = member['name'] + + # For sequence-type member + if member.get('array'): + node_num = 'n%sNodes' % Terms.upperInitial(tname) + tmpvars.append(node_num) + tmpvars.append('nodes') + counter = counterName(member['name']) + + report_missing = '' + if member.get('required'): + report_missing = ' else ' + render(T_MISSING_ERROR, tname=tname) + + if mtype['meta'] != 'Struct': + item = 'def->%s[i] = virXMLNodeContentString(tnode);' % name + else: + item = _assignValue(name + '[i]', 'tnode') + + return render(T_PARSE_MEMBER_MORE, name=name, counter=counter, + number=node_num, item=indent(item, 2), + report_missing=report_missing, tname=tname) + + # For ordinary member + mdvar = member['name'].replace('.', '_') + if member.get('xmlattr') or mtype['meta'] != 'Struct': + mdvar += 'Str' + else: + mdvar += 'Node' + tmpvars.append(mdvar) + + blocks = BlockAssembler() + blocks.append(_readXMLByXPath(mdvar)) + blocks.append(_assignValueOnCondition(name, mdvar)) + return blocks.output() + + +def align(funcname): + return ' ' * (len(funcname) + 1) + + +T_PARSE_FUNC_DECL = ''' +int +${funcname}(${args}); +''' + +T_PARSE_FUNC_IMPL = ''' +int +${funcname}(${args}) +{ + ${declare_vars} + VIR_USED(instname); + VIR_USED(opaque); + + if (!def) + goto error; + + ${body} + + return 0; + + error: + ${cleanup_vars} + ${typename}Clear(def); + return -1; +} +''' + +T_PARSE_FUNC_POST_INVOKE = ''' +if (${funcname}Hook(${args}) < 0) + goto error; +''' + + +def _handleTmpVars(tmpvars): + 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) + + return '\n'.join(heads), '\n'.join(tails) + + +def makeParseFunc(writer, atype): + if 'genparse' not in atype: + return + + typename = atype['name'] + funcname = typename + 'ParseXML' + alignment = align(funcname) + + formal_args = [ + 'xmlNodePtr node', typename + 'Ptr def', + 'const char *instname', 'void *opaque' + ] + + actual_args = ['node', 'def', 'instname', 'opaque'] + + if 'namespace' in atype: + formal_args.append('xmlXPathContextPtr ctxt') + actual_args.append('ctxt') + formal_args.append('virNetworkXMLOptionPtr xmlopt') + actual_args.append('xmlopt') + + kwargs = {'funcname': funcname, 'typename': typename, + '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 atype['genparse'] in ['withhook', 'concisehook']: + if atype['genparse'] == 'withhook': + 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, + args=actual_args) + blocks.append(post) + + if atype['genparse'] == 'withhook': + 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 + 'Hook', + args=connector.join(formal_args)) + + writer.write(atype, 'parsefunc', '.h', decl) + + if 'namespace' in atype: + blocks.append(T_NAMESPACE_PARSE.strip()) + + kwargs['body'] = indent(blocks.output('\n\n'), 1) + + declare_vars, cleanup_vars = _handleTmpVars(tmpvars) + kwargs['declare_vars'] = indent(declare_vars, 1) + kwargs['cleanup_vars'] = indent(cleanup_vars, 1) + + impl = renderByDict(T_PARSE_FUNC_IMPL, kwargs) + writer.write(atype, 'parsefunc', '.c', impl) + + +T_FORMAT_FUNC_DECL = ''' +int +${typename}FormatBuf(virBufferPtr buf, +${alignment}const char *name, +${alignment}const ${typename} *def, +${alignment}void *opaque); +''' + +T_FORMAT_FUNC_IMPL = ''' +int +${typename}FormatBuf(virBufferPtr buf, +${alignment}const char *name, +${alignment}const ${typename} *def, +${alignment}void *opaque) +{ + VIR_USED(opaque); + + if (!def) + return 0; + + ${format_members} + + return 0; +} +''' + +T_FORMAT_CHECK_DECL = ''' +bool +${typename}Check(const ${typename} *def, void *opaque); +''' + +T_FORMAT_CHECK_IMPL = ''' +bool +${typename}Check(const ${typename} *def, void *opaque) +{ + VIR_USED(opaque); + + if (!def) + return false; + + return ${check}; +} +''' + +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_SINGLE = ''' +if (${condition}) + ${body} +''' + +T_IF_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 ${tname} type %d"), + ${var}); + return -1; +} +virBufferAsprintf(buf, "${layout}", str); +''' + + +def formatMember(member, require, ret_checks): + if not member.get('xmlattr') and not member.get('xmlelem'): + return None + + mtype = TypeTable().get(member['type']) + + # + # Helper functions. + # + def _checkOnCondition(var): + if member.get('array'): + return None + + t = TypeTable().get(member['type']) + + ret = None + if 'checkformat' in member: + ret = '%s(&%s, opaque)' % (member['checkformat'], var) + elif member['pointer']: + ret = var + elif member.get('specified'): + ret = var + '_specified' + if ret.startswith('&'): + ret = ret[1:] + elif t['meta'] == 'Struct': + ret = '%sCheck(&%s, opaque)' % (t['name'], var) + elif member.get('required'): + pass + elif t['meta'] == 'Enum': + ret = var + elif t['meta'] == 'Builtin': + if t['name'] in ['Chars', 'UChars']: + ret = var + '[0]' + else: + ret = var + + return ret + + def _handleMore(code): + code = indent(code, 2) + counter = counterName(member['name']) + ret_checks.append('def->' + counter) + if singleline(code): + return render(T_LOOP_SINGLE, counter=counter, body=code) + else: + return render(T_LOOP_MULTI, counter=counter, body=code) + + def _format(layout, var): + tmpl = '${funcname}(buf, "${layout}", ${var})' + + funcname = 'virBufferAsprintf' + has_return = False + if member.get('callback') or mtype['meta'] == 'Struct': + if member.get('callback'): + funcname = member['callback'] + 'FormatBuf' + else: + funcname = mtype['name'] + 'FormatBuf' + + has_return = True + if not member['pointer'] and \ + mtype['name'] not in ['Chars', 'UChars']: + var = '&' + var + + var = '%s, opaque' % var + elif mtype['meta'] == 'Enum': + name = mtype['name'] + if not name.endswith('Type'): + name += 'Type' + tmpl = render(T_FORMAT_MEMBER_OF_ENUM, + fullname=name, tname=member['xmlattr']) + elif mtype['meta'] in ['String', 'Chars', 'UChars']: + funcname = 'virBufferEscapeString' + elif mtype['name'] == 'Bool': + truevalue = member.get('truevalue', 'yes') + if truevalue == 'yes': + var = '%s ? "yes" : "no"' % var + elif truevalue == 'on': + var = '%s ? "on" : "off"' % var + else: + var = '%s ? "%s" : ""' % (var, truevalue) + + code = render(tmpl, funcname=funcname, layout=layout, var=var) + if has_return: + code += ' < 0' + code = render(T_IF_SINGLE, condition=code, body='return -1;') + elif mtype['meta'] not in ['Enum']: + code += ';' + + return code + + def _handleAttr(tagname, var): + if 'xmlattr' not in member: + return None + + fmt = '%s' + if member.get('format.fmt'): + fmt = member['format.fmt'] + elif mtype['meta'] == 'Builtin': + fmt = BUILTIN_TYPES[mtype['name']].get('fmt', '%s') + + layout = " %s='%s'" % (tagname, fmt) + return _format(layout, var) + + def _handleElem(tagname, var): + if 'xmlattr' in member: + return None + + if mtype['meta'] != 'Struct': + layout = '<%s>%%s</%s>\\n' % (tagname, tagname) + else: + layout = tagname + + code = _format(layout, var) + return code + + # + # Main routine + # + name = member['name'] + if member.get('array'): + name = name + '[i]' + var = 'def->' + name + + ret = None + if 'xmlattr' in member: + tagname = member['xmlattr'] + else: + tagname = member['xmlelem'] + + 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_SINGLE, condition=checks, body=ret) + else: + ret = render(T_IF_MULTI, condition=checks, body=ret) + + if member.get('array'): + return _handleMore(ret) + + if checks: + if '&&' in checks or '||' in checks: + checks = '(%s)' % checks + ret_checks.append(checks) + + return ret + + +def makeFormatFunc(writer, atype): + if 'genformat' not in atype: + return + + # + # Helper functions. + # + def _formatMembers(): + attrs = [] + elems = [] + check_attrs = [] + check_elems = [] + + for member in atype['members']: + attr = formatMember(member, 'attribute', check_attrs) + if attr: + attrs.append(attr) + + elem = formatMember(member, 'element', check_elems) + 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'] = checks + ret.append(render(T_IF_SINGLE, condition='!(%s)' % checks, + body='return 0;')) + + ret.append('virBufferAsprintf(buf, "<%s", name);') + + if 'namespace' in atype: + ret.append(T_NAMESPACE_FORMAT_BEGIN.strip()) + + ret.extend(attrs) + + if elems: + 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 'namespace' in atype: + 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. + # + typename = atype['name'] + alignment = align(typename + 'FormatBuf') + + kwargs = {'alignment': alignment, 'typename': typename, + 'format_members': indent(_formatMembers(), 1)} + + decl = renderByDict(T_FORMAT_FUNC_DECL, kwargs) + writer.write(atype, 'formatfunc', '.h', decl) + + impl = renderByDict(T_FORMAT_FUNC_IMPL, kwargs) + writer.write(atype, 'formatfunc', '.c', impl) + + if atype.get('check'): + decl = render(T_FORMAT_CHECK_DECL, typename=typename) + writer.write(atype, 'formatfunc', '.h', decl) + + impl = render(T_FORMAT_CHECK_IMPL, + typename=typename, check=atype['check']) + writer.write(atype, 'formatfunc', '.c', impl) + + +def showDirective(atype): + print('\n###### Directive ######\n') + print(json.dumps(atype, indent=4)) diff --git a/build-aux/generator/go b/build-aux/generator/go new file mode 100755 index 0000000..9e30e08 --- /dev/null +++ b/build-aux/generator/go @@ -0,0 +1,14 @@ +# This is a command-line tool + +libclang_line=`ldconfig -p | grep libclang` +export libclang_path=`expr "$libclang_line" : '.* => \(.*\)$'` +if test -z "$libclang_path"; then + echo "libclang is required by libvirt\n" + exit -1 +fi + +WORK_DIR=$(cd $(dirname $0); pwd) +export PYTHONDONTWRITEBYTECODE=1 +export topdir="${WORK_DIR}/../.." +export builddir="${WORK_DIR}/../../build" +${WORK_DIR}/main.py $@ diff --git a/build-aux/generator/main.py b/build-aux/generator/main.py new file mode 100755 index 0000000..78a90f1 --- /dev/null +++ b/build-aux/generator/main.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +# +# 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 os +import re +import sys +import argparse +from clang.cindex import Config, Index, CursorKind +from clang.cindex import SourceLocation, SourceRange, TokenKind +from datetime import datetime +from directive import TypeTable, showDirective +from directive import makeClearFunc, makeParseFunc, makeFormatFunc +from utils import Terms + +TOOL_DESC = ''' +Generate xml parse/format functions based on directives. + +Subcommand:\n + list: List types. By default, only list structs tagged by + 'genparse'/'genformat'. When the option '-a' is specified, + list all types discovered by this tool.\n + show: Show the target type's directives and its code for preview. + Specify target type by its name. 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, 'c' for clearfunc; 'p' for parsefunc; + 'f' for formatfunc.\n + The option '-k' is only valid for show and generate. +''' + +# Three builtin types need to be handled specially: +# 'char *' => String +# 'char XXXX[...]' => Chars +# 'unsigned char XXXX[...]' => UChars +BUILTIN_MAP = { + 'bool': 'Bool', + 'char': 'Char', + 'unsigned char': 'UChar', + 'int': 'Int', + 'unsigned int': 'UInt', + 'long': 'Long', + 'unsigned long': 'ULong', + 'long long': 'LongLong', + 'unsigned long long': 'ULongLong', + 'uint8_t': 'U8', + 'uint32_t': 'U32', +} + + +def getBuiltinType(ctype, ptr=False, size=None): + if ctype == 'char': + if ptr: + return 'String' + elif size: + return 'Chars' + + if ctype == 'unsigned char' and size: + return 'UChars' + + return BUILTIN_MAP.get(ctype, None) + + +def cursorLineExtent(cursor, tu): + loc = cursor.location + start = SourceLocation.from_position(tu, loc.file, loc.line, 1) + end = SourceLocation.from_position(tu, loc.file, loc.line, -1) + return SourceRange.from_locations(start, end) + + +def getTokens(cursor, tu): + return tu.get_tokens(extent=cursorLineExtent(cursor, tu)) + + +DIRECTIVES = [ + 'genparse', 'genformat', 'namespace', 'xmlattr', 'xmlelem', + 'required', 'array', 'specified', 'callback', 'truevalue', 'checkformat' +] + + +def createDirectives(text): + tlist = re.findall(r'/\*(.*)\*/', text) + if len(tlist) != 1: + return None + + tlist = tlist[0].split(',') + if len(tlist) == 0: + return None + + directives = {} + for item in tlist: + item = item.strip() + if ':' in item: + key, value = item.split(':') + else: + key, value = item, None + + if key in DIRECTIVES: + directives[key] = value + return directives + + +def getDirectives(tokens, cursor): + for token in tokens: + if token.location.column <= cursor.location.column: + continue + if token.kind == TokenKind.COMMENT: + directive = createDirectives(token.spelling) + if directive: + return directive + return None + + +def determinType(kvs, tokens, cursor): + prefix = [] + kind = None + for token in tokens: + if token.location.column >= cursor.location.column: + break + if not kind: + kind = token.kind + prefix.append(token.spelling) + + suffix = [] + for token in tokens: + if token.spelling == ';': + break + suffix.append(token.spelling) + + size = None + if len(suffix) == 3 and suffix[0] == '[' and suffix[2] == ']': + size = suffix[1] + + assert kind in [TokenKind.IDENTIFIER, TokenKind.KEYWORD], \ + 'Bad field "%s".' % cursor.spelling + + assert prefix + typename = ' '.join(prefix) + + # For array, remove the most-outer pointer + if kvs.get('array'): + if typename.endswith('Ptr'): + typename = typename[:-3] + elif typename.endswith('*'): + typename = typename[:-1].strip() + + ptr = False + if typename.endswith('Ptr'): + typename = typename[:-3] + ptr = True + elif prefix[-1] == '*': + typename = typename[:-1].strip() + ptr = True + + ret = getBuiltinType(typename, ptr, size) + if ret: + typename = ret + + kvs.update({'type': typename, 'pointer': ptr}) + if size: + kvs['size'] = size + return kvs + + +def analyseMember(cursor, tu): + dvs = getDirectives(getTokens(cursor, tu), cursor) + if not dvs: + return None + + kvs = {'name': cursor.spelling} + kvs.update(dvs) + + # Formalize member + for key in ['array', 'required', 'specified']: + if key in kvs: + kvs[key] = True + + if 'checkformat' in kvs: + assert kvs.get('checkformat'), 'Directive "checkformat" is None' + + for tag in ['xmlattr', 'xmlelem']: + if tag in kvs: + if not kvs[tag]: + if kvs.get('array'): + kvs[tag] = Terms.singularize(kvs['name']) + else: + kvs[tag] = kvs['name'] + + return determinType(kvs, getTokens(cursor, tu), cursor) + + +def analyseStruct(struct, cursor, tu): + tokens = getTokens(cursor, tu) + kvs = getDirectives(tokens, cursor) + if kvs: + path, _ = os.path.splitext(cursor.location.file.name) + path, filename = os.path.split(path) + _, dirname = os.path.split(path) + kvs['output'] = dirname + '/' + filename + struct.update(kvs) + + inner_members = [] + for child in cursor.get_children(): + if inner_members: + # Flatten the members of embedded struct + for member in inner_members: + member['name'] = child.spelling + '.' + member['name'] + struct['members'].append(member) + inner_members = [] + continue + + if child.kind == CursorKind.STRUCT_DECL: + for ichild in child.get_children(): + member = analyseMember(ichild, tu) + if member: + inner_members.append(member) + continue + + member = analyseMember(child, tu) + if member: + struct['members'].append(member) + + return struct + + +def discoverStructures(tu): + for cursor in tu.cursor.get_children(): + if cursor.kind == CursorKind.STRUCT_DECL and cursor.is_definition(): + # Detect structs + name = cursor.spelling + if not name: + continue + if name.startswith('_'): + name = name[1:] + struct = {'name': name, 'meta': 'Struct', 'members': []} + analyseStruct(struct, cursor, tu) + TypeTable().register(struct) + elif cursor.kind == CursorKind.TYPEDEF_DECL: + # Detect enums + # We can't seek out enums by CursorKind.ENUM_DECL, + # since almost all enums are anonymous. + token = cursor.get_tokens() + try: + next(token) # skip 'typedef' + if next(token).spelling == 'enum': + enum = {'name': cursor.spelling, 'meta': 'Enum'} + TypeTable().register(enum) + except StopIteration: + pass + + +class CodeWriter(object): + def __init__(self, args, builddir): + self._builddir = builddir + self._cmd = args.cmd + self._files = {} + self._filters = {} + 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 build-aux/generator */\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['name']: + if extname == '.h': + info = Terms.upperInitial(kind) + print('\n###### %s ######' % info) + print('\n[.h]') + else: + print('\n[.c]') + print('\n' + content) + return + + assert self._cmd == 'generate' + + if atype.get('output'): + lfs = '\n' if extname == '.h' else '\n\n' + path = self._builddir + '/src/' + atype['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() + + +def getHFiles(path): + retlist = [] + for fname in os.listdir(path): + if fname.endswith('.h'): + retlist.append(os.path.join(path, fname)) + return retlist + + +HELP_LIST = 'list structs tagged by "genparse"/"genformat"' +HELP_LIST_ALL = 'list all discovered types' + +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=HELP_LIST) + parser_list.add_argument('-a', dest='list_all', action='store_true', + default=False, help=HELP_LIST_ALL) + 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('###### Generator: start ... ######') + if not args.kinds: + print("[dry run]: no kinds specified for 'generate'") + + timestamp = datetime.now() + topdir = os.environ.get('topdir', None) + builddir = os.environ.get('builddir', None) + assert topdir and builddir, 'Set env "topdir" and "builddir".' + + libclang_path = os.environ.get('libclang_path') + assert libclang_path, 'No libclang library.' + Config.set_library_file(libclang_path) + + # Examine all *.h in "$(topdir)/src/[util|conf]" + index = Index.create() + hfiles = getHFiles(topdir + '/src/util') + getHFiles(topdir + '/src/conf') + for hfile in hfiles: + tu = index.parse(hfile) + discoverStructures(tu) # find all structs and enums + + if args.cmd == 'list': + print('%-64s %s' % ('TYPENAME', 'META')) + for name, kvs in TypeTable().items(): + if not args.list_all: + if not ('genparse' in kvs or 'genparse' in kvs): + continue + print('%-64s %s' % (name, kvs['meta'])) + sys.exit(0) + elif args.cmd == 'show': + assert args.target, args + atype = TypeTable().get(args.target) + if not atype: + sys.exit(0) + showDirective(atype) + + writer = CodeWriter(args, builddir) + + for atype in TypeTable().values(): + makeClearFunc(writer, atype) + makeParseFunc(writer, atype) + makeFormatFunc(writer, atype) + + writer.complete() + + if args.cmd == 'generate': + elapse = (datetime.now() - timestamp).microseconds + print('\n###### Generator: elapse %d(us) ######\n' % elapse) + + sys.exit(0) diff --git a/build-aux/generator/utils.py b/build-aux/generator/utils.py new file mode 100644 index 0000000..c65045a --- /dev/null +++ b/build-aux/generator/utils.py @@ -0,0 +1,100 @@ +# +# 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 + + +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 = {'addresses': 'address'} + + @classmethod + def singularize(cls, name): + ret = cls.plurals.get(name, None) + if ret: + return ret + assert name.endswith('s') + return name[:-1] + + # 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:] + + +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() + + +class BlockAssembler(list): + def append(self, block): + if block: + super(BlockAssembler, self).append(block) + + def output(self, connector='\n'): + return connector.join(self) + + +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): + if not name.islower(): + name = Terms.upperInitial(name) + return 'n' + name diff --git a/po/POTFILES.in b/po/POTFILES.in index 6607e29..2d7eb1e 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@/build-aux/generator/directive.py @SRCDIR@/src/access/viraccessdriverpolkit.c @SRCDIR@/src/access/viraccessmanager.c @SRCDIR@/src/admin/admin_server.c -- 2.17.1

On Wed, Jun 10, 2020 at 09:20:29AM +0800, Shi Lei wrote:
This tool is used to generate parsexml/formatbuf functions for structs. It is based on libclang and its python-binding. Some directives (such as genparse, xmlattr, etc.) need to be added on the declarations of structs to direct the tool.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++
FWIW, we've tried to standardize on scripts/ directory for any helper scripts used during the build process. Calling it "generator" is probably too broad, as we've lots of diffferent generators. I think this would would be a good fit as scripts/xmlgen/
po/POTFILES.in | 1 + 5 files changed, 1370 insertions(+) create mode 100644 build-aux/generator/directive.py create mode 100755 build-aux/generator/go create mode 100755 build-aux/generator/main.py create mode 100644 build-aux/generator/utils.py
+T_CLEAR_FUNC_IMPL = ''' +void +${typename}Clear(${typename}Ptr def) +{ + if (!def) + return; + + ${body} +} +''' + +T_CLEAR_FUNC_DECL = ''' +void +${typename}Clear(${typename}Ptr def);
In general I think we have a preference for ${typename}Free functions, rather than ${typename}Clear. A Clear() func is needed when a struct is often stack allocated, or when the method allocating & free'ing the struct is different from the method clearing it. The latter case is generally considered bad practice - it is safer and easier to verify code when clearing and freeing is done at the same same.
+def clearMember(member): + mtype = TypeTable().get(member['type']) + + refname = 'def->%s' % member['name'] + if member.get('array'): + refname += '[i]' + + code = '' + if mtype['meta'] == 'Struct': + if member['pointer'] and not mtype.get('external'): + code = '%sClear(%s);' % (mtype['name'], refname) + code += '\nVIR_FREE(%s);' % refname
We should use g_free() for any new code.
+ else: + code = '%sClear(&%s);' % (mtype['name'], refname) + elif mtype['name'] == 'String': + code = 'VIR_FREE(%s);' % refname + elif mtype['name'] in ['Chars', 'UChars']: + code = 'memset(%s, 0, sizeof(%s));' % (refname, refname) + elif not member.get('array'): + code = '%s = 0;' % refname + + if member.get('specified'): + assert not member.get('array'), "'specified' can't come with 'array'." + code += '\n%s_specified = false;' % refname + + if member.get('array') and code: + 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);' % member['name'] + code += '\ndef->%s = 0;' % counter + + return code + + +T_CLEAR_NAMESPACE = ''' +if (def->namespaceData && def->ns.free) + (def->ns.free)(def->namespaceData); +''' + + +def makeClearFunc(writer, atype): + if 'genparse' not in atype: + return + + blocks = BlockAssembler() + for member in atype['members']: + blocks.append(clearMember(member)) + + if 'namespace' in atype: + blocks.append(T_CLEAR_NAMESPACE.strip()) + + body = indent(blocks.output('\n\n'), 1) + + impl = render(T_CLEAR_FUNC_IMPL, typename=atype['name'], body=body) + writer.write(atype, 'clearfunc', '.c', impl) + + decl = render(T_CLEAR_FUNC_DECL, typename=atype['name']) + writer.write(atype, 'clearfunc', '.h', decl)
Bearing in mind the above, I'd say we should create Free() funcs, not Clear() funcs here.
+ + +# +# Templates for parsing member block +# +T_READ_ATTR_BY_PROP = '${mdvar} = virXMLPropString(node, "${tname}");' +T_READ_ELEM_BY_PROP = '${mdvar} = virXMLChildNode(node, "${tname}");' +T_READ_ELEM_CONTENT = '${mdvar} = virXMLChildNodeContent(node, "${tname}");' + +T_PARSE_MEMBER_MORE = ''' +${number} = virXMLChildNodeSet(node, "${tname}", &nodes); +if (${number} > 0) { + size_t i; + + if (VIR_ALLOC_N(def->${name}, ${number}) < 0) + goto error; + + for (i = 0; i < ${number}; i++) { + xmlNodePtr tnode = nodes[i]; + ${item} + } + def->${counter} = ${number}; + VIR_FREE(nodes); +} else if (${number} < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Invalid ${tname} element found."));
For error messages we should avoid using python string substitution. Instead we want todo virReportError(VIR_ERR_XML_ERROR, _("Invalid %s element found."). "${tname}"); This means that when we repeat this code with many different values of ${tname}, we're only getting 1 string for the translation team to deal with. The same point for other virReportError error callsin this file, I won't mention them all individually.
+T_ALLOC_MEMORY = ''' +if (VIR_ALLOC(def->${name}) < 0) + goto error; +'''
We prefer g_new0 for new code now. As a general rule for any code, if there is a GLib API that works then we should use that, instead of a libvirt API.
+T_PARSE_FUNC_DECL = ''' +int +${funcname}(${args}); +''' + +T_PARSE_FUNC_IMPL = ''' +int +${funcname}(${args}) +{ + ${declare_vars} + VIR_USED(instname); + VIR_USED(opaque);
There should not be any need for this macro. Instead change the function declaration to add G_GNUC_UNUSED for "instname" and "opaque". This annotation is harmless if the variable really is used.
+ + if (!def) + goto error; + + ${body} + + return 0; + + error: + ${cleanup_vars} + ${typename}Clear(def);
IIUC, the caller of this method is responsible for allocating 'def', and so the caller should be responsible for running ${typename}Free(def). IOW, we should not need to call Clear for this.
+ return -1; +}
+def _handleTmpVars(tmpvars): + 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) + + return '\n'.join(heads), '\n'.join(tails)
One thing I noticed in the generated code is that we;re duplicating the strings twice: Consider this snippet of generated code: g_autofree char *nameStr = NULL; nameStr = virXMLPropString(node, "name"); if (nameStr == NULL) { virReportError(VIR_ERR_XML_ERROR, _("Missing 'name' setting in '%s'"), instname); goto error; } def->name = g_strdup(nameStr); virXMLPropString already duplicates the string, and then we g_strdup again. So we should just get rid of the g_autofree and directly assign def->name = nameStr.
diff --git a/build-aux/generator/go b/build-aux/generator/go new file mode 100755 index 0000000..9e30e08 --- /dev/null +++ b/build-aux/generator/go @@ -0,0 +1,14 @@ +# This is a command-line tool + +libclang_line=`ldconfig -p | grep libclang`
This needs to be libclang.so, otherwise it can match on the wrong library: $ ldconfig -p | grep libclang libclang.so.10 (libc6,x86-64) => /lib64/libclang.so.10 libclang.so (libc6,x86-64) => /lib64/libclang.so libclang-cpp.so.10 (libc6,x86-64) => /lib64/libclang-cpp.so.10 libclang-cpp.so (libc6,x86-64) => /lib64/libclang-cpp.so
+export libclang_path=`expr "$libclang_line" : '.* => \(.*\)$'` +if test -z "$libclang_path"; then + echo "libclang is required by libvirt\n" + exit -1 +fi
diff --git a/build-aux/generator/main.py b/build-aux/generator/main.py new file mode 100755 index 0000000..78a90f1 --- /dev/null +++ b/build-aux/generator/main.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +# +# 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/>. +#
+if __name__ == "__main__":
+ + libclang_path = os.environ.get('libclang_path') + assert libclang_path, 'No libclang library.' + Config.set_library_file(libclang_path)
I'm wondering if we really need this at all ? AFAICT, the libclang python library works fine without it, so this is only required if trying to use a non-standard libclang, which I think ought to be possible already by setting LD_LIBRARY_PATH 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, Jun 10, 2020 at 09:20:29AM +0800, Shi Lei wrote:
This tool is used to generate parsexml/formatbuf functions for structs. It is based on libclang and its python-binding. Some directives (such as genparse, xmlattr, etc.) need to be added on the declarations of structs to direct the tool.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++
FWIW, we've tried to standardize on scripts/ directory for any helper scripts used during the build process. Calling it "generator" is probably too broad, as we've lots of diffferent generators.
I think this would would be a good fit as scripts/xmlgen/
Okay. I move it there.
po/POTFILES.in | 1 + 5 files changed, 1370 insertions(+) create mode 100644 build-aux/generator/directive.py create mode 100755 build-aux/generator/go create mode 100755 build-aux/generator/main.py create mode 100644 build-aux/generator/utils.py
+T_CLEAR_FUNC_IMPL = ''' +void +${typename}Clear(${typename}Ptr def) +{ + if (!def) + return; + + ${body} +} +''' + +T_CLEAR_FUNC_DECL = ''' +void +${typename}Clear(${typename}Ptr def);
In general I think we have a preference for ${typename}Free functions, rather than ${typename}Clear.
A Clear() func is needed when a struct is often stack allocated, or when the method allocating & free'ing the struct is different from the method clearing it. The latter case is generally considered bad practice - it is safer and easier to verify code when clearing and freeing is done at the same same.
+def clearMember(member): + mtype = TypeTable().get(member['type']) + + refname = 'def->%s' % member['name'] + if member.get('array'): + refname += '[i]' + + code = '' + if mtype['meta'] == 'Struct': + if member['pointer'] and not mtype.get('external'): + code = '%sClear(%s);' % (mtype['name'], refname) + code += '\nVIR_FREE(%s);' % refname
We should use g_free() for any new code.
Okay.
+ else: + code = '%sClear(&%s);' % (mtype['name'], refname) + elif mtype['name'] == 'String': + code = 'VIR_FREE(%s);' % refname + elif mtype['name'] in ['Chars', 'UChars']: + code = 'memset(%s, 0, sizeof(%s));' % (refname, refname) + elif not member.get('array'): + code = '%s = 0;' % refname + + if member.get('specified'): + assert not member.get('array'), "'specified' can't come with 'array'." + code += '\n%s_specified = false;' % refname + + if member.get('array') and code: + 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);' % member['name'] + code += '\ndef->%s = 0;' % counter + + return code + + +T_CLEAR_NAMESPACE = ''' +if (def->namespaceData && def->ns.free) + (def->ns.free)(def->namespaceData); +''' + + +def makeClearFunc(writer, atype): + if 'genparse' not in atype: + return + + blocks = BlockAssembler() + for member in atype['members']: + blocks.append(clearMember(member)) + + if 'namespace' in atype: + blocks.append(T_CLEAR_NAMESPACE.strip()) + + body = indent(blocks.output('\n\n'), 1) + + impl = render(T_CLEAR_FUNC_IMPL, typename=atype['name'], body=body) + writer.write(atype, 'clearfunc', '.c', impl) + + decl = render(T_CLEAR_FUNC_DECL, typename=atype['name']) + writer.write(atype, 'clearfunc', '.h', decl)
Bearing in mind the above, I'd say we should create Free() funcs, not Clear() funcs here.
+ + +# +# Templates for parsing member block +# +T_READ_ATTR_BY_PROP = '${mdvar} = virXMLPropString(node, "${tname}");' +T_READ_ELEM_BY_PROP = '${mdvar} = virXMLChildNode(node, "${tname}");' +T_READ_ELEM_CONTENT = '${mdvar} = virXMLChildNodeContent(node, "${tname}");' + +T_PARSE_MEMBER_MORE = ''' +${number} = virXMLChildNodeSet(node, "${tname}", &nodes); +if (${number} > 0) { + size_t i; + + if (VIR_ALLOC_N(def->${name}, ${number}) < 0) + goto error; + + for (i = 0; i < ${number}; i++) { + xmlNodePtr tnode = nodes[i]; + ${item} + } + def->${counter} = ${number}; + VIR_FREE(nodes); +} else if (${number} < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Invalid ${tname} element found."));
For error messages we should avoid using python string substitution. Instead we want todo
virReportError(VIR_ERR_XML_ERROR, _("Invalid %s element found."). "${tname}");
This means that when we repeat this code with many different values of ${tname}, we're only getting 1 string for the translation team to deal with.
The same point for other virReportError error callsin this file, I won't mention them all individually.
Got it. Thanks!
+T_ALLOC_MEMORY = ''' +if (VIR_ALLOC(def->${name}) < 0) + goto error; +'''
We prefer g_new0 for new code now.
As a general rule for any code, if there is a GLib API that works then we should use that, instead of a libvirt API.
Okay.
+T_PARSE_FUNC_DECL = ''' +int +${funcname}(${args}); +''' + +T_PARSE_FUNC_IMPL = ''' +int +${funcname}(${args}) +{ + ${declare_vars} + VIR_USED(instname); + VIR_USED(opaque);
There should not be any need for this macro. Instead change the function declaration to add G_GNUC_UNUSED for "instname" and "opaque". This annotation is harmless if the variable really is used.
Okay.
+ + if (!def) + goto error; + + ${body} + + return 0; + + error: + ${cleanup_vars} + ${typename}Clear(def);
IIUC, the caller of this method is responsible for allocating 'def', and so the caller should be responsible for running ${typename}Free(def). IOW, we should not need to call Clear for this.
I suggest we can retain it. Sometimes the caller doesn't call Free, so this clear function ensures that members of 'def' will be cleared or freed on error. And it will not hurt performance, since it is only called on error.
+ return -1; +}
+def _handleTmpVars(tmpvars): + 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) + + return '\n'.join(heads), '\n'.join(tails)
One thing I noticed in the generated code is that we;re duplicating the strings twice:
Consider this snippet of generated code:
g_autofree char *nameStr = NULL;
nameStr = virXMLPropString(node, "name"); if (nameStr == NULL) { virReportError(VIR_ERR_XML_ERROR, _("Missing 'name' setting in '%s'"), instname); goto error; }
def->name = g_strdup(nameStr);
virXMLPropString already duplicates the string, and then we g_strdup again. So we should just get rid of the g_autofree and directly assign def->name = nameStr.
Okay. I would consider how to modify it.
diff --git a/build-aux/generator/go b/build-aux/generator/go new file mode 100755 index 0000000..9e30e08 --- /dev/null +++ b/build-aux/generator/go @@ -0,0 +1,14 @@ +# This is a command-line tool + +libclang_line=`ldconfig -p | grep libclang`
This needs to be libclang.so, otherwise it can match on the wrong library:
$ ldconfig -p | grep libclang libclang.so.10 (libc6,x86-64) => /lib64/libclang.so.10 libclang.so (libc6,x86-64) => /lib64/libclang.so libclang-cpp.so.10 (libc6,x86-64) => /lib64/libclang-cpp.so.10 libclang-cpp.so (libc6,x86-64) => /lib64/libclang-cpp.so
Okay.
+export libclang_path=`expr "$libclang_line" : '.* => \(.*\)$'` +if test -z "$libclang_path"; then + echo "libclang is required by libvirt\n" + exit -1 +fi
diff --git a/build-aux/generator/main.py b/build-aux/generator/main.py new file mode 100755 index 0000000..78a90f1 --- /dev/null +++ b/build-aux/generator/main.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +# +# 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/>. +#
+if __name__ == "__main__":
+ + libclang_path = os.environ.get('libclang_path') + assert libclang_path, 'No libclang library.' + Config.set_library_file(libclang_path)
I'm wondering if we really need this at all ? AFAICT, the libclang python library works fine without it, so this is only required if trying to use a non-standard libclang, which I think ought to be possible already by setting LD_LIBRARY_PATH
I'm working on Ubuntu. On Ubuntu 20.04 LTS and 18.04, I find: # ldconfig -p | grep libclang libclang-10.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libclang-10.so.1 libclang-10.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libclang-10.so There's no libclang.so! If I install python3 bindings from the standard apt repository, it works! By default, this version just find 'libclang-10.so'. But if I use 'pip3' to install python3 bindings, it doesn't work. Because it is another verison. This version find 'libclang.so' and it can't find it! So I think we can retain these lines to adapt to the distinction among platforms. 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, Jul 01, 2020 at 12:06:36AM +0800, Shi Lei wrote:
On Wed, Jun 10, 2020 at 09:20:29AM +0800, Shi Lei wrote:
This tool is used to generate parsexml/formatbuf functions for structs. It is based on libclang and its python-binding. Some directives (such as genparse, xmlattr, etc.) need to be added on the declarations of structs to direct the tool.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++
+if __name__ == "__main__":
+ + libclang_path = os.environ.get('libclang_path') + assert libclang_path, 'No libclang library.' + Config.set_library_file(libclang_path)
I'm wondering if we really need this at all ? AFAICT, the libclang python library works fine without it, so this is only required if trying to use a non-standard libclang, which I think ought to be possible already by setting LD_LIBRARY_PATH
I'm working on Ubuntu. On Ubuntu 20.04 LTS and 18.04, I find:
# ldconfig -p | grep libclang libclang-10.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libclang-10.so.1 libclang-10.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libclang-10.so
There's no libclang.so!
If I install python3 bindings from the standard apt repository, it works! By default, this version just find 'libclang-10.so'.
But if I use 'pip3' to install python3 bindings, it doesn't work. Because it is another verison. This version find 'libclang.so' and it can't find it!
So I think we can retain these lines to adapt to the distinction among platforms.
Generally libvirt only cares about working against the distro provided versions of packages. So if the apt installed python binding works that is sufficient. If we want to support an alternative libclang though, I'd suggest that instead of trying to figure it out magically, just have an optional arg to configure, eg --with-libclang=/lib/x86_64-linux-gnu/libclang-10.so By default don't set any libclang in the python binding - just let it use its built-in default. 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 :|

Make sure libclang and its python3 binding have been installed. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- configure.ac | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/configure.ac b/configure.ac index 6c8ac2f..747e52a 100644 --- a/configure.ac +++ b/configure.ac @@ -707,6 +707,18 @@ if test -z "$FLAKE8"; then AC_MSG_WARN(['flake8' binary is required to check python code style]) fi +dnl Need libclang and its python3 binding to generate some functions +LIBCLANG_LINE=`ldconfig -p | grep libclang` +LIBCLANG_PATH=`expr "$LIBCLANG_LINE" : '.* => \(.*\)$'` +if test -z "$LIBCLANG_PATH"; then + AC_MSG_ERROR([libclang is required by libvirt]) +fi +HAVE_PY3_CLANG=`python3 -c 'import clang.cindex;print("ok")' 2>/dev/null` +if test -z "$HAVE_PY3_CLANG"; then + AC_MSG_ERROR(['python3-clang' is required. Execute 'pip3 install clang'.]) +fi +AC_SUBST(LIBCLANG_PATH) + dnl Python3 < 3.7 treats the C locale as 7-bit only. dnl We must force env vars so it treats it as UTF-8 dnl regardless of the user's locale. -- 2.17.1

On Wed, Jun 10, 2020 at 09:20:30AM +0800, Shi Lei wrote:
Make sure libclang and its python3 binding have been installed.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- configure.ac | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/configure.ac b/configure.ac index 6c8ac2f..747e52a 100644 --- a/configure.ac +++ b/configure.ac @@ -707,6 +707,18 @@ if test -z "$FLAKE8"; then AC_MSG_WARN(['flake8' binary is required to check python code style]) fi
+dnl Need libclang and its python3 binding to generate some functions +LIBCLANG_LINE=`ldconfig -p | grep libclang`
As with previous patch, this ends up matching libclang-cpp.so for my host and thus fails. IMHO, we shouldn't be trying to get the path at all - just let the python library use its default....
+LIBCLANG_PATH=`expr "$LIBCLANG_LINE" : '.* => \(.*\)$'` +if test -z "$LIBCLANG_PATH"; then + AC_MSG_ERROR([libclang is required by libvirt]) +fi
So all this can go.
+HAVE_PY3_CLANG=`python3 -c 'import clang.cindex;print("ok")' 2>/dev/null` +if test -z "$HAVE_PY3_CLANG"; then + AC_MSG_ERROR(['python3-clang' is required. Execute 'pip3 install clang'.]) +fi
but this is needed to check for the require python lib.
+AC_SUBST(LIBCLANG_PATH) + dnl Python3 < 3.7 treats the C locale as 7-bit only. dnl We must force env vars so it treats it as UTF-8 dnl regardless of the user's locale. -- 2.17.1
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, Jun 10, 2020 at 09:20:30AM +0800, Shi Lei wrote:
Make sure libclang and its python3 binding have been installed.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- configure.ac | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/configure.ac b/configure.ac index 6c8ac2f..747e52a 100644 --- a/configure.ac +++ b/configure.ac @@ -707,6 +707,18 @@ if test -z "$FLAKE8"; then AC_MSG_WARN(['flake8' binary is required to check python code style]) fi +dnl Need libclang and its python3 binding to generate some functions +LIBCLANG_LINE=`ldconfig -p | grep libclang`
As with previous patch, this ends up matching libclang-cpp.so for my host and thus fails.
IMHO, we shouldn't be trying to get the path at all - just let the python library use its default....
As I mentioned in previous patch, I suggest we should reconsider its necessity.
+LIBCLANG_PATH=`expr "$LIBCLANG_LINE" : '.* => \(.*\)$'` +if test -z "$LIBCLANG_PATH"; then + AC_MSG_ERROR([libclang is required by libvirt]) +fi
So all this can go.
+HAVE_PY3_CLANG=`python3 -c 'import clang.cindex;print("ok")' 2>/dev/null` +if test -z "$HAVE_PY3_CLANG"; then + AC_MSG_ERROR(['python3-clang' is required. Execute 'pip3 install clang'.]) +fi
but this is needed to check for the require python lib.
Okay. Regards, Shi Lei
+AC_SUBST(LIBCLANG_PATH) + dnl Python3 < 3.7 treats the C locale as 7-bit only. dnl We must force env vars so it treats it as UTF-8 dnl regardless of the user's locale. -- 2.17.1
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 :|

Let makefile call the generator-tool whenever the c header files change. Only check those header files under src/conf and src/util. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/Makefile.am | 15 +++++++++++++++ src/access/Makefile.inc.am | 2 +- src/conf/Makefile.inc.am | 11 ++++++++++- 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 | 12 +++++++++--- src/vbox/Makefile.inc.am | 2 +- tests/Makefile.am | 2 ++ tools/Makefile.am | 2 ++ 18 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 12dd6b8..224d786 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,6 +24,7 @@ AM_CPPFLAGS = -I$(top_srcdir) \ -I$(top_srcdir)/include \ -I$(srcdir)/util \ -I./util \ + -I./conf \ -DIN_LIBVIRT \ -Dabs_top_builddir="\"$(abs_top_builddir)\"" \ -Dabs_top_srcdir="\"$(abs_top_srcdir)\"" \ @@ -87,6 +88,20 @@ sbin_PROGRAMS = bin_PROGRAMS = DRIVER_SOURCES = +GENERATED_FILES_STAMP = .generated_files.stamp +UTIL_HEADS = $(wildcard $(top_srcdir)/src/util/*.h) +CONF_HEADS = $(wildcard $(top_srcdir)/src/conf/*.h) +GENERATOR_SRC = $(wildcard $(top_srcdir)/build-aux/generator/*.py) + +$(GENERATED_FILES_STAMP): $(UTIL_HEADS) $(CONF_HEADS) $(GENERATOR_SRC) + $(AM_V_GEN)topdir=$(top_srcdir) builddir=$(top_builddir) \ + libclang_path=$(LIBCLANG_PATH) \ + $(PYTHON) -B $(top_srcdir)/build-aux/generator/main.py \ + generate -k cpf && touch $@ + +MAINTAINERCLEANFILES += $(GENERATED_FILES_STAMP) +CLEANFILES += $(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..3bd2199 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,16 @@ CONF_SOURCES = \ $(DEVICE_CONF_SOURCES) \ $(NULL) +BUILT_SOURCES += $(CONF_GENERATED_SOURCES) +$(CONF_GENERATED_SOURCES): $(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 46a43e6..03ccef1 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 b8c2e1e..fdbefb5 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 196a30e..d260f75 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 7885636..ee3c3bc 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 20db809..774b681 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 6a7fc08..680ec88 100644 --- a/src/qemu/Makefile.inc.am +++ b/src/qemu/Makefile.inc.am @@ -101,6 +101,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 1b1be83..4e42608 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 a8390f8..198b666 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 2f46d24..556d07b 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 5bc60cb..4056f1d 100644 --- a/src/util/Makefile.inc.am +++ b/src/util/Makefile.inc.am @@ -1,5 +1,8 @@ # vim: filetype=automake +UTIL_GENERATED_SOURCES = \ + $(NULL) + # These files are not related to driver APIs. Simply generic # helper APIs for various purposes UTIL_SOURCES = \ @@ -236,9 +239,9 @@ UTIL_SOURCES = \ util/virmdev.h \ util/virfilecache.c \ util/virfilecache.h \ + $(UTIL_GENERATED_SOURCES) \ $(NULL) - EXTRA_DIST += \ $(srcdir)/keycodemapdb/data/keymaps.csv \ $(srcdir)/keycodemapdb/tools/keymap-gen \ @@ -253,8 +256,9 @@ KEYTABLES = \ $(KEYNAMES:%=util/virkeynametable_%.h) \ $(NULL) -BUILT_SOURCES += $(KEYTABLES) -CLEANFILES += $(KEYTABLES) +BUILT_SOURCES += $(KEYTABLES) $(UTIL_GENERATED_SOURCES) +CLEANFILES += $(KEYTABLES) $(UTIL_GENERATED_SOURCES) +MAINTAINERCLEANFILES += $(KEYTABLES) $(UTIL_GENERATED_SOURCES) UTIL_IO_HELPER_SOURCES = util/iohelper.c @@ -300,6 +304,8 @@ libvirt_util_la_LIBADD = \ $(NULL) +$(UTIL_GENERATED_SOURCES): $(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 c5c6d53..7348475 100644 --- a/src/vbox/Makefile.inc.am +++ b/src/vbox/Makefile.inc.am @@ -48,7 +48,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 f5766a7..486de26 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

The macro VIR_USED is used in generated parse/format functions to avoid args-unused warnings. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/internal.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/internal.h b/src/internal.h index e181218..315f12d 100644 --- a/src/internal.h +++ b/src/internal.h @@ -502,3 +502,5 @@ enum { # define fprintf(fh, ...) g_fprintf(fh, __VA_ARGS__) #endif /* VIR_NO_GLIB_STDIO */ + +#define VIR_USED(var) do { break; } while(var) -- 2.17.1

On Wed, Jun 10, 2020 at 09:20:32AM +0800, Shi Lei wrote:
The macro VIR_USED is used in generated parse/format functions to avoid args-unused warnings.
I mentioned in the earlier patch that we can just use G_GNUC_UNUSED on any parameters which might be unused. It doesn't matter if they really are unused or not.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/internal.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/src/internal.h b/src/internal.h index e181218..315f12d 100644 --- a/src/internal.h +++ b/src/internal.h @@ -502,3 +502,5 @@ enum { # define fprintf(fh, ...) g_fprintf(fh, __VA_ARGS__)
#endif /* VIR_NO_GLIB_STDIO */ + +#define VIR_USED(var) do { break; } while(var) -- 2.17.1
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, Jun 10, 2020 at 09:20:32AM +0800, Shi Lei wrote:
The macro VIR_USED is used in generated parse/format functions to avoid args-unused warnings.
I mentioned in the earlier patch that we can just use G_GNUC_UNUSED on any parameters which might be unused. It doesn't matter if they really are unused or not.
Okay. Regards, Shi Lei
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/internal.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/src/internal.h b/src/internal.h index e181218..315f12d 100644 --- a/src/internal.h +++ b/src/internal.h @@ -502,3 +502,5 @@ enum { # define fprintf(fh, ...) g_fprintf(fh, __VA_ARGS__) #endif /* VIR_NO_GLIB_STDIO */ + +#define VIR_USED(var) do { break; } while(var) -- 2.17.1
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 :|

Add these helper functions to parse xml without using xmlXPathContext. 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 02b59ea..a2edbc1 100644 --- a/src/util/virxml.c +++ b/src/util/virxml.c @@ -1405,3 +1405,60 @@ virXMLNamespaceRegister(xmlXPathContextPtr ctxt, return 0; } + + +/** + * 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; +} 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

On Wed, Jun 10, 2020 at 09:20:33AM +0800, Shi Lei wrote:
Add these helper functions to parse xml without using xmlXPathContext.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/util/virxml.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/util/virxml.h | 3 +++ 2 files changed, 60 insertions(+)
Should be added to src/libvirt_private.syms too 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, Jun 10, 2020 at 09:20:33AM +0800, Shi Lei wrote:
Add these helper functions to parse xml without using xmlXPathContext.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/util/virxml.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/util/virxml.h | 3 +++ 2 files changed, 60 insertions(+)
Should be added to src/libvirt_private.syms too
I wonder why we need to do this?! 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, Jul 01, 2020 at 12:07:13AM +0800, Shi Lei wrote:
On Wed, Jun 10, 2020 at 09:20:33AM +0800, Shi Lei wrote:
Add these helper functions to parse xml without using xmlXPathContext.
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/util/virxml.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/util/virxml.h | 3 +++ 2 files changed, 60 insertions(+)
Should be added to src/libvirt_private.syms too
I wonder why we need to do this?!
Your particular series doesn't need it, but in general any internal function that ends up in libvirt.so needs to be added to the libvirt_private.syms file. This makes it possible to call the function from the dlopen'd modules like libvirt_driver_qemu.so. So we always add to the libvirt_private.syums file, even if we don't technically need that right now. 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 :|

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 48 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index f1d22b2..47aaef3 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -904,26 +904,25 @@ virNetworkDNSSrvDefParseXML(const char *networkName, static int -virNetworkDNSTxtDefParseXML(const char *networkName, - xmlNodePtr node, - virNetworkDNSTxtDefPtr def, - bool partialOkay) +virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, + virNetworkDNSTxtDefPtr def, + const char *networkName, + void *opaque) { const char *bad = " ,"; + bool partialOkay = false; + + if (opaque) + partialOkay = *((bool *) opaque); - 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) { + + 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); @@ -936,6 +935,33 @@ virNetworkDNSTxtDefParseXML(const char *networkName, "in DNS TXT record of network %s"), networkName); goto error; } + + return 0; + + error: + return -1; +} + + +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 (virNetworkDNSTxtDefParseXMLHook(node, def, networkName, + &partialOkay) < 0) + goto error; + return 0; error: -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- po/POTFILES.in | 1 + src/conf/Makefile.inc.am | 2 ++ src/conf/network_conf.c | 47 ++++++---------------------------------- src/conf/network_conf.h | 8 ++++--- 4 files changed, 15 insertions(+), 43 deletions(-) diff --git a/po/POTFILES.in b/po/POTFILES.in index 2d7eb1e..a0bd358 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@/build-aux/generator/directive.py diff --git a/src/conf/Makefile.inc.am b/src/conf/Makefile.inc.am index 3bd2199..0b36a8b 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 47aaef3..964a8a7 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -174,14 +174,6 @@ virNetworkIPDefClear(virNetworkIPDefPtr def) } -static void -virNetworkDNSTxtDefClear(virNetworkDNSTxtDefPtr def) -{ - VIR_FREE(def->name); - VIR_FREE(def->value); -} - - static void virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def) { @@ -903,7 +895,7 @@ virNetworkDNSSrvDefParseXML(const char *networkName, } -static int +int virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSTxtDefPtr def, const char *networkName, @@ -943,33 +935,6 @@ virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node 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 (virNetworkDNSTxtDefParseXMLHook(node, def, networkName, - &partialOkay) < 0) - goto error; - - return 0; - - error: - virNetworkDNSTxtDefClear(def); - return -1; -} - - static int virNetworkDNSDefParseXML(const char *networkName, xmlNodePtr node, @@ -1100,10 +1065,10 @@ 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, NULL) < 0) goto cleanup; - } + def->ntxts++; } } @@ -3714,6 +3679,7 @@ virNetworkDefUpdateDNSTxt(virNetworkDefPtr def, virNetworkDNSTxtDef txt; bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST || command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST); + bool notAdd; memset(&txt, 0, sizeof(txt)); @@ -3727,7 +3693,8 @@ virNetworkDefUpdateDNSTxt(virNetworkDefPtr def, if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "txt") < 0) goto cleanup; - if (virNetworkDNSTxtDefParseXML(def->name, ctxt->node, &txt, !isAdd) < 0) + notAdd = !isAdd; + if (virNetworkDNSTxtDefParseXML(ctxt->node, &txt, def->name, ¬Add) < 0) goto cleanup; for (foundIdx = 0; foundIdx < dns->ntxts; foundIdx++) { diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index f2dc388..eac8a76 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -130,9 +130,9 @@ struct _virNetworkDHCPHostDef { typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; -struct _virNetworkDNSTxtDef { - char *name; - char *value; +struct _virNetworkDNSTxtDef { /* genparse:concisehook */ + char *name; /* xmlattr, required */ + char *value; /* xmlattr */ }; typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; @@ -440,3 +440,5 @@ virNetworkDefUpdateSection(virNetworkDefPtr def, unsigned int flags); /* virNetworkUpdateFlags */ VIR_ENUM_DECL(virNetworkTaint); + +#include "network_conf.generated.h" -- 2.17.1

On Wed, Jun 10, 2020 at 09:20:35AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- po/POTFILES.in | 1 + src/conf/Makefile.inc.am | 2 ++ src/conf/network_conf.c | 47 ++++++---------------------------------- src/conf/network_conf.h | 8 ++++--- 4 files changed, 15 insertions(+), 43 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 47aaef3..964a8a7 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -174,14 +174,6 @@ virNetworkIPDefClear(virNetworkIPDefPtr def) }
-static void -virNetworkDNSTxtDefClear(virNetworkDNSTxtDefPtr def) -{ - VIR_FREE(def->name); - VIR_FREE(def->value); -} - - static void virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def) { @@ -903,7 +895,7 @@ virNetworkDNSSrvDefParseXML(const char *networkName, }
-static int +int virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSTxtDefPtr def, const char *networkName, @@ -943,33 +935,6 @@ virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node 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 (virNetworkDNSTxtDefParseXMLHook(node, def, networkName, - &partialOkay) < 0) - goto error; - - return 0; - - error: - virNetworkDNSTxtDefClear(def); - return -1; -}
Comparing this old code to the new generated code: int virNetworkDNSTxtDefParseXML(xmlNodePtr node, virNetworkDNSTxtDefPtr def, const char *instname, void *opaque) { g_autofree char *nameStr = NULL; g_autofree char *valueStr = NULL; VIR_USED(instname); VIR_USED(opaque); if (!def) goto error; nameStr = virXMLPropString(node, "name"); if (nameStr == NULL) { virReportError(VIR_ERR_XML_ERROR, _("Missing 'name' setting in '%s'"), instname); goto error; } def->name = g_strdup(nameStr); valueStr = virXMLPropString(node, "value"); if (valueStr) def->value = g_strdup(valueStr); if (virNetworkDNSTxtDefParseXMLHook(node, def, instname, opaque) < 0) goto error; return 0; error: virNetworkDNSTxtDefClear(def); return -1; } The main changes I'd suggest are * Change virReportError(VIR_ERR_XML_ERROR, _("Missing 'name' setting in '%s'"), instname); To virReportError(VIR_ERR_XML_ERROR, _("Missing '%s' attribute in '%s'"), "name", instname); so that it reduces the number of translatable strings * Remove the extra g_strdup's In an earlier patch I complained about your use of Clear() functions. Now I see this patch though, I understand why you were using Clear() - this pointer is just a member of an array, so we can't directly Free() it. So I withdraw my objection on the earlier patch about use of Clear() 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, Jun 10, 2020 at 09:20:35AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- po/POTFILES.in | 1 + src/conf/Makefile.inc.am | 2 ++ src/conf/network_conf.c | 47 ++++++---------------------------------- src/conf/network_conf.h | 8 ++++--- 4 files changed, 15 insertions(+), 43 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 47aaef3..964a8a7 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -174,14 +174,6 @@ virNetworkIPDefClear(virNetworkIPDefPtr def) } -static void -virNetworkDNSTxtDefClear(virNetworkDNSTxtDefPtr def) -{ - VIR_FREE(def->name); - VIR_FREE(def->value); -} - - static void virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def) { @@ -903,7 +895,7 @@ virNetworkDNSSrvDefParseXML(const char *networkName, } -static int +int virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSTxtDefPtr def, const char *networkName, @@ -943,33 +935,6 @@ virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node 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 (virNetworkDNSTxtDefParseXMLHook(node, def, networkName, - &partialOkay) < 0) - goto error; - - return 0; - - error: - virNetworkDNSTxtDefClear(def); - return -1; -}
Comparing this old code to the new generated code:
int virNetworkDNSTxtDefParseXML(xmlNodePtr node, virNetworkDNSTxtDefPtr def, const char *instname, void *opaque) { g_autofree char *nameStr = NULL; g_autofree char *valueStr = NULL; VIR_USED(instname); VIR_USED(opaque);
if (!def) goto error;
nameStr = virXMLPropString(node, "name"); if (nameStr == NULL) { virReportError(VIR_ERR_XML_ERROR, _("Missing 'name' setting in '%s'"), instname); goto error; }
def->name = g_strdup(nameStr);
valueStr = virXMLPropString(node, "value"); if (valueStr) def->value = g_strdup(valueStr);
if (virNetworkDNSTxtDefParseXMLHook(node, def, instname, opaque) < 0) goto error;
return 0;
error: virNetworkDNSTxtDefClear(def); return -1; }
The main changes I'd suggest are
* Change virReportError(VIR_ERR_XML_ERROR, _("Missing 'name' setting in '%s'"), instname);
To
virReportError(VIR_ERR_XML_ERROR, _("Missing '%s' attribute in '%s'"), "name", instname);
so that it reduces the number of translatable strings
* Remove the extra g_strdup's
In an earlier patch I complained about your use of Clear() functions. Now I see this patch though, I understand why you were using Clear() - this pointer is just a member of an array, so we can't directly Free() it. So I withdraw my objection on the earlier patch about use of Clear()
Got it! 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 :|

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 4 ++-- src/conf/network_conf.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 964a8a7..b807bac 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2280,8 +2280,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], NULL) < 0) + return -1; } for (i = 0; i < def->nsrvs; i++) { diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index eac8a76..b3c2895 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -130,7 +130,7 @@ struct _virNetworkDHCPHostDef { typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; -struct _virNetworkDNSTxtDef { /* genparse:concisehook */ +struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ char *name; /* xmlattr, required */ char *value; /* xmlattr */ }; -- 2.17.1

On Wed, Jun 10, 2020 at 09:20:36AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 4 ++-- src/conf/network_conf.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 964a8a7..b807bac 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2280,8 +2280,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], NULL) < 0) + return -1; }
For sake of review, the new code looks like this: int virNetworkDNSTxtDefFormatBuf(virBufferPtr buf, const char *name, const virNetworkDNSTxtDef *def, void *opaque) { VIR_USED(opaque); if (!def) return 0; if (!(def->name || def->value)) return 0; virBufferAsprintf(buf, "<%s", name); if (def->name) virBufferAsprintf(buf, " name='%s'", def->name); if (def->value) virBufferAsprintf(buf, " value='%s'", def->value); virBufferAddLit(buf, "/>\n"); return 0; } This is a lot longer, but obviously the code is more general purpose. I'm not sure why we need to pass "txt" into this method though. Can't we just hardcode "<txt" instead of formatting "<%s", name ? 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 Mon, Jun 29, 2020 at 13:52:51 +0100, Daniel Berrange wrote:
On Wed, Jun 10, 2020 at 09:20:36AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 4 ++-- src/conf/network_conf.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 964a8a7..b807bac 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2280,8 +2280,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], NULL) < 0) + return -1; }
For sake of review, the new code looks like this:
int virNetworkDNSTxtDefFormatBuf(virBufferPtr buf, const char *name, const virNetworkDNSTxtDef *def, void *opaque) { VIR_USED(opaque);
if (!def) return 0;
if (!(def->name || def->value)) return 0;
virBufferAsprintf(buf, "<%s", name);
if (def->name) virBufferAsprintf(buf, " name='%s'", def->name);
Specifically, these are wrong as they don't use virBufferEscapeString for formatting an XML thus the string won't have XML entities escaped. Looks like this must be applied everywhere where the string comes from the user.

On Mon, Jun 29, 2020 at 03:13:30PM +0200, Peter Krempa wrote:
On Mon, Jun 29, 2020 at 13:52:51 +0100, Daniel Berrange wrote:
On Wed, Jun 10, 2020 at 09:20:36AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 4 ++-- src/conf/network_conf.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 964a8a7..b807bac 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2280,8 +2280,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], NULL) < 0) + return -1; }
For sake of review, the new code looks like this:
int virNetworkDNSTxtDefFormatBuf(virBufferPtr buf, const char *name, const virNetworkDNSTxtDef *def, void *opaque) { VIR_USED(opaque);
if (!def) return 0;
if (!(def->name || def->value)) return 0;
virBufferAsprintf(buf, "<%s", name);
if (def->name) virBufferAsprintf(buf, " name='%s'", def->name);
Specifically, these are wrong as they don't use virBufferEscapeString for formatting an XML thus the string won't have XML entities escaped.
Looks like this must be applied everywhere where the string comes from the user.
With hand written code we've tried to optimize where we do escaping, but really we should just be escaping pretty much all string values. The only reasonable place to omit escaping is if dealing with a string that came from an enum conversion. So I think the code generator to just do full escaping, unless it is easy for the generator to skip with enums. 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, Jun 10, 2020 at 09:20:36AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 4 ++-- src/conf/network_conf.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 964a8a7..b807bac 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2280,8 +2280,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], NULL) < 0) + return -1; }
For sake of review, the new code looks like this:
int virNetworkDNSTxtDefFormatBuf(virBufferPtr buf, const char *name, const virNetworkDNSTxtDef *def, void *opaque) { VIR_USED(opaque);
if (!def) return 0;
if (!(def->name || def->value)) return 0;
virBufferAsprintf(buf, "<%s", name);
if (def->name) virBufferAsprintf(buf, " name='%s'", def->name);
if (def->value) virBufferAsprintf(buf, " value='%s'", def->value);
virBufferAddLit(buf, "/>\n");
return 0; }
This is a lot longer, but obviously the code is more general purpose.
I'm not sure why we need to pass "txt" into this method though. Can't we just hardcode "<txt" instead of formatting "<%s", name ?
The format function is generated based on Struct. A struct doesn't hold element name. Only a member can hold element/attribute name. So this name should be passed into format function as a argument. E.g., virNetworkDNSTxtDefFormatBuf is generated based on virNetworkDNSTxtDef, and 'txt' is specified by the member txts of virNetworkDNSDef. And also, if there're two members which have the same struct-type, they can reuse a unique format function and pass different tag names into it. 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 :|

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 115 +++++++++++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 24 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index b807bac..21b13ad 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -785,23 +785,29 @@ virNetworkDNSHostDefParseXML(const char *networkName, "_-+/*" static int -virNetworkDNSSrvDefParseXML(const char *networkName, - xmlNodePtr node, - xmlXPathContextPtr ctxt, - virNetworkDNSSrvDefPtr def, - bool partialOkay) +virNetworkDNSSrvDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, + virNetworkDNSSrvDefPtr def, + const char *networkName, + void *opaque, + const char *domainStr G_GNUC_UNUSED, + const char *serviceStr G_GNUC_UNUSED, + const char *protocolStr G_GNUC_UNUSED, + const char *targetStr G_GNUC_UNUSED, + const char *portStr, + const char *priorityStr, + const char *weightStr) { - int ret; - VIR_XPATH_NODE_AUTORESTORE(ctxt); - - ctxt->node = node; + bool partialOkay = false; + if (opaque) + partialOkay = *((bool *) opaque); - if (!(def->service = virXMLPropString(node, "service")) && !partialOkay) { + if (!def->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, @@ -819,13 +825,14 @@ virNetworkDNSSrvDefParseXML(const char *networkName, } } - if (!(def->protocol = virXMLPropString(node, "protocol")) && !partialOkay) { + if (!def->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, @@ -835,19 +842,14 @@ virNetworkDNSSrvDefParseXML(const char *networkName, goto error; } - /* 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) { + 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); goto error; } - if (ret == -2 || (ret >= 0 && (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'"), @@ -855,15 +857,14 @@ virNetworkDNSSrvDefParseXML(const char *networkName, goto error; } - ret = virXPathUInt("string(./@priority)", ctxt, &def->priority); - if (ret >= 0 && !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); goto error; } - if (ret == -2 || (ret >= 0 && def->priority > 65535)) { + if (priorityStr && def->priority > 65535) { virReportError(VIR_ERR_XML_DETAIL, _("Invalid DNS SRV priority attribute " "for service '%s' in network '%s'"), @@ -871,15 +872,14 @@ virNetworkDNSSrvDefParseXML(const char *networkName, goto error; } - ret = virXPathUInt("string(./@weight)", ctxt, &def->weight); - if (ret >= 0 && !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); goto error; } - if (ret == -2 || (ret >= 0 && def->weight > 65535)) { + if (weightStr && def->weight > 65535) { virReportError(VIR_ERR_XML_DETAIL, _("invalid DNS SRV weight attribute " "for service '%s' in network '%s'"), @@ -889,6 +889,73 @@ virNetworkDNSSrvDefParseXML(const char *networkName, return 0; + error: + return -1; +} + + +static int +virNetworkDNSSrvDefParseXML(const char *networkName, + xmlNodePtr node, + xmlXPathContextPtr ctxt, + virNetworkDNSSrvDefPtr def, + bool partialOkay) +{ + g_autofree char *portStr = NULL; + g_autofree char *priorityStr = NULL; + g_autofree char *weightStr = NULL; + VIR_XPATH_NODE_AUTORESTORE(ctxt); + + 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"); + + portStr = virXMLPropString(node, "port"); + if (portStr) { + if (virStrToLong_uip(portStr, NULL, 0, &def->port) < 0) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid DNS SRV port attribute " + "for service '%s' in network '%s'"), + def->service, networkName); + goto error; + } + } + + priorityStr = virXMLPropString(node, "priority"); + if (priorityStr) { + if (virStrToLong_uip(priorityStr, NULL, 0, &def->priority) < 0) { + virReportError(VIR_ERR_XML_DETAIL, + _("Invalid DNS SRV priority attribute " + "for service '%s' in network '%s'"), + def->service, networkName); + goto error; + } + } + + weightStr = virXMLPropString(node, "weight"); + if (weightStr) { + if (virStrToLong_uip(weightStr, NULL, 0, &def->weight) < 0) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid DNS SRV weight attribute " + "for service '%s' in network '%s'"), + def->service, networkName); + goto error; + } + } + + if (virNetworkDNSSrvDefParseXMLHook(node, def, networkName, &partialOkay, + def->domain, def->service, + def->protocol, def->target, + portStr, priorityStr, weightStr) < 0) + goto error; + + return 0; + error: virNetworkDNSSrvDefClear(def); return -1; -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 88 +++-------------------------------------- src/conf/network_conf.h | 16 ++++---- 2 files changed, 14 insertions(+), 90 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 21b13ad..bfdc10b 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -183,16 +183,6 @@ virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def) } -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) { @@ -784,7 +774,7 @@ virNetworkDNSHostDefParseXML(const char *networkName, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \ "_-+/*" -static int +int virNetworkDNSSrvDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSSrvDefPtr def, const char *networkName, @@ -894,74 +884,6 @@ virNetworkDNSSrvDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, } -static int -virNetworkDNSSrvDefParseXML(const char *networkName, - xmlNodePtr node, - xmlXPathContextPtr ctxt, - virNetworkDNSSrvDefPtr def, - bool partialOkay) -{ - g_autofree char *portStr = NULL; - g_autofree char *priorityStr = NULL; - g_autofree char *weightStr = NULL; - VIR_XPATH_NODE_AUTORESTORE(ctxt); - - 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"); - - portStr = virXMLPropString(node, "port"); - if (portStr) { - if (virStrToLong_uip(portStr, NULL, 0, &def->port) < 0) { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid DNS SRV port attribute " - "for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - } - - priorityStr = virXMLPropString(node, "priority"); - if (priorityStr) { - if (virStrToLong_uip(priorityStr, NULL, 0, &def->priority) < 0) { - virReportError(VIR_ERR_XML_DETAIL, - _("Invalid DNS SRV priority attribute " - "for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - } - - weightStr = virXMLPropString(node, "weight"); - if (weightStr) { - if (virStrToLong_uip(weightStr, NULL, 0, &def->weight) < 0) { - virReportError(VIR_ERR_XML_DETAIL, - _("invalid DNS SRV weight attribute " - "for service '%s' in network '%s'"), - def->service, networkName); - goto error; - } - } - - if (virNetworkDNSSrvDefParseXMLHook(node, def, networkName, &partialOkay, - def->domain, def->service, - def->protocol, def->target, - portStr, priorityStr, weightStr) < 0) - goto error; - - return 0; - - error: - virNetworkDNSSrvDefClear(def); - return -1; -} - - int virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSTxtDefPtr def, @@ -1112,8 +1034,8 @@ 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], + networkName, NULL) < 0) { goto cleanup; } def->nsrvs++; @@ -3661,6 +3583,7 @@ virNetworkDefUpdateDNSSrv(virNetworkDefPtr def, bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST || command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST); int foundCt = 0; + bool notAdd; memset(&srv, 0, sizeof(srv)); @@ -3674,7 +3597,8 @@ virNetworkDefUpdateDNSSrv(virNetworkDefPtr def, if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "srv") < 0) goto cleanup; - if (virNetworkDNSSrvDefParseXML(def->name, ctxt->node, ctxt, &srv, !isAdd) < 0) + notAdd = !isAdd; + if (virNetworkDNSSrvDefParseXML(ctxt->node, &srv, def->name, ¬Add) < 0) goto cleanup; for (i = 0; i < dns->nsrvs; i++) { diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index b3c2895..3a4d829 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -137,14 +137,14 @@ struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ 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; +struct _virNetworkDNSSrvDef { /* genparse:withhook */ + char *domain; /* xmlattr */ + char *service; /* xmlattr */ + char *protocol; /* xmlattr */ + char *target; /* xmlattr */ + unsigned int port; /* xmlattr */ + unsigned int priority; /* xmlattr */ + unsigned int weight; /* xmlattr */ }; typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 18 ++---------------- src/conf/network_conf.h | 4 ++-- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index bfdc10b..e8e7922 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2275,22 +2275,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], NULL) < 0) + return -1; } } diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 3a4d829..c867f27 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -137,10 +137,10 @@ struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; -struct _virNetworkDNSSrvDef { /* genparse:withhook */ - char *domain; /* xmlattr */ +struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ char *service; /* xmlattr */ char *protocol; /* xmlattr */ + char *domain; /* xmlattr */ char *target; /* xmlattr */ unsigned int port; /* xmlattr */ unsigned int priority; /* xmlattr */ -- 2.17.1

On Wed, Jun 10, 2020 at 09:20:39AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 18 ++---------------- src/conf/network_conf.h | 4 ++-- 2 files changed, 4 insertions(+), 18 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index bfdc10b..e8e7922 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2275,22 +2275,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], NULL) < 0) + return -1; } }
diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 3a4d829..c867f27 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -137,10 +137,10 @@ struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */
typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; -struct _virNetworkDNSSrvDef { /* genparse:withhook */ - char *domain; /* xmlattr */ +struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ char *service; /* xmlattr */ char *protocol; /* xmlattr */ + char *domain; /* xmlattr */ char *target; /* xmlattr */ unsigned int port; /* xmlattr */ unsigned int priority; /* xmlattr */
Why did the order of fields in the struct change ? Was this to make sure that auto-generated code formats attributes in the same order as the old code ? If so, that is ok. 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, Jun 10, 2020 at 09:20:39AM +0800, Shi Lei wrote:
Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 18 ++---------------- src/conf/network_conf.h | 4 ++-- 2 files changed, 4 insertions(+), 18 deletions(-)
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index bfdc10b..e8e7922 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2275,22 +2275,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], NULL) < 0) + return -1; } } diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 3a4d829..c867f27 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -137,10 +137,10 @@ struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; -struct _virNetworkDNSSrvDef { /* genparse:withhook */ - char *domain; /* xmlattr */ +struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ char *service; /* xmlattr */ char *protocol; /* xmlattr */ + char *domain; /* xmlattr */ char *target; /* xmlattr */ unsigned int port; /* xmlattr */ unsigned int priority; /* xmlattr */
Why did the order of fields in the struct change ? Was this to make sure that auto-generated code formats attributes in the same order as the old code ? If so, that is ok.
Yes, I just want to keep the same order with the old format function. 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 :|

Implement the parsexml/formatbuf functions for virSocketAddr. Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/util/virsocketaddr.c | 38 ++++++++++++++++++++++++++++++++++++++ src/util/virsocketaddr.h | 22 ++++++++++++++++++---- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c index a858a69..fd29678 100644 --- a/src/util/virsocketaddr.c +++ b/src/util/virsocketaddr.c @@ -157,6 +157,14 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) return len; } +int virSocketAddrParseXML(const char *val, + virSocketAddrPtr addr, + const char *instname G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED) +{ + return virSocketAddrParse(addr, val, AF_UNSPEC); +} + /** * virSocketAddrParseAny: * @addr: where to store the return value, optional. @@ -1316,3 +1324,33 @@ virSocketAddrFree(virSocketAddrPtr addr) { VIR_FREE(addr); } + +void +virSocketAddrClear(virSocketAddrPtr addr) +{ + memset(addr, 0, sizeof(virSocketAddr)); +} + +int +virSocketAddrFormatBuf(virBufferPtr buf, + const char *fmt, + const virSocketAddr *addr, + void *opaque G_GNUC_UNUSED) +{ + 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; +} + +bool +virSocketAddrCheck(const virSocketAddr *addr, void *opaque G_GNUC_UNUSED) +{ + return VIR_SOCKET_ADDR_VALID(addr); +} diff --git a/src/util/virsocketaddr.h b/src/util/virsocketaddr.h index d06e751..7ef4f35 100644 --- a/src/util/virsocketaddr.h +++ b/src/util/virsocketaddr.h @@ -18,11 +18,14 @@ #pragma once +#include "virbuffer.h" #include "virsocket.h" #define VIR_LOOPBACK_IPV4_ADDR "127.0.0.1" -typedef struct { +typedef struct _virSocketAddr virSocketAddr; +typedef virSocketAddr *virSocketAddrPtr; +struct _virSocketAddr { union { struct sockaddr sa; struct sockaddr_storage stor; @@ -33,7 +36,7 @@ typedef struct { #endif } data; socklen_t len; -} virSocketAddr; +}; #define VIR_SOCKET_ADDR_VALID(s) \ ((s)->data.sa.sa_family != AF_UNSPEC) @@ -50,8 +53,6 @@ typedef struct { #define VIR_SOCKET_ADDR_IPV4_ARPA "in-addr.arpa" #define VIR_SOCKET_ADDR_IPV6_ARPA "ip6.arpa" -typedef virSocketAddr *virSocketAddrPtr; - typedef struct _virSocketAddrRange virSocketAddrRange; typedef virSocketAddrRange *virSocketAddrRangePtr; struct _virSocketAddrRange { @@ -70,6 +71,11 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family); +int virSocketAddrParseXML(const char *val, + virSocketAddrPtr addr, + const char *instname, + void *opaque); + int virSocketAddrParseAny(virSocketAddrPtr addr, const char *val, int family, @@ -93,6 +99,11 @@ char *virSocketAddrFormatFull(const virSocketAddr *addr, bool withService, const char *separator); +int virSocketAddrFormatBuf(virBufferPtr buf, + const char *fmt, + const virSocketAddr *addr, + void *opaque); + char *virSocketAddrGetPath(virSocketAddrPtr addr); int virSocketAddrSetPort(virSocketAddrPtr addr, int port); @@ -145,5 +156,8 @@ 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); + +bool virSocketAddrCheck(const virSocketAddr *addr, void *opaque); -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 64 ++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index e8e7922..7a3dcd4 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -692,29 +692,62 @@ virNetworkDHCPDefParseXML(const char *networkName, static int -virNetworkDNSHostDefParseXML(const char *networkName, - xmlNodePtr node, - virNetworkDNSHostDefPtr def, - bool partialOkay) +virNetworkDNSHostDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, + virNetworkDNSHostDefPtr def, + const char *networkName, + void *opaque, + char *ip, + int nHostnameNodes G_GNUC_UNUSED) { - xmlNodePtr cur; - char *ip; + bool partialOkay = false; - if (!(ip = virXMLPropString(node, "ip")) && !partialOkay) { + if (opaque) + partialOkay = *((bool *) opaque); + + if (!ip && !partialOkay) { virReportError(VIR_ERR_XML_DETAIL, _("Missing IP address in network '%s' DNS HOST record"), networkName); goto error; } + 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); + goto error; + } + + return 0; + + error: + return -1; +} + + +static int +virNetworkDNSHostDefParseXML(const char *networkName, + xmlNodePtr node, + virNetworkDNSHostDefPtr def, + bool partialOkay) +{ + xmlNodePtr cur; + g_autofree char *ip = NULL; + + 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; } - VIR_FREE(ip); cur = node->children; while (cur != NULL) { @@ -737,19 +770,10 @@ 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 (virNetworkDNSHostDefParseXMLHook(node, def, networkName, &partialOkay, + ip, def->nnames) < 0) goto error; - } return 0; -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 72 ++++------------------------------------- src/conf/network_conf.h | 6 ++-- 2 files changed, 10 insertions(+), 68 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 7a3dcd4..29ef3cf 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -174,15 +174,6 @@ virNetworkIPDefClear(virNetworkIPDefPtr def) } -static void -virNetworkDNSHostDefClear(virNetworkDNSHostDefPtr def) -{ - while (def->nnames) - VIR_FREE(def->names[--def->nnames]); - VIR_FREE(def->names); -} - - static void virNetworkDNSForwarderClear(virNetworkDNSForwarderPtr def) { @@ -691,12 +682,12 @@ virNetworkDHCPDefParseXML(const char *networkName, } -static int +int virNetworkDNSHostDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSHostDefPtr def, const char *networkName, void *opaque, - char *ip, + const char *ip, int nHostnameNodes G_GNUC_UNUSED) { bool partialOkay = false; @@ -732,57 +723,6 @@ virNetworkDNSHostDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, } -static int -virNetworkDNSHostDefParseXML(const char *networkName, - xmlNodePtr node, - virNetworkDNSHostDefPtr def, - bool partialOkay) -{ - xmlNodePtr cur; - g_autofree char *ip = NULL; - - 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); - 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 (virNetworkDNSHostDefParseXMLHook(node, def, networkName, &partialOkay, - ip, def->nnames) < 0) - goto error; - - return 0; - - error: - 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 "." @@ -1038,8 +978,8 @@ 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], + networkName, NULL) < 0) { goto cleanup; } def->nhosts++; @@ -3498,6 +3438,7 @@ virNetworkDefUpdateDNSHost(virNetworkDefPtr def, bool isAdd = (command == VIR_NETWORK_UPDATE_COMMAND_ADD_FIRST || command == VIR_NETWORK_UPDATE_COMMAND_ADD_LAST); int foundCt = 0; + bool notAdd; memset(&host, 0, sizeof(host)); @@ -3511,7 +3452,8 @@ virNetworkDefUpdateDNSHost(virNetworkDefPtr def, if (virNetworkDefUpdateCheckElementName(def, ctxt->node, "host") < 0) goto cleanup; - if (virNetworkDNSHostDefParseXML(def->name, ctxt->node, &host, !isAdd) < 0) + notAdd = !isAdd; + if (virNetworkDNSHostDefParseXML(ctxt->node, &host, def->name, ¬Add) < 0) goto cleanup; for (i = 0; i < dns->nhosts; i++) { diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index c867f27..b715dc3 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -149,10 +149,10 @@ struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; -struct _virNetworkDNSHostDef { - virSocketAddr ip; +struct _virNetworkDNSHostDef { /* genparse:withhook */ + virSocketAddr ip; /* xmlattr */ size_t nnames; - char **names; + char **names; /* xmlelem:hostname, array */ }; -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 15 +++------------ src/conf/network_conf.h | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 29ef3cf..1fea580 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -2176,7 +2176,7 @@ static int virNetworkDNSDefFormat(virBufferPtr buf, const virNetworkDNSDef *def) { - size_t i, j; + size_t i; if (!(def->enable || def->forwardPlainNames || def->nfwds || def->nhosts || def->nsrvs || def->ntxts)) @@ -2246,17 +2246,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], NULL) < 0) + return -1; } } virBufferAdjustIndent(buf, -2); diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index b715dc3..a5a4939 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -149,7 +149,7 @@ struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; -struct _virNetworkDNSHostDef { /* genparse:withhook */ +struct _virNetworkDNSHostDef { /* genparse:withhook, genformat */ virSocketAddr ip; /* xmlattr */ size_t nnames; char **names; /* xmlelem:hostname, array */ -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 74 +++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 1fea580..e1790bc 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -888,6 +888,57 @@ virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, } +static int +virNetworkDNSForwarderParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, + virNetworkDNSForwarderPtr def, + const char *instname G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED, + const char *addr, + const char *domain G_GNUC_UNUSED) +{ + if (!(addr || def->domain)) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Invalid forwarder element, must contain " + "at least one of addr or domain")); + return -1; + } + + return 0; +} + + +static int +virNetworkDNSForwarderParseXML(xmlNodePtr node, + virNetworkDNSForwarderPtr def, + const char *networkName, + void *opaque) +{ + char *addr = virXMLPropString(node, "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); + goto cleanup; + } + def->domain = virXMLPropString(node, "domain"); + + if (virNetworkDNSForwarderParseXMLHook(node, def, networkName, opaque, + addr, def->domain) < 0) + goto cleanup; + + VIR_FREE(addr); + + return 0; + + cleanup: + return -1; +} + + static int virNetworkDNSDefParseXML(const char *networkName, xmlNodePtr node, @@ -943,25 +994,12 @@ 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, + NULL) < 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->nfwds++; } } -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 41 +---------------------------------------- src/conf/network_conf.h | 6 +++--- 2 files changed, 4 insertions(+), 43 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index e1790bc..8610fbc 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -174,13 +174,6 @@ virNetworkIPDefClear(virNetworkIPDefPtr def) } -static void -virNetworkDNSForwarderClear(virNetworkDNSForwarderPtr def) -{ - VIR_FREE(def->domain); -} - - static void virNetworkDNSDefClear(virNetworkDNSDefPtr def) { @@ -888,7 +881,7 @@ virNetworkDNSTxtDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, } -static int +int virNetworkDNSForwarderParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSForwarderPtr def, const char *instname G_GNUC_UNUSED, @@ -907,38 +900,6 @@ virNetworkDNSForwarderParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, } -static int -virNetworkDNSForwarderParseXML(xmlNodePtr node, - virNetworkDNSForwarderPtr def, - const char *networkName, - void *opaque) -{ - char *addr = virXMLPropString(node, "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); - goto cleanup; - } - def->domain = virXMLPropString(node, "domain"); - - if (virNetworkDNSForwarderParseXMLHook(node, def, networkName, opaque, - addr, def->domain) < 0) - goto cleanup; - - VIR_FREE(addr); - - return 0; - - cleanup: - return -1; -} - - static int virNetworkDNSDefParseXML(const char *networkName, xmlNodePtr node, diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index a5a4939..f14eef2 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -158,9 +158,9 @@ struct _virNetworkDNSHostDef { /* genparse:withhook, genformat */ typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; -struct _virNetworkDNSForwarder { - virSocketAddr addr; - char *domain; +struct _virNetworkDNSForwarder { /* genparse:withhook */ + virSocketAddr addr; /* xmlattr */ + char *domain; /* xmlattr */ }; typedef struct _virNetworkDNSDef virNetworkDNSDef; -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 21 ++++----------------- src/conf/network_conf.h | 4 ++-- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 8610fbc..3b93772 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -886,8 +886,8 @@ virNetworkDNSForwarderParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSForwarderPtr def, const char *instname G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED, - const char *addr, - const char *domain G_GNUC_UNUSED) + const char *domain G_GNUC_UNUSED, + const char *addr) { if (!(addr || def->domain)) { virReportError(VIR_ERR_XML_ERROR, "%s", @@ -2213,22 +2213,9 @@ virNetworkDNSDefFormat(virBufferPtr buf, virBufferAdjustIndent(buf, 2); for (i = 0; i < def->nfwds; 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], NULL) < 0) return -1; - - virBufferAsprintf(buf, " addr='%s'", addr); - VIR_FREE(addr); - } - virBufferAddLit(buf, "/>\n"); } for (i = 0; i < def->ntxts; i++) { diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index f14eef2..1313c3e 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -158,9 +158,9 @@ struct _virNetworkDNSHostDef { /* genparse:withhook, genformat */ typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; -struct _virNetworkDNSForwarder { /* genparse:withhook */ - virSocketAddr addr; /* xmlattr */ +struct _virNetworkDNSForwarder { /* genparse:withhook, genformat */ char *domain; /* xmlattr */ + virSocketAddr addr; /* xmlattr */ }; typedef struct _virNetworkDNSDef virNetworkDNSDef; -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 3b93772..4c0751f 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -900,6 +900,30 @@ virNetworkDNSForwarderParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, } +static int +virNetworkDNSDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, + virNetworkDNSDefPtr def, + const char *networkName, + void *opaque G_GNUC_UNUSED, + const char *enable G_GNUC_UNUSED, + const char *forwardPlainNames G_GNUC_UNUSED, + int ntxts, + int nhosts, + int nsrvs, + int nfwds) +{ + if (def->enable == VIR_TRISTATE_BOOL_NO && + (nfwds || nhosts || nsrvs || ntxts)) { + virReportError(VIR_ERR_XML_ERROR, + _("Extra data in disabled network '%s'"), + networkName); + return -1; + } + + return 0; +} + + static int virNetworkDNSDefParseXML(const char *networkName, xmlNodePtr node, @@ -1025,13 +1049,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 (virNetworkDNSDefParseXMLHook(node, def, networkName, NULL, + enable, forwardPlainNames, + ntxts, nhosts, nsrvs, nfwds) < 0) goto cleanup; - } ret = 0; cleanup: -- 2.17.1

Signed-off-by: Shi Lei <shi_lei@massclouds.com> --- src/conf/network_conf.c | 178 +----------------------------------- src/conf/network_conf.h | 16 ++-- src/network/bridge_driver.c | 2 +- 3 files changed, 14 insertions(+), 182 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index 4c0751f..db2b75f 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -174,32 +174,6 @@ virNetworkIPDefClear(virNetworkIPDefPtr def) } -static void -virNetworkDNSDefClear(virNetworkDNSDefPtr def) -{ - if (def->forwarders) { - while (def->nfwds) - virNetworkDNSForwarderClear(&def->forwarders[--def->nfwds]); - 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) { @@ -900,7 +874,7 @@ virNetworkDNSForwarderParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, } -static int +int virNetworkDNSDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, virNetworkDNSDefPtr def, const char *networkName, @@ -924,148 +898,6 @@ virNetworkDNSDefParseXMLHook(xmlNodePtr node 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; - VIR_XPATH_NODE_AUTORESTORE(ctxt); - - 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, - NULL) < 0) - goto cleanup; - - def->nfwds++; - } - } - - 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], - networkName, NULL) < 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], - networkName, NULL) < 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, NULL) < 0) - goto cleanup; - - def->ntxts++; - } - } - - if (virNetworkDNSDefParseXMLHook(node, def, networkName, NULL, - enable, forwardPlainNames, - ntxts, nhosts, nsrvs, nfwds) < 0) - goto cleanup; - - ret = 0; - cleanup: - VIR_FREE(enable); - VIR_FREE(forwardPlainNames); - VIR_FREE(fwdNodes); - VIR_FREE(hostNodes); - VIR_FREE(srvNodes); - VIR_FREE(txtNodes); - return ret; -} - - static int virNetworkIPDefParseXML(const char *networkName, xmlNodePtr node, @@ -1852,7 +1684,7 @@ virNetworkDefParseXML(xmlXPathContextPtr ctxt, dnsNode = virXPathNode("./dns", ctxt); if (dnsNode != NULL && - virNetworkDNSDefParseXML(def->name, dnsNode, ctxt, &def->dns) < 0) { ++ virNetworkDNSDefParseXML(dnsNode, &def->dns, def->name, NULL) < 0) { goto error; } @@ -2198,7 +2030,7 @@ virNetworkDNSDefFormat(virBufferPtr buf, { size_t i; - if (!(def->enable || def->forwardPlainNames || def->nfwds || def->nhosts || + if (!(def->enable || def->forwardPlainNames || def->nforwarders || def->nhosts || def->nsrvs || def->ntxts)) return 0; @@ -2225,7 +2057,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; } @@ -2233,7 +2065,7 @@ virNetworkDNSDefFormat(virBufferPtr buf, virBufferAddLit(buf, ">\n"); virBufferAdjustIndent(buf, 2); - for (i = 0; i < def->nfwds; i++) { + for (i = 0; i < def->nforwarders; i++) { if (virNetworkDNSForwarderFormatBuf(buf, "forwarder", &def->forwarders[i], NULL) < 0) return -1; diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 1313c3e..462d97d 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -165,17 +165,17 @@ struct _virNetworkDNSForwarder { /* genparse:withhook, genformat */ typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; -struct _virNetworkDNSDef { - int enable; /* enum virTristateBool */ - int forwardPlainNames; /* enum virTristateBool */ +struct _virNetworkDNSDef { /* genparse:withhook */ + virTristateBool enable; /* xmlattr */ + virTristateBool forwardPlainNames; /* xmlattr */ size_t ntxts; - virNetworkDNSTxtDefPtr txts; + virNetworkDNSTxtDefPtr txts; /* xmlelem, array */ size_t nhosts; - virNetworkDNSHostDefPtr hosts; + virNetworkDNSHostDefPtr hosts; /* xmlelem, array */ size_t nsrvs; - virNetworkDNSSrvDefPtr srvs; - size_t nfwds; - virNetworkDNSForwarderPtr forwarders; + virNetworkDNSSrvDefPtr srvs; /* xmlelem, array */ + size_t nforwarders; + virNetworkDNSForwarderPtr forwarders; /* xmlelem, array */ }; typedef struct _virNetworkIPDef virNetworkIPDef; diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index 47d5d95..6e09174 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -1139,7 +1139,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> --- src/conf/network_conf.c | 77 ++--------------------------------------- src/conf/network_conf.h | 10 +++--- 2 files changed, 8 insertions(+), 79 deletions(-) diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c index db2b75f..604e589 100644 --- a/src/conf/network_conf.c +++ b/src/conf/network_conf.c @@ -881,10 +881,10 @@ virNetworkDNSDefParseXMLHook(xmlNodePtr node G_GNUC_UNUSED, void *opaque G_GNUC_UNUSED, const char *enable G_GNUC_UNUSED, const char *forwardPlainNames G_GNUC_UNUSED, + int nfwds, int ntxts, - int nhosts, int nsrvs, - int nfwds) + int nhosts) { if (def->enable == VIR_TRISTATE_BOOL_NO && (nfwds || nhosts || nsrvs || ntxts)) { @@ -2024,77 +2024,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], NULL) < 0) - return -1; - } - - for (i = 0; i < def->ntxts; i++) { - if (virNetworkDNSTxtDefFormatBuf(buf, "txt", &def->txts[i], NULL) < 0) - return -1; - } - - for (i = 0; i < def->nsrvs; i++) { - if (def->srvs[i].service && def->srvs[i].protocol) { - if (virNetworkDNSSrvDefFormatBuf(buf, "srv", &def->srvs[i], NULL) < 0) - return -1; - } - } - - if (def->nhosts) { - for (i = 0; i < def->nhosts; i++) { - if (virNetworkDNSHostDefFormatBuf(buf, "host", &def->hosts[i], NULL) < 0) - return -1; - } - } - virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</dns>\n"); - return 0; -} - - static int virNetworkIPDefFormat(virBufferPtr buf, const virNetworkIPDef *def) @@ -2502,7 +2431,7 @@ virNetworkDefFormatBuf(virBufferPtr buf, virBufferAddLit(buf, "/>\n"); } - if (virNetworkDNSDefFormat(buf, &def->dns) < 0) + if (virNetworkDNSDefFormatBuf(buf, "dns", &def->dns, NULL) < 0) return -1; if (virNetDevVlanFormat(&def->vlan, buf) < 0) diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h index 462d97d..cfbd7c9 100644 --- a/src/conf/network_conf.h +++ b/src/conf/network_conf.h @@ -165,17 +165,17 @@ struct _virNetworkDNSForwarder { /* genparse:withhook, genformat */ typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; -struct _virNetworkDNSDef { /* genparse:withhook */ +struct _virNetworkDNSDef { /* genparse:withhook, genformat */ virTristateBool enable; /* xmlattr */ virTristateBool forwardPlainNames; /* xmlattr */ + size_t nforwarders; + virNetworkDNSForwarderPtr forwarders; /* xmlelem, array */ size_t ntxts; virNetworkDNSTxtDefPtr txts; /* xmlelem, array */ - size_t nhosts; - virNetworkDNSHostDefPtr hosts; /* xmlelem, array */ size_t nsrvs; virNetworkDNSSrvDefPtr srvs; /* xmlelem, array */ - size_t nforwarders; - virNetworkDNSForwarderPtr forwarders; /* xmlelem, array */ + size_t nhosts; + virNetworkDNSHostDefPtr hosts; /* xmlelem, array */ }; typedef struct _virNetworkIPDef virNetworkIPDef; -- 2.17.1

On Wed, Jun 10, 2020 at 09:20:28AM +0800, Shi Lei wrote:
Last RFC: [https://www.redhat.com/archives/libvir-list/2020-April/msg00970.html] In last RFC, I suggested we can generate object-model code based on relax-ng files and Daniel gave it some comments.
Follow the suggestion from Daniel, I make another try to generate parsexml/formatbuf functions automatically.
============ Directives ============
Still, we need several directives that can direct a tool to generate functions. These directives work on the declarations of structs. For example:
typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ char *name; /* xmlattr, required */ char *value; /* xmlattr */ };
typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ char *service; /* xmlattr */ char *protocol; /* xmlattr */ char *domain; /* xmlattr */ char *target; /* xmlattr */ unsigned int port; /* xmlattr */ unsigned int priority; /* xmlattr */ unsigned int weight; /* xmlattr */ };
typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { /* genparse:withhook, genformat */ virSocketAddr ip; /* xmlattr */ size_t nnames; char **names; /* xmlelem:hostname, array */ };
typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; struct _virNetworkDNSForwarder { /* genparse:withhook, genformat */ char *domain; /* xmlattr */ virSocketAddr addr; /* xmlattr */ };
typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; struct _virNetworkDNSDef { /* genparse:withhook, genformat */ virTristateBool enable; /* xmlattr */ virTristateBool forwardPlainNames; /* xmlattr */ size_t nforwarders; virNetworkDNSForwarderPtr forwarders; /* xmlelem, array */ size_t ntxts; virNetworkDNSTxtDefPtr txts; /* xmlelem, array */ size_t nsrvs; virNetworkDNSSrvDefPtr srvs; /* xmlelem, array */ size_t nhosts; virNetworkDNSHostDefPtr hosts; /* xmlelem, array */ };
Explanation for these directives:
- genparse[:withhook|concisehook]
Only work on a struct. Generate parsexml function for this struct only if 'genparse' is specified. The function name is based on the struct-name and suffixed with 'ParseXML'. E.g., for struct virNetworkDNSDef, its parsexml function is virNetworkDNSDefParseXML.
If a parsexml function includes some error-checking code, it needs a post-process hook to hold them. Specify 'withhook' or 'concisehook' to setup a hook point in the parsexml function.
Executing the tool manually can show the declaration of hook function. E.g. check declaration of 'virNetworkDNSDefParseXMLHook'.
# ./build-aux/generator/go show virNetworkDNSDef -kp
int virNetworkDNSDefParseXMLHook(xmlNodePtr node, virNetworkDNSDefPtr def, const char *instname, void *opaque, const char *enableStr, const char *forwardPlainNamesStr, int nForwarderNodes, int nTxtNodes, int nSrvNodes, int nHostNodes);
Some helper arguments (such as 'enableStr', 'nTxtNodes', etc.) are passed in to indicate the existence of fields. If these arguments are useless, specify 'concisehook' to omit them.
When 'genparse' is specified, clear function for this struct is also generated implicitly, it is because the generated parsexml function needs to call the clear function.
- genformat
Only work on a struct. Generate formatbuf function for this struct only if 'genformat' is specified. The function name is based on struct-name and suffixed with 'FormatBuf'.
- xmlattr[:thename]
Parse/Format the field as an XML attribute. If 'thename' is specified, use it as the XML attribute name; or use the filed name.
- xmlelem[:thename]
Parse/Format the field as a child struct. If 'thename' is specified, use it as the XML element name; or use the filed name.
- array
Parse/Format the field as an array. Its related field is a counter, which name should follow the pattern: n + 'field_name'.
- required
The field must have a corresponding item defined in XML.
This all looks pretty reasonable and simple to understand to me. I think the complex one is going to arrive when we need to deal with discriminated unions. eg the <forward mode='passthrough|nat|hostdev'> where the @mode attribute determines which child struct and elements are permitted.
======= Tool =======
The Tool is based on libclang and its python-binding.
It has three subcommands: 'list', 'show' and 'generate'. The 'list' and 'show' are used for previewing generated code; the 'generate' is prepared to be invoked by Makefile whenever the c header files under 'src/conf' and 'src/util' have changed.
It looks like libclang is available on all platforms that we need to support. The python binding appears available except on RHEL-7, but even then we "pip install" it if required. Looking at the generator code you've provided, I think it makes sense to use libclang. Any code based on a custom parser would have been more complex than what you've done and less reliable.
build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++
Once our xml generator is feature complete, this is fixed overhead, so...
src/conf/network_conf.c | 487 ++++-------------- src/conf/network_conf.h | 54 +-
These two are the really compelling parts of the diffstat. Overall I like the way this series looks. The code generator is naturally more complex than the current code, but the code it generates will be better than the hand written code because it will be behaving consistently with our desired spec. eg it will be capable of optimizing XML output to use self-closing <tag/> instead of empty <tag></tag>. It can guarantee that we have all XML escaping done correctly. It can ensure we're free'ing stuff in all code paths in a consistent manner, etc. Also the generator is fixed one time overhead, and that eliminates ongoing overhead of writing XML. Now that we're using struct annotations instead of XML RHG schema additions I think the effects are easier to follow. So overall, I think this idea of doing XML generators is a good one and will benefit us over the long term. 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, Jun 10, 2020 at 09:20:28AM +0800, Shi Lei wrote:
Last RFC: [https://www.redhat.com/archives/libvir-list/2020-April/msg00970.html] In last RFC, I suggested we can generate object-model code based on relax-ng files and Daniel gave it some comments.
Follow the suggestion from Daniel, I make another try to generate parsexml/formatbuf functions automatically.
============ Directives ============
Still, we need several directives that can direct a tool to generate functions. These directives work on the declarations of structs. For example:
typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ char *name; /* xmlattr, required */ char *value; /* xmlattr */ };
typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ char *service; /* xmlattr */ char *protocol; /* xmlattr */ char *domain; /* xmlattr */ char *target; /* xmlattr */ unsigned int port; /* xmlattr */ unsigned int priority; /* xmlattr */ unsigned int weight; /* xmlattr */ };
typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { /* genparse:withhook, genformat */ virSocketAddr ip; /* xmlattr */ size_t nnames; char **names; /* xmlelem:hostname, array */ };
typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; struct _virNetworkDNSForwarder { /* genparse:withhook, genformat */ char *domain; /* xmlattr */ virSocketAddr addr; /* xmlattr */ };
typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; struct _virNetworkDNSDef { /* genparse:withhook, genformat */ virTristateBool enable; /* xmlattr */ virTristateBool forwardPlainNames; /* xmlattr */ size_t nforwarders; virNetworkDNSForwarderPtr forwarders; /* xmlelem, array */ size_t ntxts; virNetworkDNSTxtDefPtr txts; /* xmlelem, array */ size_t nsrvs; virNetworkDNSSrvDefPtr srvs; /* xmlelem, array */ size_t nhosts; virNetworkDNSHostDefPtr hosts; /* xmlelem, array */ };
Explanation for these directives:
- genparse[:withhook|concisehook]
Only work on a struct. Generate parsexml function for this struct only if 'genparse' is specified. The function name is based on the struct-name and suffixed with 'ParseXML'. E.g., for struct virNetworkDNSDef, its parsexml function is virNetworkDNSDefParseXML.
If a parsexml function includes some error-checking code, it needs a post-process hook to hold them. Specify 'withhook' or 'concisehook' to setup a hook point in the parsexml function.
Executing the tool manually can show the declaration of hook function. E.g. check declaration of 'virNetworkDNSDefParseXMLHook'.
# ./build-aux/generator/go show virNetworkDNSDef -kp
int virNetworkDNSDefParseXMLHook(xmlNodePtr node, virNetworkDNSDefPtr def, const char *instname, void *opaque, const char *enableStr, const char *forwardPlainNamesStr, int nForwarderNodes, int nTxtNodes, int nSrvNodes, int nHostNodes);
Some helper arguments (such as 'enableStr', 'nTxtNodes', etc.) are passed in to indicate the existence of fields. If these arguments are useless, specify 'concisehook' to omit them.
When 'genparse' is specified, clear function for this struct is also generated implicitly, it is because the generated parsexml function needs to call the clear function.
- genformat
Only work on a struct. Generate formatbuf function for this struct only if 'genformat' is specified. The function name is based on struct-name and suffixed with 'FormatBuf'.
- xmlattr[:thename]
Parse/Format the field as an XML attribute. If 'thename' is specified, use it as the XML attribute name; or use the filed name.
- xmlelem[:thename] Parse/Format the field as a child struct. If 'thename' is specified, use it as the XML element name; or use the filed name.
- array
Parse/Format the field as an array. Its related field is a counter, which name should follow the pattern: n + 'field_name'.
- required
The field must have a corresponding item defined in XML.
This all looks pretty reasonable and simple to understand to me.
I think the complex one is going to arrive when we need to deal with discriminated unions. eg the <forward mode='passthrough|nat|hostdev'> where the @mode attribute determines which child struct and elements are permitted.
I suggest we can now check this in a hook function. If we deal with discriminated unions, I think we should introduce some directives or rules to indicate the relationship between union values and child struct. I'm afraid that the generator and the new directives will be too complicated. Do you have any suggestion about this? Regards, Shi Lei
======= Tool =======
The Tool is based on libclang and its python-binding.
It has three subcommands: 'list', 'show' and 'generate'. The 'list' and 'show' are used for previewing generated code; the 'generate' is prepared to be invoked by Makefile whenever the c header files under 'src/conf' and 'src/util' have changed.
It looks like libclang is available on all platforms that we need to support. The python binding appears available except on RHEL-7, but even then we "pip install" it if required.
Looking at the generator code you've provided, I think it makes sense to use libclang. Any code based on a custom parser would have been more complex than what you've done and less reliable.
build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++
Once our xml generator is feature complete, this is fixed overhead, so...
src/conf/network_conf.c | 487 ++++-------------- src/conf/network_conf.h | 54 +-
These two are the really compelling parts of the diffstat.
Overall I like the way this series looks.
The code generator is naturally more complex than the current code, but the code it generates will be better than the hand written code because it will be behaving consistently with our desired spec. eg it will be capable of optimizing XML output to use self-closing <tag/> instead of empty <tag></tag>. It can guarantee that we have all XML escaping done correctly. It can ensure we're free'ing stuff in all code paths in a consistent manner, etc.
Also the generator is fixed one time overhead, and that eliminates ongoing overhead of writing XML.
Now that we're using struct annotations instead of XML RHG schema additions I think the effects are easier to follow.
So overall, I think this idea of doing XML generators is a good one and will benefit us over the long term.
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, Jun 10, 2020 at 09:20:28AM +0800, Shi Lei wrote:
Last RFC: [https://www.redhat.com/archives/libvir-list/2020-April/msg00970.html] In last RFC, I suggested we can generate object-model code based on relax-ng files and Daniel gave it some comments.
Follow the suggestion from Daniel, I make another try to generate parsexml/formatbuf functions automatically.
============ Directives ============
Still, we need several directives that can direct a tool to generate functions. These directives work on the declarations of structs. For example:
typedef struct _virNetworkDNSTxtDef virNetworkDNSTxtDef; typedef virNetworkDNSTxtDef *virNetworkDNSTxtDefPtr; struct _virNetworkDNSTxtDef { /* genparse:concisehook, genformat */ char *name; /* xmlattr, required */ char *value; /* xmlattr */ };
typedef struct _virNetworkDNSSrvDef virNetworkDNSSrvDef; typedef virNetworkDNSSrvDef *virNetworkDNSSrvDefPtr; struct _virNetworkDNSSrvDef { /* genparse:withhook, genformat */ char *service; /* xmlattr */ char *protocol; /* xmlattr */ char *domain; /* xmlattr */ char *target; /* xmlattr */ unsigned int port; /* xmlattr */ unsigned int priority; /* xmlattr */ unsigned int weight; /* xmlattr */ };
typedef struct _virNetworkDNSHostDef virNetworkDNSHostDef; typedef virNetworkDNSHostDef *virNetworkDNSHostDefPtr; struct _virNetworkDNSHostDef { /* genparse:withhook, genformat */ virSocketAddr ip; /* xmlattr */ size_t nnames; char **names; /* xmlelem:hostname, array */ };
typedef struct _virNetworkDNSForwarder virNetworkDNSForwarder; typedef virNetworkDNSForwarder *virNetworkDNSForwarderPtr; struct _virNetworkDNSForwarder { /* genparse:withhook, genformat */ char *domain; /* xmlattr */ virSocketAddr addr; /* xmlattr */ };
typedef struct _virNetworkDNSDef virNetworkDNSDef; typedef virNetworkDNSDef *virNetworkDNSDefPtr; struct _virNetworkDNSDef { /* genparse:withhook, genformat */ virTristateBool enable; /* xmlattr */ virTristateBool forwardPlainNames; /* xmlattr */ size_t nforwarders; virNetworkDNSForwarderPtr forwarders; /* xmlelem, array */ size_t ntxts; virNetworkDNSTxtDefPtr txts; /* xmlelem, array */ size_t nsrvs; virNetworkDNSSrvDefPtr srvs; /* xmlelem, array */ size_t nhosts; virNetworkDNSHostDefPtr hosts; /* xmlelem, array */ };
Explanation for these directives:
- genparse[:withhook|concisehook]
Only work on a struct. Generate parsexml function for this struct only if 'genparse' is specified. The function name is based on the struct-name and suffixed with 'ParseXML'. E.g., for struct virNetworkDNSDef, its parsexml function is virNetworkDNSDefParseXML.
If a parsexml function includes some error-checking code, it needs a post-process hook to hold them. Specify 'withhook' or 'concisehook' to setup a hook point in the parsexml function.
Executing the tool manually can show the declaration of hook function. E.g. check declaration of 'virNetworkDNSDefParseXMLHook'.
# ./build-aux/generator/go show virNetworkDNSDef -kp
int virNetworkDNSDefParseXMLHook(xmlNodePtr node, virNetworkDNSDefPtr def, const char *instname, void *opaque, const char *enableStr, const char *forwardPlainNamesStr, int nForwarderNodes, int nTxtNodes, int nSrvNodes, int nHostNodes);
Some helper arguments (such as 'enableStr', 'nTxtNodes', etc.) are passed in to indicate the existence of fields. If these arguments are useless, specify 'concisehook' to omit them.
When 'genparse' is specified, clear function for this struct is also generated implicitly, it is because the generated parsexml function needs to call the clear function.
- genformat
Only work on a struct. Generate formatbuf function for this struct only if 'genformat' is specified. The function name is based on struct-name and suffixed with 'FormatBuf'.
- xmlattr[:thename]
Parse/Format the field as an XML attribute. If 'thename' is specified, use it as the XML attribute name; or use the filed name.
- xmlelem[:thename] Parse/Format the field as a child struct. If 'thename' is specified, use it as the XML element name; or use the filed name.
- array
Parse/Format the field as an array. Its related field is a counter, which name should follow the pattern: n + 'field_name'.
- required
The field must have a corresponding item defined in XML.
This all looks pretty reasonable and simple to understand to me.
I think the complex one is going to arrive when we need to deal with discriminated unions. eg the <forward mode='passthrough|nat|hostdev'> where the @mode attribute determines which child struct and elements are permitted.
I suggest we can now check this in a hook function. If we deal with discriminated unions, I think we should introduce some directives or rules to indicate the relationship between union values and child struct. I'm afraid that the generator and the new directives will be too complicated.
Do you have any suggestion about this? The current layout of many of our structs is unhelpful, because while we
On Wed, Jul 01, 2020 at 12:06:32AM +0800, Shi Lei wrote: treat the elements contents as a union, the struct is often flat. I think we'll need to fix this as part of adopting the generator, so that we always use a union for structs. Taking one of the more simple examples struct _virDomainHostdevSubsys { int type; /* enum virDomainHostdevSubsysType */ union { virDomainHostdevSubsysUSB usb; virDomainHostdevSubsysPCI pci; virDomainHostdevSubsysSCSI scsi; virDomainHostdevSubsysSCSIVHost scsi_host; virDomainHostdevSubsysMediatedDev mdev; } u; }; The "int type" field is the union discriminator and has a string conversion via the enum. So we can annotate the union to say which field to use as the discriminator to switch parsing based on: struct _virDomainHostdevSubsys { virDomainHostdevSubsysType type; /* xmlattr */ union { virDomainHostdevSubsysUSB usb; virDomainHostdevSubsysPCI pci; virDomainHostdevSubsysSCSI scsi; virDomainHostdevSubsysSCSIVHost scsi_host; virDomainHostdevSubsysMediatedDev mdev; } u; /* xmlswitch:type */ }; We can declare that the name of each union member should match the string form of the "type" attribute. eg with type="usb", we expect the union to have a field "usb". So with type="usb", we'll parse the virDomainHostdevSubsysUSB struct as the content. 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 :|
participants (3)
-
Daniel P. Berrangé
-
Peter Krempa
-
Shi Lei