blob: 8cd92a490e9a12363b15451247147cef705ec681 [file] [log] [blame]
Michal Vasko7b1ad1a2020-11-02 15:41:27 +01001/**
2 * @file schema_features.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief Schema feature handling
5 *
6 * Copyright (c) 2015 - 2020 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 */
14
15#define _GNU_SOURCE
16
17#include "schema_features.h"
18
19#include <assert.h>
20#include <ctype.h>
Michal Vasko7b1ad1a2020-11-02 15:41:27 +010021#include <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
Michal Vasko7b1ad1a2020-11-02 15:41:27 +010025
26#include "common.h"
Michal Vasko7b1ad1a2020-11-02 15:41:27 +010027#include "log.h"
Michal Vasko7b1ad1a2020-11-02 15:41:27 +010028#include "set.h"
29#include "tree.h"
Radek Krejci47fab892020-11-05 17:02:41 +010030#include "tree_data.h"
31#include "tree_schema.h"
Michal Vasko7b1ad1a2020-11-02 15:41:27 +010032#include "tree_schema_internal.h"
Michal Vasko7b1ad1a2020-11-02 15:41:27 +010033
34uint8_t
35lysc_iff_getop(uint8_t *list, size_t pos)
36{
37 uint8_t *item;
38 uint8_t mask = 3, result;
39
40 item = &list[pos / 4];
41 result = (*item) & (mask << 2 * (pos % 4));
42 return result >> 2 * (pos % 4);
43}
44
45static LY_ERR
46lysc_iffeature_value_(const struct lysc_iffeature *iff, size_t *index_e, size_t *index_f)
47{
48 uint8_t op;
49 LY_ERR a, b;
50
51 op = lysc_iff_getop(iff->expr, *index_e);
52 (*index_e)++;
53
54 switch (op) {
55 case LYS_IFF_F:
56 /* resolve feature */
57 return (iff->features[(*index_f)++]->flags & LYS_FENABLED) ? LY_SUCCESS : LY_ENOT;
58 case LYS_IFF_NOT:
59 /* invert result */
60 return lysc_iffeature_value_(iff, index_e, index_f) == LY_SUCCESS ? LY_ENOT : LY_SUCCESS;
61 case LYS_IFF_AND:
62 case LYS_IFF_OR:
63 a = lysc_iffeature_value_(iff, index_e, index_f);
64 b = lysc_iffeature_value_(iff, index_e, index_f);
65 if (op == LYS_IFF_AND) {
66 if ((a == LY_SUCCESS) && (b == LY_SUCCESS)) {
67 return LY_SUCCESS;
68 } else {
69 return LY_ENOT;
70 }
71 } else { /* LYS_IFF_OR */
72 if ((a == LY_SUCCESS) || (b == LY_SUCCESS)) {
73 return LY_SUCCESS;
74 } else {
75 return LY_ENOT;
76 }
77 }
78 }
79
80 return LY_ENOT;
81}
82
83API LY_ERR
84lysc_iffeature_value(const struct lysc_iffeature *iff)
85{
86 size_t index_e = 0, index_f = 0;
87
88 LY_CHECK_ARG_RET(NULL, iff, LY_EINVAL);
89
90 if (iff->expr) {
91 return lysc_iffeature_value_(iff, &index_e, &index_f);
92 }
93 return LY_ENOT;
94}
95
96API struct lysp_feature *
97lysp_feature_next(const struct lysp_feature *last, const struct lysp_module *pmod, uint32_t *idx)
98{
99 struct lysp_feature *features;
100
101 if (!*idx) {
102 /* module features */
103 features = pmod->features;
104 } else if (pmod->includes && ((*idx - 1) < LY_ARRAY_COUNT(pmod->includes))) {
105 /* submodule features */
106 features = pmod->includes[*idx - 1].submodule->features;
107 } else {
108 /* no more features */
109 return NULL;
110 }
111
112 /* get the next feature */
113 if (features && (!last || (&features[LY_ARRAY_COUNT(features) - 1] != last))) {
114 return !last ? &features[0] : (struct lysp_feature *)last + 1;
115 }
116
117 /* no more features in current (sub)module */
118 ++(*idx);
119 return lysp_feature_next(NULL, pmod, idx);
120}
121
122/**
123 * @brief Find a feature of the given name and referenced in the given module.
124 *
125 * @param[in] pmod Module where the feature was referenced (used to resolve prefix of the feature).
126 * @param[in] name Name of the feature including possible prefix.
127 * @param[in] len Length of the string representing the feature identifier in the name variable (mandatory!).
128 * @param[in] prefixed Whether the feature name can be prefixed.
129 * @return Pointer to the feature structure if found, NULL otherwise.
130 */
131static struct lysp_feature *
132lysp_feature_find(const struct lysp_module *pmod, const char *name, size_t len, ly_bool prefixed)
133{
134 const struct lys_module *mod;
135 const char *ptr;
136 struct lysp_feature *f = NULL;
137 uint32_t idx = 0;
138
139 assert(pmod);
140
141 if (prefixed && (ptr = ly_strnchr(name, ':', len))) {
142 /* we have a prefixed feature */
143 mod = ly_resolve_prefix(pmod->mod->ctx, name, ptr - name, LY_PREF_SCHEMA, (void *)pmod);
144 LY_CHECK_RET(!mod, NULL);
145
146 pmod = mod->parsed;
147 len = len - (ptr - name) - 1;
148 name = ptr + 1;
149 }
150
151 /* we have the correct module, get the feature */
152 while ((f = lysp_feature_next(f, pmod, &idx))) {
153 if (!ly_strncmp(f->name, name, len)) {
154 return f;
155 }
156 }
157
158 return NULL;
159}
160
161API LY_ERR
162lys_feature_value(const struct lys_module *module, const char *feature)
163{
164 const struct lysp_feature *f;
165
166 LY_CHECK_ARG_RET(NULL, module, module->parsed, feature, LY_EINVAL);
167
168 /* search for the specified feature */
169 f = lysp_feature_find(module->parsed, feature, strlen(feature), 0);
170 LY_CHECK_RET(!f, LY_ENOTFOUND);
171
172 /* feature disabled */
173 if (!(f->flags & LYS_FENABLED)) {
174 return LY_ENOT;
175 }
176
177 /* feature enabled */
178 return LY_SUCCESS;
179}
180
181/**
182 * @brief Stack for processing if-feature expressions.
183 */
184struct iff_stack {
185 size_t size; /**< number of items in the stack */
186 size_t index; /**< first empty item */
187 uint8_t *stack; /**< stack - array of @ref ifftokens to create the if-feature expression in prefix format */
188};
189
190/**
191 * @brief Add @ref ifftokens into the stack.
192 * @param[in] stack The if-feature stack to use.
193 * @param[in] value One of the @ref ifftokens to store in the stack.
194 * @return LY_EMEM in case of memory allocation error
195 * @return LY_ESUCCESS if the value successfully stored.
196 */
197static LY_ERR
198iff_stack_push(struct iff_stack *stack, uint8_t value)
199{
200 if (stack->index == stack->size) {
201 stack->size += 4;
202 stack->stack = ly_realloc(stack->stack, stack->size * sizeof *stack->stack);
203 LY_CHECK_ERR_RET(!stack->stack, LOGMEM(NULL); stack->size = 0, LY_EMEM);
204 }
205 stack->stack[stack->index++] = value;
206 return LY_SUCCESS;
207}
208
209/**
210 * @brief Get (and remove) the last item form the stack.
211 * @param[in] stack The if-feature stack to use.
212 * @return The value from the top of the stack.
213 */
214static uint8_t
215iff_stack_pop(struct iff_stack *stack)
216{
217 assert(stack && stack->index);
218
219 stack->index--;
220 return stack->stack[stack->index];
221}
222
223/**
224 * @brief Clean up the stack.
225 * @param[in] stack The if-feature stack to use.
226 */
227static void
228iff_stack_clean(struct iff_stack *stack)
229{
230 stack->size = 0;
231 free(stack->stack);
232}
233
234/**
235 * @brief Store the @ref ifftokens (@p op) on the given position in the 2bits array
236 * (libyang format of the if-feature expression).
237 * @param[in,out] list The 2bits array to modify.
238 * @param[in] op The operand (@ref ifftokens) to store.
239 * @param[in] pos Position (0-based) where to store the given @p op.
240 */
241static void
242iff_setop(uint8_t *list, uint8_t op, size_t pos)
243{
244 uint8_t *item;
245 uint8_t mask = 3;
246
247 assert(op <= 3); /* max 2 bits */
248
249 item = &list[pos / 4];
250 mask = mask << 2 * (pos % 4);
251 *item = (*item) & ~mask;
252 *item = (*item) | (op << 2 * (pos % 4));
253}
254
255#define LYS_IFF_LP 0x04 /**< Additional, temporary, value of @ref ifftokens: ( */
256#define LYS_IFF_RP 0x08 /**< Additional, temporary, value of @ref ifftokens: ) */
257
258static LY_ERR
259lys_compile_iffeature(const struct ly_ctx *ctx, struct lysp_qname *qname, struct lysc_iffeature *iff)
260{
261 LY_ERR rc = LY_SUCCESS;
262 const char *c = qname->str;
263 int64_t i, j;
264 int8_t op_len, last_not = 0, checkversion = 0;
265 LY_ARRAY_COUNT_TYPE f_size = 0, expr_size = 0, f_exp = 1;
266 uint8_t op;
267 struct iff_stack stack = {0, 0, NULL};
268 struct lysp_feature *f;
269
270 assert(c);
271
272 /* pre-parse the expression to get sizes for arrays, also do some syntax checks of the expression */
273 for (i = j = 0; c[i]; i++) {
274 if (c[i] == '(') {
275 j++;
276 checkversion = 1;
277 continue;
278 } else if (c[i] == ')') {
279 j--;
280 continue;
281 } else if (isspace(c[i])) {
282 checkversion = 1;
283 continue;
284 }
285
286 if (!strncmp(&c[i], "not", op_len = 3) || !strncmp(&c[i], "and", op_len = 3) || !strncmp(&c[i], "or", op_len = 2)) {
287 uint64_t spaces;
288 for (spaces = 0; c[i + op_len + spaces] && isspace(c[i + op_len + spaces]); spaces++) {}
289 if (c[i + op_len + spaces] == '\0') {
290 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_SYNTAX_YANG,
291 "Invalid value \"%s\" of if-feature - unexpected end of expression.", qname->str);
292 return LY_EVALID;
293 } else if (!isspace(c[i + op_len])) {
294 /* feature name starting with the not/and/or */
295 last_not = 0;
296 f_size++;
297 } else if (c[i] == 'n') { /* not operation */
298 if (last_not) {
299 /* double not */
300 expr_size = expr_size - 2;
301 last_not = 0;
302 } else {
303 last_not = 1;
304 }
305 } else { /* and, or */
306 if (f_exp != f_size) {
307 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_SYNTAX_YANG,
308 "Invalid value \"%s\" of if-feature - missing feature/expression before \"%.*s\" operation.",
309 qname->str, op_len, &c[i]);
310 return LY_EVALID;
311 }
312 f_exp++;
313
314 /* not a not operation */
315 last_not = 0;
316 }
317 i += op_len;
318 } else {
319 f_size++;
320 last_not = 0;
321 }
322 expr_size++;
323
324 while (!isspace(c[i])) {
325 if (!c[i] || (c[i] == ')') || (c[i] == '(')) {
326 i--;
327 break;
328 }
329 i++;
330 }
331 }
332 if (j) {
333 /* not matching count of ( and ) */
334 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_SYNTAX_YANG,
335 "Invalid value \"%s\" of if-feature - non-matching opening and closing parentheses.", qname->str);
336 return LY_EVALID;
337 }
338 if (f_exp != f_size) {
339 /* features do not match the needed arguments for the logical operations */
340 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_SYNTAX_YANG,
341 "Invalid value \"%s\" of if-feature - number of features in expression does not match "
342 "the required number of operands for the operations.", qname->str);
343 return LY_EVALID;
344 }
345
346 if (checkversion || (expr_size > 1)) {
347 /* check that we have 1.1 module */
348 if (qname->mod->version != LYS_VERSION_1_1) {
349 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_SYNTAX_YANG,
350 "Invalid value \"%s\" of if-feature - YANG 1.1 expression in YANG 1.0 module.", qname->str);
351 return LY_EVALID;
352 }
353 }
354
355 /* allocate the memory */
356 LY_ARRAY_CREATE_RET(ctx, iff->features, f_size, LY_EMEM);
357 iff->expr = calloc((j = (expr_size / 4) + ((expr_size % 4) ? 1 : 0)), sizeof *iff->expr);
358 stack.stack = malloc(expr_size * sizeof *stack.stack);
359 LY_CHECK_ERR_GOTO(!stack.stack || !iff->expr, LOGMEM(ctx); rc = LY_EMEM, error);
360
361 stack.size = expr_size;
362 f_size--; expr_size--; /* used as indexes from now */
363
364 for (i--; i >= 0; i--) {
365 if (c[i] == ')') {
366 /* push it on stack */
367 iff_stack_push(&stack, LYS_IFF_RP);
368 continue;
369 } else if (c[i] == '(') {
370 /* pop from the stack into result all operators until ) */
371 while ((op = iff_stack_pop(&stack)) != LYS_IFF_RP) {
372 iff_setop(iff->expr, op, expr_size--);
373 }
374 continue;
375 } else if (isspace(c[i])) {
376 continue;
377 }
378
379 /* end of operator or operand -> find beginning and get what is it */
380 j = i + 1;
381 while (i >= 0 && !isspace(c[i]) && c[i] != '(') {
382 i--;
383 }
384 i++; /* go back by one step */
385
386 if (!strncmp(&c[i], "not", 3) && isspace(c[i + 3])) {
387 if (stack.index && (stack.stack[stack.index - 1] == LYS_IFF_NOT)) {
388 /* double not */
389 iff_stack_pop(&stack);
390 } else {
391 /* not has the highest priority, so do not pop from the stack
392 * as in case of AND and OR */
393 iff_stack_push(&stack, LYS_IFF_NOT);
394 }
395 } else if (!strncmp(&c[i], "and", 3) && isspace(c[i + 3])) {
396 /* as for OR - pop from the stack all operators with the same or higher
397 * priority and store them to the result, then push the AND to the stack */
398 while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_AND) {
399 op = iff_stack_pop(&stack);
400 iff_setop(iff->expr, op, expr_size--);
401 }
402 iff_stack_push(&stack, LYS_IFF_AND);
403 } else if (!strncmp(&c[i], "or", 2) && isspace(c[i + 2])) {
404 while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_OR) {
405 op = iff_stack_pop(&stack);
406 iff_setop(iff->expr, op, expr_size--);
407 }
408 iff_stack_push(&stack, LYS_IFF_OR);
409 } else {
410 /* feature name, length is j - i */
411
412 /* add it to the expression */
413 iff_setop(iff->expr, LYS_IFF_F, expr_size--);
414
415 /* now get the link to the feature definition */
416 f = lysp_feature_find(qname->mod, &c[i], j - i, 1);
417 if (!f) {
418 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_SYNTAX_YANG,
419 "Invalid value \"%s\" of if-feature - unable to find feature \"%.*s\".", qname->str, j - i, &c[i]);
420 rc = LY_EVALID;
421 goto error;
422 }
423 iff->features[f_size] = f;
424 LY_ARRAY_INCREMENT(iff->features);
425 f_size--;
426 }
427 }
428 while (stack.index) {
429 op = iff_stack_pop(&stack);
430 iff_setop(iff->expr, op, expr_size--);
431 }
432
433 if (++expr_size || ++f_size) {
434 /* not all expected operators and operands found */
435 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_SYNTAX_YANG,
436 "Invalid value \"%s\" of if-feature - processing error.", qname->str);
437 rc = LY_EINT;
438 } else {
439 rc = LY_SUCCESS;
440 }
441
442error:
443 /* cleanup */
444 iff_stack_clean(&stack);
445
446 return rc;
447}
448
449LY_ERR
450lys_eval_iffeatures(const struct ly_ctx *ctx, struct lysp_qname *iffeatures, ly_bool *enabled)
451{
452 LY_ERR ret;
453 struct lysc_iffeature iff = {0};
454
455 if (!iffeatures) {
456 *enabled = 1;
457 return LY_SUCCESS;
458 }
459
460 LY_CHECK_RET(lys_compile_iffeature(ctx, iffeatures, &iff));
461
462 ret = lysc_iffeature_value(&iff);
463 lysc_iffeature_free((struct ly_ctx *)ctx, &iff);
464 if (ret == LY_ENOT) {
465 *enabled = 0;
466 } else if (ret) {
467 return ret;
468 } else {
469 *enabled = 1;
470 }
471
472 return LY_SUCCESS;
473}
474
475LY_ERR
476lys_enable_features(struct lysp_module *pmod, const char **features)
477{
478 uint32_t i;
479 struct lysp_feature *f;
480 LY_ERR ret;
481
482 if (!features) {
483 /* keep all features disabled */
484 return LY_SUCCESS;
485 }
486
487 if (!strcmp(features[0], "*")) {
488 /* enable all features */
489 f = NULL;
490 i = 0;
491 while ((f = lysp_feature_next(f, pmod, &i))) {
492 f->flags |= LYS_FENABLED;
493 }
494 } else {
495 /* enable selected features */
496 for (i = 0; features[i]; ++i) {
497 /* find the feature */
498 f = lysp_feature_find(pmod, features[i], strlen(features[i]), 0);
499 if (!f) {
500 LOGERR(pmod->mod->ctx, LY_ENOTFOUND, "Feature \"%s\" not found in module \"%s\".", features[i],
501 pmod->mod->name);
502 return LY_ENOTFOUND;
503 }
504
505 /* enable feature */
506 f->flags |= LYS_FENABLED;
507 }
508 }
509
510check_iffeature:
511 /* check whether all enabled features have their if-features satisfied */
512 f = NULL;
513 i = 0;
514 while ((f = lysp_feature_next(f, pmod, &i))) {
515 if (!(f->flags & LYS_FENABLED) || !f->iffeatures) {
516 /* disabled feature or no if-features to check */
517 continue;
518 }
519
520 assert(f->iffeatures_c);
521 ret = lysc_iffeature_value(f->iffeatures_c);
522 if (ret == LY_ENOT) {
523 LOGWRN(pmod->mod->ctx, "Feature \"%s\" cannot be enabled because its \"if-feature\" is not satisfied.",
524 f->name);
525
526 /* disable feature and re-evaluate all the feature if-features again */
527 f->flags &= ~LYS_FENABLED;
528 goto check_iffeature;
529 } else if (ret) {
530 return ret;
531 } /* else if-feature satisfied */
532 }
533
534 return LY_SUCCESS;
535}
536
537/**
538 * @brief Check circular dependency of features - feature MUST NOT reference itself (via their if-feature statement).
539 *
540 * The function works in the same way as lys_compile_identity_circular_check() with different structures and error messages.
541 *
542 * @param[in] ctx Compile context for logging.
543 * @param[in] feature The feature referenced in if-feature statement (its depfeatures list is being extended by the feature
544 * being currently processed).
545 * @param[in] depfeatures The list of depending features of the feature being currently processed (not the one provided as @p feature)
546 * @return LY_SUCCESS if everything is ok.
547 * @return LY_EVALID if the feature references indirectly itself.
548 */
549static LY_ERR
550lys_compile_feature_circular_check(const struct ly_ctx *ctx, struct lysp_feature *feature, struct lysp_feature **depfeatures)
551{
552 LY_ERR ret = LY_SUCCESS;
553 LY_ARRAY_COUNT_TYPE u, v;
554 struct ly_set recursion = {0};
555 struct lysp_feature *drv;
556
557 if (!depfeatures) {
558 return LY_SUCCESS;
559 }
560
561 for (u = 0; u < LY_ARRAY_COUNT(depfeatures); ++u) {
562 if (feature == depfeatures[u]) {
563 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "Feature \"%s\" is indirectly referenced from itself.",
564 feature->name);
565 ret = LY_EVALID;
566 goto cleanup;
567 }
568 ret = ly_set_add(&recursion, depfeatures[u], 0, NULL);
569 LY_CHECK_GOTO(ret, cleanup);
570 }
571
572 for (v = 0; v < recursion.count; ++v) {
573 drv = recursion.objs[v];
574 if (!drv->depfeatures) {
575 continue;
576 }
577 for (u = 0; u < LY_ARRAY_COUNT(drv->depfeatures); ++u) {
578 if (feature == drv->depfeatures[u]) {
579 LOGVAL(ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "Feature \"%s\" is indirectly referenced from itself.",
580 feature->name);
581 ret = LY_EVALID;
582 goto cleanup;
583 }
584 ly_set_add(&recursion, drv->depfeatures[u], 0, NULL);
585 LY_CHECK_GOTO(ret, cleanup);
586 }
587 }
588
589cleanup:
590 ly_set_erase(&recursion, NULL);
591 return ret;
592}
593
594LY_ERR
595lys_compile_feature_iffeatures(struct lysp_module *pmod)
596{
597 LY_ARRAY_COUNT_TYPE u, v;
598 struct lysp_feature *f = NULL, **df;
599 uint32_t idx = 0;
600
601 while ((f = lysp_feature_next(f, pmod, &idx))) {
602 if (!f->iffeatures) {
603 continue;
604 }
605
606 /* compile if-features */
607 LY_ARRAY_CREATE_RET(pmod->mod->ctx, f->iffeatures_c, LY_ARRAY_COUNT(f->iffeatures), LY_EMEM);
608 LY_ARRAY_FOR(f->iffeatures, u) {
609 LY_ARRAY_INCREMENT(f->iffeatures_c);
610 LY_CHECK_RET(lys_compile_iffeature(pmod->mod->ctx, &(f->iffeatures)[u], &(f->iffeatures_c)[u]));
611 }
612 LY_ARRAY_FOR(f->iffeatures_c, u) {
613 LY_ARRAY_FOR(f->iffeatures_c[u].features, v) {
614 /* check for circular dependency - direct reference first,... */
615 if (f == f->iffeatures_c[u].features[v]) {
616 LOGVAL(pmod->mod->ctx, LY_VLOG_NONE, NULL, LYVE_REFERENCE, "Feature \"%s\" is referenced from itself.",
617 f->name);
618 return LY_EVALID;
619 }
620 /* ... and indirect circular reference */
621 LY_CHECK_RET(lys_compile_feature_circular_check(pmod->mod->ctx, f->iffeatures_c[u].features[v], f->depfeatures));
622
623 /* add itself into the dependants list */
624 LY_ARRAY_NEW_RET(pmod->mod->ctx, f->iffeatures_c[u].features[v]->depfeatures, df, LY_EMEM);
625 *df = f;
626 }
627 }
628 }
629
630 return LY_SUCCESS;
631}
632
633void
634lys_free_feature_iffeatures(struct lysp_module *pmod)
635{
636 struct lysp_feature *f = NULL;
637 uint32_t idx = 0;
638
639 while ((f = lysp_feature_next(f, pmod, &idx))) {
640 FREE_ARRAY(pmod->mod->ctx, f->iffeatures_c, lysc_iffeature_free);
641 f->iffeatures_c = NULL;
642 LY_ARRAY_FREE(f->depfeatures);
643 f->depfeatures = NULL;
644 }
645}