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/context.c b/src/context.c
index 3a3c10b..98ae10f 100644
--- a/src/context.c
+++ b/src/context.c
@@ -454,7 +454,11 @@
     LY_CHECK_ARG_RET(NULL, tree, ctx, LY_EINVAL);
 
     /* create a new context */
-    LY_CHECK_GOTO(ret = ly_ctx_new(search_dir, options, &ctx_new), cleanup);
+    if (*ctx == NULL) {
+        LY_CHECK_GOTO(ret = ly_ctx_new(search_dir, options, &ctx_new), cleanup);
+    } else {
+        ctx_new = *ctx;
+    }
 
     /* redundant to compile modules one-by-one */
     if (!(options & LY_CTX_EXPLICIT_COMPILE)) {
@@ -519,10 +523,12 @@
 cleanup:
     ly_set_free(set, NULL);
     ly_set_erase(&features, NULL);
-    *ctx = ctx_new;
-    if (ret) {
-        ly_ctx_destroy(*ctx);
-        *ctx = NULL;
+    if (*ctx == NULL) {
+        *ctx = ctx_new;
+        if (ret) {
+            ly_ctx_destroy(*ctx);
+            *ctx = NULL;
+        }
     }
     return ret;
 }
diff --git a/src/context.h b/src/context.h
index 71cc590..331a89f 100644
--- a/src/context.h
+++ b/src/context.h
@@ -241,7 +241,8 @@
  * @param[in] path Path to the file containing yang-library-data in the specified format
  * @param[in] format Format of the data in the provided file.
  * @param[in] options Context options, see @ref contextoptions.
- * @param[out] ctx Pointer to the created libyang context if LY_SUCCESS returned.
+ * @param[in,out] ctx If *ctx is not NULL, the existing libyang context is modified.  Otherwise, a pointer to a
+ * newly created context is returned here if LY_SUCCESS.
  * @return LY_ERR return value
  */
 LIBYANG_API_DECL LY_ERR ly_ctx_new_ylpath(const char *search_dir, const char *path, LYD_FORMAT format, int options,
@@ -257,7 +258,8 @@
  * @param[in] data String containing yang-library data in the specified format.
  * @param[in] format Format of the data in the provided file.
  * @param[in] options Context options, see @ref contextoptions.
- * @param[out] ctx Pointer to the created libyang context if LY_SUCCESS returned.
+ * @param[in,out] ctx If *ctx is not NULL, the existing libyang context is modified.  Otherwise, a pointer to a
+ * newly created context is returned here if LY_SUCCESS.
  * @return LY_ERR return value
  */
 LIBYANG_API_DECL LY_ERR ly_ctx_new_ylmem(const char *search_dir, const char *data, LYD_FORMAT format, int options,
@@ -272,7 +274,8 @@
  * If no such directory is available, NULL is accepted.
  * @param[in] tree Data tree containing yang-library data.
  * @param[in] options Context options, see @ref contextoptions.
- * @param[out] ctx Pointer to the created libyang context if LY_SUCCESS returned.
+ * @param[in,out] ctx If *ctx is not NULL, the existing libyang context is modified.  Otherwise, a pointer to a
+ * newly created context is returned here if LY_SUCCESS.
  * @return LY_ERR return value
  */
 LIBYANG_API_DECL LY_ERR ly_ctx_new_yldata(const char *search_dir, const struct lyd_node *tree, int options,
diff --git a/src/plugins_exts.h b/src/plugins_exts.h
index c473158..298557c 100644
--- a/src/plugins_exts.h
+++ b/src/plugins_exts.h
@@ -281,6 +281,27 @@
 LIBYANG_API_DECL void lyplg_ext_log(const struct lysc_ext_instance *ext, LY_LOG_LEVEL level, LY_ERR err_no, const char *path,
         const char *format, ...);
 
+/**
+ * @brief Expand parent-reference xpath expressions
+ *
+ * @param ext[in] context allocated for extension
+ * @param refs[out] set of lysc nodes matching parent-refernce xpaths
+ * @return LY_ERR value.
+ */
+LIBYANG_API_DECL LY_ERR lyplg_ext_schema_mount_get_parent_ref(const struct lysc_ext_instance *ext, struct ly_set **refs);
+
+/**
+ * @brief Allocate a new context for a particular instance of the
+ * yangmnt:mount-point extension.  Caller is responsible for destroying
+ * the resulting context.
+ *
+ * @param[in] ext Compiled extension instance.
+ * @param[out] ctx A context with modules loaded from the list found in
+ * the extension data.
+ * @return LY_ERR value.
+ */
+LIBYANG_API_DECL LY_ERR lyplg_ext_schema_mount_create_context(const struct lysc_ext_instance *ext, struct ly_ctx **ctx);
+
 /** @} pluginsExtensions */
 
 #ifdef __cplusplus
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.
  *
diff --git a/src/printer_tree.c b/src/printer_tree.c
index 963dc4c..d0d1024 100644
--- a/src/printer_tree.c
+++ b/src/printer_tree.c
@@ -93,6 +93,8 @@
 #include "common.h"
 #include "compat.h"
 #include "out_internal.h"
+#include "plugins_exts.h"
+#include "plugins_types.h"
 #include "printer_schema.h"
 #include "tree_schema_internal.h"
 #include "xpath.h"
@@ -453,6 +455,8 @@
     ly_bool iffeatures;         /**< \<if-features\>. Value 1 means that iffeatures are present and
                                      will be printed by trt_fp_print.print_features_names callback. */
     ly_bool last_one;           /**< Information about whether the node is the last. */
+    struct lysc_ext_instance
+    *mount;                     /**< Mount-point extension if flags == TRD_FLAGS_TYPE_MOUNT_POINT */
 };
 
 /**
@@ -465,7 +469,8 @@
         .name = TRP_EMPTY_NODE_NAME, \
         .type = TRP_EMPTY_TRT_TYPE, \
         .iffeatures = 0, \
-        .last_one = 1 \
+        .last_one = 1, \
+        .mount = NULL \
     }
 
 /**
@@ -687,6 +692,7 @@
                                                          If it is true then trt_tree_ctx.pn and
                                                          trt_tree_ctx.tpn are not used.
                                                          If it is false then trt_tree_ctx.cn is not used. */
+    ly_bool mounted;                                /**< This tree is a mounted schema */
     trt_actual_section section;                     /**< To which section pn points. */
     const struct lysp_module *pmod;                 /**< Parsed YANG schema tree. */
     const struct lysc_module *cmod;                 /**< Compiled YANG schema tree. */
@@ -698,6 +704,8 @@
                                                          is set to TRD_SECT_YANG_DATA. */
     };
     const struct lysc_node *cn;                     /**< Actual pointer to compiled node. */
+    const struct ly_set *parent_refs;               /**< List of schema nodes for top-level nodes found in mount
+                                                         point parent references */
 };
 
 /**
@@ -718,6 +726,15 @@
 #define TRP_TREE_CTX_GET_LYSP_NODE(CN) \
     ((const struct lysp_node *)CN->priv)
 
+/**
+ * @brief Context for mounted module
+ *
+ */
+struct trt_mount_ctx {
+    struct trt_printer_ctx pc;
+    struct trt_tree_ctx tc;
+};
+
 /** Getter function for ::trop_node_charptr(). */
 typedef const char *(*trt_get_charptr_func)(const struct lysp_node *pn);
 
@@ -742,6 +759,12 @@
 };
 
 /**********************************************************************
+ * Forward declarations
+ *********************************************************************/
+static LY_ERR trb_print_mount_point(struct trt_node *node, struct trt_wrapper wr,
+        struct trt_printer_ctx *pc, struct trt_tree_ctx *tc);
+
+/**********************************************************************
  * Definition of the general Trg functions
  *********************************************************************/
 
