blob: 284cdeec5c3021075b0594cd7d17b300496c4166 [file] [log] [blame]
Radek Krejci86d106e2018-10-18 09:53:19 +02001/**
2 * @file tree_schema_helpers.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief Parsing and validation helper functions
5 *
6 * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
Radek Krejci9ed7a192018-10-31 16:23:51 +010014#include "common.h"
Radek Krejci86d106e2018-10-18 09:53:19 +020015
16#include <ctype.h>
Radek Krejci9ed7a192018-10-31 16:23:51 +010017#include <dirent.h>
18#include <errno.h>
19#include <fcntl.h>
Radek Krejci86d106e2018-10-18 09:53:19 +020020#include <limits.h>
Radek Krejci9ed7a192018-10-31 16:23:51 +010021#include <stdlib.h>
22#include <sys/stat.h>
23#include <sys/types.h>
24#include <unistd.h>
Radek Krejci86d106e2018-10-18 09:53:19 +020025#include <time.h>
26
27#include "libyang.h"
Radek Krejci86d106e2018-10-18 09:53:19 +020028#include "tree_schema_internal.h"
29
30LY_ERR
31lysp_check_prefix(struct ly_parser_ctx *ctx, struct lysp_module *module, const char **value)
32{
33 struct lysp_import *i;
34
35 if (module->prefix && &module->prefix != value && !strcmp(module->prefix, *value)) {
36 LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_REFERENCE,
37 "Prefix \"%s\" already used as module prefix.", *value);
38 return LY_EEXIST;
39 }
40 if (module->imports) {
41 LY_ARRAY_FOR(module->imports, struct lysp_import, i) {
42 if (i->prefix && &i->prefix != value && !strcmp(i->prefix, *value)) {
43 LOGVAL(ctx->ctx, LY_VLOG_LINE, &ctx->line, LYVE_REFERENCE,
44 "Prefix \"%s\" already used to import \"%s\" module.", *value, i->name);
45 return LY_EEXIST;
46 }
47 }
48 }
49 return LY_SUCCESS;
50}
51
52LY_ERR
53lysp_check_date(struct ly_ctx *ctx, const char *date, int date_len, const char *stmt)
54{
55 int i;
56 struct tm tm, tm_;
57 char *r;
58
59 LY_CHECK_ARG_RET(ctx, date, LY_EINVAL);
60 LY_CHECK_ERR_RET(date_len != LY_REV_SIZE - 1, LOGARG(ctx, date_len), LY_EINVAL);
61
62 /* check format */
63 for (i = 0; i < date_len; i++) {
64 if (i == 4 || i == 7) {
65 if (date[i] != '-') {
66 goto error;
67 }
68 } else if (!isdigit(date[i])) {
69 goto error;
70 }
71 }
72
73 /* check content, e.g. 2018-02-31 */
74 memset(&tm, 0, sizeof tm);
75 r = strptime(date, "%Y-%m-%d", &tm);
76 if (!r || r != &date[LY_REV_SIZE - 1]) {
77 goto error;
78 }
79 memcpy(&tm_, &tm, sizeof tm);
80 mktime(&tm_); /* mktime modifies tm_ if it refers invalid date */
81 if (tm.tm_mday != tm_.tm_mday) { /* e.g 2018-02-29 -> 2018-03-01 */
82 /* checking days is enough, since other errors
83 * have been checked by strptime() */
84 goto error;
85 }
86
87 return LY_SUCCESS;
88
89error:
Radek Krejcid33273d2018-10-25 14:55:52 +020090 if (stmt) {
91 LOGVAL(ctx, LY_VLOG_NONE, NULL, LY_VCODE_INVAL, date_len, date, stmt);
92 }
Radek Krejci86d106e2018-10-18 09:53:19 +020093 return LY_EINVAL;
94}
95
96void
97lysp_sort_revisions(struct lysp_revision *revs)
98{
99 uint8_t i, r;
100 struct lysp_revision rev;
101
102 for (i = 1, r = 0; revs && i < LY_ARRAY_SIZE(revs); i++) {
Radek Krejcib7db73a2018-10-24 14:18:40 +0200103 if (strcmp(revs[i].date, revs[r].date) > 0) {
Radek Krejci86d106e2018-10-18 09:53:19 +0200104 r = i;
105 }
106 }
107
108 if (r) {
109 /* the newest revision is not on position 0, switch them */
Radek Krejci2c4e7172018-10-19 15:56:26 +0200110 memcpy(&rev, &revs[0], sizeof rev);
111 memcpy(&revs[0], &revs[r], sizeof rev);
112 memcpy(&revs[r], &rev, sizeof rev);
Radek Krejci86d106e2018-10-18 09:53:19 +0200113 }
114}
Radek Krejci151a5b72018-10-19 14:21:44 +0200115
Radek Krejci086c7132018-10-26 15:29:04 +0200116void
117lys_module_implement(struct lys_module *mod)
118{
119 assert(mod);
120 if (mod->parsed) {
121 mod->parsed->implemented = 1;
122 }
123 if (mod->compiled) {
124 mod->compiled->implemented = 1;
125 }
126}
127
Radek Krejci9ed7a192018-10-31 16:23:51 +0100128struct lysp_load_module_check_data {
129 const char *name;
130 const char *revision;
131 const char *path;
132 const char* submoduleof;
133};
134
135static LY_ERR
136lysp_load_module_check(struct ly_ctx *ctx, struct lysp_module *mod, void *data)
137{
138 struct lysp_load_module_check_data *info = data;
139 const char *filename, *dot, *rev;
140 size_t len;
141
142 if (info->name) {
143 /* check name of the parsed model */
144 if (strcmp(info->name, mod->name)) {
145 LOGERR(ctx, LY_EINVAL, "Unexpected module \"%s\" parsed instead of \"%s\").", mod->name, info->name);
146 return LY_EINVAL;
147 }
148 }
149 if (info->revision) {
150 /* check revision of the parsed model */
151 if (!mod->revs || strcmp(info->revision, mod->revs[0].date)) {
152 LOGERR(ctx, LY_EINVAL, "Module \"%s\" parsed with the wrong revision (\"%s\" instead \"%s\").", mod->name,
153 mod->revs[0].date, info->revision);
154 return LY_EINVAL;
155 }
156 }
157 if (info->submoduleof) {
158 /* check that we have really a submodule */
159 if (!mod->submodule) {
160 /* submodule is not a submodule */
161 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "Included \"%s\" schema from \"%s\" is actually not a submodule.",
162 mod->name, info->submoduleof);
163 return LY_EVALID;
164 }
165 /* check that the submodule belongs-to our module */
166 if (strcmp(info->submoduleof, mod->belongsto)) {
167 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "Included \"%s\" submodule from \"%s\" belongs-to a different module \"%s\".",
168 mod->name, info->submoduleof, mod->belongsto);
169 return LY_EVALID;
170 }
171 /* check circular dependency */
172 if (mod->parsing) {
173 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "A circular dependency (include) for module \"%s\".", mod->name);
174 return LY_EVALID;
175 }
176 }
177 if (info->path) {
178 /* check that name and revision match filename */
179 filename = strrchr(info->path, '/');
180 if (!filename) {
181 filename = info->path;
182 } else {
183 filename++;
184 }
185 /* name */
186 len = strlen(mod->name);
187 rev = strchr(filename, '@');
188 dot = strrchr(info->path, '.');
189 if (strncmp(filename, mod->name, len) ||
190 ((rev && rev != &filename[len]) || (!rev && dot != &filename[len]))) {
191 LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, mod->name);
192 }
193 /* revision */
194 if (rev) {
195 len = dot - ++rev;
196 if (!mod->revs || len != 10 || strncmp(mod->revs[0].date, rev, len)) {
197 LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename,
198 mod->revs ? mod->revs[0].date : "none");
199 }
200 }
201 }
202 return LY_SUCCESS;
203}
204
205LY_ERR
206lys_module_localfile(struct ly_ctx *ctx, const char *name, const char *revision, int implement,
207 struct lys_module **result)
208{
209 int fd;
210 char *filepath = NULL;
211 LYS_INFORMAT format;
212 struct lys_module *mod = NULL;
213 LY_ERR ret = LY_SUCCESS;
214 struct lysp_load_module_check_data check_data = {0};
215
216 LY_CHECK_RET(lys_search_localfile(ly_ctx_get_searchdirs(ctx), !(ctx->flags & LY_CTX_DISABLE_SEARCHDIR_CWD), name, revision,
217 &filepath, &format));
218 LY_CHECK_ERR_RET(!filepath, LOGERR(ctx, LY_ENOTFOUND, "Data model \"%s%s%s\" not found in local searchdirs.",
219 name, revision ? "@" : "", revision ? revision : ""), LY_ENOTFOUND);
220
221
222 LOGVRB("Loading schema from \"%s\" file.", filepath);
223
224 /* open the file */
225 fd = open(filepath, O_RDONLY);
226 LY_CHECK_ERR_GOTO(fd < 0, LOGERR(ctx, LY_ESYS, "Unable to open data model file \"%s\" (%s).",
227 filepath, strerror(errno)); ret = LY_ESYS, cleanup);
228
229 check_data.name = name;
230 check_data.revision = revision;
231 check_data.path = filepath;
232 mod = lys_parse_fd_(ctx, fd, format, implement,
233 lysp_load_module_check, &check_data);
234 close(fd);
235 LY_CHECK_ERR_GOTO(!mod, ly_errcode(ctx), cleanup);
236
237 if (!mod->parsed->filepath) {
238 char rpath[PATH_MAX];
239 if (realpath(filepath, rpath) != NULL) {
240 mod->parsed->filepath = lydict_insert(ctx, rpath, 0);
241 } else {
242 mod->parsed->filepath = lydict_insert(ctx, filepath, 0);
243 }
244 }
245
246 *result = mod;
247
248 /* success */
249cleanup:
250 free(filepath);
251 return ret;
252}
253
Radek Krejcid33273d2018-10-25 14:55:52 +0200254LY_ERR
Radek Krejci6d6e4e42018-10-29 13:28:19 +0100255lysp_load_module(struct ly_ctx *ctx, const char *name, const char *revision, int implement, int require_parsed, struct lys_module **mod)
Radek Krejci086c7132018-10-26 15:29:04 +0200256{
Radek Krejci9ed7a192018-10-31 16:23:51 +0100257 const char *module_data = NULL;
Radek Krejci086c7132018-10-26 15:29:04 +0200258 LYS_INFORMAT format = LYS_IN_UNKNOWN;
Radek Krejci9ed7a192018-10-31 16:23:51 +0100259 void (*module_data_free)(void *module_data, void *user_data) = NULL;
260 struct lysp_load_module_check_data check_data = {0};
Radek Krejci086c7132018-10-26 15:29:04 +0200261
262 /* try to get the module from the context */
263 if (revision) {
264 *mod = (struct lys_module*)ly_ctx_get_module(ctx, name, revision);
265 } else {
266 *mod = (struct lys_module*)ly_ctx_get_module_latest(ctx, name);
267 }
268
Radek Krejci6d6e4e42018-10-29 13:28:19 +0100269 if (!(*mod) || (require_parsed && !(*mod)->parsed)) {
270 (*mod) = NULL;
271
Radek Krejci086c7132018-10-26 15:29:04 +0200272 /* check collision with other implemented revision */
273 if (implement && ly_ctx_get_module_implemented(ctx, name)) {
274 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE,
275 "Module \"%s\" is already present in other implemented revision.", name);
276 return LY_EDENIED;
277 }
278
Radek Krejci9ed7a192018-10-31 16:23:51 +0100279 /* module not present in the context, get the input data and parse it */
Radek Krejci086c7132018-10-26 15:29:04 +0200280 if (!(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
281search_clb:
282 if (ctx->imp_clb) {
283 if (ctx->imp_clb(name, revision, NULL, NULL, ctx->imp_clb_data,
Radek Krejci9ed7a192018-10-31 16:23:51 +0100284 &format, &module_data, &module_data_free) == LY_SUCCESS) {
285 check_data.name = name;
286 check_data.revision = revision;
287 *mod = lys_parse_mem_(ctx, module_data, format, implement,
288 lysp_load_module_check, &check_data);
289 if (module_data_free) {
290 module_data_free((void*)module_data, ctx->imp_clb_data);
291 }
Radek Krejci086c7132018-10-26 15:29:04 +0200292 }
293 }
294 if (!(*mod) && !(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
295 goto search_file;
296 }
297 } else {
298search_file:
299 if (!(ctx->flags & LY_CTX_DISABLE_SEARCHDIRS)) {
300 /* module was not received from the callback or there is no callback set */
301 lys_module_localfile(ctx, name, revision, implement, mod);
302 }
303 if (!(*mod) && (ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
304 goto search_clb;
305 }
306 }
Radek Krejci9ed7a192018-10-31 16:23:51 +0100307
Radek Krejcid14e9692018-11-01 11:00:37 +0100308 if ((*mod) && !revision && ((*mod)->parsed->latest_revision == 1)) {
Radek Krejci9ed7a192018-10-31 16:23:51 +0100309 /* update the latest_revision flag - here we have selected the latest available schema,
310 * consider that even the callback provides correct latest revision */
311 (*mod)->parsed->latest_revision = 2;
312 }
Radek Krejci086c7132018-10-26 15:29:04 +0200313 } else {
314 /* we have module from the current context */
315 if (implement && (ly_ctx_get_module_implemented(ctx, name) != *mod)) {
316 /* check collision with other implemented revision */
317 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE,
318 "Module \"%s\" is already present in other implemented revision.", name);
319 *mod = NULL;
320 return LY_EDENIED;
321 }
322
323 /* circular check */
Radek Krejcif8f882a2018-10-31 14:51:15 +0100324 if ((*mod)->parsed && (*mod)->parsed->parsing) {
Radek Krejci086c7132018-10-26 15:29:04 +0200325 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "A circular dependency (import) for module \"%s\".", name);
326 *mod = NULL;
327 return LY_EVALID;
328 }
329 }
330 if (!(*mod)) {
Radek Krejci9ed7a192018-10-31 16:23:51 +0100331 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "%s \"%s\" module failed.", implement ? "Loading" : "Importing", name);
Radek Krejci086c7132018-10-26 15:29:04 +0200332 return LY_EVALID;
333 }
334
335 if (implement) {
336 /* mark the module implemented, check for collision was already done */
337 lys_module_implement(*mod);
338 }
Radek Krejci086c7132018-10-26 15:29:04 +0200339
340 return LY_SUCCESS;
341}
342
343LY_ERR
344lysp_load_submodule(struct ly_ctx *ctx, struct lysp_module *mod, struct lysp_include *inc)
Radek Krejcid33273d2018-10-25 14:55:52 +0200345{
346 struct lys_module *submod;
347 const char *submodule_data = NULL;
348 LYS_INFORMAT format = LYS_IN_UNKNOWN;
349 void (*submodule_data_free)(void *module_data, void *user_data) = NULL;
Radek Krejci9ed7a192018-10-31 16:23:51 +0100350 struct lysp_load_module_check_data check_data = {0};
Radek Krejcid33273d2018-10-25 14:55:52 +0200351
352 /* Try to get submodule from the context, if already present */
Radek Krejci086c7132018-10-26 15:29:04 +0200353 inc->submodule = ly_ctx_get_submodule(ctx, mod->name, inc->name, inc->rev[0] ? inc->rev : NULL);
Radek Krejcie9e987e2018-10-31 12:50:27 +0100354 if (!inc->submodule || (!inc->rev[0] && inc->submodule->latest_revision != 2)) {
Radek Krejcid33273d2018-10-25 14:55:52 +0200355 /* submodule not present in the context, get the input data and parse it */
Radek Krejci086c7132018-10-26 15:29:04 +0200356 if (!(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
Radek Krejcid33273d2018-10-25 14:55:52 +0200357search_clb:
Radek Krejci086c7132018-10-26 15:29:04 +0200358 if (ctx->imp_clb) {
359 if (ctx->imp_clb(mod->name, NULL, inc->name, inc->rev[0] ? inc->rev : NULL, ctx->imp_clb_data,
Radek Krejcid33273d2018-10-25 14:55:52 +0200360 &format, &submodule_data, &submodule_data_free) == LY_SUCCESS) {
Radek Krejci9ed7a192018-10-31 16:23:51 +0100361 check_data.name = inc->name;
362 check_data.revision = inc->rev[0] ? inc->rev : NULL;
363 check_data.submoduleof = mod->name;
364 submod = lys_parse_mem_(ctx, submodule_data, format, mod->implemented,
365 lysp_load_module_check, &check_data);
366 if (submodule_data_free) {
367 submodule_data_free((void*)submodule_data, ctx->imp_clb_data);
368 }
Radek Krejcid33273d2018-10-25 14:55:52 +0200369 }
370 }
Radek Krejci086c7132018-10-26 15:29:04 +0200371 if (!submod && !(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
Radek Krejcid33273d2018-10-25 14:55:52 +0200372 goto search_file;
373 }
374 } else {
375search_file:
Radek Krejci086c7132018-10-26 15:29:04 +0200376 if (!(ctx->flags & LY_CTX_DISABLE_SEARCHDIRS)) {
Radek Krejcid33273d2018-10-25 14:55:52 +0200377 /* module was not received from the callback or there is no callback set */
Radek Krejci086c7132018-10-26 15:29:04 +0200378 lys_module_localfile(ctx, inc->name, inc->rev[0] ? inc->rev : NULL, mod->implemented, &submod);
Radek Krejcid33273d2018-10-25 14:55:52 +0200379 }
Radek Krejci086c7132018-10-26 15:29:04 +0200380 if (!submod && (ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) {
Radek Krejcid33273d2018-10-25 14:55:52 +0200381 goto search_clb;
382 }
383 }
384 if (submod) {
Radek Krejci9ed7a192018-10-31 16:23:51 +0100385 if (!inc->rev[0] && (submod->parsed->latest_revision == 1)) {
386 /* update the latest_revision flag - here we have selected the latest available schema,
387 * consider that even the callback provides correct latest revision */
388 submod->parsed->latest_revision = 2;
Radek Krejcid33273d2018-10-25 14:55:52 +0200389 }
Radek Krejci9ed7a192018-10-31 16:23:51 +0100390
Radek Krejcid33273d2018-10-25 14:55:52 +0200391 inc->submodule = submod->parsed;
392 ++inc->submodule->refcount;
393 free(submod);
394 }
Radek Krejci2d31ea72018-10-25 15:46:42 +0200395 } else {
396 ++inc->submodule->refcount;
Radek Krejcid33273d2018-10-25 14:55:52 +0200397 }
398 if (!inc->submodule) {
Radek Krejci9ed7a192018-10-31 16:23:51 +0100399 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "Including \"%s\" submodule into \"%s\" failed.", inc->name, mod->name);
Radek Krejcid33273d2018-10-25 14:55:52 +0200400 return LY_EVALID;
401 }
402
403 return LY_SUCCESS;
404}
405
Radek Krejcice8c1592018-10-29 15:35:51 +0100406#define FIND_MODULE(TYPE, MOD) \
407 TYPE *imp; \
408 if (!strncmp((MOD)->prefix, prefix, len) && (MOD)->prefix[len] == '\0') { \
409 /* it is the prefix of the module itself */ \
Radek Krejcif8f882a2018-10-31 14:51:15 +0100410 return (struct lys_module*)ly_ctx_get_module((MOD)->ctx, (MOD)->name, ((struct lysc_module*)(MOD))->revision); \
Radek Krejcice8c1592018-10-29 15:35:51 +0100411 } \
412 /* search in imports */ \
413 LY_ARRAY_FOR((MOD)->imports, TYPE, imp) { \
414 if (!strncmp(imp->prefix, prefix, len) && (MOD)->prefix[len] == '\0') { \
415 return imp->module; \
416 } \
417 } \
418 return NULL
419
420struct lys_module *
Radek Krejci151a5b72018-10-19 14:21:44 +0200421lysc_module_find_prefix(struct lysc_module *mod, const char *prefix, size_t len)
422{
Radek Krejcice8c1592018-10-29 15:35:51 +0100423 FIND_MODULE(struct lysc_import, mod);
424}
Radek Krejci151a5b72018-10-19 14:21:44 +0200425
Radek Krejcice8c1592018-10-29 15:35:51 +0100426struct lys_module *
427lysp_module_find_prefix(struct lysp_module *mod, const char *prefix, size_t len)
428{
429 FIND_MODULE(struct lysp_import, mod);
430}
Radek Krejci151a5b72018-10-19 14:21:44 +0200431
Radek Krejcice8c1592018-10-29 15:35:51 +0100432struct lys_module *
433lys_module_find_prefix(struct lys_module *mod, const char *prefix, size_t len)
434{
435 if (mod->compiled) {
436 FIND_MODULE(struct lysc_import, mod->compiled);
437 } else {
438 FIND_MODULE(struct lysp_import, mod->parsed);
Radek Krejci151a5b72018-10-19 14:21:44 +0200439 }
Radek Krejci151a5b72018-10-19 14:21:44 +0200440}