Received: by 10.213.65.68 with SMTP id h4csp1156620imn; Sat, 31 Mar 2018 21:23:49 -0700 (PDT) X-Google-Smtp-Source: AIpwx48/mnuwjwhJdTM8C2cfxEX5ZjiFBVGIPI6i9fAsxTT/Fgh7DVeWObx7QCW0pXW2JzVr3m/9 X-Received: by 10.98.150.75 with SMTP id c72mr3768996pfe.62.1522556629512; Sat, 31 Mar 2018 21:23:49 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1522556629; cv=none; d=google.com; s=arc-20160816; b=rzaHTmePYumWs30Btbch73/b4ATNHHuWwwRXJjSD9RU7TDM6GzhR52GZ1m4OHCzSDL DAkl35N6/AIiXyoO/tku3CjoOGplsFh0sbHW41cxI9s5Zw6Atg80z6rJUa9ZfZ9rI+9g j+MknImxso2L3FRDOoNrO5Zeuj0T31t3dSZWGcnxqXNKfJSyyCKQ1CemQO8pp7au4dC6 pciQiCdP32vvFXBSmBSmo815Lc7F/FLzZVT/fKHeH708jPh3qifmfTD5Yx1jhMQ/4RIm URGp5OW1MULtqNId0xqiqLE0h5gAwt+Rvx75dwUH2T1bwMjpssGo+LDpGMfeCRKK2Zj6 KkTg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:cc:to:subject:message-id:date:from :references:in-reply-to:mime-version:dkim-signature :arc-authentication-results; bh=soTvohgKbA5Sb32IV5e0fNzbBSLn5RFM+z7Xx4eh/L0=; b=iPRiXedDy0gzz7E+hLADt41oicedwGkcE1GLk1+F4cbEW1qj3/bMK2Lh0OKc0LADfw uZwjz9/yYBvNRdGc8AJpEZsEMvnT6OPhnf2Ms9EV3/l6h+/BG+jKDm2nGDWClIQ6fE47 xt0EabKw+vTseYNhpVW5owqkh4aSjtNDiDE9tg4XGVLEvJyGaYRLsEzTFmuuZnhDrO6v 7mQVM0N4locpFz78rFKeofLGmZcRlRgN/yrmRskoJMRxrUYWqMB7OkTmhHeDINE/mWtL ukHfCq8J4/foNPXIGtcz7L+3/KkhiBr9afs74Qmux26+3W6f7dUctyp4qFK/VxVKGQ5m L0sw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=P8qoGeYl; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 31-v6si9363059plz.467.2018.03.31.21.23.06; Sat, 31 Mar 2018 21:23:49 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=P8qoGeYl; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1750860AbeDAETe (ORCPT + 99 others); Sun, 1 Apr 2018 00:19:34 -0400 Received: from mail-vk0-f65.google.com ([209.85.213.65]:37844 "EHLO mail-vk0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750710AbeDAETc (ORCPT ); Sun, 1 Apr 2018 00:19:32 -0400 Received: by mail-vk0-f65.google.com with SMTP id r19so2844169vkf.4; Sat, 31 Mar 2018 21:19:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc; bh=soTvohgKbA5Sb32IV5e0fNzbBSLn5RFM+z7Xx4eh/L0=; b=P8qoGeYlIXBP41mws7G1/6vwx6cKhdy495ua2XDAMKZUMERXD1hZKocA2h4odXFkYM W20XzwEiQQRk72Ww6oBYr5pmk1norK5BHR+0m4F3azLqkGMxnxLrDczaoB9cUOQOCnmG b4AonehNOckgfwxq+x3467kMQE4mVA36bKdV0rKwUGaxc9NB/LwxhK7XhUJVfsKLqwxt HseVFnG64eQQk8qyS1lI0WTQsivgHgyGfjdh0EpZzQ27lFzH0+I9adnCmWJznIEDhNy2 IV0i9ncAhffcHiUKhgqsLNUOE+nNqhRyjM7ORJkKB9Q1yyQb+e+mVLkLfoMC5Lj/ejXZ vinw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to:cc; bh=soTvohgKbA5Sb32IV5e0fNzbBSLn5RFM+z7Xx4eh/L0=; b=ibiuETrCtbc6CuvkQ8XBm9WucY3otWtoUD1BnDpWVImnikC+owCZM09S3Hp6ZbKc4g lG4sPAAVPWZpb0JiOumOM9iExqkSK92C3d8KGr3auCoCV6DCf0nJfifTTZ+ls58hgoFb 9/OxcsTe5AZayFINHeGYHdGp8sZoIblPK7jLXV9vbWIdqUG88R0zq72lJuLFRgWFmyBr K8gCYNk84+TEHknP125gqlvS5lmwnKa5fp66/TC2gO9v1p688rF3wTeY1kWiABZBEjm1 y/yiNcYI4etBzx1ZBDfSw4L1CIXofK64MdFEC3NG1tSOC6dLh3Y0V6b9CJq+Y4JINbwU 2FXw== X-Gm-Message-State: ALQs6tBg5P3VfiBZ/QpJxY1YdnwcqW35NOJXgcD54FXYs2stKe5LaO5/ i50Hd+6a4/f07DvuQ8n4yyScZ2DClSVmnTsFALY= X-Received: by 10.31.189.72 with SMTP id n69mr2523702vkf.89.1522556371439; Sat, 31 Mar 2018 21:19:31 -0700 (PDT) MIME-Version: 1.0 Received: by 10.103.206.5 with HTTP; Sat, 31 Mar 2018 21:19:31 -0700 (PDT) In-Reply-To: <1522128575-5326-8-git-send-email-yamada.masahiro@socionext.com> References: <1522128575-5326-1-git-send-email-yamada.masahiro@socionext.com> <1522128575-5326-8-git-send-email-yamada.masahiro@socionext.com> From: Ulf Magnusson Date: Sun, 1 Apr 2018 06:19:31 +0200 Message-ID: Subject: Re: [PATCH v2 07/21] kconfig: add function support and implement 'shell' function To: Masahiro Yamada Cc: Linux Kbuild mailing list , Sam Ravnborg , Linus Torvalds , Arnd Bergmann , Kees Cook , Thomas Gleixner , Greg Kroah-Hartman , Randy Dunlap , "Luis R . Rodriguez" , Nicolas Pitre , Linux Kernel Mailing List Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Tue, Mar 27, 2018 at 7:29 AM, Masahiro Yamada wrote: > This commit adds a new concept 'function' to do more text processing > in Kconfig. > > A function call looks like this: > > $(function arg1, arg2, arg3, ...) > > (Actually, this syntax was inspired by make.) > > Real examples will look like this: > > $(shell echo hello world) > $(cc-option -fstackprotector) > > This commit adds the basic infrastructure to add, delete, evaluate > functions, and also the first built-in function $(shell ...). 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 > --- > > Reminder for myself: > Update Documentation/kbuild/kconfig-language.txt > > > Changes in v2: > - Use 'shell' for getting stdout from the comment. > It was 'shell-stdout' in the previous version. > - Symplify the implementation since the expansion has been moved to > lexer. > > scripts/kconfig/function.c | 170 ++++++++++++++++++++++++++++++++++++++++++++ > scripts/kconfig/lkc_proto.h | 5 ++ > scripts/kconfig/util.c | 46 +++++++++--- > scripts/kconfig/zconf.y | 9 +++ > 4 files changed, 222 insertions(+), 8 deletions(-) > create mode 100644 scripts/kconfig/function.c > > diff --git a/scripts/kconfig/function.c b/scripts/kconfig/function.c > new file mode 100644 > index 0000000..913685f > --- /dev/null > +++ b/scripts/kconfig/function.c > @@ -0,0 +1,170 @@ > +// SPDX-License-Identifier: GPL-2.0 > +// > +// Copyright (C) 2018 Masahiro Yamada > + > +#include > +#include > +#include > + > +#include "list.h" > + > +#define FUNCTION_MAX_ARGS 10 > + > +static LIST_HEAD(function_list); > + > +struct function { > + char *name; > + char *(*func)(struct function *f, int argc, char *argv[]); > + struct list_head node; > +}; > + > +static struct function *func_lookup(const char *name) > +{ > + struct function *f; > + > + list_for_each_entry(f, &function_list, node) { > + if (!strcmp(name, f->name)) > + return f; > + } > + > + return NULL; > +} > + > +static void func_add(const char *name, > + char *(*func)(struct function *f, int argc, char *argv[])) > +{ > + struct function *f; > + > + f = func_lookup(name); > + if (f) { > + fprintf(stderr, "%s: function already exists. ignored.\n", name); > + return; > + } > + > + f = xmalloc(sizeof(*f)); > + f->name = xstrdup(name); > + f->func = func; > + > + list_add_tail(&f->node, &function_list); > +} > + > +static void func_del(struct function *f) > +{ > + list_del(&f->node); > + free(f->name); > + free(f); > +} > + > +static char *func_call(int argc, char *argv[]) > +{ > + struct function *f; > + > + f = func_lookup(argv[0]); > + if (!f) { > + fprintf(stderr, "%s: function not found\n", argv[0]); > + return NULL; > + } > + > + return f->func(f, argc, argv); > +} > + > +static char *func_eval(const char *func) > +{ > + char *expanded, *saveptr, *str, *token, *res; > + const char *delim; > + int argc = 0; > + char *argv[FUNCTION_MAX_ARGS]; > + > + expanded = expand_string_value(func); > + > + str = expanded; > + delim = " "; > + > + while ((token = strtok_r(str, delim, &saveptr))) { > + argv[argc++] = token; Would be nice to error out if the array is overstepped. > + str = NULL; > + delim = ","; > + } > + > + res = func_call(argc, argv); > + > + free(expanded); > + > + return res ?: xstrdup(""); > +} Since only 'macro' will take multiple parameters, I wonder if it might be better to implement the argument parsing there, and simply pass the string (minus the function name) as-is to functions. You would then be able to have ',' in shell commands, which might be required -- think gcc -Wl,option and the like. > + > +char *func_eval_n(const char *func, size_t n) > +{ > + char *tmp, *res; > + > + tmp = xmalloc(n + 1); > + memcpy(tmp, func, n); > + *(tmp + n) = '\0'; > + > + res = func_eval(tmp); > + > + free(tmp); > + > + return res; > +} > + > +/* built-in functions */ > +static char *do_shell(struct function *f, int argc, char *argv[]) > +{ > + static const char *pre = "("; > + static const char *post = ") 2>/dev/null"; Arrays seem neater, since the pointers aren't needed. > + FILE *p; > + char buf[256]; > + char *cmd; > + int ret; Could get rid of 'ret' and just do if (pclose(p) == -1) perror(cmd); > + > + if (argc != 2) > + return NULL; > + > + /* > + * Surround the command with ( ) in case it is piped commands. > + * Also, redirect stderr to /dev/null. > + */ > + cmd = xmalloc(strlen(pre) + strlen(argv[1]) + strlen(post) + 1); > + strcpy(cmd, pre); > + strcat(cmd, argv[1]); > + strcat(cmd, post); > + > + p = popen(cmd, "r"); > + if (!p) { > + perror(cmd); > + goto free; > + } > + if (fgets(buf, sizeof(buf), p)) { > + size_t len = strlen(buf); > + > + if (buf[len - 1] == '\n') > + buf[len - 1] = '\0'; > + } else { > + buf[0] = '\0'; > + } > + > + ret = pclose(p); > + if (ret == -1) > + perror(cmd); > + > +free: > + free(cmd); > + > + return xstrdup(buf); > +} > + > +void func_init(void) > +{ > + /* register built-in functions */ > + func_add("shell", do_shell); > +} > + > +void func_exit(void) > +{ > + struct function *f, *tmp; > + > + /* unregister all functions */ > + list_for_each_entry_safe(f, tmp, &function_list, node) > + func_del(f); > +} > diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h > index 9884adc..09a4f53 100644 > --- a/scripts/kconfig/lkc_proto.h > +++ b/scripts/kconfig/lkc_proto.h > @@ -48,5 +48,10 @@ const char * sym_get_string_value(struct symbol *sym); > > const char * prop_get_type_name(enum prop_type type); > > +/* function.c */ > +char *func_eval_n(const char *func, size_t n); > +void func_init(void); > +void func_exit(void); > + > /* expr.c */ > void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken); > diff --git a/scripts/kconfig/util.c b/scripts/kconfig/util.c > index 3d27c49..218b051 100644 > --- a/scripts/kconfig/util.c > +++ b/scripts/kconfig/util.c > @@ -13,9 +13,10 @@ > #include "lkc.h" > > /* > - * Expand environments embedded in the string given in argument. Environments > - * to be expanded shall be prefixed by a '$'. Unknown environment expands to > - * the empty string. > + * Expand environments and functions embedded in the string given in argument. > + * Environments to be expanded shall be prefixed by a '$'. Functions to be > + * evaluated shall be surrounded by $(). Unknown environment/function expands > + * to the empty string. > */ > char *expand_string_value(const char *in) > { > @@ -33,11 +34,40 @@ char *expand_string_value(const char *in) > while ((p = strchr(in, '$'))) { > char *new; > > - q = p + 1; > - while (isalnum(*q) || *q == '_') > - q++; > - > - new = env_expand_n(p + 1, q - p - 1); > + /* > + * If the next character is '(', it is a function. > + * Otherwise, environment. > + */ > + if (*(p + 1) == '(') { > + int nest = 0; > + > + q = p + 2; > + while (1) { > + if (*q == '\0') { > + fprintf(stderr, > + "unterminated function: %s\n", > + p); > + new = xstrdup(""); > + break; > + } else if (*q == '(') { > + nest++; > + } else if (*q == ')') { > + if (nest-- == 0) { > + new = func_eval_n(p + 2, > + q - p - 2); > + q++; > + break; > + } > + } > + q++; > + } A loop like this might work too: q = p + 1; do { if (*q == '\0') { *error* val = ... goto error; } if (*q == '(') nest++; if (*q == ')') nest--; q++; } while (nest > 0); val = func_eval_n(...) error: > + } else { > + q = p + 1; > + while (isalnum(*q) || *q == '_') > + q++; > + > + new = env_expand_n(p + 1, q - p - 1); > + } > > reslen = strlen(res) + (p - in) + strlen(new) + 1; > res = xrealloc(res, reslen); > diff --git a/scripts/kconfig/zconf.y b/scripts/kconfig/zconf.y > index d8120c7..feaea18 100644 > --- a/scripts/kconfig/zconf.y > +++ b/scripts/kconfig/zconf.y > @@ -520,11 +520,19 @@ void conf_parse(const char *name) > > zconf_initscan(name); > > + func_init(); > _menu_init(); > > if (getenv("ZCONF_DEBUG")) > yydebug = 1; > yyparse(); > + > + /* > + * Currently, functions are evaluated only when Kconfig files are > + * parsed. We can free functions here. > + */ > + func_exit(); > + > if (yynerrs) > exit(1); > if (!modules_sym) > @@ -765,4 +773,5 @@ void zconfdump(FILE *out) > #include "confdata.c" > #include "expr.c" > #include "symbol.c" > +#include "function.c" > #include "menu.c" > -- > 2.7.4 > Cheers, Ulf