YANG parser CHANGE support for including submodules
diff --git a/src/common.h b/src/common.h
index afd4d0b..fe00f7b 100644
--- a/src/common.h
+++ b/src/common.h
@@ -26,6 +26,7 @@
#include "config.h"
#include "log.h"
+#include "context.h"
#include "tree_schema.h"
#include "set.h"
#include "hash_table.h"
@@ -151,11 +152,26 @@
struct dict_table dict; /**< dictionary to effectively store strings used in the context related structures */
struct ly_set search_paths; /**< set of directories where to search for schema's imports/includes */
struct ly_set list; /**< set of YANG schemas */
+ ly_module_imp_clb imp_clb; /**< Optional callback for retrieving missing included or imported models in a custom way. */
+ void *imp_clb_data; /**< Optional private data for imp_clb() */
uint16_t module_set_id; /**< ID of the current set of schemas */
uint16_t flags; /**< context settings, see @ref contextoptions. */
pthread_key_t errlist_key; /**< key for the thread-specific list of errors related to the context */
};
+/**
+ * @brief Try to find submodule in the context. Submodules are present only in the parsed (lysp_) schema trees, if only
+ * the compiled versions of the schemas are present, the submodule cannot be returned even if it was used to compile
+ * some of the currently present schemas.
+ *
+ * @param[in] ctx Context where to search
+ * @param[in] module Name of the module where the submodule is supposed to belongs-to.
+ * @param[in] submodule Name of the submodule to find.
+ * @param[in] revision Optional revision of the submodule to find. If not specified, the latest revision is returned.
+ * @return Pointer to the specified submodule if it is present in the context.
+ */
+struct lysp_module *ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *module, const char *submodule, const char *revision);
+
/******************************************************************************
* Parsers
*****************************************************************************/
diff --git a/src/context.c b/src/context.c
index 5dcc621..585504c 100644
--- a/src/context.c
+++ b/src/context.c
@@ -261,6 +261,26 @@
return ctx->module_set_id;
}
+API void
+ly_ctx_set_module_imp_clb(struct ly_ctx *ctx, ly_module_imp_clb clb, void *user_data)
+{
+ LY_CHECK_ARG_RET(ctx, ctx,);
+
+ ctx->imp_clb = clb;
+ ctx->imp_clb_data = user_data;
+}
+
+API ly_module_imp_clb
+ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data)
+{
+ LY_CHECK_ARG_RET(ctx, ctx, NULL);
+
+ if (user_data) {
+ *user_data = ctx->imp_clb_data;
+ }
+ return ctx->imp_clb;
+}
+
/**
* @brief Iterate over the modules in the given context. Returned modules must match the given key at the offset of
* lysp_module and lysc_module structures (they are supposed to be placed at the same offset in both structures).
@@ -416,6 +436,35 @@
return ly_ctx_get_module_implemented_by(ctx, ns, offsetof(struct lysp_module, ns));
}
+struct lysp_module *
+ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *module, const char *submodule, const char *revision)
+{
+ const struct lys_module *mod;
+ struct lysp_include *inc;
+ unsigned int index = 0, u;
+
+ while ((mod = ly_ctx_get_module_by_iter(ctx, module, offsetof(struct lysp_module, name), &index))) {
+ if (!mod->parsed) {
+ continue;
+ }
+
+ LY_ARRAY_FOR(mod->parsed->includes, u) {
+ if (!strcmp(submodule, mod->parsed->includes[u].submodule->name)) {
+ inc = &mod->parsed->includes[u];
+ if (!revision) {
+ if (inc->submodule->latest_revision) {
+ return inc->submodule;
+ }
+ } else if (inc->submodule->revs && !strcmp(revision, inc->submodule->revs[0].date)) {
+ return inc->submodule;
+ }
+ }
+ }
+ }
+
+ 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 2333bb3..31493f2 100644
--- a/src/context.h
+++ b/src/context.h
@@ -152,6 +152,50 @@
uint16_t ly_ctx_get_module_set_id(const struct ly_ctx *ctx);
/**
+ * @brief Callback for retrieving missing included or imported models in a custom way.
+ *
+ * When submod_name is provided, the submodule is requested instead of the module (in this case only
+ * the module name without its revision is provided).
+ *
+ * If an @arg free_module_data callback is provided, it will be used later to free the allegedly const data
+ * which were returned by this callback.
+ *
+ * @param[in] mod_name Missing module name.
+ * @param[in] mod_rev Optional missing module revision.
+ * @param[in] submod_name Optional missing submodule name.
+ * @param[in] submod_rev Optional missing submodule revision.
+ * @param[in] user_data User-supplied callback data.
+ * @param[out] format Format of the returned module data.
+ * @param[out] module_data Requested module data.
+ * @param[out] free_module_data Callback for freeing the returned module data. If not set, the data will be left untouched.
+ * @return LY_ERR value. If the returned value differs from LY_SUCCESS, libyang continue in trying to get the module data
+ * according to the settings of its mechanism to search for the imported/included schemas.
+ */
+typedef LY_ERR (*ly_module_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));
+
+/**
+ * @brief Get the custom callback for missing import/include module retrieval.
+ *
+ * @param[in] ctx Context to read from.
+ * @param[in] user_data Optional pointer for getting the user-supplied callback data.
+ * @return Callback or NULL if not set.
+ */
+ly_module_imp_clb ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data);
+
+/**
+ * @brief Set missing include or import module callback. It is meant to be used when the models
+ * are not locally available (such as when downloading modules from a NETCONF server), it should
+ * not be required in other cases.
+ *
+ * @param[in] ctx Context that will use this callback.
+ * @param[in] clb Callback responsible for returning the missing model.
+ * @param[in] user_data Arbitrary data that will always be passed to the callback \p clb.
+ */
+void ly_ctx_set_module_imp_clb(struct ly_ctx *ctx, ly_module_imp_clb clb, void *user_data);
+
+/**
* @brief Get YANG module of the given name and revision.
*
* @param[in] ctx Context to work in.
diff --git a/src/hash_table.c b/src/hash_table.c
index a60c3da..ca93d76 100644
--- a/src/hash_table.c
+++ b/src/hash_table.c
@@ -139,8 +139,6 @@
struct dict_rec rec, *match = NULL;
char *val_p;
- LY_CHECK_ARG_RET(ctx, ctx, value,);
-
if (!value) {
return;
}
diff --git a/src/log.c b/src/log.c
index 57da747..b576437 100644
--- a/src/log.c
+++ b/src/log.c
@@ -34,6 +34,19 @@
/* how many bytes add when enlarging buffers */
#define LY_BUF_STEP 128
+API LY_ERR
+ly_errcode(const struct ly_ctx *ctx)
+{
+ struct ly_err_item *i;
+
+ i = ly_err_first(ctx);
+ if (i) {
+ return i->prev->no;
+ }
+
+ return LY_SUCCESS;
+}
+
API LY_VECODE
ly_vecode(const struct ly_ctx *ctx)
{
diff --git a/src/log.h b/src/log.h
index 95232bc..8613b18 100644
--- a/src/log.h
+++ b/src/log.h
@@ -146,6 +146,7 @@
LY_ESYS, /**< System call failure */
LY_EINVAL, /**< Invalid value */
LY_EEXIST, /**< Item already exists */
+ LY_ENOTFOUND, /**< Item does not exists */
LY_EINT, /**< Internal error */
LY_EVALID, /**< Validation failure */
LY_EPLUGIN, /**< Error reported by a plugin */
@@ -191,6 +192,14 @@
LY_VECODE ly_vecode(const struct ly_ctx *ctx);
/**
+ * @brief Get the last (thread, context-specific) error code.
+ *
+ * @param[in] ctx Relative context.
+ * @return LY_ERR value of the last error code.
+ */
+LY_ERR ly_errcode(const struct ly_ctx *ctx);
+
+/**
* @brief Get the last (thread, context-specific) error message. If the coresponding module defined
* a specific error message, it will be used instead the default one.
*
diff --git a/src/parser_yang.c b/src/parser_yang.c
index ffdcdc8..c0ef806 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -52,8 +52,6 @@
#define MOVE_INPUT(CTX, DATA, COUNT) (*(data))+=COUNT;(CTX)->indent+=COUNT
-#define LOGVAL_YANG(CTX, ...) LOGVAL((CTX)->ctx, LY_VLOG_LINE, &(CTX)->line, __VA_ARGS__)
-
/**
* @brief Loop through all substatements providing, return if there are none.
*
@@ -1291,24 +1289,31 @@
* @return LY_ERR values.
*/
static LY_ERR
-parse_include(struct ly_parser_ctx *ctx, const char **data, struct lysp_include **includes)
+parse_include(struct ly_parser_ctx *ctx, const char **data, struct lysp_module *mod)
{
- LY_ERR ret = 0;
+ LY_ERR ret = LY_SUCCESS;
char *buf, *word;
+ const char *name = NULL;
size_t word_len;
enum yang_keyword kw;
struct lysp_include *inc;
- LY_ARRAY_NEW_RET(ctx->ctx, *includes, inc, LY_EMEM);
+ LY_ARRAY_NEW_RET(ctx->ctx, mod->includes, inc, LY_EMEM);
/* get value */
ret = get_argument(ctx, data, Y_IDENTIF_ARG, &word, &buf, &word_len);
LY_CHECK_RET(ret);
+ INSERT_WORD(ctx, buf, name, word, word_len);
- INSERT_WORD(ctx, buf, inc->name, word, word_len);
- YANG_READ_SUBSTMT_FOR(ctx, data, kw, word, word_len, ret) {
- LY_CHECK_RET(ret);
-
+ ret = get_keyword(ctx, data, &kw, &word, &word_len);
+ LY_CHECK_GOTO(ret, cleanup);
+ LY_CHECK_GOTO(kw == YANG_SEMICOLON, parse_include);
+ LY_CHECK_ERR_GOTO(kw != YANG_LEFT_BRACE,
+ LOGVAL_YANG(ctx, LYVE_SYNTAX_YANG, "Invalid keyword \"%s\", expected \";\" or \"{\".", ly_stmt2str(kw));
+ ret = LY_EVALID, cleanup);
+ for (ret = get_keyword(ctx, data, &kw, &word, &word_len);
+ !ret && (kw != YANG_RIGHT_BRACE);
+ ret = get_keyword(ctx, data, &kw, &word, &word_len)) {
switch (kw) {
case YANG_DESCRIPTION:
ret = parse_text_field(ctx, data, LYEXT_SUBSTMT_DESCRIPTION, 0, &inc->dsc, Y_STR_ARG, &inc->exts);
@@ -1324,12 +1329,18 @@
break;
default:
LOGVAL_YANG(ctx, LY_VCODE_INCHILDSTMT, ly_stmt2str(kw), "include");
+ lydict_remove(ctx->ctx, name);
return LY_EVALID;
}
- LY_CHECK_RET(ret);
+ LY_CHECK_GOTO(ret, cleanup);
}
- LY_CHECK_RET(ret);
+ LY_CHECK_GOTO(ret, cleanup);
+parse_include:
+ ret = lysp_parse_include(ctx, mod, name, inc);
+
+cleanup:
+ lydict_remove(ctx->ctx, name);
return ret;
}
@@ -4512,7 +4523,7 @@
/* linkage */
case YANG_INCLUDE:
- ret = parse_include(ctx, data, &mod->includes);
+ ret = parse_include(ctx, data, mod);
break;
case YANG_IMPORT:
ret = parse_import(ctx, data, mod);
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 29e418e..e058755 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -15,6 +15,7 @@
#include "common.h"
#include <ctype.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/limits.h>
@@ -84,7 +85,9 @@
static void
lysp_include_free(struct ly_ctx *ctx, struct lysp_include *include, int dict)
{
- FREE_STRING(ctx, include->name, dict);
+ if (include->submodule && !(--include->submodule->refcount)) {
+ lysp_module_free(include->submodule);
+ }
FREE_STRING(ctx, include->dsc, dict);
FREE_STRING(ctx, include->ref, dict);
FREE_ARRAY(ctx, include->exts, lysp_ext_instance_free);
@@ -1087,10 +1090,11 @@
}
}
-static const struct lys_module *
+struct lys_module *
lys_parse_mem_(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format, const char *revision, int implement)
{
struct lys_module *mod = NULL, *latest;
+ struct lysp_module *latest_p;
LY_ERR ret;
LY_CHECK_ARG_RET(ctx, ctx, data, NULL);
@@ -1136,40 +1140,38 @@
}
}
- /* check for duplicity in the context */
- if (ly_ctx_get_module(ctx, mod->parsed->name, mod->parsed->revs ? mod->parsed->revs[0].date : NULL)) {
- if (mod->parsed->revs) {
- LOGERR(ctx, LY_EEXIST, "Module \"%s\" of revision \"%s\" is already present in the context.",
- mod->parsed->name, mod->parsed->revs[0].date);
- } else {
- LOGERR(ctx, LY_EEXIST, "Module \"%s\" with no revision is already present in the context.",
- mod->parsed->name);
- }
- lys_module_free(mod, NULL);
- return NULL;
- }
-
- /* decide the latest revision */
- latest = (struct lys_module*)ly_ctx_get_module_latest(ctx, mod->parsed->name);
- if (latest) {
- if (mod->parsed->revs) {
- if ((latest->parsed && !latest->parsed->revs) || (!latest->parsed && !latest->compiled->revs)) {
- /* latest has no revision, so mod is anyway newer */
- mod->parsed->latest_revision = 1;
- lys_latest_unset(latest);
- } else {
- if (strcmp(mod->parsed->revs[0].date, latest->parsed ? latest->parsed->revs[0].date : latest->compiled->revs[0].date) > 0) {
+ if (mod->parsed->submodule) { /* submodule */
+ /* decide the latest revision */
+ latest_p = ly_ctx_get_submodule(ctx, mod->parsed->belongsto, mod->parsed->name, NULL);
+ if (latest_p) {
+ if (mod->parsed->revs) {
+ if (!latest_p->revs) {
+ /* latest has no revision, so mod is anyway newer */
mod->parsed->latest_revision = 1;
- lys_latest_unset(latest);
+ latest_p->latest_revision = 0;
+ } else {
+ if (strcmp(mod->parsed->revs[0].date, latest_p->revs[0].date) > 0) {
+ mod->parsed->latest_revision = 1;
+ latest_p->latest_revision = 0;
+ }
}
}
+ } else {
+ mod->parsed->latest_revision = 1;
}
- } else {
- mod->parsed->latest_revision = 1;
- }
-
- /* add into context */
- ly_set_add(&ctx->list, mod, LY_SET_OPT_USEASLIST);
+ } else { /* module */
+ /* check for duplicity in the context */
+ if (ly_ctx_get_module(ctx, mod->parsed->name, mod->parsed->revs ? mod->parsed->revs[0].date : NULL)) {
+ if (mod->parsed->revs) {
+ LOGERR(ctx, LY_EEXIST, "Module \"%s\" of revision \"%s\" is already present in the context.",
+ mod->parsed->name, mod->parsed->revs[0].date);
+ } else {
+ LOGERR(ctx, LY_EEXIST, "Module \"%s\" with no revision is already present in the context.",
+ mod->parsed->name);
+ }
+ lys_module_free(mod, NULL);
+ return NULL;
+ }
#if 0
/* hack for NETCONF's edit-config's operation attribute. It is not defined in the schema, but since libyang
@@ -1185,13 +1187,46 @@
}
#endif
+ /* decide the latest revision */
+ latest = (struct lys_module*)ly_ctx_get_module_latest(ctx, mod->parsed->name);
+ if (latest) {
+ if (mod->parsed->revs) {
+ if ((latest->parsed && !latest->parsed->revs) || (!latest->parsed && !latest->compiled->revs)) {
+ /* latest has no revision, so mod is anyway newer */
+ mod->parsed->latest_revision = 1;
+ lys_latest_unset(latest);
+ } else {
+ if (strcmp(mod->parsed->revs[0].date, latest->parsed ? latest->parsed->revs[0].date : latest->compiled->revs[0].date) > 0) {
+ mod->parsed->latest_revision = 1;
+ lys_latest_unset(latest);
+ }
+ }
+ }
+ } else {
+ mod->parsed->latest_revision = 1;
+ }
+
+ /* add into context */
+ ly_set_add(&ctx->list, mod, LY_SET_OPT_USEASLIST);
+
+ }
+
return mod;
}
API const struct lys_module *
lys_parse_mem(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format)
{
- return lys_parse_mem_(ctx, data, format, NULL, 1);
+ struct lys_module *result;
+
+ result = lys_parse_mem_(ctx, data, format, NULL, 1);
+ if (result && result->parsed->submodule) {
+ LOGERR(ctx, LY_EDENIED, "Input data contains submodule \"%s\" which cannot be parsed directly without its main module.",
+ result->parsed->name);
+ lys_module_free(result, NULL);
+ return NULL;
+ }
+ return result;
}
static void
@@ -1217,10 +1252,10 @@
#endif
}
-const struct lys_module *
+struct lys_module *
lys_parse_fd_(struct ly_ctx *ctx, int fd, LYS_INFORMAT format, const char *revision, int implement)
{
- const struct lys_module *mod;
+ struct lys_module *mod;
size_t length;
char *addr;
@@ -1249,14 +1284,23 @@
API const struct lys_module *
lys_parse_fd(struct ly_ctx *ctx, int fd, LYS_INFORMAT format)
{
- return lys_parse_fd_(ctx, fd, format, NULL, 1);
+ struct lys_module *result;
+
+ result = lys_parse_fd_(ctx, fd, format, NULL, 1);
+ if (result && result->parsed->submodule) {
+ LOGERR(ctx, LY_EDENIED, "Input data contains submodule \"%s\" which cannot be parsed directly without its main module.",
+ result->parsed->name);
+ lys_module_free(result, NULL);
+ return NULL;
+ }
+ return result;
}
-API const struct lys_module *
-lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format)
+struct lys_module *
+lys_parse_path_(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format, const char *revision, int implement)
{
int fd;
- const struct lys_module *mod;
+ struct lys_module *mod;
const char *rev, *dot, *filename;
size_t len;
@@ -1265,7 +1309,7 @@
fd = open(path, O_RDONLY);
LY_CHECK_ERR_RET(fd == -1, LOGERR(ctx, LY_ESYS, "Opening file \"%s\" failed (%s).", path, strerror(errno)), NULL);
- mod = lys_parse_fd(ctx, fd, format);
+ mod = lys_parse_fd_(ctx, fd, format, revision, implement);
close(fd);
LY_CHECK_RET(!mod, NULL);
@@ -1306,3 +1350,276 @@
return mod;
}
+API const struct lys_module *
+lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format)
+{
+ struct lys_module *result;
+
+ result = lys_parse_path_(ctx, path, format, NULL, 1);
+ if (result && result->parsed->submodule) {
+ LOGERR(ctx, LY_EDENIED, "Input file \"%s\" contains submodule \"%s\" which cannot be parsed directly without its main module.",
+ path, result->parsed->name);
+ lys_module_free(result, NULL);
+ return NULL;
+ }
+ return result;
+}
+
+API LY_ERR
+lys_search_localfile(const char * const *searchpaths, int cwd, const char *name, const char *revision,
+ char **localfile, LYS_INFORMAT *format)
+{
+ size_t len, flen, match_len = 0, dir_len;
+ int i, implicit_cwd = 0, ret = EXIT_FAILURE;
+ char *wd, *wn = NULL;
+ DIR *dir = NULL;
+ struct dirent *file;
+ char *match_name = NULL;
+ LYS_INFORMAT format_aux, match_format = 0;
+ struct ly_set *dirs;
+ struct stat st;
+
+ LY_CHECK_ARG_RET(NULL, localfile, LY_EINVAL);
+
+ /* start to fill the dir fifo with the context's search path (if set)
+ * and the current working directory */
+ dirs = ly_set_new();
+ if (!dirs) {
+ LOGMEM(NULL);
+ return EXIT_FAILURE;
+ }
+
+ len = strlen(name);
+ if (cwd) {
+ wd = get_current_dir_name();
+ if (!wd) {
+ LOGMEM(NULL);
+ goto cleanup;
+ } else {
+ /* add implicit current working directory (./) to be searched,
+ * this directory is not searched recursively */
+ if (ly_set_add(dirs, wd, 0) == -1) {
+ goto cleanup;
+ }
+ implicit_cwd = 1;
+ }
+ }
+ if (searchpaths) {
+ for (i = 0; searchpaths[i]; i++) {
+ /* check for duplicities with the implicit current working directory */
+ if (implicit_cwd && !strcmp(dirs->objs[0], searchpaths[i])) {
+ implicit_cwd = 0;
+ continue;
+ }
+ wd = strdup(searchpaths[i]);
+ if (!wd) {
+ LOGMEM(NULL);
+ goto cleanup;
+ } else if (ly_set_add(dirs, wd, 0) == -1) {
+ goto cleanup;
+ }
+ }
+ }
+ wd = NULL;
+
+ /* start searching */
+ while (dirs->count) {
+ free(wd);
+ free(wn); wn = NULL;
+
+ dirs->count--;
+ wd = (char *)dirs->objs[dirs->count];
+ dirs->objs[dirs->count] = NULL;
+ LOGVRB("Searching for \"%s\" in %s.", name, wd);
+
+ if (dir) {
+ closedir(dir);
+ }
+ dir = opendir(wd);
+ dir_len = strlen(wd);
+ if (!dir) {
+ LOGWRN(NULL, "Unable to open directory \"%s\" for searching (sub)modules (%s).", wd, strerror(errno));
+ } else {
+ while ((file = readdir(dir))) {
+ if (!strcmp(".", file->d_name) || !strcmp("..", file->d_name)) {
+ /* skip . and .. */
+ continue;
+ }
+ free(wn);
+ if (asprintf(&wn, "%s/%s", wd, file->d_name) == -1) {
+ LOGMEM(NULL);
+ goto cleanup;
+ }
+ if (stat(wn, &st) == -1) {
+ LOGWRN(NULL, "Unable to get information about \"%s\" file in \"%s\" when searching for (sub)modules (%s)",
+ file->d_name, wd, strerror(errno));
+ continue;
+ }
+ if (S_ISDIR(st.st_mode) && (dirs->count || !implicit_cwd)) {
+ /* we have another subdirectory in searchpath to explore,
+ * subdirectories are not taken into account in current working dir (dirs->set.g[0]) */
+ if (ly_set_add(dirs, wn, 0) == -1) {
+ goto cleanup;
+ }
+ /* continue with the next item in current directory */
+ wn = NULL;
+ continue;
+ } else if (!S_ISREG(st.st_mode)) {
+ /* not a regular file (note that we see the target of symlinks instead of symlinks */
+ continue;
+ }
+
+ /* here we know that the item is a file which can contain a module */
+ if (strncmp(name, file->d_name, len) ||
+ (file->d_name[len] != '.' && file->d_name[len] != '@')) {
+ /* different filename than the module we search for */
+ continue;
+ }
+
+ /* get type according to filename suffix */
+ flen = strlen(file->d_name);
+ if (!strcmp(&file->d_name[flen - 4], ".yin")) {
+ format_aux = LYS_IN_YIN;
+ } else if (!strcmp(&file->d_name[flen - 5], ".yang")) {
+ format_aux = LYS_IN_YANG;
+ } else {
+ /* not supportde suffix/file format */
+ continue;
+ }
+
+ if (revision) {
+ /* we look for the specific revision, try to get it from the filename */
+ if (file->d_name[len] == '@') {
+ /* check revision from the filename */
+ if (strncmp(revision, &file->d_name[len + 1], strlen(revision))) {
+ /* another revision */
+ continue;
+ } else {
+ /* exact revision */
+ free(match_name);
+ match_name = wn;
+ wn = NULL;
+ match_len = dir_len + 1 + len;
+ match_format = format_aux;
+ goto success;
+ }
+ } else {
+ /* continue trying to find exact revision match, use this only if not found */
+ free(match_name);
+ match_name = wn;
+ wn = NULL;
+ match_len = dir_len + 1 +len;
+ match_format = format_aux;
+ continue;
+ }
+ } else {
+ /* remember the revision and try to find the newest one */
+ if (match_name) {
+ if (file->d_name[len] != '@' ||
+ lysp_check_date(NULL, &file->d_name[len + 1], flen - (format_aux == LYS_IN_YANG ? 5 : 4) - len - 1, NULL)) {
+ continue;
+ } else if (match_name[match_len] == '@' &&
+ (strncmp(&match_name[match_len + 1], &file->d_name[len + 1], LY_REV_SIZE - 1) >= 0)) {
+ continue;
+ }
+ free(match_name);
+ }
+
+ match_name = wn;
+ wn = NULL;
+ match_len = dir_len + 1 + len;
+ match_format = format_aux;
+ continue;
+ }
+ }
+ }
+ }
+
+success:
+ (*localfile) = match_name;
+ match_name = NULL;
+ if (format) {
+ (*format) = match_format;
+ }
+ ret = EXIT_SUCCESS;
+
+cleanup:
+ free(wn);
+ free(wd);
+ if (dir) {
+ closedir(dir);
+ }
+ free(match_name);
+ ly_set_free(dirs, free);
+
+ return ret;
+}
+
+LY_ERR
+lys_module_localfile(struct ly_ctx *ctx, const char *name, const char *revision, int implement,
+ struct lys_module **result)
+{
+ size_t len;
+ int fd;
+ char *filepath = NULL, *dot, *rev, *filename;
+ LYS_INFORMAT format;
+ struct lys_module *mod = NULL;
+ LY_ERR ret = LY_SUCCESS;
+
+ LY_CHECK_RET(lys_search_localfile(ly_ctx_get_searchdirs(ctx), !(ctx->flags & LY_CTX_DISABLE_SEARCHDIR_CWD), name, revision,
+ &filepath, &format));
+ LY_CHECK_ERR_RET(!filepath, LOGERR(ctx, LY_ENOTFOUND, "Data model \"%s%s%s\" not found in local searchdirs.",
+ name, revision ? "@" : "", revision ? revision : ""), LY_ENOTFOUND);
+
+
+ LOGVRB("Loading schema from \"%s\" file.", filepath);
+
+ /* open the file */
+ fd = open(filepath, O_RDONLY);
+ LY_CHECK_ERR_GOTO(fd < 0, LOGERR(ctx, LY_ESYS, "Unable to open data model file \"%s\" (%s).",
+ filepath, strerror(errno)); ret = LY_ESYS, cleanup);
+
+ mod = lys_parse_fd_(ctx, fd, format, revision, implement);
+ close(fd);
+ LY_CHECK_ERR_GOTO(!mod, ly_errcode(ctx), cleanup);
+
+ /* check that name and revision match filename */
+ filename = strrchr(filepath, '/');
+ if (!filename) {
+ filename = filepath;
+ } else {
+ filename++;
+ }
+ /* name */
+ len = strlen(mod->parsed->name);
+ rev = strchr(filename, '@');
+ dot = strrchr(filepath, '.');
+ if (strncmp(filename, mod->parsed->name, len) ||
+ ((rev && rev != &filename[len]) || (!rev && dot != &filename[len]))) {
+ LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, mod->parsed->name);
+ }
+ /* revision */
+ if (rev) {
+ len = dot - ++rev;
+ if (!mod->parsed->revs || len != 10 || strncmp(mod->parsed->revs[0].date, rev, len)) {
+ LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename,
+ mod->parsed->revs ? mod->parsed->revs[0].date : "none");
+ }
+ }
+
+ if (!mod->parsed->filepath) {
+ char rpath[PATH_MAX];
+ if (realpath(filepath, rpath) != NULL) {
+ mod->parsed->filepath = lydict_insert(ctx, rpath, 0);
+ } else {
+ mod->parsed->filepath = lydict_insert(ctx, filepath, 0);
+ }
+ }
+
+ *result = mod;
+
+ /* success */
+cleanup:
+ free(filepath);
+ return ret;
+}
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 3b939ea..6bcbda1 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -174,7 +174,7 @@
* @brief YANG include-stmt
*/
struct lysp_include {
- const char *name; /**< name of the submodule to include (mandatory) */
+ struct lysp_module *submodule; /**< pointer to the parsed submodule structure (mandatory) */
const char *dsc; /**< description */
const char *ref; /**< reference */
struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */
@@ -772,6 +772,7 @@
uint8_t latest_revision:1; /**< flag if the module was loaded without specific revision and is
the latest revision found */
uint8_t version:4; /**< yang-version (LYS_VERSION values) */
+ uint16_t refcount; /**< 0 in modules, number of includes of a submodules */
};
/**
@@ -965,7 +966,7 @@
const struct lys_module *lys_parse_fd(struct ly_ctx *ctx, int fd, LYS_INFORMAT format);
/**
- * @brief Load a schema into the specified context from a file.
+ * @brief Read a schema into the specified context from a file.
*
* @param[in] ctx libyang context where to process the data model.
* @param[in] path Path to the file with the model in the specified format.
@@ -975,6 +976,23 @@
const struct lys_module *lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format);
/**
+ * @brief Search for the schema file in the specified searchpaths.
+ *
+ * @param[in] searchpaths NULL-terminated array of paths to be searched (recursively). Current working
+ * directory is searched automatically (but non-recursively if not in the provided list). Caller can use
+ * result of the ly_ctx_get_searchdirs().
+ * @param[in] cwd Flag to implicitly search also in the current working directory (non-recursively).
+ * @param[in] name Name of the schema to find.
+ * @param[in] revision Revision of the schema to find. If NULL, the newest found schema filepath is returned.
+ * @param[out] localfile Mandatory output variable containing absolute path of the found schema. If no schema
+ * complying the provided restriction is found, NULL is set.
+ * @param[out] format Optional output variable containing expected format of the schema document according to the
+ * file suffix.
+ * @return LY_ERR value (LY_SUCCESS is returned even if the file is not found, then the *localfile is NULL).
+ */
+LY_ERR lys_search_localfile(const char * const *searchpaths, int cwd, const char *name, const char *revision, char **localfile, LYS_INFORMAT *format);
+
+/**
* @defgroup scflags Schema compile flags
* @ingroup schematree
*
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index b95505a..6657702 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -81,7 +81,9 @@
return LY_SUCCESS;
error:
- LOGVAL(ctx, LY_VLOG_NONE, NULL, LY_VCODE_INVAL, date_len, date, stmt);
+ if (stmt) {
+ LOGVAL(ctx, LY_VLOG_NONE, NULL, LY_VCODE_INVAL, date_len, date, stmt);
+ }
return LY_EINVAL;
}
@@ -105,6 +107,76 @@
}
}
+LY_ERR
+lysp_parse_include(struct ly_parser_ctx *ctx, struct lysp_module *mod, const char *name, struct lysp_include *inc)
+{
+ struct lys_module *submod;
+ const char *submodule_data = NULL;
+ LYS_INFORMAT format = LYS_IN_UNKNOWN;
+ void (*submodule_data_free)(void *module_data, void *user_data) = NULL;
+
+ /* Try to get submodule from the context, if already present */
+ inc->submodule = ly_ctx_get_submodule(ctx->ctx, mod->name, name, inc->rev[0] ? inc->rev : NULL);
+ if (!inc->submodule) {
+ /* submodule not present in the context, get the input data and parse it */
+ if (!(ctx->ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
+search_clb:
+ if (ctx->ctx->imp_clb) {
+ if (ctx->ctx->imp_clb(mod->name, NULL, name, inc->rev, ctx->ctx->imp_clb_data,
+ &format, &submodule_data, &submodule_data_free) == LY_SUCCESS) {
+ submod = lys_parse_mem_(ctx->ctx, submodule_data, format, inc->rev[0] ? inc->rev : NULL, mod->implemented);
+ }
+ }
+ if (!submod && !(ctx->ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
+ goto search_file;
+ }
+ } else {
+search_file:
+ if (!(ctx->ctx->flags & LY_CTX_DISABLE_SEARCHDIRS)) {
+ /* module was not received from the callback or there is no callback set */
+ lys_module_localfile(ctx->ctx, name, inc->rev[0] ? inc->rev : NULL, mod->implemented, &submod);
+ if (inc->submodule) {
+ ++inc->submodule->refcount;
+ }
+ }
+ if (!submod && (ctx->ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
+ goto search_clb;
+ }
+ }
+ if (submod) {
+ /* check that we have really a submodule */
+ if (!submod->parsed->submodule) {
+ /* submodule is not a submodule */
+ LOGVAL_YANG(ctx, LYVE_REFERENCE, "Included \"%s\" schema from \"%s\" is actually not a submodule.", name, mod->name);
+ lys_module_free(submod, NULL);
+ /* fix list of modules in context, since it was already changed */
+ --ctx->ctx->list.count;
+ return LY_EVALID;
+ }
+ /* check that the submodule belongs-to our module */
+ if (strcmp(mod->name, submod->parsed->belongsto)) {
+ LOGVAL_YANG(ctx, LYVE_REFERENCE, "Included \"%s\" submodule from \"%s\" belongs-to a different module \"%s\".",
+ name, mod->name, submod->parsed->belongsto);
+ lys_module_free(submod, NULL);
+ return LY_EVALID;
+ }
+ inc->submodule = submod->parsed;
+ ++inc->submodule->refcount;
+ free(submod);
+ }
+ }
+ if (!inc->submodule) {
+ if (ly_errcode(ctx->ctx) != LY_EVALID) {
+ LOGVAL_YANG(ctx, LY_VCODE_INVAL, strlen(name), name, "include");
+ } else {
+ LOGVAL_YANG(ctx, LYVE_REFERENCE, "Including \"%s\" submodule into \"%s\" failed.", name, mod->name);
+ }
+ return LY_EVALID;
+ }
+
+ return LY_SUCCESS;
+}
+
struct lysc_module *
lysc_module_find_prefix(struct lysc_module *mod, const char *prefix, size_t len)
{
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index da8789c..46727d4 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -15,6 +15,8 @@
#ifndef LY_TREE_SCHEMA_INTERNAL_H_
#define LY_TREE_SCHEMA_INTERNAL_H_
+#define LOGVAL_YANG(CTX, ...) LOGVAL((CTX)->ctx, LY_VLOG_LINE, &(CTX)->line, __VA_ARGS__)
+
/**
* @brief List of YANG statement groups - the (sub)module's substatements
*/
@@ -73,6 +75,18 @@
void lysp_sort_revisions(struct lysp_revision *revs);
/**
+ * @brief Parse included submodule into the simply parsed YANG module.
+ *
+ * @param[in] ctx yang parser context.
+ * @param[in] mod Module including a submodule.
+ * @param[in] name Name of the submodule to include.
+ * @param[in,out] inc Include structure holding all available information about the include statement, the parsed
+ * submodule is stored into this structure.
+ * @return LY_ERR value.
+ */
+LY_ERR lysp_parse_include(struct ly_parser_ctx *ctx, struct lysp_module *mod, const char *name, struct lysp_include *inc);
+
+/**
* @brief Find the module referenced by prefix in the provided mod.
*
* @param[in] mod Schema module where the prefix was used.
@@ -83,6 +97,80 @@
struct lysc_module *lysc_module_find_prefix(struct lysc_module *mod, const char *prefix, size_t len);
/**
+ * @brief Parse YANG module and submodule from a string.
+ *
+ * In contrast to public lys_parse_mem(), also submodules can be parsed here. However,
+ * while the modules are added into the context, submodules not. The latest_revision
+ * flag is updated in both cases.
+ *
+ * @param[in] ctx libyang context where to process the data model.
+ * @param[in] data The string containing the dumped data model in the specified
+ * format.
+ * @param[in] format Format of the input data (YANG or YIN).
+ * @param[in] revision If a specific revision is required, it is checked before the parsed
+ * schema is accepted.
+ * @param[in] implement Flag if the schema is supposed to be marked as implemented.
+ * @return Pointer to the data model structure or NULL on error.
+ */
+struct lys_module *lys_parse_mem_(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format, const char *revision, int implement);
+
+/**
+ * @brief Parse YANG module and submodule from a file descriptor.
+ *
+ * In contrast to public lys_parse_mem(), also submodules can be parsed here. However,
+ * while the modules are added into the context, submodules not. The latest_revision
+ * flag is updated in both cases.
+ *
+ * \note Current implementation supports only reading data from standard (disk) file, not from sockets, pipes, etc.
+ *
+ * @param[in] ctx libyang context where to process the data model.
+ * @param[in] fd File descriptor of a regular file (e.g. sockets are not supported) containing the schema
+ * in the specified format.
+ * @param[in] format Format of the input data (YANG or YIN).
+ * @param[in] revision If a specific revision is required, it is checked before the parsed
+ * schema is accepted.
+ * @param[in] implement Flag if the schema is supposed to be marked as implemented.
+ * @return Pointer to the data model structure or NULL on error.
+ */
+struct lys_module *lys_parse_fd_(struct ly_ctx *ctx, int fd, LYS_INFORMAT format, const char *revision, int implement);
+
+/**
+ * @brief Parse YANG module and submodule from a file descriptor.
+ *
+ * In contrast to public lys_parse_mem(), also submodules can be parsed here. However,
+ * while the modules are added into the context, submodules not. The latest_revision
+ * flag is updated in both cases.
+ *
+ * \note Current implementation supports only reading data from standard (disk) file, not from sockets, pipes, etc.
+ *
+ * @brief REad a schema into the specified context from a file.
+ *
+ * @param[in] ctx libyang context where to process the data model.
+ * @param[in] path Path to the file with the model in the specified format.
+ * @param[in] format Format of the input data (YANG or YIN).
+ * @param[in] revision If a specific revision is required, it is checked before the parsed
+ * schema is accepted.
+ * @param[in] implement Flag if the schema is supposed to be marked as implemented.
+ * @return Pointer to the data model structure or NULL on error.
+ */
+struct lys_module *lys_parse_path_(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format, const char *revision, int implement);
+
+/**
+ * @brief Load the (sub)module into the context.
+ *
+ * This function does not check the presence of the (sub)module in context, it should be done before calling this function.
+ *
+ * module_name and submodule_name are alternatives - only one of the
+ *
+ * @param[in] ctx libyang context where to work.
+ * @param[in] name Name of the (sub)module to load.
+ * @param[in] revision Optional revision of the (sub)module to load, if NULL the newest revision is being loaded.
+ * @param[in] implement Flag if the (sub)module is supposed to be marked as implemented.
+ * @param[out] result Parsed YANG schema tree of the requested module. If it is a module, it is already in the context!
+ * @return LY_ERR value, in case of LY_SUCCESS, the \arg result is always provided.
+ */
+LY_ERR lys_module_localfile(struct ly_ctx *ctx, const char *name, const char *revision, int implement, struct lys_module **result);
+/**
* @brief Free the schema structure. It just frees, it does not remove the schema from its context.
* @param[in,out] module Schema module structure to free.
* @param[in] private_destructor Function to remove private data from the compiled schema tree.