#define _GNU_SOURCE
#include "diff.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "compat.h"
#include "context.h"
#include "log.h"
#include "ly_common.h"
#include "plugins_exts.h"
#include "plugins_exts/metadata.h"
#include "plugins_types.h"
#include "set.h"
#include "tree.h"
#include "tree_data.h"
#include "tree_data_internal.h"
#include "tree_edit.h"
#include "tree_schema.h"
#include "tree_schema_internal.h"
#define LOGERR_META(ctx, meta_name, node) \
{ \
char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \
LOGERR(ctx, LY_EINVAL, "Failed to find metadata \"%s\" for node \"%s\".", meta_name, __path); \
free(__path); \
}
#define LOGERR_NOINST(ctx, node) \
{ \
char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \
LOGERR(ctx, LY_EINVAL, "Failed to find node \"%s\" instance in data.", __path); \
free(__path); \
}
#define LOGERR_UNEXPVAL(ctx, node, data_source) \
{ \
char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \
LOGERR(ctx, LY_EINVAL, "Unexpected value of node \"%s\" in %s.", __path, data_source); \
free(__path); \
}
#define LOGERR_MERGEOP(ctx, node, src_op, trg_op) \
{ \
char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \
LOGERR(ctx, LY_EINVAL, "Unable to merge operation \"%s\" with \"%s\" for node \"%s\".", \
lyd_diff_op2str(trg_op), lyd_diff_op2str(src_op), __path); \
free(__path); \
}
static const char *
lyd_diff_op2str(enum lyd_diff_op op)
{
switch (op) {
case LYD_DIFF_OP_CREATE:
return "create";
case LYD_DIFF_OP_DELETE:
return "delete";
case LYD_DIFF_OP_REPLACE:
return "replace";
case LYD_DIFF_OP_NONE:
return "none";
}
LOGINT(NULL);
return NULL;
}
static enum lyd_diff_op
lyd_diff_str2op(const char *str)
{
switch (str[0]) {
case 'c':
assert(!strcmp(str, "create"));
return LYD_DIFF_OP_CREATE;
case 'd':
assert(!strcmp(str, "delete"));
return LYD_DIFF_OP_DELETE;
case 'r':
assert(!strcmp(str, "replace"));
return LYD_DIFF_OP_REPLACE;
case 'n':
assert(!strcmp(str, "none"));
return LYD_DIFF_OP_NONE;
}
LOGINT(NULL);
return 0;
}
static LY_ERR
lyd_diff_add_create_nested_userord(struct lyd_node *node)
{
LY_ERR rc = LY_SUCCESS;
const char *meta_name, *meta_val;
size_t buflen = 0, bufused = 0;
uint32_t pos;
char *dyn = NULL;
assert(lysc_is_userordered(node->schema));
if (lysc_is_dup_inst_list(node->schema)) {
meta_name = "yang:position";
pos = lyd_list_pos(node);
if (asprintf(&dyn, "%" PRIu32, pos) == -1) {
LOGMEM(LYD_CTX(node));
rc = LY_EMEM;
goto cleanup;
}
meta_val = dyn;
} else if (node->schema->nodetype == LYS_LIST) {
meta_name = "yang:key";
if (node->prev->next && (node->prev->schema == node->schema)) {
LY_CHECK_GOTO(rc = lyd_path_list_predicate(node->prev, &dyn, &buflen, &bufused, 0), cleanup);
meta_val = dyn;
} else {
meta_val = "";
}
} else {
meta_name = "yang:value";
if (node->prev->next && (node->prev->schema == node->schema)) {
meta_val = lyd_get_value(node->prev);
} else {
meta_val = "";
}
}
LY_CHECK_GOTO(rc = lyd_new_meta(NULL, node, NULL, meta_name, meta_val, LYD_NEW_VAL_STORE_ONLY, NULL), cleanup);
cleanup:
free(dyn);
return rc;
}
static void
lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr)
{
struct lyd_meta *m;
struct lyd_attr *a;
if (meta) {
*meta = NULL;
}
if (attr) {
*attr = NULL;
}
if (node->schema) {
assert(meta);
LY_LIST_FOR(node->meta, m) {
if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) {
*meta = m;
break;
}
}
} else {
assert(attr);
LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) {
if (strcmp(a->name.name, name)) {
continue;
}
switch (a->format) {
case LY_VALUE_JSON:
if (strcmp(a->name.module_name, "yang")) {
continue;
}
break;
case LY_VALUE_XML:
if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) {
continue;
}
break;
default:
LOGINT(LYD_CTX(node));
return;
}
*attr = a;
break;
}
}
}
static LY_ERR
lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op)
{
struct lyd_meta *meta = NULL;
struct lyd_attr *attr = NULL;
const struct lyd_node *diff_parent;
const char *str;
char *path;
for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) {
lyd_diff_find_meta(diff_parent, "operation", &meta, &attr);
if (!meta && !attr) {
continue;
}
str = meta ? lyd_get_meta_value(meta) : attr->value;
if ((str[0] == 'r') && (diff_parent != diff_node)) {
continue;
}
*op = lyd_diff_str2op(str);
return LY_SUCCESS;
}
path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0);
LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path);
free(path);
return LY_EINT;
}
static void
lyd_diff_del_meta(struct lyd_node *node, const char *name)
{
struct lyd_meta *meta;
struct lyd_attr *attr;
lyd_diff_find_meta(node, name, &meta, &attr);
if (meta) {
lyd_free_meta_single(meta);
} else if (attr) {
lyd_free_attr_single(LYD_CTX(node), attr);
}
}
static void
lyd_diff_insert_sibling(struct lyd_node *sibling, struct lyd_node *node, struct lyd_node **first_sibling)
{
assert(node && first_sibling);
lyd_unlink_ignore_lyds(NULL, node);
*first_sibling = lyd_first_sibling(sibling);
lyd_insert_node(NULL, first_sibling, node, LYD_INSERT_NODE_LAST_BY_SCHEMA);
}
LY_ERR
lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_default, const char *orig_value,
const char *key, const char *value, const char *position, const char *orig_key, const char *orig_position,
struct lyd_node **diff)
{
struct lyd_node *dup, *siblings, *match = NULL, *diff_parent = NULL, *elem;
const struct lyd_node *parent = NULL;
enum lyd_diff_op cur_op;
struct lyd_meta *meta;
uint32_t diff_opts;
assert(diff);
assert((node->schema->nodetype != LYS_LEAF) || (op != LYD_DIFF_OP_REPLACE) || (orig_default && orig_value));
assert((node->schema->nodetype != LYS_LIST) || !(node->schema->flags & LYS_ORDBY_USER) || (op != LYD_DIFF_OP_CREATE) ||
(lysc_is_dup_inst_list(node->schema) && position) || key);
assert((node->schema->nodetype != LYS_LEAFLIST) || !(node->schema->flags & LYS_ORDBY_USER) ||
(op != LYD_DIFF_OP_CREATE) || (lysc_is_dup_inst_list(node->schema) && position) || value);
assert((node->schema->nodetype != LYS_LIST) || !(node->schema->flags & LYS_ORDBY_USER) || (op != LYD_DIFF_OP_REPLACE) ||
(lysc_is_dup_inst_list(node->schema) && position && orig_position) || (key && orig_key));
assert((node->schema->nodetype != LYS_LEAFLIST) || !(node->schema->flags & LYS_ORDBY_USER) ||
(op != LYD_DIFF_OP_REPLACE) || (lysc_is_dup_inst_list(node->schema) && position && orig_position) ||
(value && orig_value));
siblings = *diff;
do {
parent = node;
while (parent->parent && (!diff_parent || (parent->parent->schema != diff_parent->schema))) {
parent = lyd_parent(parent);
}
if (lysc_is_dup_inst_list(parent->schema)) {
match = NULL;
break;
}
if (lyd_find_sibling_first(siblings, parent, &match)) {
break;
}
diff_parent = match;
siblings = lyd_child_no_keys(match);
} while (parent != node);
if (match && (parent == node)) {
assert(!lyd_diff_get_op(diff_parent, &cur_op) && (cur_op == LYD_DIFF_OP_NONE));
(void)cur_op;
if (lysc_is_userordered(diff_parent->schema)) {
for (elem = diff_parent; elem->next && (elem->next->schema == elem->schema); elem = elem->next) {}
if (elem != diff_parent) {
LY_CHECK_RET(lyd_insert_after(elem, diff_parent));
}
}
lyd_diff_del_meta(diff_parent, "operation");
LY_LIST_FOR(lyd_child_no_keys(diff_parent), elem) {
lyd_diff_find_meta(elem, "operation", &meta, NULL);
if (meta) {
continue;
}
LY_CHECK_RET(lyd_new_meta(NULL, elem, NULL, "yang:operation", "none", LYD_NEW_VAL_STORE_ONLY, NULL));
}
dup = diff_parent;
} else {
diff_opts = LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS | LYD_DUP_NO_LYDS;
if ((op != LYD_DIFF_OP_REPLACE) || !lysc_is_userordered(node->schema) || (node->schema->flags & LYS_CONFIG_R)) {
diff_opts |= LYD_DUP_RECURSIVE;
}
if (diff_parent) {
LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
diff_opts, &dup));
} else {
LY_CHECK_RET(lyd_dup_single(node, NULL, diff_opts, &dup));
}
if (!diff_parent) {
diff_parent = lyd_parent(dup);
while (diff_parent && diff_parent->parent) {
diff_parent = lyd_parent(diff_parent);
}
} else {
diff_parent = dup;
while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) {
diff_parent = lyd_parent(diff_parent);
}
}
if (!diff_parent) {
lyd_diff_insert_sibling(*diff, dup, diff);
} else if (!diff_parent->parent) {
lyd_diff_insert_sibling(*diff, diff_parent, diff);
}
if (diff_parent && (diff_parent != dup)) {
LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", LYD_NEW_VAL_STORE_ONLY, NULL));
}
}
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:operation", lyd_diff_op2str(op), LYD_NEW_VAL_STORE_ONLY, NULL));
if (op == LYD_DIFF_OP_CREATE) {
LYD_TREE_DFS_BEGIN(dup, elem) {
if ((elem != dup) && lysc_is_userordered(elem->schema)) {
LY_CHECK_RET(lyd_diff_add_create_nested_userord(elem));
}
LYD_TREE_DFS_END(dup, elem);
}
}
if (orig_default) {
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-default", orig_default, LYD_NEW_VAL_STORE_ONLY, NULL));
}
if (orig_value) {
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-value", orig_value, LYD_NEW_VAL_STORE_ONLY, NULL));
}
if (key) {
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:key", key, LYD_NEW_VAL_STORE_ONLY, NULL));
}
if (value) {
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:value", value, LYD_NEW_VAL_STORE_ONLY, NULL));
}
if (position) {
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:position", position, LYD_NEW_VAL_STORE_ONLY, NULL));
}
if (orig_key) {
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-key", orig_key, LYD_NEW_VAL_STORE_ONLY, NULL));
}
if (orig_position) {
LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-position", orig_position, LYD_NEW_VAL_STORE_ONLY, NULL));
}
return LY_SUCCESS;
}
static struct lyd_diff_userord *
lyd_diff_userord_get(const struct lyd_node *first, const struct lysc_node *schema, struct lyd_diff_userord **userord)
{
struct lyd_diff_userord *item;
struct lyd_node *iter;
const struct lyd_node **node;
LY_ARRAY_COUNT_TYPE u;
LY_ARRAY_FOR(*userord, u) {
if ((*userord)[u].schema == schema) {
return &(*userord)[u];
}
}
LY_ARRAY_NEW_RET(schema->module->ctx, *userord, item, NULL);
item->schema = schema;
item->pos = 0;
item->inst = NULL;
if (first) {
LYD_LIST_FOR_INST(lyd_first_sibling(first), first->schema, iter) {
LY_ARRAY_NEW_RET(schema->module->ctx, item->inst, node, NULL);
*node = iter;
}
}
return item;
}
static LY_ERR
lyd_diff_userord_attrs(const struct lyd_node *first, const struct lyd_node *second, uint16_t options,
struct lyd_diff_userord *userord_item, enum lyd_diff_op *op, const char **orig_default, char **value,
char **orig_value, char **key, char **orig_key, char **position, char **orig_position)
{
LY_ERR rc = LY_SUCCESS;
const struct lysc_node *schema;
size_t buflen, bufused;
uint32_t first_pos, second_pos, comp_opts;
assert(first || second);
*orig_default = NULL;
*value = NULL;
*orig_value = NULL;
*key = NULL;
*orig_key = NULL;
*position = NULL;
*orig_position = NULL;
schema = first ? first->schema : second->schema;
assert(lysc_is_userordered(schema));
if (first) {
for (first_pos = 0; first_pos < LY_ARRAY_COUNT(userord_item->inst); ++first_pos) {
if (userord_item->inst[first_pos] == first) {
break;
}
}
assert(first_pos < LY_ARRAY_COUNT(userord_item->inst));
} else {
first_pos = 0;
}
second_pos = userord_item->pos++;
if (!second) {
*op = LYD_DIFF_OP_DELETE;
} else if (!first) {
*op = LYD_DIFF_OP_CREATE;
} else {
comp_opts = lysc_is_dup_inst_list(second->schema) ? LYD_COMPARE_FULL_RECURSION : 0;
if (lyd_compare_single(second, userord_item->inst[second_pos], comp_opts)) {
*op = LYD_DIFF_OP_REPLACE;
} else if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) {
*op = LYD_DIFF_OP_NONE;
} else {
return LY_ENOT;
}
}
if ((schema->nodetype == LYS_LEAFLIST) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_NONE))) {
if (first->flags & LYD_DEFAULT) {
*orig_default = "true";
} else {
*orig_default = "false";
}
}
if ((schema->nodetype == LYS_LEAFLIST) && !lysc_is_dup_inst_list(schema) &&
((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_CREATE))) {
if (second_pos) {
*value = strdup(lyd_get_value(userord_item->inst[second_pos - 1]));
LY_CHECK_ERR_GOTO(!*value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
} else {
*value = strdup("");
LY_CHECK_ERR_GOTO(!*value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
}
}
if ((schema->nodetype == LYS_LEAFLIST) && !lysc_is_dup_inst_list(schema) &&
((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_DELETE))) {
if (first_pos) {
*orig_value = strdup(lyd_get_value(userord_item->inst[first_pos - 1]));
LY_CHECK_ERR_GOTO(!*orig_value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
} else {
*orig_value = strdup("");
LY_CHECK_ERR_GOTO(!*orig_value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
}
}
if ((schema->nodetype == LYS_LIST) && !lysc_is_dup_inst_list(schema) &&
((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_CREATE))) {
if (second_pos) {
buflen = bufused = 0;
LY_CHECK_GOTO(rc = lyd_path_list_predicate(userord_item->inst[second_pos - 1], key, &buflen, &bufused, 0), cleanup);
} else {
*key = strdup("");
LY_CHECK_ERR_GOTO(!*key, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
}
}
if ((schema->nodetype == LYS_LIST) && !lysc_is_dup_inst_list(schema) &&
((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_DELETE))) {
if (first_pos) {
buflen = bufused = 0;
LY_CHECK_GOTO(rc = lyd_path_list_predicate(userord_item->inst[first_pos - 1], orig_key, &buflen, &bufused, 0), cleanup);
} else {
*orig_key = strdup("");
LY_CHECK_ERR_GOTO(!*orig_key, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
}
}
if (lysc_is_dup_inst_list(schema) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_CREATE))) {
if (second_pos) {
if (asprintf(position, "%" PRIu32, second_pos) == -1) {
LOGMEM(schema->module->ctx);
rc = LY_EMEM;
goto cleanup;
}
} else {
*position = strdup("");
LY_CHECK_ERR_GOTO(!*position, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
}
}
if (lysc_is_dup_inst_list(schema) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_DELETE))) {
if (first_pos) {
if (asprintf(orig_position, "%" PRIu32, first_pos) == -1) {
LOGMEM(schema->module->ctx);
rc = LY_EMEM;
goto cleanup;
}
} else {
*orig_position = strdup("");
LY_CHECK_ERR_GOTO(!*orig_position, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup);
}
}
if (*op == LYD_DIFF_OP_CREATE) {
LY_ARRAY_CREATE_GOTO(schema->module->ctx, userord_item->inst, 1, rc, cleanup);
if (second_pos < LY_ARRAY_COUNT(userord_item->inst)) {
memmove(userord_item->inst + second_pos + 1, userord_item->inst + second_pos,
(LY_ARRAY_COUNT(userord_item->inst) - second_pos) * sizeof *userord_item->inst);
}
LY_ARRAY_INCREMENT(userord_item->inst);
userord_item->inst[second_pos] = second;
} else if (*op == LYD_DIFF_OP_DELETE) {
if (first_pos + 1 < LY_ARRAY_COUNT(userord_item->inst)) {
memmove(userord_item->inst + first_pos, userord_item->inst + first_pos + 1,
(LY_ARRAY_COUNT(userord_item->inst) - first_pos - 1) * sizeof *userord_item->inst);
}
LY_ARRAY_DECREMENT(userord_item->inst);
} else if (*op == LYD_DIFF_OP_REPLACE) {
memmove(userord_item->inst + second_pos + 1, userord_item->inst + second_pos,
(first_pos - second_pos) * sizeof *userord_item->inst);
userord_item->inst[second_pos] = first;
}
cleanup:
if (rc) {
free(*value);
*value = NULL;
free(*orig_value);
*orig_value = NULL;
free(*key);
*key = NULL;
free(*orig_key);
*orig_key = NULL;
free(*position);
*position = NULL;
free(*orig_position);
*orig_position = NULL;
}
return rc;
}
static LY_ERR
lyd_diff_attrs(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, enum lyd_diff_op *op,
const char **orig_default, char **orig_value)
{
const struct lysc_node *schema;
const char *str_val;
assert(first || second);
*orig_default = NULL;
*orig_value = NULL;
schema = first ? first->schema : second->schema;
assert(!lysc_is_userordered(schema));
if (!second) {
*op = LYD_DIFF_OP_DELETE;
} else if (!first) {
*op = LYD_DIFF_OP_CREATE;
} else {
switch (schema->nodetype) {
case LYS_CONTAINER:
case LYS_RPC:
case LYS_ACTION:
case LYS_NOTIF:
return LY_ENOT;
case LYS_LIST:
case LYS_LEAFLIST:
if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) {
*op = LYD_DIFF_OP_NONE;
} else {
return LY_ENOT;
}
break;
case LYS_LEAF:
case LYS_ANYXML:
case LYS_ANYDATA:
if (lyd_compare_single(first, second, 0)) {
*op = LYD_DIFF_OP_REPLACE;
} else if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) {
*op = LYD_DIFF_OP_NONE;
} else {
return LY_ENOT;
}
break;
default:
LOGINT_RET(schema->module->ctx);
}
}
if ((schema->nodetype & LYD_NODE_TERM) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_NONE))) {
if (first->flags & LYD_DEFAULT) {
*orig_default = "true";
} else {
*orig_default = "false";
}
}
if ((schema->nodetype & (LYS_LEAF | LYS_ANYDATA)) && (*op == LYD_DIFF_OP_REPLACE)) {
if (schema->nodetype == LYS_LEAF) {
str_val = lyd_get_value(first);
*orig_value = strdup(str_val ? str_val : "");
LY_CHECK_ERR_RET(!*orig_value, LOGMEM(schema->module->ctx), LY_EMEM);
} else {
LY_CHECK_RET(lyd_any_value_str(first, orig_value));
}
}
return LY_SUCCESS;
}
static LY_ERR
lyd_diff_find_match(const struct lyd_node *siblings, const struct lyd_node *target, ly_bool defaults,
struct ly_ht **dup_inst_ht, struct lyd_node **match)
{
LY_ERR r;
if (!target->schema) {
r = lyd_find_sibling_opaq_next(siblings, LYD_NAME(target), match);
} else if (target->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) {
r = lyd_find_sibling_first(siblings, target, match);
} else {
r = lyd_find_sibling_val(siblings, target->schema, NULL, 0, match);
}
if (r && (r != LY_ENOTFOUND)) {
return r;
}
LY_CHECK_RET(lyd_dup_inst_next(match, siblings, dup_inst_ht));
if (*match && ((*match)->flags & LYD_DEFAULT) && !defaults) {
*match = NULL;
}
return LY_SUCCESS;
}
static LY_ERR
lyd_diff_siblings_r(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, ly_bool nosiblings,
struct lyd_node **diff)
{
LY_ERR ret = LY_SUCCESS;
const struct lyd_node *iter_first, *iter_second;
struct lyd_node *match_second, *match_first;
struct lyd_diff_userord *userord = NULL, *userord_item;
struct ly_ht *dup_inst_first = NULL, *dup_inst_second = NULL;
LY_ARRAY_COUNT_TYPE u;
enum lyd_diff_op op;
const char *orig_default;
char *orig_value, *key, *value, *position, *orig_key, *orig_position;
LY_LIST_FOR(first, iter_first) {
if (!iter_first->schema) {
continue;
}
assert(!(iter_first->schema->flags & LYS_KEY));
if ((iter_first->flags & LYD_DEFAULT) && !(options & LYD_DIFF_DEFAULTS)) {
continue;
}
LY_CHECK_GOTO(ret = lyd_diff_find_match(second, iter_first, options & LYD_DIFF_DEFAULTS, &dup_inst_second,
&match_second), cleanup);
if (lysc_is_userordered(iter_first->schema)) {
userord_item = lyd_diff_userord_get(iter_first, iter_first->schema, &userord);
LY_CHECK_ERR_GOTO(!userord_item, LOGMEM(LYD_CTX(iter_first)); ret = LY_EMEM, cleanup);
if (!match_second) {
LY_CHECK_GOTO(ret = lyd_diff_userord_attrs(iter_first, match_second, options, userord_item, &op,
&orig_default, &value, &orig_value, &key, &orig_key, &position, &orig_position), cleanup);
assert(op == LYD_DIFF_OP_DELETE);
ret = lyd_diff_add(iter_first, op, orig_default, orig_value, key, value, position, orig_key,
orig_position, diff);
free(orig_value);
free(key);
free(value);
free(position);
free(orig_key);
free(orig_position);
LY_CHECK_GOTO(ret, cleanup);
}
} else {
ret = lyd_diff_attrs(iter_first, match_second, options, &op, &orig_default, &orig_value);
if (!ret) {
if (op == LYD_DIFF_OP_DELETE) {
ret = lyd_diff_add(iter_first, op, orig_default, orig_value, NULL, NULL, NULL, NULL, NULL, diff);
} else {
assert(match_second);
ret = lyd_diff_add(match_second, op, orig_default, orig_value, NULL, NULL, NULL, NULL, NULL, diff);
}
free(orig_value);
LY_CHECK_GOTO(ret, cleanup);
} else if (ret == LY_ENOT) {
ret = LY_SUCCESS;
} else {
goto cleanup;
}
}
if (match_second) {
LY_CHECK_GOTO(ret = lyd_diff_siblings_r(lyd_child_no_keys(iter_first), lyd_child_no_keys(match_second),
options, 0, diff), cleanup);
}
if (nosiblings) {
break;
}
}
LY_ARRAY_FOR(userord, u) {
userord[u].pos = 0;
}
LY_LIST_FOR(second, iter_second) {
if (!iter_second->schema) {
continue;
}
assert(!(iter_second->schema->flags & LYS_KEY));
if ((iter_second->flags & LYD_DEFAULT) && !(options & LYD_DIFF_DEFAULTS)) {
continue;
}
LY_CHECK_GOTO(ret = lyd_diff_find_match(first, iter_second, options & LYD_DIFF_DEFAULTS, &dup_inst_first,
&match_first), cleanup);
if (lysc_is_userordered(iter_second->schema)) {
userord_item = lyd_diff_userord_get(match_first, iter_second->schema, &userord);
LY_CHECK_ERR_GOTO(!userord_item, LOGMEM(LYD_CTX(iter_second)); ret = LY_EMEM, cleanup);
ret = lyd_diff_userord_attrs(match_first, iter_second, options, userord_item, &op, &orig_default,
&value, &orig_value, &key, &orig_key, &position, &orig_position);
if (!ret) {
ret = lyd_diff_add(iter_second, op, orig_default, orig_value, key, value, position, orig_key,
orig_position, diff);
free(orig_value);
free(key);
free(value);
free(position);
free(orig_key);
free(orig_position);
LY_CHECK_GOTO(ret, cleanup);
} else if (ret == LY_ENOT) {
ret = LY_SUCCESS;
} else {
goto cleanup;
}
} else if (!match_first) {
LY_CHECK_GOTO(ret = lyd_diff_attrs(match_first, iter_second, options, &op, &orig_default, &orig_value), cleanup);
assert(op == LYD_DIFF_OP_CREATE);
ret = lyd_diff_add(iter_second, op, orig_default, orig_value, NULL, NULL, NULL, NULL, NULL, diff);
free(orig_value);
LY_CHECK_GOTO(ret, cleanup);
}
if (nosiblings) {
break;
}
}
cleanup:
lyd_dup_inst_free(dup_inst_first);
lyd_dup_inst_free(dup_inst_second);
LY_ARRAY_FOR(userord, u) {
LY_ARRAY_FREE(userord[u].inst);
}
LY_ARRAY_FREE(userord);
return ret;
}
static LY_ERR
lyd_diff(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, ly_bool nosiblings,
struct lyd_node **diff)
{
const struct ly_ctx *ctx;
LY_CHECK_ARG_RET(NULL, diff, LY_EINVAL);
if (first) {
ctx = LYD_CTX(first);
} else if (second) {
ctx = LYD_CTX(second);
} else {
ctx = NULL;
}
if (first && second && (lysc_data_parent(first->schema) != lysc_data_parent(second->schema))) {
LOGERR(ctx, LY_EINVAL, "Invalid arguments - cannot create diff for unrelated data (%s()).", __func__);
return LY_EINVAL;
}
*diff = NULL;
return lyd_diff_siblings_r(first, second, options, nosiblings, diff);
}
LIBYANG_API_DEF LY_ERR
lyd_diff_tree(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, struct lyd_node **diff)
{
return lyd_diff(first, second, options, 1, diff);
}
LIBYANG_API_DEF LY_ERR
lyd_diff_siblings(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, struct lyd_node **diff)
{
return lyd_diff(first, second, options, 0, diff);
}
static LY_ERR
lyd_diff_insert(struct lyd_node **first_node, struct lyd_node *parent_node, struct lyd_node *new_node,
const char *userord_anchor)
{
LY_ERR ret;
struct lyd_node *anchor;
uint32_t pos, anchor_pos;
int found;
assert(new_node);
if (!*first_node) {
if (!parent_node) {
*first_node = new_node;
return LY_SUCCESS;
}
if (userord_anchor) {
LOGERR(LYD_CTX(new_node), LY_EINVAL, "Node \"%s\" instance to insert next to not found.",
new_node->schema->name);
return LY_EINVAL;
}
return lyd_insert_child(parent_node, new_node);
}
assert(!(*first_node)->parent || (lyd_parent(*first_node) == parent_node));
if (!lysc_is_userordered(new_node->schema)) {
return lyd_insert_sibling(*first_node, new_node, first_node);
}
if (userord_anchor) {
if (lysc_is_dup_inst_list(new_node->schema)) {
anchor_pos = atoi(userord_anchor);
if (!anchor_pos) {
LOGERR(LYD_CTX(new_node), LY_EINVAL, "Invalid user-ordered anchor value \"%s\".", userord_anchor);
return LY_EINVAL;
}
found = 0;
pos = 1;
LYD_LIST_FOR_INST(*first_node, new_node->schema, anchor) {
if (pos == anchor_pos) {
found = 1;
break;
}
++pos;
}
if (!found) {
LOGERR(LYD_CTX(new_node), LY_EINVAL, "Node \"%s\" instance to insert next to not found.",
new_node->schema->name);
return LY_EINVAL;
}
} else {
ret = lyd_find_sibling_val(*first_node, new_node->schema, userord_anchor, 0, &anchor);
if (ret == LY_ENOTFOUND) {
LOGERR(LYD_CTX(new_node), LY_EINVAL, "Node \"%s\" instance to insert next to not found.",
new_node->schema->name);
return LY_EINVAL;
} else if (ret) {
return ret;
}
}
LY_CHECK_RET(lyd_insert_after(anchor, new_node));
assert(new_node->prev == anchor);
if (*first_node == new_node) {
*first_node = anchor;
}
} else {
ret = lyd_find_sibling_val(*first_node, new_node->schema, NULL, 0, &anchor);
LY_CHECK_RET(ret && (ret != LY_ENOTFOUND), ret);
if (anchor) {
LY_CHECK_RET(lyd_insert_before(anchor, new_node));
if ((*first_node)->prev->next) {
assert(!new_node->prev->next);
*first_node = new_node;
}
} else {
LY_CHECK_RET(lyd_insert_sibling(*first_node, new_node, first_node));
}
}
return LY_SUCCESS;
}
static LY_ERR
lyd_diff_apply_r(struct lyd_node **first_node, struct lyd_node *parent_node, const struct lyd_node *diff_node,
lyd_diff_cb diff_cb, void *cb_data, struct ly_ht **dup_inst)
{
LY_ERR ret;
struct lyd_node *match, *diff_child;
const char *str_val, *meta_str;
enum lyd_diff_op op;
struct lyd_meta *meta;
struct ly_ht *child_dup_inst = NULL;
const struct ly_ctx *ctx = LYD_CTX(diff_node);
LY_CHECK_RET(lyd_diff_get_op(diff_node, &op));
if (lysc_is_userordered(diff_node->schema) && ((op == LYD_DIFF_OP_CREATE) || (op == LYD_DIFF_OP_REPLACE))) {
if (op == LYD_DIFF_OP_REPLACE) {
LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match));
LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL);
} else {
LY_CHECK_RET(lyd_dup_single(diff_node, NULL, LYD_DUP_NO_META, &match));
}
if (lysc_is_dup_inst_list(diff_node->schema)) {
meta_str = "yang:position";
} else if (diff_node->schema->nodetype == LYS_LIST) {
meta_str = "yang:key";
} else {
meta_str = "yang:value";
}
meta = lyd_find_meta(diff_node->meta, NULL, meta_str);
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_str, diff_node), LY_EINVAL);
str_val = lyd_get_meta_value(meta);
if (str_val[0]) {
ret = lyd_diff_insert(first_node, parent_node, match, str_val);
} else {
ret = lyd_diff_insert(first_node, parent_node, match, NULL);
}
if (ret) {
if (op == LYD_DIFF_OP_CREATE) {
lyd_free_tree(match);
}
return ret;
}
goto next_iter_r;
}
switch (op) {
case LYD_DIFF_OP_NONE:
LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match));
LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL);
if (match->schema->nodetype & LYD_NODE_TERM) {
if (diff_node->flags & LYD_DEFAULT) {
match->flags |= LYD_DEFAULT;
} else {
match->flags &= ~LYD_DEFAULT;
}
} else {
if (!lyd_child_no_keys(diff_node)) {
LOGERR(ctx, LY_EINVAL, "Operation \"none\" is invalid for node \"%s\" without children.",
LYD_NAME(diff_node));
return LY_EINVAL;
}
}
break;
case LYD_DIFF_OP_CREATE:
LY_CHECK_RET(lyd_dup_single(diff_node, NULL, LYD_DUP_NO_META, &match));
ret = 0;
if (parent_node) {
if (match->flags & LYD_EXT) {
ret = lyplg_ext_insert(parent_node, match);
} else {
ret = lyd_insert_child(parent_node, match);
}
} else {
ret = lyd_insert_sibling(*first_node, match, first_node);
}
if (ret) {
lyd_free_tree(match);
return ret;
}
break;
case LYD_DIFF_OP_DELETE:
LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match));
LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL);
if ((match == *first_node) && !match->parent) {
assert(!parent_node);
*first_node = (*first_node)->next;
}
lyd_free_tree(match);
return LY_SUCCESS;
case LYD_DIFF_OP_REPLACE:
if (!(diff_node->schema->nodetype & (LYS_LEAF | LYS_ANYDATA))) {
LOGERR(ctx, LY_EINVAL, "Operation \"replace\" is invalid for %s node \"%s\".",
lys_nodetype2str(diff_node->schema->nodetype), LYD_NAME(diff_node));
return LY_EINVAL;
}
LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match));
LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL);
if (diff_node->schema->nodetype == LYS_LEAF) {
ret = lyd_change_term(match, lyd_get_value(diff_node));
LY_CHECK_ERR_RET(ret && (ret != LY_EEXIST), LOGERR_UNEXPVAL(ctx, match, "data"), LY_EINVAL);
} else {
struct lyd_node_any *any = (struct lyd_node_any *)diff_node;
LY_CHECK_RET(lyd_any_copy_value(match, &any->value, any->value_type));
}
match->flags = diff_node->flags;
break;
default:
LOGINT_RET(ctx);
}
next_iter_r:
if (diff_cb) {
LY_CHECK_RET(diff_cb(diff_node, match, cb_data));
}
ret = LY_SUCCESS;
LY_LIST_FOR(lyd_child_no_keys(diff_node), diff_child) {
ret = lyd_diff_apply_r(lyd_node_child_p(match), match, diff_child, diff_cb, cb_data, &child_dup_inst);
if (ret) {
break;
}
}
lyd_dup_inst_free(child_dup_inst);
return ret;
}
LIBYANG_API_DEF LY_ERR
lyd_diff_apply_module(struct lyd_node **data, const struct lyd_node *diff, const struct lys_module *mod,
lyd_diff_cb diff_cb, void *cb_data)
{
const struct lyd_node *root;
struct ly_ht *dup_inst = NULL;
LY_ERR ret = LY_SUCCESS;
LY_LIST_FOR(diff, root) {
if (mod && (lyd_owner_module(root) != mod)) {
continue;
}
ret = lyd_diff_apply_r(data, NULL, root, diff_cb, cb_data, &dup_inst);
if (ret) {
break;
}
}
lyd_dup_inst_free(dup_inst);
return ret;
}
LIBYANG_API_DEF LY_ERR
lyd_diff_apply_all(struct lyd_node **data, const struct lyd_node *diff)
{
return lyd_diff_apply_module(data, diff, NULL, NULL, NULL);
}
static LY_ERR
lyd_diff_merge_none(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff)
{
switch (cur_op) {
case LYD_DIFF_OP_NONE:
case LYD_DIFF_OP_CREATE:
case LYD_DIFF_OP_REPLACE:
if (src_diff->schema->nodetype & LYD_NODE_TERM) {
diff_match->flags &= ~LYD_DEFAULT;
diff_match->flags |= src_diff->flags & LYD_DEFAULT;
}
break;
default:
LOGERR_MERGEOP(LYD_CTX(diff_match), diff_match, cur_op, LYD_DIFF_OP_NONE);
return LY_EINVAL;
}
return LY_SUCCESS;
}
static LY_ERR
lyd_diff_change_op(struct lyd_node *node, enum lyd_diff_op op)
{
lyd_diff_del_meta(node, "operation");
if (node->schema) {
return lyd_new_meta(LYD_CTX(node), node, NULL, "yang:operation", lyd_diff_op2str(op), LYD_NEW_VAL_STORE_ONLY, NULL);
} else {
return lyd_new_attr(node, "yang", "operation", lyd_diff_op2str(op), NULL);
}
}
static LY_ERR
lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff)
{
LY_ERR ret;
const char *str_val, *meta_name, *orig_meta_name;
struct lyd_meta *meta;
const struct lys_module *mod;
const struct lyd_node_any *any;
const struct ly_ctx *ctx = LYD_CTX(diff_match);
mod = ly_ctx_get_module_latest(LYD_CTX(diff_match), "yang");
assert(mod);
switch (cur_op) {
case LYD_DIFF_OP_REPLACE:
case LYD_DIFF_OP_CREATE:
switch (diff_match->schema->nodetype) {
case LYS_LIST:
case LYS_LEAFLIST:
assert(lysc_is_userordered(diff_match->schema));
if (lysc_is_dup_inst_list(diff_match->schema)) {
meta_name = "position";
} else if (diff_match->schema->nodetype == LYS_LIST) {
meta_name = "key";
} else {
meta_name = "value";
}
lyd_diff_del_meta(diff_match, meta_name);
meta = lyd_find_meta(src_diff->meta, mod, meta_name);
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL);
LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL));
break;
case LYS_LEAF:
if (!lyd_compare_single(diff_match, src_diff, 0)) {
LOGERR_UNEXPVAL(ctx, diff_match, "target diff");
return LY_EINVAL;
}
if (lyd_change_term(diff_match, lyd_get_value(src_diff))) {
LOGINT_RET(LYD_CTX(src_diff));
}
if (cur_op == LYD_DIFF_OP_REPLACE) {
meta = lyd_find_meta(diff_match->meta, mod, "orig-value");
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, "orig-value", diff_match), LY_EINVAL);
str_val = lyd_get_meta_value(meta);
ret = lyd_value_compare((struct lyd_node_term *)diff_match, str_val, strlen(str_val));
if (!ret) {
lyd_free_meta_single(meta);
LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE));
}
}
diff_match->flags &= ~LYD_DEFAULT;
diff_match->flags |= src_diff->flags & LYD_DEFAULT;
break;
case LYS_ANYXML:
case LYS_ANYDATA:
if (!lyd_compare_single(diff_match, src_diff, 0)) {
LOGERR_UNEXPVAL(ctx, diff_match, "target diff");
return LY_EINVAL;
}
any = (struct lyd_node_any *)src_diff;
LY_CHECK_RET(lyd_any_copy_value(diff_match, &any->value, any->value_type));
break;
default:
LOGINT_RET(LYD_CTX(src_diff));
}
break;
case LYD_DIFF_OP_NONE:
switch (diff_match->schema->nodetype) {
case LYS_LIST:
assert(lysc_is_userordered(diff_match->schema));
LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE));
if (lysc_is_dup_inst_list(diff_match->schema)) {
meta_name = "position";
orig_meta_name = "orig-position";
} else {
meta_name = "key";
orig_meta_name = "orig-key";
}
meta = lyd_find_meta(src_diff->meta, mod, orig_meta_name);
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, orig_meta_name, src_diff), LY_EINVAL);
LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL));
meta = lyd_find_meta(src_diff->meta, mod, meta_name);
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL);
LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL));
break;
case LYS_LEAF:
LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE));
if (lyd_change_term(diff_match, lyd_get_value(src_diff))) {
LOGINT_RET(LYD_CTX(src_diff));
}
break;
default:
LOGINT_RET(LYD_CTX(src_diff));
}
break;
default:
LOGERR_MERGEOP(ctx, diff_match, cur_op, LYD_DIFF_OP_REPLACE);
return LY_EINVAL;
}
return LY_SUCCESS;
}
static LY_ERR
lyd_diff_merge_create(struct lyd_node **diff_match, struct lyd_node **diff, enum lyd_diff_op cur_op,
const struct lyd_node *src_diff, uint16_t options)
{
struct lyd_node *child, *src_dup, *to_free = NULL;
const struct lysc_node_leaf *sleaf = NULL;
uint32_t trg_flags;
const char *meta_name, *orig_meta_name;
struct lyd_meta *meta, *orig_meta;
const struct ly_ctx *ctx = LYD_CTX(*diff_match);
LY_ERR r;
LY_CHECK_ERR_RET(!src_diff->schema, LOGINT(ctx), LY_EINT);
switch (cur_op) {
case LYD_DIFF_OP_DELETE:
trg_flags = (*diff_match)->flags;
if (lysc_is_userordered(src_diff->schema)) {
assert((*diff_match)->schema);
if (lysc_is_dup_inst_list((*diff_match)->schema)) {
meta_name = "yang:position";
orig_meta_name = "yang:orig-position";
} else if ((*diff_match)->schema->nodetype == LYS_LIST) {
meta_name = "yang:key";
orig_meta_name = "yang:orig-key";
} else {
meta_name = "yang:value";
orig_meta_name = "yang:orig-value";
}
meta = lyd_find_meta(src_diff->meta, NULL, meta_name);
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL);
orig_meta = lyd_find_meta((*diff_match)->meta, NULL, orig_meta_name);
LY_CHECK_ERR_RET(!orig_meta, LOGERR_META(ctx, orig_meta_name, *diff_match), LY_EINVAL);
if (strcmp(lyd_get_meta_value(meta), lyd_get_meta_value(orig_meta))) {
LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_REPLACE));
LY_CHECK_RET(lyd_dup_meta_single(meta, *diff_match, NULL));
} else {
LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE));
lyd_free_meta_single(orig_meta);
}
} else if (src_diff->schema->nodetype == LYS_LEAF) {
if (options & LYD_DIFF_MERGE_DEFAULTS) {
sleaf = (struct lysc_node_leaf *)src_diff->schema;
}
if (sleaf && sleaf->dflt && !sleaf->dflt->realtype->plugin->compare(ctx, sleaf->dflt,
&((struct lyd_node_term *)src_diff)->value)) {
LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE));
} else if (!lyd_compare_single(*diff_match, src_diff, 0)) {
LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE));
} else if ((*diff_match)->schema) {
LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_REPLACE));
LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), *diff_match, NULL, "yang:orig-value",
lyd_get_value(*diff_match), LYD_NEW_VAL_STORE_ONLY, NULL));
LY_CHECK_RET(lyd_change_term(*diff_match, lyd_get_value(src_diff)));
} else {
LY_CHECK_RET(lyd_dup_single(src_diff, (*diff_match)->parent, LYD_DUP_NO_META | LYD_DUP_WITH_FLAGS, &src_dup));
if (!(*diff_match)->parent) {
LY_CHECK_RET(lyd_insert_sibling(*diff_match, src_dup, diff));
}
to_free = *diff_match;
*diff_match = src_dup;
r = lyd_new_meta(ctx, src_dup, NULL, "yang:orig-value", lyd_get_value(to_free), LYD_NEW_VAL_STORE_ONLY, NULL);
lyd_free_tree(to_free);
LY_CHECK_RET(r);
LY_CHECK_RET(lyd_new_meta(ctx, src_dup, NULL, "yang:operation", lyd_diff_op2str(LYD_DIFF_OP_REPLACE), LYD_NEW_VAL_STORE_ONLY, NULL));
}
} else {
LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE));
}
assert((*diff_match)->schema);
if ((*diff_match)->schema->nodetype & LYD_NODE_TERM) {
LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), *diff_match, NULL, "yang:orig-default",
trg_flags & LYD_DEFAULT ? "true" : "false", LYD_NEW_VAL_STORE_ONLY, NULL));
(*diff_match)->flags &= ~LYD_DEFAULT;
(*diff_match)->flags |= src_diff->flags & LYD_DEFAULT;
}
LY_LIST_FOR(lyd_child_no_keys(*diff_match), child) {
LY_CHECK_RET(lyd_diff_change_op(child, LYD_DIFF_OP_DELETE));
}
break;
default:
LOGERR_MERGEOP(LYD_CTX(src_diff), *diff_match, cur_op, LYD_DIFF_OP_CREATE);
return LY_EINVAL;
}
return LY_SUCCESS;
}
static LY_ERR
lyd_diff_merge_delete(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff)
{
struct lyd_node *child;
struct lyd_meta *meta;
struct lyd_attr *attr;
const char *meta_name;
const struct ly_ctx *ctx = LYD_CTX(diff_match);
LY_ERR r;
LY_CHECK_ERR_RET(lyd_compare_single(diff_match, src_diff, 0), LOGINT(LYD_CTX(src_diff)), LY_EINT);
switch (cur_op) {
case LYD_DIFF_OP_CREATE:
LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE));
if (diff_match->schema->nodetype & LYD_NODE_TERM) {
LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), diff_match, NULL, "yang:orig-default",
src_diff->flags & LYD_DEFAULT ? "true" : "false", LYD_NEW_VAL_STORE_ONLY, NULL));
}
break;
case LYD_DIFF_OP_REPLACE:
if (lysc_is_userordered(diff_match->schema)) {
if (lysc_is_dup_inst_list(diff_match->schema)) {
meta_name = "position";
} else if (diff_match->schema->nodetype == LYS_LIST) {
meta_name = "key";
} else {
meta_name = "value";
}
} else {
assert(diff_match->schema->nodetype == LYS_LEAF);
meta = lyd_find_meta(diff_match->meta, NULL, "yang:orig-value");
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, "yang:orig-value", diff_match), LY_EINVAL);
if (lyd_change_term(diff_match, lyd_get_meta_value(meta))) {
LOGERR_UNEXPVAL(ctx, diff_match, "target diff");
return LY_EINVAL;
}
meta = lyd_find_meta(diff_match->meta, NULL, "yang:orig-default");
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, "yang:orig-default", diff_match), LY_EINVAL);
diff_match->flags &= ~LYD_DEFAULT;
if (meta->value.boolean) {
diff_match->flags |= LYD_DEFAULT;
}
lyd_free_meta_single(meta);
meta_name = "orig-value";
}
lyd_diff_del_meta(diff_match, meta_name);
LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_DELETE));
break;
case LYD_DIFF_OP_NONE:
LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_DELETE));
break;
default:
LOGERR_MERGEOP(LYD_CTX(diff_match), diff_match, cur_op, LYD_DIFF_OP_DELETE);
return LY_EINVAL;
}
if (!lysc_is_dup_inst_list(diff_match->schema)) {
LY_LIST_FOR(lyd_child_no_keys(diff_match), child) {
lyd_diff_find_meta(child, "operation", &meta, &attr);
if (meta || attr) {
continue;
}
if (!child->schema) {
r = lyd_find_sibling_opaq_next(lyd_child(src_diff), LYD_NAME(child), NULL);
} else if (child->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) {
r = lyd_find_sibling_first(lyd_child(src_diff), child, NULL);
} else {
r = lyd_find_sibling_val(lyd_child(src_diff), child->schema, NULL, 0, NULL);
}
if (!r) {
LY_CHECK_RET(lyd_diff_change_op(child, cur_op));
} else if (r != LY_ENOTFOUND) {
return r;
}
}
}
return LY_SUCCESS;
}
static int
lyd_diff_is_redundant(struct lyd_node *diff)
{
enum lyd_diff_op op;
struct lyd_meta *meta, *orig_val_meta = NULL, *val_meta = NULL;
struct lyd_node *child;
const struct lys_module *mod;
const char *str, *orig_meta_name, *meta_name;
assert(diff);
if (lysc_is_dup_inst_list(diff->schema)) {
child = NULL;
} else {
child = lyd_child_no_keys(diff);
}
mod = ly_ctx_get_module_latest(LYD_CTX(diff), "yang");
assert(mod);
LY_CHECK_RET(lyd_diff_get_op(diff, &op), 0);
if ((op == LYD_DIFF_OP_REPLACE) && lysc_is_userordered(diff->schema)) {
if (lysc_is_dup_inst_list(diff->schema)) {
meta_name = "position";
orig_meta_name = "orig-position";
} else if (diff->schema->nodetype == LYS_LIST) {
meta_name = "key";
orig_meta_name = "orig-key";
} else {
meta_name = "value";
orig_meta_name = "orig-value";
}
orig_val_meta = lyd_find_meta(diff->meta, mod, orig_meta_name);
val_meta = lyd_find_meta(diff->meta, mod, meta_name);
assert(orig_val_meta && val_meta);
if (!lyd_compare_meta(orig_val_meta, val_meta)) {
lyd_free_meta_single(orig_val_meta);
lyd_free_meta_single(val_meta);
if (child) {
lyd_diff_change_op(diff, LYD_DIFF_OP_NONE);
return 0;
}
return 1;
}
} else if (op == LYD_DIFF_OP_NONE) {
if (!diff->schema) {
return 1;
}
if (diff->schema->nodetype & LYD_NODE_TERM) {
meta = lyd_find_meta(diff->meta, mod, "orig-default");
assert(meta);
str = lyd_get_meta_value(meta);
if ((!strcmp(str, "true") && (diff->flags & LYD_DEFAULT)) || (!strcmp(str, "false") && !(diff->flags & LYD_DEFAULT))) {
return 1;
}
return 0;
}
}
if (!child && (op == LYD_DIFF_OP_NONE)) {
return 1;
}
return 0;
}
static LY_ERR
lyd_diff_merge_r(const struct lyd_node *src_diff, struct lyd_node *diff_parent, lyd_diff_cb diff_cb, void *cb_data,
struct ly_ht **dup_inst, uint16_t options, struct lyd_node **diff)
{
LY_ERR ret = LY_SUCCESS;
struct lyd_node *child, *diff_node = NULL;
enum lyd_diff_op src_op, cur_op;
struct ly_ht *child_dup_inst = NULL;
LY_CHECK_RET(lyd_diff_get_op(src_diff, &src_op));
LY_CHECK_RET(lyd_diff_find_match(diff_parent ? lyd_child_no_keys(diff_parent) : *diff, src_diff, 1, dup_inst, &diff_node));
if (diff_node) {
LY_CHECK_RET(lyd_diff_get_op(diff_node, &cur_op));
switch (src_op) {
case LYD_DIFF_OP_REPLACE:
ret = lyd_diff_merge_replace(diff_node, cur_op, src_diff);
break;
case LYD_DIFF_OP_CREATE:
if ((cur_op == LYD_DIFF_OP_CREATE) && lysc_is_dup_inst_list(diff_node->schema)) {
goto add_diff;
}
ret = lyd_diff_merge_create(&diff_node, diff, cur_op, src_diff, options);
break;
case LYD_DIFF_OP_DELETE:
ret = lyd_diff_merge_delete(diff_node, cur_op, src_diff);
break;
case LYD_DIFF_OP_NONE:
assert((src_diff->schema->nodetype != LYS_LIST) || !lysc_is_dup_inst_list(src_diff->schema));
ret = lyd_diff_merge_none(diff_node, cur_op, src_diff);
break;
default:
LOGINT_RET(LYD_CTX(src_diff));
}
if (ret) {
LOGERR(LYD_CTX(src_diff), LY_EOTHER, "Merging operation \"%s\" failed.", lyd_diff_op2str(src_op));
return ret;
}
if (diff_cb) {
LY_CHECK_RET(diff_cb(src_diff, diff_node, cb_data));
}
diff_parent = diff_node;
if (!lysc_is_dup_inst_list(src_diff->schema)) {
LY_LIST_FOR(lyd_child_no_keys(src_diff), child) {
ret = lyd_diff_merge_r(child, diff_parent, diff_cb, cb_data, &child_dup_inst, options, diff);
if (ret) {
break;
}
}
lyd_dup_inst_free(child_dup_inst);
LY_CHECK_RET(ret);
}
} else {
add_diff:
if ((src_diff->flags & LYD_EXT) && diff_parent) {
LY_CHECK_RET(lyd_dup_single_to_ctx(src_diff, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS | LYD_DUP_NO_LYDS, &diff_node));
} else {
LY_CHECK_RET(lyd_dup_single(src_diff, (struct lyd_node_inner *)diff_parent,
LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS | LYD_DUP_NO_LYDS, &diff_node));
}
if (!diff_parent) {
lyd_diff_insert_sibling(*diff, diff_node, diff);
}
LY_CHECK_RET(lyd_diff_change_op(diff_node, src_op));
if (diff_cb) {
LY_CHECK_RET(diff_cb(NULL, diff_node, cb_data));
}
diff_parent = diff_node;
}
if (lyd_diff_is_redundant(diff_parent)) {
if (diff_parent == *diff) {
*diff = (*diff)->next;
}
lyd_free_tree(diff_parent);
}
return LY_SUCCESS;
}
LIBYANG_API_DEF LY_ERR
lyd_diff_merge_module(struct lyd_node **diff, const struct lyd_node *src_diff, const struct lys_module *mod,
lyd_diff_cb diff_cb, void *cb_data, uint16_t options)
{
const struct lyd_node *src_root;
struct ly_ht *dup_inst = NULL;
LY_ERR ret = LY_SUCCESS;
LY_LIST_FOR(src_diff, src_root) {
if (mod && (lyd_owner_module(src_root) != mod)) {
continue;
}
LY_CHECK_GOTO(ret = lyd_diff_merge_r(src_root, NULL, diff_cb, cb_data, &dup_inst, options, diff), cleanup);
}
cleanup:
lyd_dup_inst_free(dup_inst);
return ret;
}
LIBYANG_API_DEF LY_ERR
lyd_diff_merge_tree(struct lyd_node **diff_first, struct lyd_node *diff_parent, const struct lyd_node *src_sibling,
lyd_diff_cb diff_cb, void *cb_data, uint16_t options)
{
LY_ERR ret;
struct ly_ht *dup_inst = NULL;
if (!src_sibling) {
return LY_SUCCESS;
}
ret = lyd_diff_merge_r(src_sibling, diff_parent, diff_cb, cb_data, &dup_inst, options, diff_first);
lyd_dup_inst_free(dup_inst);
return ret;
}
LIBYANG_API_DEF LY_ERR
lyd_diff_merge_all(struct lyd_node **diff, const struct lyd_node *src_diff, uint16_t options)
{
return lyd_diff_merge_module(diff, src_diff, NULL, NULL, NULL, options);
}
static LY_ERR
lyd_diff_reverse_value(struct lyd_node *node, const struct lys_module *mod)
{
LY_ERR ret = LY_SUCCESS;
struct lyd_meta *meta;
const char *val1 = NULL;
char *val2;
uint32_t flags;
assert(node->schema->nodetype & (LYS_LEAF | LYS_ANYDATA));
meta = lyd_find_meta(node->meta, mod, "orig-value");
LY_CHECK_ERR_RET(!meta, LOGERR_META(LYD_CTX(node), "orig-value", node), LY_EINVAL);
val1 = lyd_get_meta_value(meta);
if (node->schema->nodetype == LYS_LEAF) {
val2 = strdup(lyd_get_value(node));
} else {
LY_CHECK_RET(lyd_any_value_str(node, &val2));
}
flags = node->flags;
if (node->schema->nodetype == LYS_LEAF) {
LY_CHECK_GOTO(ret = lyd_change_term(node, val1), cleanup);
} else {
union lyd_any_value anyval = {.str = val1};
LY_CHECK_GOTO(ret = lyd_any_copy_value(node, &anyval, LYD_ANYDATA_STRING), cleanup);
}
node->flags = flags;
LY_CHECK_GOTO(ret = lyd_change_meta(meta, val2), cleanup);
cleanup:
free(val2);
return ret;
}
static LY_ERR
lyd_diff_reverse_default(struct lyd_node *node, const struct lys_module *mod)
{
struct lyd_meta *meta;
uint32_t flag1, flag2;
meta = lyd_find_meta(node->meta, mod, "orig-default");
LY_CHECK_ERR_RET(!meta, LOGINT(mod->ctx), LY_EINT);
if (meta->value.boolean) {
flag1 = LYD_DEFAULT;
} else {
flag1 = 0;
}
flag2 = node->flags & LYD_DEFAULT;
if (flag1 == flag2) {
return LY_SUCCESS;
}
node->flags &= ~LYD_DEFAULT;
node->flags |= flag1;
LY_CHECK_RET(lyd_change_meta(meta, flag2 ? "true" : "false"));
return LY_SUCCESS;
}
static LY_ERR
lyd_diff_reverse_meta(struct lyd_node *node, const struct lys_module *mod, const char *name1, const char *name2)
{
LY_ERR ret = LY_SUCCESS;
struct lyd_meta *meta1, *meta2;
const char *val1 = NULL;
char *val2 = NULL;
meta1 = lyd_find_meta(node->meta, mod, name1);
LY_CHECK_ERR_RET(!meta1, LOGERR_META(LYD_CTX(node), name1, node), LY_EINVAL);
meta2 = lyd_find_meta(node->meta, mod, name2);
LY_CHECK_ERR_RET(!meta2, LOGERR_META(LYD_CTX(node), name2, node), LY_EINVAL);
val1 = lyd_get_meta_value(meta1);
val2 = strdup(lyd_get_meta_value(meta2));
LY_CHECK_GOTO(ret = lyd_change_meta(meta1, val2), cleanup);
LY_CHECK_GOTO(ret = lyd_change_meta(meta2, val1), cleanup);
cleanup:
free(val2);
return ret;
}
static LY_ERR
lyd_diff_reverse_remove_op_r(struct lyd_node *diff, enum lyd_diff_op op)
{
struct lyd_node *elem;
struct lyd_meta *meta;
LYD_TREE_DFS_BEGIN(diff, elem) {
meta = lyd_find_meta(elem->meta, NULL, "yang:operation");
if (meta) {
LY_CHECK_ERR_RET(lyd_diff_str2op(lyd_get_meta_value(meta)) != op, LOGINT(LYD_CTX(diff)), LY_EINT);
lyd_free_meta_single(meta);
}
LYD_TREE_DFS_END(diff, elem);
}
return LY_SUCCESS;
}
LIBYANG_API_DEF LY_ERR
lyd_diff_reverse_all(const struct lyd_node *src_diff, struct lyd_node **diff)
{
LY_ERR ret = LY_SUCCESS;
const struct lys_module *mod;
struct lyd_node *root, *elem, *iter;
enum lyd_diff_op op;
LY_CHECK_ARG_RET(NULL, diff, LY_EINVAL);
if (!src_diff) {
*diff = NULL;
return LY_SUCCESS;
}
LY_CHECK_RET(lyd_dup_siblings(src_diff, NULL, LYD_DUP_RECURSIVE | LYD_DUP_NO_LYDS, diff));
mod = ly_ctx_get_module_latest(LYD_CTX(src_diff), "yang");
LY_CHECK_ERR_GOTO(!mod, LOGINT(LYD_CTX(src_diff)); ret = LY_EINT, cleanup);
LY_LIST_FOR(*diff, root) {
LYD_TREE_DFS_BEGIN(root, elem) {
if (!lysc_is_key(elem->schema)) {
LY_CHECK_GOTO(ret = lyd_diff_get_op(elem, &op), cleanup);
switch (op) {
case LYD_DIFF_OP_CREATE:
LY_CHECK_GOTO(ret = lyd_diff_change_op(elem, LYD_DIFF_OP_DELETE), cleanup);
LY_LIST_FOR(lyd_child(elem), iter) {
lyd_diff_reverse_remove_op_r(iter, LYD_DIFF_OP_CREATE);
}
LYD_TREE_DFS_continue = 1;
break;
case LYD_DIFF_OP_DELETE:
LY_CHECK_GOTO(ret = lyd_diff_change_op(elem, LYD_DIFF_OP_CREATE), cleanup);
LY_LIST_FOR(lyd_child(elem), iter) {
lyd_diff_reverse_remove_op_r(iter, LYD_DIFF_OP_DELETE);
}
LYD_TREE_DFS_continue = 1;
break;
case LYD_DIFF_OP_REPLACE:
switch (elem->schema->nodetype) {
case LYS_LEAF:
LY_CHECK_GOTO(ret = lyd_diff_reverse_value(elem, mod), cleanup);
LY_CHECK_GOTO(ret = lyd_diff_reverse_default(elem, mod), cleanup);
break;
case LYS_ANYXML:
case LYS_ANYDATA:
LY_CHECK_GOTO(ret = lyd_diff_reverse_value(elem, mod), cleanup);
break;
case LYS_LEAFLIST:
LY_CHECK_GOTO(ret = lyd_diff_reverse_default(elem, mod), cleanup);
if (lysc_is_dup_inst_list(elem->schema)) {
LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-position", "position"), cleanup);
} else {
LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-value", "value"), cleanup);
}
break;
case LYS_LIST:
if (lysc_is_dup_inst_list(elem->schema)) {
LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-position", "position"), cleanup);
} else {
LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-key", "key"), cleanup);
}
break;
default:
LOGINT(LYD_CTX(src_diff));
ret = LY_EINT;
goto cleanup;
}
break;
case LYD_DIFF_OP_NONE:
switch (elem->schema->nodetype) {
case LYS_LEAF:
case LYS_LEAFLIST:
LY_CHECK_GOTO(ret = lyd_diff_reverse_default(elem, mod), cleanup);
break;
default:
break;
}
break;
}
}
LYD_TREE_DFS_END(root, elem);
}
}
cleanup:
if (ret) {
lyd_free_siblings(*diff);
*diff = NULL;
}
return ret;
}