@@ -905,6 +928,20 @@
 }
 
 /**
+ * @brief Set '|' symbol to connect current level nodes in a module.
+ * This is only used to connect all top-level nodes in all modules under
+ * a schema mount point.
+ * @param[in] wr is the wrapper to be marked
+ * @return New wrapper which is marked at actual position.
+ */
+static struct trt_wrapper
+trp_wrapper_set_mark_top(struct trt_wrapper wr)
+{
+    wr.bit_marks1 |= 1U << wr.actual_pos;
+    return wr;
+}
+
+/**
  * @brief Setting ' ' symbol if node is last sibling otherwise set '|'.
  * @param[in] wr is actual wrapper.
  * @param[in] last_one is flag. Value 1 saying if the node is the last
@@ -2601,6 +2638,49 @@
 }
 
 /**
+ * @brief Check if container has yangmt:mount-point extension.
+ * @param[in] cn is pointer to container or list.
+ * @param[out] mount is assigned a pointer to the extension instance, if found
+ * @return 1 if container has mount-point.
+ */
+static ly_bool
+troc_node_has_mount(const struct lysc_node *cn, struct lysc_ext_instance **mount)
+{
+    struct lysc_ext_instance *ext;
+    uint64_t u;
+
+    /* The schema-mount extension plugin has already made sure that
+     * there is only one mount-point here.
+     */
+    LY_ARRAY_FOR(cn->exts, u) {
+        ext = &cn->exts[u];
+        if (strcmp(ext->def->module->name, "ietf-yang-schema-mount") ||
+                strcmp(ext->def->name, "mount-point")) {
+            continue;
+        }
+        *mount = ext;
+        return 1;
+    }
+    return 0;
+}
+
+static ly_bool
+trop_node_has_mount(const struct lysp_node *pn)
+{
+    struct lysp_ext_instance *ext;
+    uint64_t u;
+
+    LY_ARRAY_FOR(pn->exts, u) {
+        ext = &pn->exts[u];
+        if (strcmp(ext->name, "yangmnt:mount-point")) {
+            continue;
+        }
+        return 1;
+    }
+    return 0;
+}
+
+/**
  * @brief Get leaflist's path without lysp_node type control.
  * @param[in] pn is pointer to the leaflist.
  */
