context FETAURE yang-library data generation

Also added list of deriving modules for each module.
diff --git a/src/context.c b/src/context.c
index a908e7f..1b0a7c0 100644
--- a/src/context.c
+++ b/src/context.c
@@ -11,6 +11,7 @@
  *
  *     https://opensource.org/licenses/BSD-3-Clause
  */
+#define _GNU_SOUCRE /* asprintf */
 
 #include "common.h"
 
@@ -533,6 +534,253 @@
     }
 }
 
+static LY_ERR
+ylib_feature(struct lyd_node *parent, const struct lys_module *cur_mod)
+{
+    LY_ARRAY_SIZE_TYPE i;
+    struct lyd_node *node;
+
+    if (!cur_mod->implemented) {
+        /* no features can be enabled */
+        return LY_SUCCESS;
+    }
+
+    LY_ARRAY_FOR(cur_mod->compiled->features, i) {
+        if (!(cur_mod->compiled->features[i].flags & LYS_FENABLED)) {
+            continue;
+        }
+
+        node = lyd_new_term(parent, NULL, "feature", cur_mod->compiled->features[i].name);
+        LY_CHECK_RET(!node, LY_EOTHER);
+    }
+
+    return LY_SUCCESS;
+}
+
+static LY_ERR
+ylib_deviation(struct lyd_node *parent, const struct lys_module *cur_mod, int bis)
+{
+    LY_ARRAY_SIZE_TYPE i;
+    struct lyd_node *node;
+    struct lys_module *mod;
+
+    if (!cur_mod->implemented) {
+        /* no deviations of the module for certain */
+        return LY_SUCCESS;
+    }
+
+    LY_ARRAY_FOR(cur_mod->compiled->deviated_by, i) {
+        mod = cur_mod->compiled->deviated_by[i];
+
+        if (bis) {
+            node = lyd_new_term(parent, NULL, "deviation", mod->name);
+            LY_CHECK_RET(!node, LY_EOTHER);
+        } else {
+            node = lyd_new_list(parent, NULL, "deviation", mod->name, (mod->parsed->revs ? mod->parsed->revs[0].date : ""));
+            LY_CHECK_RET(!node, LY_EOTHER);
+        }
+    }
+
+    return LY_SUCCESS;
+}
+
+static LY_ERR
+ylib_submodules(struct lyd_node *parent, const struct lys_module *cur_mod, int bis)
+{
+    LY_ARRAY_SIZE_TYPE i;
+    struct lyd_node *node, *cont;
+    struct lysp_submodule *submod;
+    int ret;
+    char *str;
+
+    LY_ARRAY_FOR(cur_mod->parsed->includes, i) {
+        submod = cur_mod->parsed->includes[i].submodule;
+
+        if (bis) {
+            cont = lyd_new_list(parent, NULL, "submodule", submod->name);
+            LY_CHECK_RET(!cont, LY_EOTHER);
+
+            if (submod->revs) {
+                node = lyd_new_term(cont, NULL, "revision", submod->revs[0].date);
+                LY_CHECK_RET(!node, LY_EOTHER);
+            }
+        } else {
+            cont = lyd_new_list(parent, NULL, "submodule", submod->name, (submod->revs ? submod->revs[0].date : ""));
+            LY_CHECK_RET(!cont, LY_EOTHER);
+        }
+
+        if (submod->filepath) {
+            ret = asprintf(&str, "file://%s", submod->filepath);
+            LY_CHECK_ERR_RET(ret == -1, LOGMEM(cur_mod->ctx), LY_EMEM);
+
+            node = lyd_new_term(cont, NULL, bis ? "location" : "schema", str);
+            LY_CHECK_RET(!node, LY_EOTHER);
+            free(str);
+        }
+    }
+
+    return LY_SUCCESS;
+}
+
+API uint16_t
+ly_ctx_get_yanglib_id(const struct ly_ctx *ctx)
+{
+    return ctx->module_set_id;
+}
+
+API struct lyd_node *
+ly_ctx_get_yanglib_data(const struct ly_ctx *ctx)
+{
+    uint32_t i;
+    int bis = 0, ret;
+    char id[8], *str;
+    const struct lys_module *mod;
+    struct lyd_node *root = NULL, *root_bis = NULL, *cont, *set_bis, *node;
+
+    LY_CHECK_ARG_RET(ctx, ctx, NULL);
+
+    mod = ly_ctx_get_module_implemented(ctx, "ietf-yang-library");
+    LY_CHECK_ERR_RET(!mod, LOGERR(ctx, LY_EINVAL, "Module \"ietf-yang-library\" is not implemented."), NULL);
+
+    if (mod->parsed->revs && !strcmp(mod->parsed->revs[0].date, "2016-06-21")) {
+        bis = 0;
+    } else if (mod->parsed->revs && !strcmp(mod->parsed->revs[0].date, IETF_YANG_LIB_REV)) {
+        bis = 1;
+    } else {
+        LOGERR(ctx, LY_EINVAL, "Incompatible ietf-yang-library version in context.");
+        return NULL;
+    }
+
+    root = lyd_new_inner(NULL, mod, "modules-state");
+    LY_CHECK_GOTO(!root, error);
+
+    if (bis) {
+        root_bis = lyd_new_inner(NULL, mod, "yang-library");
+        LY_CHECK_GOTO(!root_bis, error);
+
+        set_bis = lyd_new_list(root_bis, NULL, "module-set", "complete");
+        LY_CHECK_GOTO(!set_bis, error);
+    }
+
+    for (i = 0; i < ctx->list.count; ++i) {
+        mod = ctx->list.objs[i];
+
+        /*
+         * deprecated legacy
+         */
+        cont = lyd_new_list(root, NULL, "module", mod->name, (mod->parsed->revs ? mod->parsed->revs[0].date : ""));
+        LY_CHECK_GOTO(!cont, error);
+
+        /* schema */
+        if (mod->filepath) {
+            ret = asprintf(&str, "file://%s", mod->filepath);
+            LY_CHECK_ERR_GOTO(ret == -1, LOGMEM(ctx), error);
+
+            node = lyd_new_term(cont, NULL, "schema", str);
+            free(str);
+            LY_CHECK_GOTO(!node, error);
+        }
+
+        /* namespace */
+        node = lyd_new_term(cont, NULL, "namespace", mod->ns);
+        LY_CHECK_GOTO(!node, error);
+
+        /* feature leaf-list */
+        LY_CHECK_GOTO(ylib_feature(cont, mod), error);
+
+        /* deviation list */
+        LY_CHECK_GOTO(ylib_deviation(cont, mod, 0), error);
+
+        /* conformance-type */
+        node = lyd_new_term(cont, NULL, "conformance-type", (mod->implemented ? "implement" : "import"));
+        LY_CHECK_GOTO(!node, error);
+
+        /* submodule list */
+        LY_CHECK_GOTO(ylib_submodules(cont, mod, 0), error);
+
+        /*
+         * current revision
+         */
+        if (bis) {
+            /* name and revision */
+            if (mod->implemented) {
+                cont = lyd_new_list(set_bis, NULL, "module", mod->name);
+                LY_CHECK_GOTO(!cont, error);
+
+                if (mod->parsed->revs) {
+                    node = lyd_new_term(cont, NULL, "revision", mod->parsed->revs[0].date);
+                    LY_CHECK_GOTO(!node, error);
+                }
+            } else {
+                cont = lyd_new_list(set_bis, NULL, "import-only-module", mod->name,
+                                    (mod->parsed->revs ? mod->parsed->revs[0].date : ""));
+                LY_CHECK_GOTO(!cont, error);
+            }
+
+            /* namespace */
+            node = lyd_new_term(cont, NULL, "namespace", mod->ns);
+            LY_CHECK_GOTO(!node, error);
+
+            /* location */
+            if (mod->filepath) {
+                ret = asprintf(&str, "file://%s", mod->filepath);
+                LY_CHECK_ERR_GOTO(ret == -1, LOGMEM(ctx), error);
+
+                node = lyd_new_term(cont, NULL, "schema", str);
+                free(str);
+                LY_CHECK_GOTO(!node, error);
+            }
+
+            /* submodule list */
+            LY_CHECK_GOTO(ylib_submodules(cont, mod, 1), error);
+
+            /* feature list */
+            LY_CHECK_GOTO(ylib_feature(cont, mod), error);
+
+            /* deviation */
+            LY_CHECK_GOTO(ylib_deviation(cont, mod, 1), error);
+        }
+    }
+
+    /* IDs */
+    sprintf(id, "%u", ctx->module_set_id);
+    node = lyd_new_term(root, NULL, "module-set-id", id);
+    LY_CHECK_GOTO(!node, error);
+
+    if (bis) {
+        /* create one complete schema */
+        cont = lyd_new_list(root_bis, NULL, "schema", "complete");
+        LY_CHECK_GOTO(!cont, error);
+
+        node = lyd_new_term(cont, NULL, "module-set", "complete");
+        LY_CHECK_GOTO(!node, error);
+
+        /* content-id */
+        node = lyd_new_term(root_bis, NULL, "content-id", id);
+        LY_CHECK_GOTO(!node, error);
+    }
+
+    if (root_bis) {
+        if (lyd_insert_sibling(root_bis, root)) {
+            goto error;
+        }
+        root = root_bis;
+        root_bis = 0;
+    }
+
+    /* TODO uncomment once lefref validation works
+    if (lyd_validate(&root, NULL, LYD_VALOPT_DATA_ONLY)) {
+        goto error;
+    }*/
+
+    return root;
+
+error:
+    lyd_free_all(root);
+    lyd_free_all(root_bis);
+    return NULL;
+}
+
 API void
 ly_ctx_destroy(struct ly_ctx *ctx, void (*private_destructor)(const struct lysc_node *node, void *priv))
 {
diff --git a/src/context.h b/src/context.h
index 53634cd..a1b771b 100644
--- a/src/context.h
+++ b/src/context.h
@@ -431,6 +431,25 @@
 const struct lys_module *ly_ctx_load_module(struct ly_ctx *ctx, const char *name, const char *revision);
 
 /**
+ * @brief Get current ID of the modules set. The value is available also
+ * as module-set-id in ::ly_ctx_get_yanglib_data() result.
+ *
+ * @param[in] ctx Context to be examined.
+ * @return Numeric identifier of the current context's modules set.
+ */
+uint16_t ly_ctx_get_yanglib_id(const struct ly_ctx *ctx);
+
+/**
+ * @brief Get data of the internal ietf-yang-library module with information about all the loaded modules.
+ * ietf-yang-library module must be loaded.
+ *
+ * @param[in] ctx Context with the modules.
+ * @return Generated data, must be freed,
+ * @return NULL on error.
+ */
+struct lyd_node *ly_ctx_get_yanglib_data(const struct ly_ctx *ctx);
+
+/**
  * @brief Free all internal structures of the specified context.
  *
  * The function should be used before terminating the application to destroy
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 9917b5d..3742a06 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -1652,6 +1652,7 @@
     struct lysc_action *rpcs;        /**< list of RPCs ([sized array](@ref sizedarrays)) */
     struct lysc_notif *notifs;       /**< list of notifications ([sized array](@ref sizedarrays)) */
     struct lysc_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
+    struct lys_module **deviated_by; /**< List of modules that deviate this module ([sized array](@ref sizedarrays)) */
 };
 
 /**
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index a49a5de..2f0f278 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -5869,7 +5869,7 @@
     int i, changed_type;
     size_t prefix_len, name_len;
     const char *prefix, *name, *nodeid, *dflt;
-    struct lys_module *mod;
+    struct lys_module *mod, **dev_mod;
     uint32_t min, max;
     uint16_t flags;
 
@@ -6783,6 +6783,10 @@
             goto cleanup;
         }
 
+        /* add this module into the target module deviated_by */
+        LY_ARRAY_NEW_GOTO(ctx->ctx, devs[u]->target->module->compiled->deviated_by, dev_mod, ret, cleanup);
+        *dev_mod = mod_p->mod;
+
         lysc_update_path(ctx, NULL, NULL);
     }
 
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index c6406bf..957082c 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -844,6 +844,7 @@
     FREE_ARRAY(ctx, module->rpcs, lysc_action_free);
     FREE_ARRAY(ctx, module->notifs, lysc_notif_free);
     FREE_ARRAY(ctx, module->exts, lysc_ext_instance_free);
