From 3b31596dc8ecf52f4f5d5c890e767d820dedc97b Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Mon, 25 Jun 2018 17:17:47 +0000 Subject: [PATCH] config(8): Add `envvar` support envvar allows adding individual environment variables to the kernel's static environment without the overhead of pulling in a full file. envvar in a config looks like: envvar some_var=5 All envvar-provided variables will be added after the env file is processed, so envvar keys that exist in the previous env will be overwritten by whatever value is set here in the kernel configuration directly. As an aside, envvar lines are intentionally tokenized differently from basically every other line. We used a named state when ENVVAR is encountered to gobble up the rest of the line, which will later be cleaned and validated in post-processing by sanitize_envline. This turns out to be the simplest and cleanest way to allow the flexibility that kenv does while not compromising on silly hacks. Reviewed by: ian (also contributor of sanitize_envline rewrite) MFC after: 1 week Differential Revision: https://reviews.freebsd.org/D15962 --- usr.sbin/config/config.5 | 16 +++++- usr.sbin/config/config.h | 7 +++ usr.sbin/config/config.y | 11 ++++ usr.sbin/config/lang.l | 10 ++++ usr.sbin/config/main.c | 1 + usr.sbin/config/mkmakefile.c | 97 ++++++++++++++++++++++++++---------- 6 files changed, 114 insertions(+), 28 deletions(-) diff --git a/usr.sbin/config/config.5 b/usr.sbin/config/config.5 index dc22bd7952df..6dc8e1544d83 100644 --- a/usr.sbin/config/config.5 +++ b/usr.sbin/config/config.5 @@ -23,7 +23,7 @@ .\" .\" $FreeBSD$ .\" -.Dd February 21, 2016 +.Dd June 22, 2018 .Dt CONFIG 5 .Os .Sh NAME @@ -124,6 +124,20 @@ the compiled-in environment instead, unless the boot environment contains This directive is useful for setting kernel tunables in embedded environments that do not start from .Xr loader 8 . +.\" -------- ENVVAR -------- +.Pp +.It Ic envvar Ar setting +Specifies an individual environment setting to be added to the kernel's +compiled-in environment. +.Ar setting +must be of the form +.Dq Va name=value . +Optional quotes are supported in both name and value. +All environment variables specified with +.Ic envvar +will be set after any +.Ic env +files are included. .\" -------- FILES -------- .Pp .It Ic files Ar filename diff --git a/usr.sbin/config/config.h b/usr.sbin/config/config.h index 1b32ecc9b898..faff48513a34 100644 --- a/usr.sbin/config/config.h +++ b/usr.sbin/config/config.h @@ -140,6 +140,13 @@ struct opt_list { SLIST_HEAD(, opt_list) otab; +struct envvar { + char *env_str; + STAILQ_ENTRY(envvar) envvar_next; +}; + +STAILQ_HEAD(envvar_head, envvar) envvars; + struct hint { char *hint_name; STAILQ_ENTRY(hint) hint_next; diff --git a/usr.sbin/config/config.y b/usr.sbin/config/config.y index 1f9801d6de53..2310f0247612 100644 --- a/usr.sbin/config/config.y +++ b/usr.sbin/config/config.y @@ -12,6 +12,7 @@ %token DEVICE %token NODEVICE %token ENV +%token ENVVAR %token EQUALS %token PLUSEQUALS %token HINTS @@ -26,6 +27,7 @@ %token INCLUDE %token FILES +%token ENVLINE %token ID %token NUMBER @@ -193,6 +195,15 @@ Config_spec: env = $2; envmode = 1; } | + ENVVAR ENVLINE { + struct envvar *envvar; + + envvar = (struct envvar *)calloc(1, sizeof (struct envvar)); + if (envvar == NULL) + err(EXIT_FAILURE, "calloc"); + envvar->env_str = $2; + STAILQ_INSERT_TAIL(&envvars, envvar, envvar_next); + } | HINTS ID { struct hint *hint; diff --git a/usr.sbin/config/lang.l b/usr.sbin/config/lang.l index 6f28d7fc55af..727346ec122a 100644 --- a/usr.sbin/config/lang.l +++ b/usr.sbin/config/lang.l @@ -70,6 +70,7 @@ struct kt { { "nodevice", NODEVICE }, { "nodevices", NODEVICE }, { "env", ENV }, + { "envvar", ENVVAR }, { "hints", HINTS }, { "ident", IDENT }, { "machine", ARCH }, /* MACHINE is defined in /sys/param.h */ @@ -105,7 +106,13 @@ int yyerror(const char *); ID [A-Za-z_][-A-Za-z_0-9]* PATH [./][-/.%^A-Za-z_0-9]+ %START TOEOL +%START ENVC %% +[^#\n]* { + BEGIN 0; + yylval.str = strdup(yytext); + return ENVLINE; + } {ID} { int i; @@ -115,6 +122,9 @@ PATH [./][-/.%^A-Za-z_0-9]+ yylval.str = strdup(yytext); return ID; } + /* We'll glom onto the rest of an envvar line */ + if (i == ENVVAR) + BEGIN ENVC; return i; } \\\"[^"]+\\\" { diff --git a/usr.sbin/config/main.c b/usr.sbin/config/main.c index 69499b4fa565..e1f2aadcadbd 100644 --- a/usr.sbin/config/main.c +++ b/usr.sbin/config/main.c @@ -204,6 +204,7 @@ main(int argc, char **argv) STAILQ_INIT(&fntab); STAILQ_INIT(&ftab); STAILQ_INIT(&hints); + STAILQ_INIT(&envvars); if (yyparse()) exit(3); diff --git a/usr.sbin/config/mkmakefile.c b/usr.sbin/config/mkmakefile.c index 69393675b14a..154533305172 100644 --- a/usr.sbin/config/mkmakefile.c +++ b/usr.sbin/config/mkmakefile.c @@ -46,6 +46,7 @@ static const char rcsid[] = #include #include #include +#include #include #include #include @@ -238,6 +239,63 @@ makehints(void) moveifchanged(path("hints.c.new"), path("hints.c")); } +static void +sanitize_envline(char *result, const char *src) +{ + const char *eq; + char c, *dst; + bool leading; + + /* If there is no '=' it's not a well-formed name=value line. */ + if ((eq = strchr(src, '=')) == NULL) { + *result = 0; + return; + } + dst = result; + + /* Copy chars before the '=', skipping any leading spaces/quotes. */ + leading = true; + while (src < eq) { + c = *src++; + if (leading && (isspace(c) || c == '"')) + continue; + *dst++ = c; + leading = false; + } + + /* If it was all leading space, we don't have a well-formed line. */ + if (leading) { + *result = 0; + return; + } + + /* Trim spaces/quotes immediately before the '=', then copy the '='. */ + while (isspace(dst[-1]) || dst[-1] == '"') + --dst; + *dst++ = *src++; + + /* Copy chars after the '=', skipping any leading whitespace. */ + leading = true; + while ((c = *src++) != 0) { + if (leading && (isspace(c) || c == '"')) + continue; + *dst++ = c; + leading = false; + } + + /* If it was all leading space, it's a valid 'var=' (nil value). */ + if (leading) { + *dst = 0; + return; + } + + /* Trim trailing whitespace and quotes. */ + while (isspace(dst[-1]) || dst[-1] == '"') + --dst; + + *dst = 0; +} + /* * Build env.c from the skeleton */ @@ -245,8 +303,8 @@ void makeenv(void) { FILE *ifp, *ofp; - char line[BUFSIZ]; - char *s; + char line[BUFSIZ], result[BUFSIZ], *linep; + struct envvar *envvar; if (env) { ifp = fopen(env, "r"); @@ -265,35 +323,20 @@ makeenv(void) fprintf(ofp, "char static_env[] = {\n"); if (ifp) { while (fgets(line, BUFSIZ, ifp) != NULL) { - /* zap trailing CR and/or LF */ - while ((s = strrchr(line, '\n')) != NULL) - *s = '\0'; - while ((s = strrchr(line, '\r')) != NULL) - *s = '\0'; - /* remove # comments */ - s = strchr(line, '#'); - if (s) - *s = '\0'; - /* remove any whitespace and " characters */ - s = line; - while (*s) { - if (*s == ' ' || *s == '\t' || *s == '"') { - while (*s) { - s[0] = s[1]; - s++; - } - /* start over */ - s = line; - continue; - } - s++; - } + sanitize_envline(result, line); /* anything left? */ - if (*line == '\0') + if (*result == '\0') continue; - fprintf(ofp, "\"%s\\0\"\n", line); + fprintf(ofp, "\"%s\\0\"\n", result); } } + STAILQ_FOREACH(envvar, &envvars, envvar_next) { + linep = envvar->env_str; + sanitize_envline(result, linep); + if (*result == '\0') + continue; + fprintf(ofp, "\"%s\\0\"\n", result); + } fprintf(ofp, "\"\\0\"\n};\n"); if (ifp) fclose(ifp);