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
This commit is contained in:
Kyle Evans 2018-06-25 17:17:47 +00:00
parent ccbbd187b1
commit 3b31596dc8
6 changed files with 114 additions and 28 deletions

View File

@ -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

View File

@ -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;

View File

@ -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 <str> ENVLINE
%token <str> ID
%token <val> 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;

View File

@ -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
%%
<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;
}
\\\"[^"]+\\\" {

View File

@ -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);

View File

@ -46,6 +46,7 @@ static const char rcsid[] =
#include <ctype.h>
#include <err.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
@ -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);