+    LY_ARRAY_FREE(module->deviated_by);
 
     free(module);
 }
diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt
index ca5c5b5..c236413 100644
--- a/tests/utests/CMakeLists.txt
+++ b/tests/utests/CMakeLists.txt
@@ -5,6 +5,7 @@
     utest:test_context
     utest:test_xml
     utest:test_xpath
+    utest:test_yanglib
     utest:schema/test_schema
     utest:schema/test_parser_yang
     utest:schema/test_parser_yin
@@ -37,6 +38,7 @@
     " "
     " "
     " "
+    " "
     " ")
 set(tests ${tests} ${local_tests} PARENT_SCOPE)
 set(tests_wraps ${tests_wraps} ${local_tests_wraps} PARENT_SCOPE)
diff --git a/tests/utests/test_yanglib.c b/tests/utests/test_yanglib.c
new file mode 100644
index 0000000..eb35cff
--- /dev/null
+++ b/tests/utests/test_yanglib.c
@@ -0,0 +1,214 @@
+/**
+ * @file test_yanglib.c
+ * @author: Michal Vasko <mvasko@cesnet.cz>
+ * @brief unit tests for ietf-yang-library data
+ *
+ * Copyright (c) 2020 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 "tests/config.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "../../src/context.h"
+#include "../../src/tree_schema.h"
+
+#define BUFSIZE 1024
+char logbuf[BUFSIZE] = {0};
+int store = -1; /* negative for infinite logging, positive for limited logging */
+
+struct ly_ctx *ctx; /* context for tests */
+
+/* set to 0 to printing error messages to stderr instead of checking them in code */
+#define ENABLE_LOGGER_CHECKING 1
+
+#if ENABLE_LOGGER_CHECKING
+static void
+logger(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+    (void) level; /* unused */
+    if (store) {
+        if (path && path[0]) {
+            snprintf(logbuf, BUFSIZE - 1, "%s %s", msg, path);
+        } else {
+            strncpy(logbuf, msg, BUFSIZE - 1);
+        }
+        if (store > 0) {
+            --store;
+        }
+    }
+}
+#endif
+
+static LY_ERR
+test_imp_clb(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev, void *user_data,
+             LYS_INFORMAT *format, const char **module_data, void (**free_module_data)(void *model_data, void *user_data))
+{
+    const char *schema_a_sub =
+    "submodule a_sub {"
+        "belongs-to a {"
+            "prefix a;"
+        "}"
+        "yang-version 1.1;"
+
+        "feature feat1;"
+
+        "list l3 {"
+            "key \"a\";"
+            "leaf a {"
+                "type uint16;"
+            "}"
+            "leaf b {"
+                "type uint16;"
+            "}"
+        "}"
+    "}";
+
+    assert_string_equal(mod_name, "a");
+    assert_null(mod_rev);
+    if (!submod_name) {
+        return LY_ENOTFOUND;
+    }
+    assert_string_equal(submod_name, "a_sub");
+    assert_null(sub_rev);
+    assert_null(user_data);
+
+    *format = LYS_IN_YANG;
+    *module_data = schema_a_sub;
+    *free_module_data = NULL;
+    return LY_SUCCESS;
+}
+
+static int
+setup(void **state)
+{
+    (void) state; /* unused */
+
+    const char *schema_a =
+    "module a {"
+        "namespace urn:tests:a;"
+        "prefix a;"
+        "yang-version 1.1;"
+
+        "include a_sub;"
+
+        "list l2 {"
+            "key \"a\";"
+            "leaf a {"
+                "type uint16;"
+            "}"
+            "leaf b {"
+                "type uint16;"
+            "}"
+        "}"
+    "}";
+    const char *schema_b =
+    "module b {"
+        "namespace urn:tests:b;"
+        "prefix b;"
+        "yang-version 1.1;"
+
+        "import a {"
+            "prefix a;"
+        "}"
+
+        "deviation /a:l2 {"
+            "deviate add {"
+                "max-elements 40;"
+            "}"
+        "}"
+
+        "leaf foo {"
+            "type string;"
+        "}"
+    "}";
+    const struct lys_module *mod;
+
+#if ENABLE_LOGGER_CHECKING
+    ly_set_log_clb(logger, 1);
+#endif
+
+    assert_int_equal(LY_SUCCESS, ly_ctx_new(TESTS_DIR_MODULES_YANG, 0, &ctx));
+    ly_ctx_set_module_imp_clb(ctx, test_imp_clb, NULL);
+
+    mod = lys_parse_mem(ctx, schema_a, LYS_IN_YANG);
+    assert_non_null(mod);
+    assert_int_equal(LY_SUCCESS, lys_feature_enable(mod, "feat1"));
+    assert_non_null(lys_parse_mem(ctx, schema_b, LYS_IN_YANG));
+
+    return 0;
+}
+
+static int
+teardown(void **state)
+{
+#if ENABLE_LOGGER_CHECKING
+    if (*state) {
+        fprintf(stderr, "%s\n", logbuf);
+    }
+#else
+    (void) state; /* unused */
+#endif
+
+    ly_ctx_destroy(ctx, NULL);
+    ctx = NULL;
+
+    return 0;
+}
+
+void
+logbuf_clean(void)
+{
+    logbuf[0] = '\0';
+}
+
+#if ENABLE_LOGGER_CHECKING
+#   define logbuf_assert(str) assert_string_equal(logbuf, str)
+#else
+#   define logbuf_assert(str)
+#endif
+
+static void
+test_yanglib(void **state)
+{
+    *state = test_yanglib;
+
+    struct lyd_node *tree;
+    struct ly_set *set;
+    LY_ERR ret;
+
+    tree = ly_ctx_get_yanglib_data(ctx);
+    assert_non_null(tree);
+
+    /* make sure there is "a" with a submodule and deviation */
+    ret = lyd_find_xpath(tree, "/ietf-yang-library:yang-library/module-set/module[name='a'][submodule/name='a_sub']"
+                         "[feature='feat1'][deviation='b']", &set);
+    assert_int_equal(ret, LY_SUCCESS);
+
+    assert_int_equal(set->count, 1);
+    ly_set_free(set, NULL);
+
+    lyd_free_all(tree);
+    *state = NULL;
+}
+
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_yanglib, setup, teardown),
+    };
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}