From 19b576e68d1eb396821dde6f5ff7fb9848d338cc Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:44 +0900 Subject: [PATCH 01/16] kconfig: make default prompt of mainmenu less specific If "mainmenu" is not specified, "Linux Kernel Configuration" is used as a default prompt. Given that Kconfig is used in other projects than Linux, let's use a more generic prompt, "Main menu". Suggested-by: Sam Ravnborg Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/zconf.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/zconf.y b/config/zconf.y index 228a14b..e31e3d3 100644 --- a/config/zconf.y +++ b/config/zconf.y @@ -533,7 +533,7 @@ void conf_parse(const char *name) if (!menu_has_prompt(&rootmenu)) { current_entry = &rootmenu; - menu_add_prompt(P_MENU, "Linux Kernel Configuration", NULL); + menu_add_prompt(P_MENU, "Main menu", NULL); } menu_finalize(&rootmenu); -- 2.31.1 From a6b2c7e5248c8300ebb3393d746ec20ca92cabc3 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:45 +0900 Subject: [PATCH 02/16] kconfig: add built-in function support This commit adds a new concept 'function' to do more text processing in Kconfig. A function call looks like this: $(function,arg1,arg2,arg3,...) This commit adds the basic infrastructure to expand functions. Change the text expansion helpers to take arguments. Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/preprocess.c | 142 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 12 deletions(-) diff --git a/config/preprocess.c b/config/preprocess.c index a2eb2eb..f32a496 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -10,6 +10,10 @@ #include "list.h" +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static char *expand_string_with_args(const char *in, int argc, char *argv[]); + static void __attribute__((noreturn)) pperror(const char *format, ...) { va_list ap; @@ -92,20 +96,123 @@ void env_write_dep(FILE *f, const char *autoconfig_name) } } -static char *eval_clause(const char *str, size_t len) +/* + * Built-in functions + */ +struct function { + const char *name; + unsigned int min_args; + unsigned int max_args; + char *(*func)(int argc, char *argv[]); +}; + +static const struct function function_table[] = { + /* Name MIN MAX Function */ +}; + +#define FUNCTION_MAX_ARGS 16 + +static char *function_expand(const char *name, int argc, char *argv[]) { - char *tmp, *name, *res; + const struct function *f; + int i; + + for (i = 0; i < ARRAY_SIZE(function_table); i++) { + f = &function_table[i]; + if (strcmp(f->name, name)) + continue; + + if (argc < f->min_args) + pperror("too few function arguments passed to '%s'", + name); + + if (argc > f->max_args) + pperror("too many function arguments passed to '%s'", + name); + + return f->func(argc, argv); + } + + return NULL; +} + +/* + * Evaluate a clause with arguments. argc/argv are arguments from the upper + * function call. + * + * Returned string must be freed when done + */ +static char *eval_clause(const char *str, size_t len, int argc, char *argv[]) +{ + char *tmp, *name, *res, *prev, *p; + int new_argc = 0; + char *new_argv[FUNCTION_MAX_ARGS]; + int nest = 0; + int i; tmp = xstrndup(str, len); - name = expand_string(tmp); + prev = p = tmp; + + /* + * Split into tokens + * The function name and arguments are separated by a comma. + * For example, if the function call is like this: + * $(foo,$(x),$(y)) + * + * The input string for this helper should be: + * foo,$(x),$(y) + * + * and split into: + * new_argv[0] = 'foo' + * new_argv[1] = '$(x)' + * new_argv[2] = '$(y)' + */ + while (*p) { + if (nest == 0 && *p == ',') { + *p = 0; + if (new_argc >= FUNCTION_MAX_ARGS) + pperror("too many function arguments"); + new_argv[new_argc++] = prev; + prev = p + 1; + } else if (*p == '(') { + nest++; + } else if (*p == ')') { + nest--; + } - res = env_expand(name); + p++; + } + new_argv[new_argc++] = prev; + + /* + * Shift arguments + * new_argv[0] represents a function name or a variable name. Put it + * into 'name', then shift the rest of the arguments. This simplifies + * 'const' handling. + */ + name = expand_string_with_args(new_argv[0], argc, argv); + new_argc--; + for (i = 0; i < new_argc; i++) + new_argv[i] = expand_string_with_args(new_argv[i + 1], + argc, argv); + + /* Look for built-in functions */ + res = function_expand(name, new_argc, new_argv); if (res) goto free; + /* Last, try environment variable */ + if (new_argc == 0) { + res = env_expand(name); + if (res) + goto free; + } + res = xstrdup(""); free: + for (i = 0; i < new_argc; i++) + free(new_argv[i]); free(name); free(tmp); @@ -124,14 +231,14 @@ free: * after the corresponding closing parenthesis, in this case, *str will be * $(BAR) */ -char *expand_dollar(const char **str) +static char *expand_dollar_with_args(const char **str, int argc, char *argv[]) { const char *p = *str; const char *q; int nest = 0; /* - * In Kconfig, variable references always start with "$(". + * In Kconfig, variable/function references always start with "$(". * Neither single-letter variables as in $A nor curly braces as in ${CC} * are supported. '$' not followed by '(' loses its special meaning. */ @@ -158,10 +265,16 @@ char *expand_dollar(const char **str) /* Advance 'str' to after the expanded initial portion of the string */ *str = q + 1; - return eval_clause(p, q - p); + return eval_clause(p, q - p, argc, argv); +} + +char *expand_dollar(const char **str) +{ + return expand_dollar_with_args(str, 0, NULL); } -static char *__expand_string(const char **str, bool (*is_end)(char c)) +static char *__expand_string(const char **str, bool (*is_end)(char c), + int argc, char *argv[]) { const char *in, *p; char *expansion, *out; @@ -177,7 +290,7 @@ static char *__expand_string(const char **str, bool (*is_end)(char c)) if (*p == '$') { in_len = p - in; p++; - expansion = expand_dollar(&p); + expansion = expand_dollar_with_args(&p, argc, argv); out_len += in_len + strlen(expansion); out = xrealloc(out, out_len); strncat(out, in, in_len); @@ -210,13 +323,18 @@ static bool is_end_of_str(char c) } /* - * Expand variables in the given string. Undefined variables + * Expand variables and functions in the given string. Undefined variables * expand to an empty string. * The returned string must be freed when done. */ +static char *expand_string_with_args(const char *in, int argc, char *argv[]) +{ + return __expand_string(&in, is_end_of_str, argc, argv); +} + char *expand_string(const char *in) { - return __expand_string(&in, is_end_of_str); + return expand_string_with_args(in, 0, NULL); } static bool is_end_of_token(char c) @@ -234,5 +352,5 @@ static bool is_end_of_token(char c) */ char *expand_one_token(const char **str) { - return __expand_string(str, is_end_of_token); + return __expand_string(str, is_end_of_token, 0, NULL); } -- 2.31.1 From 2344a769a43b21f2a45a0397cc6d518b7cea7e33 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:46 +0900 Subject: [PATCH 03/16] kconfig: add 'shell' built-in function This accepts a single command to execute. It returns the standard output from it. [Example code] config HELLO string default "$(shell,echo hello world)" config Y def_bool $(shell,echo y) [Result] $ make -s alldefconfig && tail -n 2 .config CONFIG_HELLO="hello world" CONFIG_Y=y Caveat: Like environments, functions are expanded in the lexer. You cannot pass symbols to function arguments. This is a limitation to simplify the implementation. I want to avoid the dynamic function evaluation, which would introduce much more complexity. Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/preprocess.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/config/preprocess.c b/config/preprocess.c index f32a496..528be59 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -106,8 +106,49 @@ struct function { char *(*func)(int argc, char *argv[]); }; +static char *do_shell(int argc, char *argv[]) +{ + FILE *p; + char buf[256]; + char *cmd; + size_t nread; + int i; + + cmd = argv[0]; + + p = popen(cmd, "r"); + if (!p) { + perror(cmd); + exit(1); + } + + nread = fread(buf, 1, sizeof(buf), p); + if (nread == sizeof(buf)) + nread--; + + /* remove trailing new lines */ + while (buf[nread - 1] == '\n') + nread--; + + buf[nread] = 0; + + /* replace a new line with a space */ + for (i = 0; i < nread; i++) { + if (buf[i] == '\n') + buf[i] = ' '; + } + + if (pclose(p) == -1) { + perror(cmd); + exit(1); + } + + return xstrdup(buf); +} + static const struct function function_table[] = { /* Name MIN MAX Function */ + { "shell", 1, 1, do_shell }, }; #define FUNCTION_MAX_ARGS 16 -- 2.31.1 From 5b345dbb8935d88986224f18f63d605bfa64e12c Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:48 +0900 Subject: [PATCH 04/16] kconfig: begin PARAM state only when seeing a command keyword Currently, any statement line starts with a keyword with TF_COMMAND flag. So, the following three lines are dead code. alloc_string(yytext, yyleng); zconflval.string = text; return T_WORD; If a T_WORD token is returned in this context, it will cause syntax error in the parser anyway. The next commit will support the assignment statement where a line starts with an arbitrary identifier. So, I want the lexer to switch to the PARAM state only when it sees a command keyword. Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/zconf.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/zconf.l b/config/zconf.l index 3c3f52a..fde86ba 100644 --- a/config/zconf.l +++ b/config/zconf.l @@ -102,10 +102,10 @@ n [A-Za-z0-9_-] { {n}+ { const struct kconf_id *id = kconf_id_lookup(yytext, yyleng); - BEGIN(PARAM); current_pos.file = current_file; current_pos.lineno = yylineno; if (id && id->flags & TF_COMMAND) { + BEGIN(PARAM); yylval.id = id; return id->token; } -- 2.31.1 From b68427cdb960942fede090e843b046749a0789f3 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:49 +0900 Subject: [PATCH 05/16] kconfig: support user-defined function and recursively expanded variable Now, we got a basic ability to test compiler capability in Kconfig. config CC_HAS_STACKPROTECTOR def_bool $(shell,($(CC) -Werror -fstack-protector -E -x c /dev/null -o /dev/null 2>/dev/null) && echo y || echo n) This works, but it is ugly to repeat this long boilerplate. We want to describe like this: config CC_HAS_STACKPROTECTOR bool default $(cc-option,-fstack-protector) It is straight-forward to add a new function, but I do not like to hard-code specialized functions like that. Hence, here is another feature, user-defined function. This works as a textual shorthand with parameterization. A user-defined function is defined by using the = operator, and can be referenced in the same way as built-in functions. A user-defined function in Make is referenced like $(call my-func,arg1,arg2), but I omitted the 'call' to make the syntax shorter. The definition of a user-defined function contains $(1), $(2), etc. in its body to reference the parameters. It is grammatically valid to pass more or fewer arguments when calling it. We already exploit this feature in our makefiles; scripts/Kbuild.include defines cc-option which takes two arguments at most, but most of the callers pass only one argument. By the way, a variable is supported as a subset of this feature since a variable is "a user-defined function with zero argument". In this context, I mean "variable" as recursively expanded variable. I will add a different flavored variable in the next commit. The code above can be written as follows: [Example Code] success = $(shell,($(1)) >/dev/null 2>&1 && echo y || echo n) cc-option = $(success,$(CC) -Werror $(1) -E -x c /dev/null -o /dev/null) config CC_HAS_STACKPROTECTOR def_bool $(cc-option,-fstack-protector) [Result] $ make -s alldefconfig && tail -n 1 .config CONFIG_CC_HAS_STACKPROTECTOR=y Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/lkc_proto.h | 2 ++ config/preprocess.c | 86 ++++++++++++++++++++++++++++++++++++++++++++- config/zconf.l | 17 +++++++-- config/zconf.y | 19 +++++++++- 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/config/lkc_proto.h b/config/lkc_proto.h index c46929f..2b16d6e 100644 --- a/config/lkc_proto.h +++ b/config/lkc_proto.h @@ -50,6 +50,8 @@ const char * prop_get_type_name(enum prop_type type); /* preprocess.c */ void env_write_dep(FILE *f, const char *auto_conf_name); +void variable_add(const char *name, const char *value); +void variable_all_del(void); char *expand_string(const char *in); char *expand_dollar(const char **str); char *expand_one_token(const char **str); diff --git a/config/preprocess.c b/config/preprocess.c index 528be59..46487fe 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -177,6 +177,72 @@ static char *function_expand(const char *name, int argc, char *argv[]) return NULL; } +/* + * Variables (and user-defined functions) + */ +static LIST_HEAD(variable_list); + +struct variable { + char *name; + char *value; + struct list_head node; +}; + +static struct variable *variable_lookup(const char *name) +{ + struct variable *v; + + list_for_each_entry(v, &variable_list, node) { + if (!strcmp(name, v->name)) + return v; + } + + return NULL; +} + +static char *variable_expand(const char *name, int argc, char *argv[]) +{ + struct variable *v; + + v = variable_lookup(name); + if (!v) + return NULL; + + return expand_string_with_args(v->value, argc, argv); +} + +void variable_add(const char *name, const char *value) +{ + struct variable *v; + + v = variable_lookup(name); + if (v) { + free(v->value); + } else { + v = xmalloc(sizeof(*v)); + v->name = xstrdup(name); + list_add_tail(&v->node, &variable_list); + } + + v->value = xstrdup(value); +} + +static void variable_del(struct variable *v) +{ + list_del(&v->node); + free(v->name); + free(v->value); + free(v); +} + +void variable_all_del(void) +{ + struct variable *v, *tmp; + + list_for_each_entry_safe(v, tmp, &variable_list, node) + variable_del(v); +} + /* * Evaluate a clause with arguments. argc/argv are arguments from the upper * function call. @@ -185,14 +251,26 @@ static char *function_expand(const char *name, int argc, char *argv[]) */ static char *eval_clause(const char *str, size_t len, int argc, char *argv[]) { - char *tmp, *name, *res, *prev, *p; + char *tmp, *name, *res, *endptr, *prev, *p; int new_argc = 0; char *new_argv[FUNCTION_MAX_ARGS]; int nest = 0; int i; + unsigned long n; tmp = xstrndup(str, len); + /* + * If variable name is '1', '2', etc. It is generally an argument + * from a user-function call (i.e. local-scope variable). If not + * available, then look-up global-scope variables. + */ + n = strtoul(tmp, &endptr, 10); + if (!*endptr && n > 0 && n <= argc) { + res = xstrdup(argv[n - 1]); + goto free_tmp; + } + prev = p = tmp; /* @@ -238,6 +316,11 @@ static char *eval_clause(const char *str, size_t len, int argc, char *argv[]) new_argv[i] = expand_string_with_args(new_argv[i + 1], argc, argv); + /* Search for variables */ + res = variable_expand(name, new_argc, new_argv); + if (res) + goto free; + /* Look for built-in functions */ res = function_expand(name, new_argc, new_argv); if (res) @@ -255,6 +338,7 @@ free: for (i = 0; i < new_argc; i++) free(new_argv[i]); free(name); +free_tmp: free(tmp); return res; diff --git a/config/zconf.l b/config/zconf.l index fde86ba..f1add95 100644 --- a/config/zconf.l +++ b/config/zconf.l @@ -1,12 +1,13 @@ %option nostdinit noyywrap never-interactive full ecs %option 8bit nodefault yylineno -%x COMMAND HELP STRING PARAM +%x COMMAND HELP STRING PARAM ASSIGN_VAL %{ /* * Copyright (C) 2002 Roman Zippel * Released under the terms of the GNU GPL v2.0. */ +#include #include #include #include @@ -111,8 +112,10 @@ n [A-Za-z0-9_-] } alloc_string(yytext, yyleng); yylval.string = text; - return T_WORD; + return T_VARIABLE; } + "=" { BEGIN(ASSIGN_VAL); return T_ASSIGN; } + [[:blank:]]+ . warn_ignored_character(*yytext); \n { BEGIN(INITIAL); @@ -120,6 +123,16 @@ n [A-Za-z0-9_-] } } +{ + [^[:blank:]\n]+.* { + alloc_string(yytext, yyleng); + yylval.string = text; + return T_ASSIGN_VAL; + } + \n { BEGIN(INITIAL); return T_EOL; } + . +} + { "&&" return T_AND; "||" return T_OR; diff --git a/config/zconf.y b/config/zconf.y index e31e3d3..addd4a6 100644 --- a/config/zconf.y +++ b/config/zconf.y @@ -77,6 +77,9 @@ static struct menu *current_menu, *current_entry; %token T_CLOSE_PAREN %token T_OPEN_PAREN %token T_EOL +%token T_VARIABLE +%token T_ASSIGN +%token T_ASSIGN_VAL %left T_OR %left T_AND @@ -92,7 +95,7 @@ static struct menu *current_menu, *current_entry; %type end %type option_name %type if_entry menu_entry choice_entry -%type symbol_option_arg word_opt +%type symbol_option_arg word_opt assign_val %destructor { fprintf(stderr, "%s:%d: missing end statement for this entry\n", @@ -143,6 +146,7 @@ common_stmt: | config_stmt | menuconfig_stmt | source_stmt + | assignment_stmt ; option_error: @@ -511,6 +515,15 @@ symbol: nonconst_symbol word_opt: /* empty */ { $$ = NULL; } | T_WORD +/* assignment statement */ + +assignment_stmt: T_VARIABLE T_ASSIGN assign_val T_EOL { variable_add($1, $3); free($1); free($3); } + +assign_val: + /* empty */ { $$ = xstrdup(""); }; + | T_ASSIGN_VAL +; + %% void conf_parse(const char *name) @@ -526,6 +539,10 @@ void conf_parse(const char *name) if (getenv("ZCONF_DEBUG")) yydebug = 1; yyparse(); + + /* Variables are expanded in the parse phase. We can free them here. */ + variable_all_del(); + if (yynerrs) exit(1); if (!modules_sym) -- 2.31.1 From dd88a7e5b482637a26c67ffa1cdf7ebed204161d Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:50 +0900 Subject: [PATCH 06/16] kconfig: support simply expanded variable The previous commit added variable and user-defined function. They work similarly in the sense that the evaluation is deferred until they are used. This commit adds another type of variable, simply expanded variable, as we see in Make. The := operator defines a simply expanded variable, expanding the righthand side immediately. This works like traditional programming language variables. Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/lkc_proto.h | 7 ++++++- config/preprocess.c | 19 ++++++++++++++++--- config/zconf.l | 3 ++- config/zconf.y | 5 +++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/config/lkc_proto.h b/config/lkc_proto.h index 2b16d6e..6303193 100644 --- a/config/lkc_proto.h +++ b/config/lkc_proto.h @@ -49,8 +49,13 @@ const char * sym_get_string_value(struct symbol *sym); const char * prop_get_type_name(enum prop_type type); /* preprocess.c */ +enum variable_flavor { + VAR_SIMPLE, + VAR_RECURSIVE, +}; void env_write_dep(FILE *f, const char *auto_conf_name); -void variable_add(const char *name, const char *value); +void variable_add(const char *name, const char *value, + enum variable_flavor flavor); void variable_all_del(void); char *expand_string(const char *in); char *expand_dollar(const char **str); diff --git a/config/preprocess.c b/config/preprocess.c index 46487fe..d103683 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -185,6 +185,7 @@ static LIST_HEAD(variable_list); struct variable { char *name; char *value; + enum variable_flavor flavor; struct list_head node; }; @@ -203,15 +204,22 @@ static struct variable *variable_lookup(const char *name) static char *variable_expand(const char *name, int argc, char *argv[]) { struct variable *v; + char *res; v = variable_lookup(name); if (!v) return NULL; - return expand_string_with_args(v->value, argc, argv); + if (v->flavor == VAR_RECURSIVE) + res = expand_string_with_args(v->value, argc, argv); + else + res = xstrdup(v->value); + + return res; } -void variable_add(const char *name, const char *value) +void variable_add(const char *name, const char *value, + enum variable_flavor flavor) { struct variable *v; @@ -224,7 +232,12 @@ void variable_add(const char *name, const char *value) list_add_tail(&v->node, &variable_list); } - v->value = xstrdup(value); + v->flavor = flavor; + + if (flavor == VAR_SIMPLE) + v->value = expand_string(value); + else + v->value = xstrdup(value); } static void variable_del(struct variable *v) diff --git a/config/zconf.l b/config/zconf.l index f1add95..d0f5b13 100644 --- a/config/zconf.l +++ b/config/zconf.l @@ -114,7 +114,8 @@ n [A-Za-z0-9_-] yylval.string = text; return T_VARIABLE; } - "=" { BEGIN(ASSIGN_VAL); return T_ASSIGN; } + "=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_RECURSIVE; return T_ASSIGN; } + ":=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_SIMPLE; return T_ASSIGN; } [[:blank:]]+ . warn_ignored_character(*yytext); \n { diff --git a/config/zconf.y b/config/zconf.y index addd4a6..a4b1775 100644 --- a/config/zconf.y +++ b/config/zconf.y @@ -41,6 +41,7 @@ static struct menu *current_menu, *current_entry; struct expr *expr; struct menu *menu; const struct kconf_id *id; + enum variable_flavor flavor; } %token T_MAINMENU @@ -78,7 +79,7 @@ static struct menu *current_menu, *current_entry; %token T_OPEN_PAREN %token T_EOL %token T_VARIABLE -%token T_ASSIGN +%token T_ASSIGN %token T_ASSIGN_VAL %left T_OR @@ -517,7 +518,7 @@ word_opt: /* empty */ { $$ = NULL; } /* assignment statement */ -assignment_stmt: T_VARIABLE T_ASSIGN assign_val T_EOL { variable_add($1, $3); free($1); free($3); } +assignment_stmt: T_VARIABLE T_ASSIGN assign_val T_EOL { variable_add($1, $3, $2); free($1); free($3); } assign_val: /* empty */ { $$ = xstrdup(""); }; -- 2.31.1 From c9645e9489aac917a4883ddecc857b1c6fcc2c38 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:51 +0900 Subject: [PATCH 07/16] kconfig: support append assignment operator Support += operator. This appends a space and the text on the righthand side to a variable. The timing of the evaluation of the righthand side depends on the flavor of the variable. If the lefthand side was originally defined as a simple variable, the righthand side is expanded immediately. Otherwise, the expansion is deferred. Appending something to an undefined variable results in a recursive variable. To implement this, we need to remember the flavor of variables. Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/lkc_proto.h | 1 + config/preprocess.c | 28 +++++++++++++++++++++++++--- config/zconf.l | 1 + 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/config/lkc_proto.h b/config/lkc_proto.h index 6303193..a8b7a33 100644 --- a/config/lkc_proto.h +++ b/config/lkc_proto.h @@ -52,6 +52,7 @@ const char * prop_get_type_name(enum prop_type type); enum variable_flavor { VAR_SIMPLE, VAR_RECURSIVE, + VAR_APPEND, }; void env_write_dep(FILE *f, const char *auto_conf_name); void variable_add(const char *name, const char *value, diff --git a/config/preprocess.c b/config/preprocess.c index d103683..56aa1f0 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -222,11 +222,23 @@ void variable_add(const char *name, const char *value, enum variable_flavor flavor) { struct variable *v; + char *new_value; + bool append = false; v = variable_lookup(name); if (v) { - free(v->value); + /* For defined variables, += inherits the existing flavor */ + if (flavor == VAR_APPEND) { + flavor = v->flavor; + append = true; + } else { + free(v->value); + } } else { + /* For undefined variables, += assumes the recursive flavor */ + if (flavor == VAR_APPEND) + flavor = VAR_RECURSIVE; + v = xmalloc(sizeof(*v)); v->name = xstrdup(name); list_add_tail(&v->node, &variable_list); @@ -235,9 +247,19 @@ void variable_add(const char *name, const char *value, v->flavor = flavor; if (flavor == VAR_SIMPLE) - v->value = expand_string(value); + new_value = expand_string(value); else - v->value = xstrdup(value); + new_value = xstrdup(value); + + if (append) { + v->value = xrealloc(v->value, + strlen(v->value) + strlen(new_value) + 2); + strcat(v->value, " "); + strcat(v->value, new_value); + free(new_value); + } else { + v->value = new_value; + } } static void variable_del(struct variable *v) diff --git a/config/zconf.l b/config/zconf.l index d0f5b13..62a0973 100644 --- a/config/zconf.l +++ b/config/zconf.l @@ -116,6 +116,7 @@ n [A-Za-z0-9_-] } "=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_RECURSIVE; return T_ASSIGN; } ":=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_SIMPLE; return T_ASSIGN; } + "+=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_APPEND; return T_ASSIGN; } [[:blank:]]+ . warn_ignored_character(*yytext); \n { -- 2.31.1 From f3aea9e2017b600dde932ddaf200a4fd0d56eb06 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:52 +0900 Subject: [PATCH 08/16] kconfig: expand lefthand side of assignment statement Make expands the lefthand side of assignment statements. In fact, Kbuild relies on it since kernel makefiles mostly look like this: obj-$(CONFIG_FOO) += foo.o Do likewise in Kconfig. Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/zconf.l | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/zconf.l b/config/zconf.l index 62a0973..d2d9d87 100644 --- a/config/zconf.l +++ b/config/zconf.l @@ -114,6 +114,13 @@ n [A-Za-z0-9_-] yylval.string = text; return T_VARIABLE; } + ({n}|$)+ { + /* this token includes at least one '$' */ + yylval.string = expand_token(yytext, yyleng); + if (strlen(yylval.string)) + return T_VARIABLE; + free(yylval.string); + } "=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_RECURSIVE; return T_ASSIGN; } ":=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_SIMPLE; return T_ASSIGN; } "+=" { BEGIN(ASSIGN_VAL); yylval.flavor = VAR_APPEND; return T_ASSIGN; } -- 2.31.1 From 715ca322b4f23a2b15adb34d0d829ae34c04dede Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:53 +0900 Subject: [PATCH 09/16] kconfig: add 'info', 'warning-if', and 'error-if' built-in functions Syntax: $(info,) $(warning-if,,) $(error-if,, part is y. Kconfig does not implement the lazy expansion as used in the 'if' 'and, 'or' functions in Make. In other words, Kconfig does not support conditional expansion. The unconditional 'error' function would always terminate the parsing, hence would be useless in Kconfig. Signed-off-by: Masahiro Yamada Reviewed-by: Kees Cook Signed-off-by: Christian Lamparter --- config/preprocess.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/config/preprocess.c b/config/preprocess.c index 56aa1f0..5ee58ee 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -106,6 +106,21 @@ struct function { char *(*func)(int argc, char *argv[]); }; +static char *do_error_if(int argc, char *argv[]) +{ + if (!strcmp(argv[0], "y")) + pperror("%s", argv[1]); + + return NULL; +} + +static char *do_info(int argc, char *argv[]) +{ + printf("%s\n", argv[0]); + + return xstrdup(""); +} + static char *do_shell(int argc, char *argv[]) { FILE *p; @@ -146,9 +161,21 @@ static char *do_shell(int argc, char *argv[]) return xstrdup(buf); } +static char *do_warning_if(int argc, char *argv[]) +{ + if (!strcmp(argv[0], "y")) + fprintf(stderr, "%s:%d: %s\n", + current_file->name, yylineno, argv[1]); + + return xstrdup(""); +} + static const struct function function_table[] = { /* Name MIN MAX Function */ + { "error-if", 2, 2, do_error_if }, + { "info", 1, 1, do_info }, { "shell", 1, 1, do_shell }, + { "warning-if", 2, 2, do_warning_if }, }; #define FUNCTION_MAX_ARGS 16 -- 2.31.1 From 6598140e430d622be926aed43ec19fec8326cd11 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:54 +0900 Subject: [PATCH 10/16] kconfig: add 'filename' and 'lineno' built-in variables The special variables, $(filename) and $(lineno), are expanded to a file name and its line number being parsed, respectively. Suggested-by: Randy Dunlap Signed-off-by: Masahiro Yamada Reviewed-by: Kees Cook Signed-off-by: Christian Lamparter --- config/preprocess.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/config/preprocess.c b/config/preprocess.c index 5ee58ee..0574039 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -114,6 +114,11 @@ static char *do_error_if(int argc, char *argv[]) return NULL; } +static char *do_filename(int argc, char *argv[]) +{ + return xstrdup(current_file->name); +} + static char *do_info(int argc, char *argv[]) { printf("%s\n", argv[0]); @@ -121,6 +126,15 @@ static char *do_info(int argc, char *argv[]) return xstrdup(""); } +static char *do_lineno(int argc, char *argv[]) +{ + char buf[16]; + + sprintf(buf, "%d", yylineno); + + return xstrdup(buf); +} + static char *do_shell(int argc, char *argv[]) { FILE *p; @@ -173,7 +187,9 @@ static char *do_warning_if(int argc, char *argv[]) static const struct function function_table[] = { /* Name MIN MAX Function */ { "error-if", 2, 2, do_error_if }, + { "filename", 0, 0, do_filename }, { "info", 1, 1, do_info }, + { "lineno", 0, 0, do_lineno }, { "shell", 1, 1, do_shell }, { "warning-if", 2, 2, do_warning_if }, }; -- 2.31.1 From 5fc8a231376143a6132f10e8bb24950df57f9e98 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Mon, 28 May 2018 18:21:55 +0900 Subject: [PATCH 11/16] kconfig: error out if a recursive variable references itself When using a recursively expanded variable, it is a common mistake to make circular reference. For example, Make terminates the following code: X = $(X) Y := $(X) Let's detect the circular expansion in Kconfig, too. On the other hand, a function that recurses itself is a commonly-used programming technique. So, Make does not check recursion in the reference with 'call'. For example, the following code continues running eternally: X = $(call X) Y := $(X) Kconfig allows circular expansion if one or more arguments are given, but terminates when the same function is recursively invoked 1000 times, assuming it is a programming mistake. Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/preprocess.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config/preprocess.c b/config/preprocess.c index 0574039..65da87f 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -229,6 +229,7 @@ struct variable { char *name; char *value; enum variable_flavor flavor; + int exp_count; struct list_head node; }; @@ -253,11 +254,22 @@ static char *variable_expand(const char *name, int argc, char *argv[]) if (!v) return NULL; + if (argc == 0 && v->exp_count) + pperror("Recursive variable '%s' references itself (eventually)", + name); + + if (v->exp_count > 1000) + pperror("Too deep recursive expansion"); + + v->exp_count++; + if (v->flavor == VAR_RECURSIVE) res = expand_string_with_args(v->value, argc, argv); else res = xstrdup(v->value); + v->exp_count--; + return res; } @@ -284,6 +296,7 @@ void variable_add(const char *name, const char *value, v = xmalloc(sizeof(*v)); v->name = xstrdup(name); + v->exp_count = 0; list_add_tail(&v->node, &variable_list); } -- 2.31.1 From 3aac2a9540ce3501a24d542209eb67d0a9e6571e Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Sat, 2 Jun 2018 09:02:09 -0700 Subject: [PATCH 12/16] kconfig: Avoid format overflow warning from GCC 8.1 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit In file included from scripts/kconfig/zconf.tab.c:2485: scripts/kconfig/confdata.c: In function ‘conf_write’: scripts/kconfig/confdata.c:773:22: warning: ‘%s’ directive writing likely 7 or more bytes into a region of size between 1 and 4097 [-Wformat-overflow=] sprintf(newname, "%s%s", dirname, basename); ^~ scripts/kconfig/confdata.c:773:19: note: assuming directive output of 7 bytes sprintf(newname, "%s%s", dirname, basename); ^~~~~~ scripts/kconfig/confdata.c:773:2: note: ‘sprintf’ output 1 or more bytes (assuming 4104) into a destination of size 4097 sprintf(newname, "%s%s", dirname, basename); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ scripts/kconfig/confdata.c:776:23: warning: ‘.tmpconfig.’ directive writing 11 bytes into a region of size between 1 and 4097 [-Wformat-overflow=] sprintf(tmpname, "%s.tmpconfig.%d", dirname, (int)getpid()); ^~~~~~~~~~~ scripts/kconfig/confdata.c:776:3: note: ‘sprintf’ output between 13 and 4119 bytes into a destination of size 4097 sprintf(tmpname, "%s.tmpconfig.%d", dirname, (int)getpid()); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Increase the size of tmpname and newname to make GCC happy. Cc: stable@vger.kernel.org Signed-off-by: Nathan Chancellor Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/confdata.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/confdata.c b/config/confdata.c index d9f0fca..4af0fbb 100644 --- a/config/confdata.c +++ b/config/confdata.c @@ -766,7 +766,7 @@ int conf_write(const char *name) struct menu *menu; const char *basename; const char *str; - char dirname[PATH_MAX+1], tmpname[PATH_MAX+1], newname[PATH_MAX+1]; + char dirname[PATH_MAX+1], tmpname[PATH_MAX+22], newname[PATH_MAX+8]; char *env; dirname[0] = 0; -- 2.31.1 From 1e015f2575b9d43c0106ab0392ba8c02fa8e9370 Mon Sep 17 00:00:00 2001 From: Dirk Gouders Date: Thu, 21 Jun 2018 15:30:54 +0200 Subject: [PATCH 13/16] kconfig: fix line numbers for if-entries in menu tree The line numers for if-entries in the menu tree are off by one or more lines which is confusing when debugging for correctness of unrelated changes. According to the git log, commit a02f0570ae201c49 (kconfig: improve error handling in the parser) was the last one that changed that part of the parser and replaced "if_entry: T_IF expr T_EOL" by "if_entry: T_IF expr nl" but the commit message does not state why this has been done. When reverting that part of the commit, only the line numers are corrected (checked with cdebug = DEBUG_PARSE in zconf.y), otherwise the menu tree remains unchanged (checked with zconfdump() enabled in conf.c). An example for the corrected line numbers: drivers/soc/Kconfig:15:source drivers/soc/tegra/Kconfig drivers/soc/tegra/Kconfig:4:if drivers/soc/tegra/Kconfig:6:if changes to: drivers/soc/Kconfig:15:source drivers/soc/tegra/Kconfig drivers/soc/tegra/Kconfig:1:if drivers/soc/tegra/Kconfig:4:if Signed-off-by: Dirk Gouders Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/zconf.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/zconf.y b/config/zconf.y index a4b1775..0599a84 100644 --- a/config/zconf.y +++ b/config/zconf.y @@ -31,7 +31,7 @@ struct symbol *symbol_hash[SYMBOL_HASHSIZE]; static struct menu *current_menu, *current_entry; %} -%expect 32 +%expect 31 %union { @@ -337,7 +337,7 @@ choice_block: /* if entry */ -if_entry: T_IF expr nl +if_entry: T_IF expr T_EOL { printd(DEBUG_PARSE, "%s:%d:if\n", zconf_curname(), zconf_lineno()); menu_add_entry(NULL); -- 2.31.1 From 9625eb0fa4e880b87250d8596bdae6ae1d95a21c Mon Sep 17 00:00:00 2001 From: Dirk Gouders Date: Fri, 22 Jun 2018 21:27:38 +0200 Subject: [PATCH 14/16] kconfig: handle P_SYMBOL in print_symbol() Each symbol has a property of type P_SYMBOL since commit 59e89e3ddf85 (kconfig: save location of config symbols). Handle those properties in print_symbol(). Further, place a pointer to print_symbol() in the comment above the list of known property type. Signed-off-by: Dirk Gouders Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/expr.h | 3 +++ config/zconf.y | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/config/expr.h b/config/expr.h index 94a383b..f63b41b 100644 --- a/config/expr.h +++ b/config/expr.h @@ -171,6 +171,9 @@ struct symbol { * config BAZ * int "BAZ Value" * range 1..255 + * + * Please, also check zconf.y:print_symbol() when modifying the + * list of property types! */ enum prop_type { P_UNKNOWN, diff --git a/config/zconf.y b/config/zconf.y index 0599a84..d9a0952 100644 --- a/config/zconf.y +++ b/config/zconf.y @@ -718,6 +718,10 @@ static void print_symbol(FILE *out, struct menu *menu) print_quoted_string(out, prop->text); fputc('\n', out); break; + case P_SYMBOL: + fputs( " symbol ", out); + fprintf(out, "%s\n", prop->sym->name); + break; default: fprintf(out, " unknown prop %d!\n", prop->type); break; -- 2.31.1 From c38004b50bcc9378ad1981165dc8dfbb3445c0a9 Mon Sep 17 00:00:00 2001 From: Jerry James Date: Sat, 23 Jun 2018 22:49:04 +0200 Subject: [PATCH 15/16] kconfig: loop boundary condition fix If buf[-1] just happens to hold the byte 0x0A, then nread can wrap around to (size_t)-1, leading to invalid memory accesses. This has caused segmentation faults when trying to build the latest kernel snapshots for i686 in Fedora: https://bugzilla.redhat.com/show_bug.cgi?id=1592374 Signed-off-by: Jerry James [alexpl@fedoraproject.org: reformatted patch for submission] Signed-off-by: Alexander Ploumistos Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/preprocess.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/preprocess.c b/config/preprocess.c index 65da87f..5ca2df7 100644 --- a/config/preprocess.c +++ b/config/preprocess.c @@ -156,7 +156,7 @@ static char *do_shell(int argc, char *argv[]) nread--; /* remove trailing new lines */ - while (buf[nread - 1] == '\n') + while (nread > 0 && buf[nread - 1] == '\n') nread--; buf[nread] = 0; -- 2.31.1 From 7f0e8ccbc37c5853e8c9ac3256ddb789b77c92c8 Mon Sep 17 00:00:00 2001 From: Dirk Gouders Date: Tue, 3 Jul 2018 14:43:31 +0200 Subject: [PATCH 16/16] kconfig: rename SYMBOL_AUTO to SYMBOL_NO_WRITE Over time, the use of the flag SYMBOL_AUTO changed from initially marking three automatically generated symbols ARCH, KERNELRELEASE and UNAME_RELEASE to today's effect of protecting symbols from being written out. Currently, only symbols of type CHOICE and those with option defconf_list set have that flag set. Reflect that change in semantics in the flag's name. Signed-off-by: Dirk Gouders Signed-off-by: Masahiro Yamada Signed-off-by: Christian Lamparter --- config/confdata.c | 4 ++-- config/expr.h | 2 +- config/menu.c | 2 +- config/symbol.c | 2 +- config/zconf.y | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/confdata.c b/config/confdata.c index 4af0fbb..481b07b 100644 --- a/config/confdata.c +++ b/config/confdata.c @@ -397,7 +397,7 @@ int conf_read(const char *name) for_all_symbols(i, sym) { sym_calc_value(sym); - if (sym_is_choice(sym) || (sym->flags & SYMBOL_AUTO)) + if (sym_is_choice(sym) || (sym->flags & SYMBOL_NO_WRITE)) continue; if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) { /* check that calculated value agrees with saved value */ @@ -878,7 +878,7 @@ static int conf_split_config(void) res = 0; for_all_symbols(i, sym) { sym_calc_value(sym); - if ((sym->flags & SYMBOL_AUTO) || !sym->name) + if ((sym->flags & SYMBOL_NO_WRITE) || !sym->name) continue; if (sym->flags & SYMBOL_WRITE) { if (sym->flags & SYMBOL_DEF_AUTO) { diff --git a/config/expr.h b/config/expr.h index f63b41b..84a5199 100644 --- a/config/expr.h +++ b/config/expr.h @@ -141,7 +141,7 @@ struct symbol { #define SYMBOL_OPTIONAL 0x0100 /* choice is optional - values can be 'n' */ #define SYMBOL_WRITE 0x0200 /* write symbol to file (KCONFIG_CONFIG) */ #define SYMBOL_CHANGED 0x0400 /* ? */ -#define SYMBOL_AUTO 0x1000 /* value from environment variable */ +#define SYMBOL_NO_WRITE 0x1000 /* Symbol for internal use only; it will not be written */ #define SYMBOL_CHECKED 0x2000 /* used during dependency checking */ #define SYMBOL_WARNED 0x8000 /* warning has been issued */ diff --git a/config/menu.c b/config/menu.c index 379a119..4cf15d4 100644 --- a/config/menu.c +++ b/config/menu.c @@ -212,7 +212,7 @@ void menu_add_option(int token, char *arg) sym_defconfig_list = current_entry->sym; else if (sym_defconfig_list != current_entry->sym) zconf_error("trying to redefine defconfig symbol"); - sym_defconfig_list->flags |= SYMBOL_AUTO; + sym_defconfig_list->flags |= SYMBOL_NO_WRITE; break; case T_OPT_ALLNOCONFIG_Y: current_entry->sym->flags |= SYMBOL_ALLNOCONFIG_Y; diff --git a/config/symbol.c b/config/symbol.c index 7c9a88e..869a5e8 100644 --- a/config/symbol.c +++ b/config/symbol.c @@ -463,7 +463,7 @@ void sym_calc_value(struct symbol *sym) } } - if (sym->flags & SYMBOL_AUTO) + if (sym->flags & SYMBOL_NO_WRITE) sym->flags &= ~SYMBOL_WRITE; if (sym->flags & SYMBOL_NEED_SET_CHOICE_VALUES) diff --git a/config/zconf.y b/config/zconf.y index d9a0952..496bf27 100644 --- a/config/zconf.y +++ b/config/zconf.y @@ -265,7 +265,7 @@ symbol_option_arg: choice: T_CHOICE word_opt T_EOL { struct symbol *sym = sym_lookup($2, SYMBOL_CHOICE); - sym->flags |= SYMBOL_AUTO; + sym->flags |= SYMBOL_NO_WRITE; menu_add_entry(sym); menu_add_expr(P_CHOICE, NULL, NULL); free($2); -- 2.31.1