@@ -2739,8 +2819,10 @@
  * Obtained from the trt_parent_cache.
  */
 static trt_node_type
-trop_resolve_node_type(const struct lysp_node *pn, const struct lysp_node_list *ca_last_list)
+trop_resolve_node_type(const struct trt_tree_ctx *tc, const struct lysp_node_list *ca_last_list)
 {
+    const struct lysp_node *pn = tc->pn;
+
     if (pn->nodetype & (LYS_INPUT | LYS_OUTPUT)) {
         return TRD_NODE_ELSE;
     } else if (pn->nodetype & LYS_CASE) {
@@ -2749,6 +2831,15 @@
         return TRD_NODE_OPTIONAL_CHOICE;
     } else if (pn->nodetype & LYS_CHOICE) {
         return TRD_NODE_CHOICE;
+    } else if (tc->mounted && (tc->pn->parent == NULL)) {
+        if (tc->parent_refs) {
+            for (uint32_t v = 0; v < tc->parent_refs->count; v++) {
+                if (!strcmp(tc->pmod->mod->ns, tc->parent_refs->snodes[v]->module->ns)) {
+                    return TRD_NODE_TOP_LEVEL2;
+                }
+            }
+        }
+        return TRD_NODE_TOP_LEVEL1;
     } else if ((pn->nodetype & LYS_CONTAINER) && (trop_container_has_presence(pn))) {
         return TRD_NODE_CONTAINER;
     } else if ((pn->nodetype & LYS_LIST) && (trop_list_has_keys(pn))) {
@@ -2810,14 +2901,15 @@
     /* <status> */
     ret.status = trop_resolve_status(pn->nodetype, pn->flags, ca.lys_status);
 
-    /* TODO: TRD_FLAGS_TYPE_MOUNT_POINT aka "mp" is not supported right now. */
     /* <flags> */
-    ret.flags = trop_resolve_flags(pn->nodetype, pn->flags, ca.ancestor, ca.lys_config);
+    if (trop_node_has_mount(pn)) {
+        ret.flags = TRD_FLAGS_TYPE_MOUNT_POINT;
+    } else {
+        ret.flags = trop_resolve_flags(pn->nodetype, pn->flags, ca.ancestor, ca.lys_config);
+    }
 
-    /* TODO: TRD_NODE_TOP_LEVEL1 aka '/' is not supported right now. */
-    /* TODO: TRD_NODE_TOP_LEVEL2 aka '@' is not supported right now. */
     /* set type of the node */
-    ret.name.type = trop_resolve_node_type(pn, ca.last_list);
+    ret.name.type = trop_resolve_node_type(tc, ca.last_list);
 
     /* The parsed tree is not compiled, so no node can be augmented
      * from another module. This means that nodes from the parsed tree
@@ -3128,7 +3220,7 @@
  * @param[in] flags is current lysc_node.flags.
  */
 static trt_node_type
-troc_resolve_node_type(uint16_t nodetype, uint16_t flags)
+troc_resolve_node_type(const struct trt_tree_ctx *tc, uint16_t nodetype, uint16_t flags)
 {
     if (nodetype & (LYS_INPUT | LYS_OUTPUT)) {
         return TRD_NODE_ELSE;
@@ -3138,6 +3230,15 @@
         return TRD_NODE_OPTIONAL_CHOICE;
     } else if (nodetype & LYS_CHOICE) {
         return TRD_NODE_CHOICE;
+    } else if (tc->mounted && (tc->cn->parent == NULL)) {
+        if (tc->parent_refs) {
+            for (uint32_t v = 0; v < tc->parent_refs->count; v++) {
+                if (!strcmp(tc->cn->module->ns, tc->parent_refs->snodes[v]->module->ns)) {
+                    return TRD_NODE_TOP_LEVEL2;
+                }
+            }
+        }
+        return TRD_NODE_TOP_LEVEL1;
     } else if ((nodetype & LYS_CONTAINER) && (flags & LYS_PRESENCE)) {
         return TRD_NODE_CONTAINER;
     } else if ((nodetype & LYS_LIST) && !(flags & LYS_KEYLESS)) {
@@ -3196,14 +3297,15 @@
     /* <status> */
     ret.status = tro_flags2status(cn->flags);
 
-    /* TODO: TRD_FLAGS_TYPE_MOUNT_POINT aka "mp" is not supported right now. */
     /* <flags> */
-    ret.flags = troc_resolve_flags(cn->nodetype, cn->flags);
+    if (troc_node_has_mount(cn, &ret.mount)) {
+        ret.flags = TRD_FLAGS_TYPE_MOUNT_POINT;
+    } else {
+        ret.flags = troc_resolve_flags(cn->nodetype, cn->flags);
+    }
 
-    /* TODO: TRD_NODE_TOP_LEVEL1 aka '/' is not supported right now. */
-    /* TODO: TRD_NODE_TOP_LEVEL2 aka '@' is not supported right now. */
     /* set type of the node */
-    ret.name.type = troc_resolve_node_type(cn->nodetype, cn->flags);
+    ret.name.type = troc_resolve_node_type(tc, cn->nodetype, cn->flags);
 
     /* <prefix> */
     ret.name.module_prefix = troc_resolve_node_prefix(cn, tc->cmod);
@@ -3402,6 +3504,23 @@
     /* after -> print actual node with default indent */
     trp_print_entire_node(node, TRP_INIT_PCK_PRINT(tc, pc->fp.print),
             TRP_INIT_PCK_INDENT(wr, ind), pc->max_line_length, pc->out);
+    if (node.flags == TRD_FLAGS_TYPE_MOUNT_POINT) {
+        struct trt_wrapper wr_mount;
+        struct tro_getters get;
+
+        wr_mount = pc->fp.read.if_sibling_exists(tc) ?
+                trp_wrapper_set_mark(wr) : trp_wrapper_set_shift(wr);
+
+        get = tc->lysc_tree ? troc_init_getters() : trop_init_getters();
+        if (get.child(tc->lysc_tree ? (void *)tc->cn : (void *)tc->pn)) {
+            /* If this node has a child, we need to draw a vertical line
+             * from the last mounted module to the first child
+             */
+            wr_mount = trp_wrapper_set_mark_top(wr_mount);
+        }
+
+        trb_print_mount_point(&node, wr_mount, pc, tc);
+    }
 }
 
 /**
@@ -3623,13 +3742,14 @@
 
 /**
  * @brief Calculate the wrapper about how deep in the tree the node is.
+ * @param[in] wr_in A wrapper to use as a starting point
  * @param[in] node from which to count.
  * @return wrapper for @p node.
  */
 static struct trt_wrapper
-trb_count_depth(const struct lysc_node *node)
+trb_count_depth(const struct trt_wrapper *wr_in, const struct lysc_node *node)
 {
-    struct trt_wrapper wr = TRP_INIT_WRAPPER_TOP;
+    struct trt_wrapper wr = wr_in ? *wr_in : TRP_INIT_WRAPPER_TOP;
     const struct lysc_node *parent;
 
     if (!node) {
@@ -3654,7 +3774,7 @@
  * @return wrapper for @p node.
  */
 static void
-trb_print_parents(const struct lysc_node *node, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc)
+trb_print_parents(const struct lysc_node *node, struct trt_wrapper *wr_in, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc)
 {
     struct trt_wrapper wr;
     struct trt_node print_node;
@@ -3665,11 +3785,11 @@
     if (!node) {
         return;
     }
-    trb_print_parents(node->parent, pc, tc);
+    trb_print_parents(node->parent, wr_in, pc, tc);
 
     /* setup for printing */
     tc->cn = node;
-    wr = trb_count_depth(node);
+    wr = trb_count_depth(wr_in, node);
 
     /* print node */
     ly_print_(pc->out, "\n");
@@ -3750,6 +3870,7 @@
     struct trt_parent_cache new_ca;
 
     trb_print_entire_node(node, max_gap_before_type, wr, pc, tc);
+
     /* go to the actual node's child */
     new_ca = tro_parent_cache_for_child(ca, tc);
     node = pc->fp.modify.next_child(ca, tc);
@@ -4292,11 +4413,11 @@
     trm_lysc_tree_ctx(node->module, new_out, line_length, &pc, &tc);
 
     trp_print_keyword_stmt(pc.fp.read.module_name(&tc), pc.max_line_length, 0, pc.out);
-    trb_print_parents(node, &pc, &tc);
+    trb_print_parents(node, NULL, &pc, &tc);
 
     if (!(options & LYS_PRINT_NO_SUBSTMT)) {
         tc.cn = lysc_node_child(node);
-        wr = trb_count_depth(tc.cn);
+        wr = trb_count_depth(NULL, tc.cn);
         trb_print_family_tree(wr, &pc, &tc);
     }
     ly_print_(out, "\n");
@@ -4337,3 +4458,101 @@
 
     return erc;
 }
+
+static LY_ERR
+trb_print_mount_point(struct trt_node *node, struct trt_wrapper wr, struct trt_printer_ctx *pc,
+        struct trt_tree_ctx *UNUSED(tc))
+{
+    struct ly_ctx *ext_ctx = NULL;
+    const struct lys_module *mod;
+    struct trt_tree_ctx tmptc;
+    struct trt_wrapper tmpwr;
+    struct trt_printer_ctx tmppc;
+    struct trt_mount_ctx *mc;
+    struct ly_set *modules;
+    struct ly_set *refs = NULL;
+    LY_ERR err = LY_SUCCESS;
+
+    if (!node->mount) {
+        /* modules are parsed only */
+        return LY_SUCCESS;
+    }
+
+    if (lyplg_ext_schema_mount_create_context(node->mount, &ext_ctx)) {
+        /* Void mount point */
+        return LY_SUCCESS;
+    }
+
+    if (ly_set_new(&modules)) {
+        err = LY_SUCCESS;
+        goto out_ctx;
+    }
+
+    lyplg_ext_schema_mount_get_parent_ref(node->mount, &refs);
+
+    /* build new list of modules to print.  This list will omit internal
+     * modules, modules with no nodes (e.g., iana-if-types) and modules
+     * that were loaded as the result of a parent-reference.
+     */
+    uint32_t v = ly_ctx_internal_modules_count(ext_ctx);
+
+    tmppc = *pc;
+    while ((mod = ly_ctx_get_module_iter(ext_ctx, &v))) {
+        uint32_t siblings;
+        ly_bool from_parent_ref = 0;
+
+        for (uint32_t pr = 0; refs && pr < refs->count; pr++) {
+            if (!strcmp(mod->ns, refs->snodes[pr]->module->ns)) {
+                from_parent_ref = 1;
+                break;
+            }
+        }
+        if (from_parent_ref) {
+            continue;
+        }
+
+        if ((ext_ctx->flags & LY_CTX_SET_PRIV_PARSED) && mod->compiled) {
+            trm_lysc_tree_ctx(mod, pc->out, pc->max_line_length, &tmppc, &tmptc);
+        } else {
+            trm_lysp_tree_ctx(mod, pc->out, pc->max_line_length, &tmppc, &tmptc);
+        }
+
+        siblings = (tmptc.pn || tmptc.cn) ?
+                trb_get_number_of_siblings(tmppc.fp.modify, &tmptc) : 0;
+        if (siblings == 0) {
+            continue;
+        }
+        tmptc.mounted = 1;
+        tmptc.parent_refs = refs;
+
+        mc = malloc(sizeof *mc);
+        if (mc == NULL) {
+            err = LY_EMEM;
+            goto out;
+        }
+
+        mc->tc = tmptc;
+        mc->pc = tmppc;
+        ly_set_add(modules, (void *)mc, 1, NULL);
+    }
+
+    for (uint32_t v = 0; v < modules->count; ++v) {
+        mc = modules->objs[v];
+        tmpwr = (v == modules->count - 1) ? wr : trp_wrapper_set_mark_top(wr);
+        trb_print_family_tree(tmpwr, &mc->pc, &mc->tc);
+    }
+
+    for (uint32_t pr = 0; refs && pr < refs->count; pr++) {
+        trm_lysc_tree_ctx(refs->snodes[pr]->module, pc->out, pc->max_line_length, &tmppc, &tmptc);
+        tmptc.mounted = 1;
+        tmptc.parent_refs = refs;
+        trb_print_parents(refs->snodes[pr], &tmpwr, pc, &tmptc);
+    }
+
+out:
+    ly_set_free(modules, free);
+    ly_set_free(refs, NULL);
+out_ctx:
+    ly_ctx_destroy(ext_ctx);
+    return err;
+}