yanglint FEATURE schema mount support (#1901)

* modify existing context if provided

In ly_ctx_new_yldata(), check if *ctx is NULL.  If so, proceed as before
with allocating a new context.  Otherwise, load modules into the existing
context.

Signed-off-by: Eric Kinzie <ekinzie@labn.net>

* add parent-reference xpath helper to schema-mount plugin

This new function produces a list of schema nodes from the expanded
parent-reference xpath expressions.

Signed-off-by: Eric Kinzie <ekinzie@labn.net>

* add context lookup to schema-mount plugin

This function allocates a new context for a particular instance of the
yangmnt:mount-point extension.

Signed-off-by: Eric Kinzie <ekinzie@labn.net>

* yanglint: schema-mount extension callback

Add a commandline argument "-x" that accepts a file containing extension
data, used to create a context for the schema-mount extension.  This file
has the same format as what is provided to the "-Y" option.  A callback
function for the schema-mount extenion is registered if the new option
is specified.

This allows validating instance data for models that include a schema
mount point.

Signed-off-by: Eric Kinzie <ekinzie@labn.net>

* yanglint: print mounted schema trees

Display "mp" flag for mount point and print mounted tree.  Also display
the / and @ opts in node names.

Signed-off-by: Eric Kinzie <ekinzie@labn.net>

* add unit test for tree mount-point flag

Test is courtesy of aPiecek <piecek@cesnet.cz>.

Signed-off-by: Eric Kinzie <ekinzie@labn.net>

* add example data files for validation with schema-mount

From tools/lint/examples directory:

% ../../../build/yanglint \
    -f json -t config -p ../../../models -p . \
    -Y ./sm-context-main.xml -x ./sm-context-extension.xml sm-data.xml

% ../../../build/yanglint \
    -f tree -p ../../../models -p . \
    -Y ./sm-context-main.xml -x ./sm-context-extension.xml sm-main.yang

Signed-off-by: Eric Kinzie <ekinzie@labn.net>

Signed-off-by: Eric Kinzie <ekinzie@labn.net>
Co-authored-by: Eric Kinzie <ekinzie@labn.net>
diff --git a/src/plugins_exts/schema_mount.c b/src/plugins_exts/schema_mount.c
index 7aed9a9..8661894 100644
--- a/src/plugins_exts/schema_mount.c
+++ b/src/plugins_exts/schema_mount.c
@@ -351,7 +351,7 @@
     struct lyplg_ext_sm *sm_data = ext->data;
     LY_ERR ret = LY_SUCCESS, r;
     struct lyd_node *node = NULL;
-    struct ly_ctx *new_ctx;
+    struct ly_ctx *new_ctx = NULL;
     uint32_t i;
     const char *content_id = NULL;
     void *mem;
@@ -442,7 +442,7 @@
 {
     struct lyplg_ext_sm *sm_data = ext->data;
     LY_ERR r;
-    struct ly_ctx *new_ctx;
+    struct ly_ctx *new_ctx = NULL;
     uint32_t i;
     void *mem;
 
@@ -538,7 +538,7 @@
 {
     LY_ERR r;
     const struct lys_module *mod;
-    const struct ly_ctx *ext_ctx;
+    const struct ly_ctx *ext_ctx = NULL;
 
     /* get context based on ietf-yang-library data */
     if ((r = schema_mount_get_ctx(ext, &ext_ctx))) {
@@ -556,6 +556,27 @@
     return *snode ? LY_SUCCESS : LY_ENOT;
 }
 
+static LY_ERR
+schema_mount_get_parent_ref(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data,
+        struct ly_set **set)
+{
+    LY_ERR ret = LY_SUCCESS;
+    char *path = NULL;
+
+    /* get all parent references of this mount point */
+    if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']"
+            "/shared-schema/parent-reference", ext->module->name, ext->argument) == -1) {
+        EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
+    }
+    if ((ret = lyd_find_xpath(ext_data, path, set))) {
+        goto cleanup;
+    }
+
+cleanup:
+    free(path);
+    return ret;
+}
+
 /**
  * @brief Duplicate all accessible parent references for a shared-schema mount point.
  *
@@ -587,12 +608,7 @@
         goto cleanup;
     }
 
-    /* get all parent references of this mount point */
-    if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']"
-            "/shared-schema/parent-reference", ext->module->name, ext->argument) == -1) {
-        EXT_LOGERR_MEM_GOTO(ext, ret, cleanup);
-    }
-    if ((ret = lyd_find_xpath(ext_data, path, &set))) {
+    if ((ret = schema_mount_get_parent_ref(ext, ext_data, &set))) {
         goto cleanup;
     }
 
@@ -671,6 +687,61 @@
     return ret;
 }
 
