2 * Copyright (c) 2013, Olivier MATZ <zer0@droids-corp.org>
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the University of California, Berkeley nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include <sys/queue.h>
34 #include "cfzy_list.h"
35 #include "cfzy_htable.h"
36 #include "cfzy_string.h"
37 #include "cfzy_expr.h"
38 #include "cfzy_confnode.h"
39 #include "cfzy_conftree.h"
41 #define LOG(level, fmt, args...) \
42 CFZY_LOG("confnode", level, fmt, ##args)
44 /* alloc a new confnode */
45 struct cfzy_confnode *cfzy_confnode_alloc(struct cfzy_conftree *conftree)
47 struct cfzy_confnode *n;
49 n = malloc(sizeof(struct cfzy_confnode));
53 memset(n, 0, sizeof(*n));
54 TAILQ_INIT(&n->children);
56 n->conftree = conftree;
58 n->expr_deps = cfzy_list_alloc();
59 if (n->expr_deps == NULL) {
64 n->default_val_list = cfzy_list_alloc();
65 if (n->default_val_list == NULL) {
66 cfzy_list_free(n->expr_deps, NULL);
74 /* Free the node given as argument, and all its associated data */
75 void cfzy_confnode_free(struct cfzy_confnode *n)
77 struct cfzy_confnode *c;
78 struct cfzy_list_elt *e;
79 struct cfzy_confnode_defvalue *defval;
81 /* free the children */
82 while ((c = TAILQ_FIRST(&n->children)) != NULL) {
83 TAILQ_REMOVE(&n->children, c, child_next);
84 cfzy_confnode_free(c);
87 /* do the specific free first */
88 if (n->ops && n->ops->free)
91 /* free the deps expression list */
92 cfzy_list_free(n->expr_deps, (void *)cfzy_expr_free);
94 /* free the deps expression list (manually free each element
95 * instead of providing a function) */
96 while ((e = TAILQ_FIRST(n->default_val_list)) != NULL) {
97 TAILQ_REMOVE(n->default_val_list, e, next);
99 cfzy_expr_free(defval->expr);
104 cfzy_list_free(n->default_val_list, NULL);
106 if (n->node_deps != NULL)
107 cfzy_list_free(n->node_deps, NULL);
109 /* free the name, help, prompt, ... */
116 if (n->default_value)
117 free(n->default_value);
120 if (n->effective_value)
121 free(n->effective_value);
122 if (n->old_user_value)
123 free(n->old_user_value);
124 if (n->old_effective_value)
125 free(n->old_effective_value);
132 /* return the list of nodes required to enable this one */
133 struct cfzy_list_head *
134 cfzy_confnode_get_deplist(const struct cfzy_confnode *n)
136 struct cfzy_list_head *node_list = NULL, *var_list= NULL;
137 struct cfzy_list_elt *e, *e2;
138 struct cfzy_expr *expr;
139 struct cfzy_confnode *c;
141 const struct cfzy_confnode_defvalue *defval;
142 const struct cfzy_conftree *conftree;
144 node_list = cfzy_list_alloc();
145 if (node_list == NULL) {
146 LOG(ERR, "cannot allocate node list\n");
150 /* get associated configuration tree */
151 conftree = n->conftree;
153 /* list of dependencies (expression list) */
154 TAILQ_FOREACH(e, n->expr_deps, next) {
158 /* get list of variables for this expression */
159 var_list = cfzy_expr_get_vars(expr);
160 if (var_list == NULL) {
161 LOG(ERR, "cannot allocate get list\n");
165 /* list of variables in this expression */
166 TAILQ_FOREACH(e2, var_list, next) {
170 c = cfzy_htable_lookup(conftree->nodes_htable, varname);
172 LOG(ERR, "cannot find node <%s>\n", varname);
176 if (cfzy_list_elt_is_in_list(node_list, c))
179 if (cfzy_list_add_tail(node_list, c) < 0) {
180 LOG(ERR, "cannot add node in list\n");
185 cfzy_list_free(var_list, free);
189 /* list of conditional default values depending on an expression */
190 TAILQ_FOREACH(e, n->default_val_list, next) {
195 /* get list of variables for this expression */
196 var_list = cfzy_expr_get_vars(expr);
197 if (var_list == NULL) {
198 LOG(ERR, "cannot allocate get list\n");
202 /* list of variables in this expression */
203 TAILQ_FOREACH(e2, var_list, next) {
207 c = cfzy_htable_lookup(conftree->nodes_htable, varname);
209 LOG(ERR, "cannot find node <%s>\n", varname);
213 if (cfzy_list_elt_is_in_list(node_list, c))
216 if (cfzy_list_add_tail(node_list, c) < 0) {
217 LOG(ERR, "cannot add node in list\n");
222 cfzy_list_free(var_list, free);
227 if (n->parent != NULL && !cfzy_list_elt_is_in_list(node_list, n->parent)) {
229 if (cfzy_list_add_tail(node_list, n->parent) < 0) {
230 LOG(ERR, "cannot add node in list\n");
238 if (node_list != NULL)
239 cfzy_list_free(node_list, NULL);
240 if (var_list != NULL)
241 cfzy_list_free(var_list, free);
246 /* Add a conditional default value to a node */
247 int cfzy_confnode_add_defval(struct cfzy_confnode *n, const char *val,
248 const char *expr_buf)
250 struct cfzy_confnode_defvalue *defval;
252 defval = malloc(sizeof(*defval));
256 memset(defval, 0, sizeof(*defval));
257 defval->expr = cfzy_expr_parse(expr_buf);
258 if (defval->expr == NULL) {
263 defval->val = strdup(val);
264 if (defval->val == NULL) {
265 cfzy_expr_free(defval->expr);
270 if (cfzy_list_add_tail(n->default_val_list, defval) < 0) {
272 cfzy_expr_free(defval->expr);
280 /* Parse a line structure (and its associated line buffer) to match an
281 * attribute of a confnode. Return 0 if the line matches, else return
282 * -1 if we cannot match a valid attribute. */
283 enum cfzy_parse_return
284 cfzy_confnode_add_attr(struct cfzy_confnode *n,
285 const struct cfzy_token_list *tklist)
287 enum cfzy_parse_return ret;
288 struct cfzy_token *first_tok, *tok;
289 const char *linebuf = tklist->linebuf;
291 first_tok = TAILQ_FIRST(&tklist->list);
293 /* specific attribute */
294 if (n->ops->add_attr != NULL) {
295 ret = n->ops->add_attr(n, tklist, linebuf);
300 /* syntax is: "prompt content" */
301 if (tklist->n_token == 2 &&
302 !strcmp(first_tok->str, "prompt")) {
304 if (n->flags & CFZY_F_NO_SET_PROMPT) {
305 LOG(ERR, "node does not support 'prompt' attr\n");
309 tok = TAILQ_NEXT(first_tok, next);
310 if (n->prompt != NULL) {
314 n->prompt = strdup(tok->str);
315 if (n->prompt == NULL) {
316 LOG(ERR, "cannot allocate prompt\n");
322 /* syntax is: "requires EXPRESSION" */
323 if (tklist->n_token > 1 &&
324 !strcmp(first_tok->str, "requires")) {
325 struct cfzy_expr *exp;
327 if (n->flags & CFZY_F_NO_SET_DEPS) {
328 LOG(ERR, "node does not support 'requires' attr\n");
332 tok = TAILQ_NEXT(first_tok, next);
333 exp = cfzy_expr_parse(linebuf + tok->offset);
335 LOG(ERR, "cannot parse expression\n");
339 cfzy_list_add_tail(n->expr_deps, exp);
343 /* syntax is: "default VALUE" */
344 if (tklist->n_token == 2 &&
345 !strcmp(first_tok->str, "default")) {
347 if (n->flags & CFZY_F_NO_SET_DEFAULT) {
348 LOG(ERR, "node does not support 'default' attr\n");
352 tok = TAILQ_NEXT(first_tok, next);
353 if (cfzy_confnode_str2bool(n, tok->str) < 0) {
354 LOG(ERR, "invalid value for this node\n");
358 if (n->default_value != NULL)
359 free(n->default_value);
360 n->default_value = strdup(tok->str);
361 if (n->default_value == NULL) {
362 LOG(ERR, "cannot add default value\n");
369 /* syntax is: "default VALUE if EXPR" */
370 if (tklist->n_token >= 4 &&
371 !strcmp(first_tok->str, "default")) {
372 struct cfzy_token *tok2;
374 tok = TAILQ_NEXT(first_tok, next); /* points to value */
375 tok2 = TAILQ_NEXT(tok, next); /* points to "if" */
377 if (strcmp(tok2->str, "if"))
380 if (n->flags & CFZY_F_NO_SET_DEFAULT) {
381 LOG(ERR, "node does not support 'default' attr\n");
385 tok2 = TAILQ_NEXT(tok2, next); /* points to expression */
387 if (cfzy_confnode_str2bool(n, tok->str) < 0) {
388 LOG(ERR, "invalid value for this node\n");
392 if (cfzy_confnode_add_defval(n, tok->str,
393 linebuf + tok2->offset) < 0) {
394 LOG(ERR, "cannot add default value\n");
404 /* write config value in file f in a dotconfig-like manner. Return 0
406 int cfzy_confnode_dotconfig_write(const struct cfzy_confnode *n, FILE *f)
409 const struct cfzy_confnode *c;
411 if (n->ops->dotconfig_write)
412 return n->ops->dotconfig_write(n, f);
414 if (n->flags & CFZY_F_DOTCONF_SHOW_TITLE) {
418 "#\n", n->prompt) < 0)
422 /* only dump the children if effective value is not defined
423 * (some nodes like "comment", "if", "menu", ... can have a
424 * NULL effective value) */
425 if (n->effective_value == NULL)
428 /* when a boolean is unset, use the "is not set" syntax */
429 if (n->flags & CFZY_F_VAL_IS_BOOL) {
430 val = cfzy_confnode_str2bool(n, n->effective_value);
435 if (fprintf(f, "# CONFIG_%s is not set\n",
436 cfzy_confnode_name(n)) < 0)
442 /* common case: dump the value, with or without quotes */
443 if (n->flags & CFZY_F_QUOTE_VAL) {
444 char *quoted_value = cfzy_string_quote(n->effective_value);
446 if (quoted_value == NULL)
449 if (fprintf(f, "CONFIG_%s=%s\n", cfzy_confnode_name(n),
457 if (fprintf(f, "CONFIG_%s=%s\n", cfzy_confnode_name(n),
458 n->effective_value) < 0)
463 TAILQ_FOREACH(c, &n->children, child_next) {
464 if (cfzy_confnode_dotconfig_write(c, f) < 0)
470 /* write config value in file f in a dotconfig-like manner. Return 0
472 int cfzy_confnode_c_hdr_write(const struct cfzy_confnode *n, FILE *f)
475 const struct cfzy_confnode *c;
477 if (n->ops->c_hdr_write)
478 return n->ops->c_hdr_write(n, f);
480 /* if no value, don't dump the value of the node, just the children */
481 if (n->effective_value == NULL)
484 if (n->flags & CFZY_F_VAL_IS_BOOL) {
485 val = cfzy_confnode_str2bool(n, n->effective_value);
489 /* bool is false -> undef */
491 if (fprintf(f, "#undef CONFIG_%s\n",
492 cfzy_confnode_name(n)) < 0)
497 /* bool is true -> #define to 1 */
498 if (fprintf(f, "#define CONFIG_%s 1\n",
499 cfzy_confnode_name(n)) < 0)
502 /* common case: dump the value, with or without quotes */
503 else if (n->flags & CFZY_F_QUOTE_VAL) {
504 char *quoted_value = cfzy_string_quote(n->effective_value);
506 if (quoted_value == NULL)
509 if (fprintf(f, "#define CONFIG_%s %s\n",
510 cfzy_confnode_name(n), quoted_value) < 0) {
517 if (fprintf(f, "#define CONFIG_%s %s\n",
518 cfzy_confnode_name(n), n->effective_value) < 0)
523 TAILQ_FOREACH(c, &n->children, child_next) {
524 if (cfzy_confnode_c_hdr_write(c, f) < 0)
530 /* Close the node beeing parsed */
531 int cfzy_confnode_close(struct cfzy_confnode *n)
534 return n->ops->close(n);
539 /* Return the boolean value of the node given the string value */
540 int cfzy_confnode_str2bool(const struct cfzy_confnode *n,
543 if (n->ops->str2bool)
544 return n->ops->str2bool(n, value);
546 /* if there is no specific function, any non-NULL value
554 /* Return a string identifying the node type ("config", "menuconfig",
556 const char *cfzy_confnode_get_type_str(const struct cfzy_confnode *n)
558 if (n->ops->get_type_str)
559 return n->ops->get_type_str(n);
564 static int __get_path(const struct cfzy_confnode *n, char *buf,
569 if (n->parent == NULL)
570 return snprintf(buf, len, "/");
572 ret = __get_path(n->parent, buf, len, 0);
579 if (n->flags & CFZY_F_INVISIBLE)
582 /* a visible node must have a name */
586 ret = snprintf(buf + off, len - off, "%s%s", n->name,
588 if (ret >= len - off)
594 /* Dump path in buffer. Invisible nodes like 'if' are ignored. */
595 int cfzy_confnode_path(const struct cfzy_confnode *n, char *buf, int len)
599 ret = __get_path(n, buf, len, 1);
605 /* dump a config file in a file */
606 int cfzy_confnode_dump(const struct cfzy_confnode *n, FILE *f, int mode)
608 const struct cfzy_confnode *c;
609 struct cfzy_confnode_defvalue *defval;
610 struct cfzy_list_elt *e;
611 struct cfzy_expr *expr;
614 if (fprintf(f, "%s\n", cfzy_confnode_get_type_str(n)) < 0)
616 if (fprintf(f, " name: %s\n", cfzy_confnode_name(n)) < 0)
618 if (fprintf(f, " prompt: %s\n", n->prompt) < 0)
620 if (cfzy_confnode_path(n, buf, sizeof(buf)) == 0) {
621 if (fprintf(f, " path: %s\n", buf) < 0)
625 if (mode == CFZY_DUMP_MODE_MINIMAL)
628 /* explicit dependencies */
629 TAILQ_FOREACH(e, n->expr_deps, next) {
631 if (cfzy_expr_to_str(expr, buf, sizeof(buf)) < 0)
633 if (fprintf(f, " requires: %s\n", buf) < 0)
637 /* XXX display valid values */
640 TAILQ_FOREACH(e, n->default_val_list, next) {
643 if (cfzy_expr_to_str(defval->expr, buf, sizeof(buf)) < 0)
646 if (fprintf(f, " default: %s if %s\n", defval->val, buf) < 0)
649 if (fprintf(f, " node_default %s\n", n->default_value) < 0)
651 if (fprintf(f, " user_value %s\n", n->user_value) < 0)
653 if (fprintf(f, " effective_value %s\n", n->effective_value) < 0)
656 if (mode == CFZY_DUMP_MODE_STANDARD)
659 if (fprintf(f, " old_user_value %s\n", n->old_user_value) < 0)
661 if (fprintf(f, " old_effective_value %s\n", n->old_effective_value) < 0)
663 if (fprintf(f, " parsed at %s:%d\n", n->filename, n->linenum) < 0)
666 if (fprintf(f, "\n") < 0)
669 if (mode == CFZY_DUMP_MODE_FULL)
673 TAILQ_FOREACH(c, &n->children, child_next) {
674 if (cfzy_confnode_dump(c, f, CFZY_DUMP_MODE_FULL_RECURSIVE) < 0)
678 /* close node if it's a directory */
679 if (n->flags & CFZY_F_IS_DIR) {
680 if (fprintf(f, "end%s\n\n", cfzy_confnode_get_type_str(n)) < 0)
687 /* set the uservalue of a node */
688 int cfzy_confnode_set_uservalue(struct cfzy_confnode *n, const char *value)
690 char *newvalue = NULL;
692 if (n->ops->set_uservalue)
693 return n->ops->set_uservalue(n, value);
695 /* check if value is valid */
696 if (cfzy_confnode_str2bool(n, value) < 0)
700 newvalue = strdup(value);
701 if (newvalue == NULL) {
702 LOG(ERR, "cannot allocate new value\n");
707 if (n->user_value != NULL)
710 n->user_value = newvalue;
714 /* Get the boolean value of a node, given its name, used as a callback
715 * by cfzy_expr_parse() when parsing an expression for
716 * cfzy_confnode_evaluate() */
717 int cfzy_confnode_get_boolvalue(const char *name,
718 const struct cfzy_conftree *conftree)
720 const struct cfzy_confnode *n;
722 n = cfzy_htable_lookup(conftree->nodes_htable, name);
726 return cfzy_confnode_str2bool(n, n->effective_value);
729 /* return 0 if a parent node is disabled or if a prerequisite
730 * expression is false, else return 1. On error, return -1. */
731 int cfzy_confnode_check_deps(struct cfzy_confnode *n)
733 struct cfzy_list_elt *e;
734 struct cfzy_expr *expr;
735 struct cfzy_confnode *parent;
736 int (*get_boolvalue)(const char *, const struct cfzy_conftree *);
737 int (*get_boolvalue_casted)(const char *, void *);
740 /* we use an intermediate variable to have a compiler warning
741 * if the prototype of cfzy_confnode_get_boolvalue changes. We
742 * know that that these types are compatible here. */
743 get_boolvalue = cfzy_confnode_get_boolvalue;
744 get_boolvalue_casted = (void *)get_boolvalue;
746 /* check the dep list first */
747 TAILQ_FOREACH(e, n->expr_deps, next) {
750 ret = cfzy_expr_eval(expr, get_boolvalue_casted, n->conftree);
754 /* the expression is evaluated to 0, we cannot enable
757 n->effective_value = NULL;
758 LOG(DEBUG, "disable node <%s> (expr dep)\n",
759 cfzy_confnode_name(n));
764 /* check the parent nodes */
765 for (parent = n->parent; parent != NULL; parent = parent->parent) {
766 ret = cfzy_confnode_str2bool(parent, parent->effective_value);
770 /* a parent node is evaluated to 0, we cannot enable
773 n->effective_value = NULL;
774 LOG(DEBUG, "disable node <%s> (parent dep)\n",
775 cfzy_confnode_name(n));
783 /* Evaluate the effective value of a node, assuming all prerequisites
784 * are already evaluated. */
785 int cfzy_confnode_evaluate(struct cfzy_confnode *n)
787 struct cfzy_list_elt *e;
788 struct cfzy_confnode_defvalue *defval;
789 int (*get_boolvalue)(const char *, const struct cfzy_conftree *);
790 int (*get_boolvalue_casted)(const char *, void *);
793 LOG(DEBUG, "evaluating node <%s>\n", cfzy_confnode_name(n));
795 /* we use an intermediate variable to have a compiler warning
796 * if the prototype of cfzy_confnode_get_boolvalue changes. We
797 * know that that these types are compatible here. */
798 get_boolvalue = cfzy_confnode_get_boolvalue;
799 get_boolvalue_casted = (void *)get_boolvalue;
801 /* remove previous effective value */
802 if (n->effective_value != NULL) {
803 free(n->effective_value);
804 n->effective_value = NULL;
807 /* check prerequisites and parents */
808 ret = cfzy_confnode_check_deps(n);
810 /* we cannot enable this node due to dependencies */
811 n->effective_value = NULL;
812 LOG(DEBUG, "disable node <%s> (expr dep)\n",
813 cfzy_confnode_name(n));
820 /* user value has the priority */
821 if (n->user_value != NULL) {
822 n->effective_value = strdup(n->user_value);
823 if (n->effective_value == NULL)
826 LOG(DEBUG, "set node <%s> to user value <%s>\n",
827 cfzy_confnode_name(n), n->effective_value);
831 /* assign a default value */
832 TAILQ_FOREACH(e, n->default_val_list, next) {
835 ret = cfzy_expr_eval(defval->expr, get_boolvalue_casted,
840 /* the expression is evaluated to 1, use this default
841 * value for this node */
843 n->effective_value = strdup(defval->val);
844 if (n->effective_value == NULL)
846 LOG(DEBUG, "set node <%s> to default_list value <%s>\n",
847 cfzy_confnode_name(n), n->effective_value);
852 if (n->default_value != NULL)
853 n->effective_value = strdup(n->default_value);
855 LOG(DEBUG, "set node <%s> to default value <%s>\n",
856 cfzy_confnode_name(n), n->effective_value);