| /*
* sai-builder env.c
*
* Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation:
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* The environment the builder's children (step scripts, sai-shell) start
* with. On unix that's a small fixed set, not the builder's own
* environment; on Windows the child can't even find the toolchain without
* SystemRoot and the Visual Studio vars, so it starts from ours.
*
* On top of that, the platform's "env" items from the builder conf are
* applied in order, see READMEs/README-builder-env.md
*/
#include <libwebsockets.h>
#include <string.h>
#include <stdlib.h>
#include "b-private.h"
#if !defined(WIN32)
static const char * const env_base[] = {
#if defined(__APPLE__)
"PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin",
#else
"PATH=/usr/local/bin:/usr/bin:/bin",
#endif
"LANG=en_US.UTF-8",
"TERM=xterm-256color",
};
#endif
/*
* Add a conf env item to the platform. value NULL means pass on the
* builder's own value of name, if it has one. value must already be in
* the conf lwsac, name is copied into it.
*/
int
saib_env_add(sai_plat_t *sp, struct lwsac **ac, const char *name,
size_t nlen, const char *value)
{
saib_env_t *e;
char *p;
if (!nlen || memchr(name, '=', nlen)) {
lwsl_err("%s: invalid env var name '%.*s'\n", __func__,
(int)nlen, name);
return 1;
}
e = lwsac_use_zero(ac, sizeof(*e), 512);
p = lwsac_use(ac, nlen + 1, 512);
if (!e || !p)
return 1;
memcpy(p, name, nlen);
p[nlen] = '\0';
e->name = p;
e->value = value;
lws_dll2_add_tail(&e->list, &sp->env_head);
/* the value may be a secret, so we don't log it */
lwsl_notice("%s: env %s%s\n", __func__, e->name,
value ? "" : " (from builder)");
return 0;
}
static int
env_name_match(const char *e, const char *name, size_t nlen)
{
size_t n;
for (n = 0; n < nlen; n++) {
#if defined(WIN32)
/* Windows env var names are case-insensitive */
char c1 = e[n], c2 = name[n];
if (c1 >= 'A' && c1 <= 'Z')
c1 = (char)(c1 + ('a' - 'A'));
if (c2 >= 'A' && c2 <= 'Z')
c2 = (char)(c2 + ('a' - 'A'));
if (!c1 || c1 != c2)
return 0;
#else
if (!e[n] || e[n] != name[n])
return 0;
#endif
}
return e[nlen] == '=';
}
static int
env_find(const char **env, int count, const char *name, size_t nlen)
{
int n;
for (n = 0; n < count; n++)
if (env_name_match(env[n], name, nlen))
return n;
return -1;
}
static int
env_name_char(char c, int first)
{
return c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(!first && c >= '0' && c <= '9');
}
/*
* Expand $NAME and ${NAME} in "in" against the env being built. $$ is a
* literal $, and so is a $ not followed by a name. An unset var expands to
* nothing. With out NULL, it just measures. Returns the expanded length,
* not including the terminating NUL that's appended when out is given.
*/
static size_t
env_expand(const char **env, int count, const char *in, char *out)
{
const char *s, *v;
size_t len = 0, nl, vl;
int brace, n;
while (*in) {
if (*in != '$' || in[1] == '$') {
if (out)
out[len] = *in;
len++;
in += *in == '$' ? 2 : 1;
continue;
}
brace = in[1] == '{';
s = in + 1 + brace;
nl = 0;
if (brace)
/* allows Windows names like ProgramFiles(x86) */
while (s[nl] && s[nl] != '}' && s[nl] != '=')
nl++;
else
while (env_name_char(s[nl], !nl))
nl++;
if (!nl || (brace && s[nl] != '}')) {
/* not a reference, the $ is literal */
if (out)
out[len] = '$';
len++;
in++;
continue;
}
in = s + nl + brace;
n = env_find(env, count, s, nl);
if (n < 0)
continue;
v = env[n] + nl + 1;
vl = strlen(v);
if (out)
memcpy(out + len, v, vl);
len += vl;
}
if (out)
out[len] = '\0';
return len;
}
/*
* Prepare the environment for a child of platform sp (which may be NULL if
* we don't know the platform, then it's just the base set) in ac. Returns a
* NULL-terminated array of "NAME=value" for lws_spawn_piped_info.env_array,
* or NULL on OOM. It only needs to live until lws_spawn_piped() returns.
*/
const char **
saib_env_build(const sai_plat_t *sp, struct lwsac **ac)
{
int count = 0, max;
const char **env;
#if defined(WIN32)
LPCH blk = GetEnvironmentStringsA();
const char *b;
if (!blk)
return NULL;
for (b = blk; *b; b += strlen(b) + 1)
count++;
max = count;
#else
max = (int)LWS_ARRAY_SIZE(env_base);
#endif
max += (int)(sp ? sp->env_head.count : 0) + 1;
env = lwsac_use(ac, sizeof(*env) * (size_t)max, 512);
if (!env)
goto bail;
#if defined(WIN32)
count = 0;
for (b = blk; *b; b += strlen(b) + 1) {
size_t l = strlen(b) + 1;
char *p = lwsac_use(ac, l, 4096);
if (!p)
goto bail;
memcpy(p, b, l);
env[count++] = p;
}
FreeEnvironmentStringsA(blk);
blk = NULL;
#else
for (count = 0; count < (int)LWS_ARRAY_SIZE(env_base); count++)
env[count] = env_base[count];
#endif
if (sp) {
lws_start_foreach_dll(struct lws_dll2 *, d, sp->env_head.head) {
const saib_env_t *e = lws_container_of(d, saib_env_t,
list);
size_t nl = strlen(e->name), vl;
const char *v = e->value;
char *p;
int n;
if (!v) {
#if defined(WIN32)
/* we started from our own env already */
continue;
#else
v = getenv(e->name);
if (!v)
continue;
/* the builder's own value is used literally */
vl = strlen(v);
#endif
} else
vl = env_expand(env, count, v, NULL);
p = lwsac_use(ac, nl + 1 + vl + 1, 512);
if (!p)
goto bail;
memcpy(p, e->name, nl);
p[nl] = '=';
if (e->value)
env_expand(env, count, v, p + nl + 1);
else
memcpy(p + nl + 1, v, vl + 1);
n = env_find(env, count, e->name, nl);
if (n < 0)
n = count++;
env[n] = p;
} lws_end_foreach_dll(d);
}
env[count] = NULL;
return env;
bail:
#if defined(WIN32)
if (blk)
FreeEnvironmentStringsA(blk);
#endif
lwsac_free(ac);
return NULL;
}
|