/** * @file parser_xml.c * @author Radek Krejci * @brief XML data parser for libyang * * Copyright (c) 2015 - 2017 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause */ #include #include #include #include #include #include #include #include "libyang.h" #include "common.h" #include "context.h" #include "parser.h" #include "tree_internal.h" #include "validation.h" #include "xml_internal.h" /* does not log */ static struct lys_node * xml_data_search_schemanode(struct lyxml_elem *xml, struct lys_node *start, int options) { struct lys_node *result, *aux; LY_TREE_FOR(start, result) { /* skip groupings ... */ if (result->nodetype == LYS_GROUPING) { continue; /* ... and output in case of RPC ... */ } else if (result->nodetype == LYS_OUTPUT && (options & LYD_OPT_RPC)) { continue; /* ... and input in case of RPC reply */ } else if (result->nodetype == LYS_INPUT && (options & LYD_OPT_RPCREPLY)) { continue; } /* go into cases, choices, uses and in RPCs into input and output */ if (result->nodetype & (LYS_CHOICE | LYS_CASE | LYS_USES | LYS_INPUT | LYS_OUTPUT)) { aux = xml_data_search_schemanode(xml, result->child, options); if (aux) { /* we have matching result */ return aux; } /* else, continue with next schema node */ continue; } /* match data nodes */ if (ly_strequal(result->name, xml->name, 1)) { /* names matches, what about namespaces? */ if (ly_strequal(lys_main_module(result->module)->ns, xml->ns->value, 1)) { /* we have matching result */ return result; } /* else, continue with next schema node */ continue; } } /* no match */ return NULL; } /* logs directly */ static int xml_get_value(struct lyd_node *node, struct lyxml_elem *xml, int editbits) { struct lyd_node_leaf_list *leaf = (struct lyd_node_leaf_list *)node; assert(node && (node->schema->nodetype & (LYS_LEAFLIST | LYS_LEAF)) && xml); leaf->value_str = lydict_insert(node->schema->module->ctx, xml->content, 0); if ((editbits & 0x20) && (node->schema->nodetype & LYS_LEAF) && (!leaf->value_str || !leaf->value_str[0])) { /* we have edit-config leaf/leaf-list with delete operation and no (empty) value, * this is not a bug since the node is just used as a kind of selection node */ leaf->value_type = LY_TYPE_UNKNOWN; return EXIT_SUCCESS; } /* the value is here converted to a JSON format if needed in case of LY_TYPE_IDENT and LY_TYPE_INST or to a * canonical form of the value */ if (!lyp_parse_value(&((struct lys_node_leaf *)leaf->schema)->type, &leaf->value_str, xml, leaf, NULL, NULL, 1, 0)) { return EXIT_FAILURE; } return EXIT_SUCCESS; } /* logs directly */ static int xml_parse_data(struct ly_ctx *ctx, struct lyxml_elem *xml, struct lyd_node *parent, struct lyd_node *first_sibling, struct lyd_node *prev, int options, struct unres_data *unres, struct lyd_node **result, struct lyd_node **act_notif, const char *yang_data_name) { const struct lys_module *mod = NULL; struct lyd_node *diter, *dlast; struct lys_node *schema = NULL, *target; const struct lys_node *ext_node; struct lys_node_augment *aug; struct lyd_attr *dattr, *dattr_iter; struct lyxml_attr *attr; struct lyxml_elem *child, *next; int i, j, havechildren, r, editbits = 0, filterflag = 0, found; uint8_t pos; int ret = 0; const char *str = NULL; char *msg; assert(xml); assert(result); *result = NULL; if (xml->flags & LYXML_ELEM_MIXED) { if (options & LYD_OPT_STRICT) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_XML, xml, "XML element with mixed content"); return -1; } else { return 0; } } if (!xml->ns || !xml->ns->value) { if (options & LYD_OPT_STRICT) { LOGVAL(ctx, LYE_XML_MISS, LY_VLOG_XML, xml, "element's", "namespace"); return -1; } else { return 0; } } /* find schema node */ if (!parent) { mod = ly_ctx_get_module_by_ns(ctx, xml->ns->value, NULL, 0); if (ctx->data_clb) { if (!mod) { mod = ctx->data_clb(ctx, NULL, xml->ns->value, 0, ctx->data_clb_data); } else if (!mod->implemented) { mod = ctx->data_clb(ctx, mod->name, mod->ns, LY_MODCLB_NOT_IMPLEMENTED, ctx->data_clb_data); } } /* get the proper schema node */ if (mod && mod->implemented && !mod->disabled) { if (options & LYD_OPT_DATA_TEMPLATE) { if (yang_data_name) { ext_node = lyp_get_yang_data_template(mod, yang_data_name, strlen(yang_data_name)); if (ext_node) { schema = *((struct lys_node **) lys_ext_complex_get_substmt(LY_STMT_CONTAINER, (struct lys_ext_instance_complex *)ext_node, NULL)); schema = xml_data_search_schemanode(xml, schema, options); } } } else { schema = xml_data_search_schemanode(xml, mod->data, options); if (!schema) { /* it still can be the specific case of this module containing an augment of another module * top-level choice or top-level choice's case, bleh */ for (j = 0; j < mod->augment_size; ++j) { aug = &mod->augment[j]; target = aug->target; if (target->nodetype & (LYS_CHOICE | LYS_CASE)) { /* 1) okay, the target is choice or case */ while (target && (target->nodetype & (LYS_CHOICE | LYS_CASE | LYS_USES))) { target = lys_parent(target); } /* 2) now, the data node will be top-level, there are only non-data schema nodes */ if (!target) { while ((schema = (struct lys_node *) lys_getnext(schema, (struct lys_node *) aug, NULL, 0))) { /* 3) alright, even the name matches, we found our schema node */ if (ly_strequal(schema->name, xml->name, 1)) { break; } } } } if (schema) { break; } } } } } } else { /* parsing some internal node, we start with parent's schema pointer */ schema = xml_data_search_schemanode(xml, parent->schema->child, options); if (ctx->data_clb) { if (schema && !lys_node_module(schema)->implemented) { ctx->data_clb(ctx, lys_node_module(schema)->name, lys_node_module(schema)->ns, LY_MODCLB_NOT_IMPLEMENTED, ctx->data_clb_data); } else if (!schema) { if (ctx->data_clb(ctx, NULL, xml->ns->value, 0, ctx->data_clb_data)) { /* context was updated, so try to find the schema node again */ schema = xml_data_search_schemanode(xml, parent->schema->child, options); } } } } mod = lys_node_module(schema); if (!mod || !mod->implemented || mod->disabled) { if (options & LYD_OPT_STRICT) { LOGVAL(ctx, LYE_INELEM, (parent ? LY_VLOG_LYD : LY_VLOG_STR), (parent ? (void *)parent : (void *)"/") , xml->name); return -1; } else { return 0; } } /* create the element structure */ switch (schema->nodetype) { case LYS_CONTAINER: case LYS_LIST: case LYS_NOTIF: case LYS_RPC: case LYS_ACTION: for (i = 0; xml->content && xml->content[i]; ++i) { if (!is_xmlws(xml->content[i])) { msg = malloc(22 + strlen(xml->content) + 1); LY_CHECK_ERR_RETURN(!msg, LOGMEM(ctx), -1); sprintf(msg, "node with text data \"%s\"", xml->content); LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_XML, xml, msg); free(msg); return -1; } } *result = calloc(1, sizeof **result); havechildren = 1; break; case LYS_LEAF: case LYS_LEAFLIST: *result = calloc(1, sizeof(struct lyd_node_leaf_list)); havechildren = 0; break; case LYS_ANYXML: case LYS_ANYDATA: *result = calloc(1, sizeof(struct lyd_node_anydata)); havechildren = 0; break; default: LOGINT(ctx); return -1; } LY_CHECK_ERR_RETURN(!(*result), LOGMEM(ctx), -1); (*result)->prev = *result; (*result)->schema = schema; (*result)->parent = parent; diter = NULL; if (schema->nodetype == LYS_LEAF && lys_is_key((struct lys_node_leaf *)schema, &pos)) { /* it is key and we need to insert it into a correct place (a key must have a parent list) */ assert(parent); for (i = 0, diter = parent->child; diter && i < pos && diter->schema->nodetype == LYS_LEAF && lys_is_key((struct lys_node_leaf *)diter->schema, NULL); i++, diter = diter->next); if (diter) { /* out of order insertion - insert list's key to the correct position, before the diter */ if (options & LYD_OPT_STRICT) { LOGVAL(ctx, LYE_INORDER, LY_VLOG_LYD, *result, schema->name, diter->schema->name); LOGVAL(ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Invalid position of the key \"%s\" in a list \"%s\".", schema->name, parent->schema->name); free(*result); *result = NULL; return -1; } else { LOGWRN(ctx, "Invalid position of the key \"%s\" in a list \"%s\".", schema->name, parent->schema->name) } if (parent->child == diter) { parent->child = *result; /* update first_sibling */ first_sibling = *result; } if (diter->prev->next) { diter->prev->next = *result; } (*result)->prev = diter->prev; diter->prev = *result; (*result)->next = diter; } } if (!diter) { /* simplified (faster) insert as the last node */ if (parent && !parent->child) { parent->child = *result; } if (prev) { (*result)->prev = prev; prev->next = *result; /* fix the "last" pointer */ first_sibling->prev = *result; } else { (*result)->prev = *result; first_sibling = *result; } } (*result)->validity = ly_new_node_validity((*result)->schema); if (resolve_applies_when(schema, 0, NULL)) { (*result)->when_status = LYD_WHEN; } /* process attributes */ for (attr = xml->attr; attr; attr = attr->next) { if (attr->type != LYXML_ATTR_STD) { continue; } else if (!attr->ns) { if ((*result)->schema->nodetype == LYS_ANYXML && ly_strequal((*result)->schema->name, "filter", 0) && (ly_strequal((*result)->schema->module->name, "ietf-netconf", 0) || ly_strequal((*result)->schema->module->name, "notifications", 0))) { /* NETCONF filter's attributes, which we implement as non-standard annotations, * they are unqualified (no namespace), but we know that we have internally defined * them in the ietf-netconf module */ str = LY_NSNC; filterflag = 1; } else { /* garbage */ goto attr_error; } } else { str = attr->ns->value; } r = lyp_fill_attr(ctx, *result, str, NULL, attr->name, attr->value, xml, &dattr); if (r == -1) { goto unlink_node_error; } else if (r == 1) { attr_error: if (!strcmp(attr->name, "default") && !strcmp(attr->ns->value, "urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults")) { /* we do not need to parse this attribute, just skip it */ continue; } if (options & LYD_OPT_STRICT) { LOGVAL(ctx, LYE_INATTR, LY_VLOG_LYD, *result, attr->name); goto unlink_node_error; } LOGWRN(ctx, "Unknown \"%s:%s\" metadata with value \"%s\", ignoring.", (attr->ns ? attr->ns->prefix : ""), attr->name, attr->value); continue; } /* special case of xpath in the value, we want to convert it to JSON */ if (filterflag && !strcmp(attr->name, "select")) { dattr->value.string = transform_xml2json(ctx, dattr->value_str, xml, 0, 0); if (!dattr->value.string) { /* problem with resolving value as xpath */ dattr->value.string = dattr->value_str; lyd_free_attr(ctx, NULL, dattr, 0); goto unlink_node_error; } lydict_remove(ctx, dattr->value_str); dattr->value_str = dattr->value.string; } /* insert into the data node */ if (!(*result)->attr) { (*result)->attr = dattr; } else { for (dattr_iter = (*result)->attr; dattr_iter->next; dattr_iter = dattr_iter->next); dattr_iter->next = dattr; } continue; } /* check insert attribute and its values */ if (options & LYD_OPT_EDIT) { if (lyp_check_edit_attr(ctx, (*result)->attr, *result, &editbits)) { goto unlink_node_error; } /* check correct filter extension attributes */ } else if (filterflag) { found = 0; /* 0 - nothing, 1 - type subtree, 2 - type xpath, 3 - select, 4 - type xpath + select */ LY_TREE_FOR((*result)->attr, dattr_iter) { if (!strcmp(dattr_iter->name, "type")) { if ((found == 1) || (found == 2) || (found == 4)) { LOGVAL(ctx, LYE_TOOMANY, LY_VLOG_LYD, (*result), "type", xml->name); goto unlink_node_error; } switch (dattr_iter->value.enm->value) { case 0: /* subtree */ if (found == 3) { LOGVAL(ctx, LYE_INATTR, LY_VLOG_LYD, (*result), dattr_iter->name); goto unlink_node_error; } assert(!found); found = 1; break; case 1: /* xpath */ if (found == 3) { found = 4; } else { assert(!found); found = 2; } break; default: LOGINT(ctx); goto unlink_node_error; } } else if (!strcmp(dattr_iter->name, "select")) { switch (found) { case 0: found = 3; break; case 1: LOGVAL(ctx, LYE_INATTR, LY_VLOG_LYD, (*result), dattr_iter->name); goto unlink_node_error; case 2: found = 4; break; case 3: case 4: LOGVAL(ctx, LYE_TOOMANY, LY_VLOG_LYD, (*result), "select", xml->name); goto unlink_node_error; default: LOGINT(ctx); goto unlink_node_error; } } } /* check if what we found is correct */ switch (found) { case 1: case 4: /* ok */ break; case 2: LOGVAL(ctx, LYE_MISSATTR, LY_VLOG_LYD, (*result), "select", xml->name); goto unlink_node_error; case 3: LOGVAL(ctx, LYE_MISSATTR, LY_VLOG_LYD, (*result), "type", xml->name); goto unlink_node_error; default: LOGINT(ctx); goto unlink_node_error; } } /* type specific processing */ if (schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { /* type detection and assigning the value */ if (xml_get_value(*result, xml, editbits)) { goto unlink_node_error; } } else if (schema->nodetype & LYS_ANYDATA) { /* store children values */ if (xml->child) { child = xml->child; /* manually unlink all siblings and correct namespaces */ xml->child = NULL; LY_TREE_FOR(child, next) { next->parent = NULL; lyxml_correct_elem_ns(ctx, next, xml, 1, 1); } ((struct lyd_node_anydata *)*result)->value_type = LYD_ANYDATA_XML; ((struct lyd_node_anydata *)*result)->value.xml = child; } else { ((struct lyd_node_anydata *)*result)->value_type = LYD_ANYDATA_CONSTSTRING; ((struct lyd_node_anydata *)*result)->value.str = lydict_insert(ctx, xml->content, 0); } } else if (schema->nodetype & (LYS_RPC | LYS_ACTION)) { if (!(options & LYD_OPT_RPC) || *act_notif) { LOGVAL(ctx, LYE_INELEM, LY_VLOG_LYD, (*result), schema->name); LOGVAL(ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Unexpected %s node \"%s\".", (schema->nodetype == LYS_RPC ? "rpc" : "action"), schema->name); goto unlink_node_error; } *act_notif = *result; } else if (schema->nodetype == LYS_NOTIF) { if (!(options & (LYD_OPT_NOTIF | LYD_OPT_NOTIF_FILTER)) || *act_notif) { LOGVAL(ctx, LYE_INELEM, LY_VLOG_LYD, (*result), schema->name); LOGVAL(ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Unexpected notification node \"%s\".", schema->name); goto unlink_node_error; } *act_notif = *result; } #ifdef LY_ENABLED_CACHE /* calculate the hash and insert it into parent (list with keys is handled when its keys are inserted) */ if (((*result)->schema->nodetype != LYS_LIST) || !((struct lys_node_list *)(*result)->schema)->keys_size) { lyd_hash(*result); lyd_insert_hash(*result); } #endif /* first part of validation checks */ if (lyv_data_context(*result, options, 1, unres)) { goto error; } /* process children */ if (havechildren && xml->child) { diter = dlast = NULL; LY_TREE_FOR_SAFE(xml->child, next, child) { r = xml_parse_data(ctx, child, *result, (*result)->child, dlast, options, unres, &diter, act_notif, yang_data_name); if (r) { goto error; } else if (options & LYD_OPT_DESTRUCT) { lyxml_free(ctx, child); } if (diter && !diter->next) { /* the child was parsed/created and it was placed as the last child. The child can be inserted * out of order (not as the last one) in case it is a list's key present out of the correct order */ dlast = diter; } } } /* if we have empty non-presence container, we keep it, but mark it as default */ if (schema->nodetype == LYS_CONTAINER && !(*result)->child && !(*result)->attr && !((struct lys_node_container *)schema)->presence) { (*result)->dflt = 1; } /* rest of validation checks */ if (lyv_data_content(*result, options, unres) || lyv_multicases(*result, NULL, prev ? &first_sibling : NULL, 0, NULL)) { goto error; } /* validation successful */ if ((*result)->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) { /* postpone checking when there will be all list/leaflist instances */ (*result)->validity |= LYD_VAL_DUP; } return ret; unlink_node_error: lyd_unlink_internal(*result, 2); error: /* cleanup */ for (i = unres->count - 1; i >= 0; i--) { /* remove unres items connected with the node being removed */ if (unres->node[i] == *result) { unres_data_del(unres, i); } } lyd_free(*result); *result = NULL; return -1; } API struct lyd_node * lyd_parse_xml(struct ly_ctx *ctx, struct lyxml_elem **root, int options, ...) { FUN_IN; va_list ap; int r; struct unres_data *unres = NULL; const struct lyd_node *rpc_act = NULL, *data_tree = NULL; struct lyd_node *result = NULL, *iter, *last, *reply_parent = NULL, *reply_top = NULL, *act_notif = NULL; struct lyxml_elem *xmlstart, *xmlelem, *xmlaux, *xmlfree = NULL; const char *yang_data_name = NULL; if (!ctx || !root) { LOGARG; return NULL; } if (lyp_data_check_options(ctx, options, __func__)) { return NULL; } if (!(*root) && !(options & LYD_OPT_RPCREPLY)) { /* empty tree */ if (options & (LYD_OPT_RPC | LYD_OPT_NOTIF)) { /* error, top level node identify RPC and Notification */ LOGERR(ctx, LY_EINVAL, "%s: *root identifies RPC/Notification so it cannot be NULL.", __func__); return NULL; } else if (!(options & LYD_OPT_RPCREPLY)) { /* others - no work is needed, just check for missing mandatory nodes */ lyd_validate(&result, options, ctx); return result; } /* continue with empty RPC reply, for which we need RPC */ } unres = calloc(1, sizeof *unres); LY_CHECK_ERR_RETURN(!unres, LOGMEM(ctx), NULL); va_start(ap, options); if (options & LYD_OPT_RPCREPLY) { rpc_act = va_arg(ap, const struct lyd_node *); if (!rpc_act || rpc_act->parent || !(rpc_act->schema->nodetype & (LYS_RPC | LYS_LIST | LYS_CONTAINER))) { LOGERR(ctx, LY_EINVAL, "%s: invalid variable parameter (const struct lyd_node *rpc_act).", __func__); goto error; } if (rpc_act->schema->nodetype == LYS_RPC) { /* RPC request */ reply_top = reply_parent = _lyd_new(NULL, rpc_act->schema, 0); } else { /* action request */ reply_top = lyd_dup(rpc_act, 1); LY_TREE_DFS_BEGIN(reply_top, iter, reply_parent) { if (reply_parent->schema->nodetype == LYS_ACTION) { break; } LY_TREE_DFS_END(reply_top, iter, reply_parent); } if (!reply_parent) { LOGERR(ctx, LY_EINVAL, "%s: invalid variable parameter (const struct lyd_node *rpc_act).", __func__); lyd_free_withsiblings(reply_top); goto error; } lyd_free_withsiblings(reply_parent->child); } } if (options & (LYD_OPT_RPC | LYD_OPT_NOTIF | LYD_OPT_RPCREPLY)) { data_tree = va_arg(ap, const struct lyd_node *); if (data_tree) { if (options & LYD_OPT_NOEXTDEPS) { LOGERR(ctx, LY_EINVAL, "%s: invalid parameter (variable arg const struct lyd_node *data_tree and LYD_OPT_NOEXTDEPS set).", __func__); goto error; } LY_TREE_FOR((struct lyd_node *)data_tree, iter) { if (iter->parent) { /* a sibling is not top-level */ LOGERR(ctx, LY_EINVAL, "%s: invalid variable parameter (const struct lyd_node *data_tree).", __func__); goto error; } } /* move it to the beginning */ for (; data_tree->prev->next; data_tree = data_tree->prev); /* LYD_OPT_NOSIBLINGS cannot be set in this case */ if (options & LYD_OPT_NOSIBLINGS) { LOGERR(ctx, LY_EINVAL, "%s: invalid parameter (variable arg const struct lyd_node *data_tree with LYD_OPT_NOSIBLINGS).", __func__); goto error; } } } if (options & LYD_OPT_DATA_TEMPLATE) { yang_data_name = va_arg(ap, const char *); } if ((*root) && !(options & LYD_OPT_NOSIBLINGS)) { /* locate the first root to process */ if ((*root)->parent) { xmlstart = (*root)->parent->child; } else { xmlstart = *root; while(xmlstart->prev->next) { xmlstart = xmlstart->prev; } } } else { xmlstart = *root; } if ((options & LYD_OPT_RPC) && !strcmp(xmlstart->name, "action") && xmlstart->ns && !strcmp(xmlstart->ns->value, LY_NSYANG)) { /* it's an action, not a simple RPC */ xmlstart = xmlstart->child; if (options & LYD_OPT_DESTRUCT) { /* free it later */ xmlfree = xmlstart->parent; } } iter = last = NULL; LY_TREE_FOR_SAFE(xmlstart, xmlaux, xmlelem) { r = xml_parse_data(ctx, xmlelem, reply_parent, result, last, options, unres, &iter, &act_notif, yang_data_name); if (r) { if (reply_top) { result = reply_top; } goto error; } else if (options & LYD_OPT_DESTRUCT) { lyxml_free(ctx, xmlelem); *root = xmlaux; } if (iter) { last = iter; if ((options & LYD_OPT_DATA_ADD_YANGLIB) && iter->schema->module == ctx->models.list[ctx->internal_module_count - 1]) { /* ietf-yang-library data present, so ignore the option to add them */ options &= ~LYD_OPT_DATA_ADD_YANGLIB; } } if (!result) { result = iter; } if (options & LYD_OPT_NOSIBLINGS) { /* stop after the first processed root */ break; } } if (reply_top) { result = reply_top; } if ((options & LYD_OPT_RPCREPLY) && (rpc_act->schema->nodetype != LYS_RPC)) { /* action reply */ act_notif = reply_parent; } else if ((options & (LYD_OPT_RPC | LYD_OPT_NOTIF)) && !act_notif) { LOGVAL(ctx, LYE_INELEM, (result ? LY_VLOG_LYD : LY_VLOG_NONE), result, (options & LYD_OPT_RPC ? "action" : "notification")); goto error; } /* add missing ietf-yang-library if requested */ if (options & LYD_OPT_DATA_ADD_YANGLIB) { if (!result) { result = ly_ctx_info(ctx); } else if (lyd_merge(result, ly_ctx_info(ctx), LYD_OPT_DESTRUCT | LYD_OPT_EXPLICIT)) { LOGERR(ctx, LY_EINT, "Adding ietf-yang-library data failed."); goto error; } } /* check for uniqueness of top-level lists/leaflists because * only the inner instances were tested in lyv_data_content() */ LY_TREE_FOR(result, iter) { if (!(iter->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) || !(iter->validity & LYD_VAL_DUP)) { continue; } if (lyv_data_dup(iter, result)) { goto error; } } /* add default values, resolve unres and check for mandatory nodes in final tree */ if (lyd_defaults_add_unres(&result, options, ctx, NULL, 0, data_tree, act_notif, unres, 1)) { goto error; } if (!(options & (LYD_OPT_TRUSTED | LYD_OPT_NOTIF_FILTER)) && lyd_check_mandatory_tree((act_notif ? act_notif : result), ctx, NULL, 0, options)) { goto error; } if (xmlfree) { lyxml_free(ctx, xmlfree); } free(unres->node); free(unres->type); free(unres); va_end(ap); return result; error: lyd_free_withsiblings(result); if (xmlfree) { lyxml_free(ctx, xmlfree); } free(unres->node); free(unres->type); free(unres); va_end(ap); return NULL; }