+LY_ERR
+lyplg_ext_schema_mount_get_parent_ref(const struct lysc_ext_instance *ext, struct ly_set **refs)
+{
+    LY_ERR res;
+    struct ly_set *pref_set = NULL;
+    struct ly_set *snode_set;
+    struct ly_set *results_set = NULL;
+    struct lyd_node *ext_data;
+    ly_bool ext_data_free;
+
+    /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
+    if ((res = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
+        return res;
+    }
+
+    LY_CHECK_GOTO(res = schema_mount_get_parent_ref(ext, ext_data, &pref_set), out);
+    if (pref_set->count == 0) {
+        goto out;
+    }
+
+    LY_CHECK_GOTO(res = ly_set_new(&results_set), out);
+
+    for (uint32_t i = 0; i < pref_set->count; ++i) {
+        struct lyd_node_term *term;
+        struct lyd_value_xpath10 *xp_val;
+        char *value;
+        struct ly_err_item *err;
+
+        term = (struct lyd_node_term *)pref_set->dnodes[i];
+        LYD_VALUE_GET(&term->value, xp_val);
+        LY_CHECK_GOTO(res = lyplg_type_print_xpath10_value(xp_val, LY_VALUE_JSON, NULL, &value, &err), out);
+        LY_CHECK_ERR_GOTO(res = lys_find_xpath(ext->module->ctx, NULL, value, 0, &snode_set), free(value), out);
+        free(value);
+        for (uint32_t sn = 0; sn < snode_set->count; sn++) {
+            struct lysc_node *snode = snode_set->snodes[sn];
+
+            if ((res = ly_set_add(results_set, snode, 0, NULL))) {
+                ly_set_free(snode_set, NULL);
+                ly_set_free(results_set, NULL);
+                goto out;
+            }
+        }
+        ly_set_free(snode_set, NULL);
+    }
+
+    *refs = results_set;
+
+out:
+    if (ext_data_free) {
+        lyd_free_all(ext_data);
+    }
+    ly_set_free(pref_set, NULL);
+    return res;
+}
+
 /**
  * @brief Validate callback for schema mount.
  */
@@ -836,6 +907,43 @@
     free(sm_data);
 }
 
+LIBYANG_API_DEF LY_ERR
+lyplg_ext_schema_mount_create_context(const struct lysc_ext_instance *ext, struct ly_ctx **ctx)
+{
+    struct lyd_node *ext_data;
+    ly_bool ext_data_free;
+    ly_bool config;
+    ly_bool shared;
+    LY_ERR res;
+
+    if (!ext->module->ctx->ext_clb) {
+        return LY_EINVAL;
+    }
+
+    if (strcmp(ext->def->module->name, "ietf-yang-schema-mount") ||
+            strcmp(ext->def->name, "mount-point")) {
+        return LY_EINVAL;
+    }
+
+    /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */
+    if ((res = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) {
+        return res;
+    }
+
+    /* learn about this mount point */
+    if ((res = schema_mount_get_smount(ext, ext_data, &config, &shared))) {
+        goto out;
+    }
+
+    res = schema_mount_create_ctx(ext, ext_data, config, ctx);
+
+out:
+    if (ext_data_free) {
+        lyd_free_all(ext_data);
+    }
+    return res;
+}
+
 /**
  * @brief Plugin descriptions for the Yang Schema Mount extension.
  *