mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
Now that the necessary functions from log.h have been moved to macro.h, we can stop including log.h in macro.h. This requires modifying source files all over the tree to include log.h instead.
1176 lines
34 KiB
C
1176 lines
34 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include "alloc-util.h"
|
|
#include "env-util.h"
|
|
#include "errno-util.h"
|
|
#include "escape.h"
|
|
#include "extract-word.h"
|
|
#include "log.h"
|
|
#include "macro.h"
|
|
#include "parse-util.h"
|
|
#include "path-util.h"
|
|
#include "process-util.h"
|
|
#include "stdio-util.h"
|
|
#include "string-util.h"
|
|
#include "strv.h"
|
|
#include "syslog-util.h"
|
|
#include "utf8.h"
|
|
|
|
/* We follow bash for the character set. Different shells have different rules. */
|
|
#define VALID_BASH_ENV_NAME_CHARS \
|
|
DIGITS LETTERS \
|
|
"_"
|
|
|
|
static bool env_name_is_valid_n(const char *e, size_t n) {
|
|
|
|
if (n == SIZE_MAX)
|
|
n = strlen_ptr(e);
|
|
|
|
if (n <= 0)
|
|
return false;
|
|
|
|
assert(e);
|
|
|
|
if (ascii_isdigit(e[0]))
|
|
return false;
|
|
|
|
/* POSIX says the overall size of the environment block cannot be > ARG_MAX, an individual assignment
|
|
* hence cannot be either. Discounting the equal sign and trailing NUL this hence leaves ARG_MAX-2 as
|
|
* longest possible variable name. */
|
|
if (n > (size_t) sysconf(_SC_ARG_MAX) - 2)
|
|
return false;
|
|
|
|
for (const char *p = e; p < e + n; p++)
|
|
if (!strchr(VALID_BASH_ENV_NAME_CHARS, *p))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool env_name_is_valid(const char *e) {
|
|
return env_name_is_valid_n(e, strlen_ptr(e));
|
|
}
|
|
|
|
bool env_value_is_valid(const char *e) {
|
|
if (!e)
|
|
return false;
|
|
|
|
if (!utf8_is_valid(e))
|
|
return false;
|
|
|
|
/* Note that variable *values* may contain control characters, in particular NL, TAB, BS, DEL, ESC…
|
|
* When printing those variables with show-environment, we'll escape them. Make sure to print
|
|
* environment variables carefully! */
|
|
|
|
/* POSIX says the overall size of the environment block cannot be > ARG_MAX, an individual assignment
|
|
* hence cannot be either. Discounting the shortest possible variable name of length 1, the equal
|
|
* sign and trailing NUL this hence leaves ARG_MAX-3 as longest possible variable value. */
|
|
if (strlen(e) > sc_arg_max() - 3)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool env_assignment_is_valid(const char *e) {
|
|
const char *eq;
|
|
|
|
eq = strchr(e, '=');
|
|
if (!eq)
|
|
return false;
|
|
|
|
if (!env_name_is_valid_n(e, eq - e))
|
|
return false;
|
|
|
|
if (!env_value_is_valid(eq + 1))
|
|
return false;
|
|
|
|
/* POSIX says the overall size of the environment block cannot be > ARG_MAX, hence the individual
|
|
* variable assignments cannot be either, but let's leave room for one trailing NUL byte. */
|
|
if (strlen(e) > sc_arg_max() - 1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool strv_env_is_valid(char **e) {
|
|
STRV_FOREACH(p, e) {
|
|
size_t k;
|
|
|
|
if (!env_assignment_is_valid(*p))
|
|
return false;
|
|
|
|
/* Check if there are duplicate assignments */
|
|
k = strcspn(*p, "=");
|
|
STRV_FOREACH(q, p + 1)
|
|
if (strneq(*p, *q, k) && (*q)[k] == '=')
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool strv_env_name_is_valid(char **l) {
|
|
STRV_FOREACH(p, l) {
|
|
if (!env_name_is_valid(*p))
|
|
return false;
|
|
|
|
if (strv_contains(p + 1, *p))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool strv_env_name_or_assignment_is_valid(char **l) {
|
|
STRV_FOREACH(p, l) {
|
|
if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p))
|
|
return false;
|
|
|
|
if (strv_contains(p + 1, *p))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int env_append(char **e, char ***k, char **a) {
|
|
assert(e);
|
|
assert(k);
|
|
assert(*k >= e);
|
|
|
|
if (!a)
|
|
return 0;
|
|
|
|
/* Expects the following arguments: 'e' shall point to the beginning of an strv we are going to append to, 'k'
|
|
* to a pointer pointing to the NULL entry at the end of the same array. 'a' shall point to another strv.
|
|
*
|
|
* This call adds every entry of 'a' to 'e', either overriding an existing matching entry, or appending to it.
|
|
*
|
|
* This call assumes 'e' has enough pre-allocated space to grow by all of 'a''s items. */
|
|
|
|
for (; *a; a++) {
|
|
char **j, *c;
|
|
size_t n;
|
|
|
|
n = strcspn(*a, "=");
|
|
if ((*a)[n] == '=')
|
|
n++;
|
|
|
|
for (j = e; j < *k; j++)
|
|
if (strneq(*j, *a, n))
|
|
break;
|
|
|
|
c = strdup(*a);
|
|
if (!c)
|
|
return -ENOMEM;
|
|
|
|
if (j >= *k) { /* Append to the end? */
|
|
(*k)[0] = c;
|
|
(*k)[1] = NULL;
|
|
(*k)++;
|
|
} else
|
|
free_and_replace(*j, c); /* Override existing item */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char** _strv_env_merge(char **first, ...) {
|
|
_cleanup_strv_free_ char **merged = NULL;
|
|
char **k;
|
|
va_list ap;
|
|
|
|
/* Merges an arbitrary number of environment sets */
|
|
|
|
size_t n = strv_length(first);
|
|
|
|
va_start(ap, first);
|
|
for (;;) {
|
|
char **l;
|
|
|
|
l = va_arg(ap, char**);
|
|
if (l == POINTER_MAX)
|
|
break;
|
|
|
|
n += strv_length(l);
|
|
}
|
|
va_end(ap);
|
|
|
|
k = merged = new(char*, n + 1);
|
|
if (!merged)
|
|
return NULL;
|
|
merged[0] = NULL;
|
|
|
|
if (env_append(merged, &k, first) < 0)
|
|
return NULL;
|
|
|
|
va_start(ap, first);
|
|
for (;;) {
|
|
char **l;
|
|
|
|
l = va_arg(ap, char**);
|
|
if (l == POINTER_MAX)
|
|
break;
|
|
|
|
if (env_append(merged, &k, l) < 0) {
|
|
va_end(ap);
|
|
return NULL;
|
|
}
|
|
}
|
|
va_end(ap);
|
|
|
|
return TAKE_PTR(merged);
|
|
}
|
|
|
|
static bool env_match(const char *t, const char *pattern) {
|
|
assert(t);
|
|
assert(pattern);
|
|
|
|
/* pattern a matches string a
|
|
* a matches a=
|
|
* a matches a=b
|
|
* a= matches a=
|
|
* a=b matches a=b
|
|
* a= does not match a
|
|
* a=b does not match a=
|
|
* a=b does not match a
|
|
* a=b does not match a=c */
|
|
|
|
if (streq(t, pattern))
|
|
return true;
|
|
|
|
if (!strchr(pattern, '=')) {
|
|
t = startswith(t, pattern);
|
|
|
|
return t && *t == '=';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool env_entry_has_name(const char *entry, const char *name) {
|
|
const char *t;
|
|
|
|
assert(entry);
|
|
assert(name);
|
|
|
|
t = startswith(entry, name);
|
|
if (!t)
|
|
return false;
|
|
|
|
return *t == '=';
|
|
}
|
|
|
|
char** strv_env_delete(char **x, size_t n_lists, ...) {
|
|
size_t n, i = 0;
|
|
_cleanup_strv_free_ char **t = NULL;
|
|
va_list ap;
|
|
|
|
/* Deletes every entry from x that is mentioned in the other
|
|
* string lists */
|
|
|
|
n = strv_length(x);
|
|
|
|
t = new(char*, n+1);
|
|
if (!t)
|
|
return NULL;
|
|
|
|
STRV_FOREACH(k, x) {
|
|
va_start(ap, n_lists);
|
|
for (size_t v = 0; v < n_lists; v++) {
|
|
char **l;
|
|
|
|
l = va_arg(ap, char**);
|
|
STRV_FOREACH(j, l)
|
|
if (env_match(*k, *j))
|
|
goto skip;
|
|
}
|
|
va_end(ap);
|
|
|
|
t[i] = strdup(*k);
|
|
if (!t[i])
|
|
return NULL;
|
|
|
|
i++;
|
|
continue;
|
|
|
|
skip:
|
|
va_end(ap);
|
|
}
|
|
|
|
t[i] = NULL;
|
|
|
|
assert(i <= n);
|
|
|
|
return TAKE_PTR(t);
|
|
}
|
|
|
|
char** strv_env_unset(char **l, const char *p) {
|
|
assert(p);
|
|
|
|
if (!l)
|
|
return NULL;
|
|
|
|
/* Drops every occurrence of the env var setting p in the
|
|
* string list. Edits in-place. */
|
|
|
|
char **f, **t;
|
|
for (f = t = l; *f; f++) {
|
|
if (env_match(*f, p)) {
|
|
free(*f);
|
|
continue;
|
|
}
|
|
|
|
*(t++) = *f;
|
|
}
|
|
|
|
*t = NULL;
|
|
return l;
|
|
}
|
|
|
|
char** strv_env_unset_many_internal(char **l, ...) {
|
|
if (!l)
|
|
return NULL;
|
|
|
|
/* Like strv_env_unset() but applies many at once. Edits in-place. */
|
|
|
|
char **f, **t;
|
|
for (f = t = l; *f; f++) {
|
|
bool found = false;
|
|
const char *p;
|
|
va_list ap;
|
|
|
|
va_start(ap, l);
|
|
|
|
while ((p = va_arg(ap, const char*)))
|
|
if (env_match(*f, p)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
if (found) {
|
|
free(*f);
|
|
continue;
|
|
}
|
|
|
|
*(t++) = *f;
|
|
}
|
|
|
|
*t = NULL;
|
|
return l;
|
|
}
|
|
|
|
int strv_env_replace_consume(char ***l, char *p) {
|
|
const char *t, *name;
|
|
int r;
|
|
|
|
assert(p);
|
|
|
|
/* Replace first occurrence of the env var or add a new one in the string list. Drop other
|
|
* occurrences. Edits in-place. Does not copy p and CONSUMES p EVEN ON FAILURE.
|
|
*
|
|
* p must be a valid key=value assignment. */
|
|
|
|
t = strchr(p, '=');
|
|
if (!t) {
|
|
free(p);
|
|
return -EINVAL;
|
|
}
|
|
|
|
name = strndupa_safe(p, t - p);
|
|
|
|
STRV_FOREACH(f, *l)
|
|
if (env_entry_has_name(*f, name)) {
|
|
free_and_replace(*f, p);
|
|
strv_env_unset(f + 1, *f);
|
|
return 0;
|
|
}
|
|
|
|
/* We didn't find a match, we need to append p or create a new strv */
|
|
r = strv_consume(l, p);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int strv_env_replace_strdup(char ***l, const char *assignment) {
|
|
/* Like strv_env_replace_consume(), but copies the argument. */
|
|
|
|
char *p = strdup(assignment);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
return strv_env_replace_consume(l, p);
|
|
}
|
|
|
|
int strv_env_replace_strdup_passthrough(char ***l, const char *assignment) {
|
|
/* Like strv_env_replace_strdup(), but pulls the variable from the environment of
|
|
* the calling program, if a variable name without value is specified.
|
|
*/
|
|
char *p;
|
|
|
|
if (strchr(assignment, '=')) {
|
|
if (!env_assignment_is_valid(assignment))
|
|
return -EINVAL;
|
|
|
|
p = strdup(assignment);
|
|
} else {
|
|
if (!env_name_is_valid(assignment))
|
|
return -EINVAL;
|
|
|
|
/* If we can't find the variable in our environment, we will use
|
|
* the empty string. This way "passthrough" is equivalent to passing
|
|
* --setenv=FOO=$FOO in the shell. */
|
|
p = strjoin(assignment, "=", secure_getenv(assignment));
|
|
}
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
return strv_env_replace_consume(l, p);
|
|
}
|
|
|
|
int strv_env_assign(char ***l, const char *key, const char *value) {
|
|
if (!env_name_is_valid(key))
|
|
return -EINVAL;
|
|
|
|
/* NULL removes assignment, "" creates an empty assignment. */
|
|
|
|
if (!value) {
|
|
strv_env_unset(*l, key);
|
|
return 0;
|
|
}
|
|
|
|
char *p = strjoin(key, "=", value);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
return strv_env_replace_consume(l, p);
|
|
}
|
|
|
|
int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) {
|
|
int r;
|
|
|
|
assert(l);
|
|
assert(key);
|
|
|
|
if (!env_name_is_valid(key))
|
|
return -EINVAL;
|
|
|
|
if (!valuef) {
|
|
strv_env_unset(*l, key);
|
|
return 0;
|
|
}
|
|
|
|
_cleanup_free_ char *value = NULL;
|
|
va_list ap;
|
|
va_start(ap, valuef);
|
|
r = vasprintf(&value, valuef, ap);
|
|
va_end(ap);
|
|
if (r < 0)
|
|
return -ENOMEM;
|
|
|
|
char *p = strjoin(key, "=", value);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
|
|
return strv_env_replace_consume(l, p);
|
|
}
|
|
|
|
int _strv_env_assign_many(char ***l, ...) {
|
|
va_list ap;
|
|
int r;
|
|
|
|
assert(l);
|
|
|
|
va_start(ap, l);
|
|
for (;;) {
|
|
const char *key, *value;
|
|
|
|
key = va_arg(ap, const char *);
|
|
if (!key)
|
|
break;
|
|
|
|
if (!env_name_is_valid(key)) {
|
|
va_end(ap);
|
|
return -EINVAL;
|
|
}
|
|
|
|
value = va_arg(ap, const char *);
|
|
if (!value) {
|
|
strv_env_unset(*l, key);
|
|
continue;
|
|
}
|
|
|
|
char *p = strjoin(key, "=", value);
|
|
if (!p) {
|
|
va_end(ap);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
r = strv_env_replace_consume(l, p);
|
|
if (r < 0) {
|
|
va_end(ap);
|
|
return r;
|
|
}
|
|
}
|
|
va_end(ap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlags flags) {
|
|
assert(name);
|
|
|
|
if (k == SIZE_MAX)
|
|
k = strlen(name);
|
|
if (k <= 0)
|
|
return NULL;
|
|
|
|
STRV_FOREACH_BACKWARDS(i, l)
|
|
if (strneq(*i, name, k) && (*i)[k] == '=')
|
|
return (char*) *i + k + 1;
|
|
|
|
if (flags & REPLACE_ENV_USE_ENVIRONMENT) {
|
|
const char *t;
|
|
|
|
/* Safety check that the name is not overly long, before we do a stack allocation */
|
|
if (k > (size_t) sysconf(_SC_ARG_MAX) - 2)
|
|
return NULL;
|
|
|
|
t = strndupa_safe(name, k);
|
|
return secure_getenv(t);
|
|
};
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char* strv_env_pairs_get(char **l, const char *name) {
|
|
char *result = NULL;
|
|
|
|
assert(name);
|
|
|
|
STRV_FOREACH_PAIR(key, value, l)
|
|
if (streq(*key, name))
|
|
result = *value;
|
|
|
|
return result;
|
|
}
|
|
|
|
int strv_env_get_merged(char **l, char ***ret) {
|
|
_cleanup_strv_free_ char **v = NULL;
|
|
size_t n = 0;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
/* This converts a strv with pairs of environment variable name + value into a strv of name and
|
|
* value concatenated with a "=" separator. E.g.
|
|
* input : { "NAME", "value", "FOO", "var" }
|
|
* output : { "NAME=value", "FOO=var" } */
|
|
|
|
STRV_FOREACH_PAIR(key, value, l) {
|
|
char *s;
|
|
|
|
s = strjoin(*key, "=", *value);
|
|
if (!s)
|
|
return -ENOMEM;
|
|
|
|
r = strv_consume_with_size(&v, &n, s);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*ret = TAKE_PTR(v);
|
|
return 0;
|
|
}
|
|
|
|
char** strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
|
|
int k = 0;
|
|
|
|
STRV_FOREACH(p, e) {
|
|
size_t n;
|
|
bool duplicate = false;
|
|
|
|
if (!env_assignment_is_valid(*p)) {
|
|
if (invalid_callback)
|
|
invalid_callback(*p, userdata);
|
|
free(*p);
|
|
continue;
|
|
}
|
|
|
|
n = strcspn(*p, "=");
|
|
STRV_FOREACH(q, p + 1)
|
|
if (strneq(*p, *q, n) && (*q)[n] == '=') {
|
|
duplicate = true;
|
|
break;
|
|
}
|
|
|
|
if (duplicate) {
|
|
free(*p);
|
|
continue;
|
|
}
|
|
|
|
e[k++] = *p;
|
|
}
|
|
|
|
if (e)
|
|
e[k] = NULL;
|
|
|
|
return e;
|
|
}
|
|
|
|
static int strv_extend_with_length(char ***l, const char *s, size_t n) {
|
|
char *c;
|
|
|
|
c = strndup(s, n);
|
|
if (!c)
|
|
return -ENOMEM;
|
|
|
|
return strv_consume(l, c);
|
|
}
|
|
|
|
static int strv_env_get_n_validated(
|
|
char **env,
|
|
const char *name,
|
|
size_t l,
|
|
ReplaceEnvFlags flags,
|
|
char **ret, /* points into the env block! do not free! */
|
|
char ***unset_variables, /* updated in place */
|
|
char ***bad_variables) { /* ditto */
|
|
|
|
char *e;
|
|
int r;
|
|
|
|
assert(l == 0 || name);
|
|
assert(ret);
|
|
|
|
if (env_name_is_valid_n(name, l)) {
|
|
e = strv_env_get_n(env, name, l, flags);
|
|
if (!e && unset_variables) {
|
|
r = strv_extend_with_length(unset_variables, name, l);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
} else {
|
|
e = NULL; /* Resolve invalid variable names the same way as unset ones */
|
|
|
|
if (bad_variables) {
|
|
r = strv_extend_with_length(bad_variables, name, l);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
}
|
|
|
|
*ret = e;
|
|
return !!e;
|
|
}
|
|
|
|
int replace_env_full(
|
|
const char *format,
|
|
size_t n,
|
|
char **env,
|
|
ReplaceEnvFlags flags,
|
|
char **ret,
|
|
char ***ret_unset_variables,
|
|
char ***ret_bad_variables) {
|
|
|
|
enum {
|
|
WORD,
|
|
CURLY,
|
|
VARIABLE,
|
|
VARIABLE_RAW,
|
|
TEST,
|
|
DEFAULT_VALUE,
|
|
ALTERNATE_VALUE,
|
|
} state = WORD;
|
|
|
|
_cleanup_strv_free_ char **unset_variables = NULL, **bad_variables = NULL;
|
|
const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */
|
|
_cleanup_free_ char *s = NULL;
|
|
char ***pu, ***pb;
|
|
size_t i, len = 0; /* len is initialized to appease gcc */
|
|
int nest = 0, r;
|
|
|
|
assert(format);
|
|
|
|
if (n == SIZE_MAX)
|
|
n = strlen(format);
|
|
|
|
pu = ret_unset_variables ? &unset_variables : NULL;
|
|
pb = ret_bad_variables ? &bad_variables : NULL;
|
|
|
|
for (e = format, i = 0; *e && i < n; e++, i++)
|
|
switch (state) {
|
|
|
|
case WORD:
|
|
if (*e == '$')
|
|
state = CURLY;
|
|
break;
|
|
|
|
case CURLY:
|
|
if (*e == '{') {
|
|
if (!strextendn(&s, word, e-word-1))
|
|
return -ENOMEM;
|
|
|
|
word = e-1;
|
|
state = VARIABLE;
|
|
nest++;
|
|
|
|
} else if (*e == '$') {
|
|
if (!strextendn(&s, word, e-word))
|
|
return -ENOMEM;
|
|
|
|
word = e+1;
|
|
state = WORD;
|
|
|
|
} else if (FLAGS_SET(flags, REPLACE_ENV_ALLOW_BRACELESS) && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
|
|
if (!strextendn(&s, word, e-word-1))
|
|
return -ENOMEM;
|
|
|
|
word = e-1;
|
|
state = VARIABLE_RAW;
|
|
|
|
} else
|
|
state = WORD;
|
|
break;
|
|
|
|
case VARIABLE:
|
|
if (*e == '}') {
|
|
char *t;
|
|
|
|
r = strv_env_get_n_validated(env, word+2, e-word-2, flags, &t, pu, pb);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!strextend(&s, t))
|
|
return -ENOMEM;
|
|
|
|
word = e+1;
|
|
state = WORD;
|
|
nest--;
|
|
} else if (*e == ':') {
|
|
if (flags & REPLACE_ENV_ALLOW_EXTENDED) {
|
|
len = e - word - 2;
|
|
state = TEST;
|
|
} else
|
|
/* Treat this as unsupported syntax, i.e. do no replacement */
|
|
state = WORD;
|
|
}
|
|
break;
|
|
|
|
case TEST:
|
|
if (*e == '-')
|
|
state = DEFAULT_VALUE;
|
|
else if (*e == '+')
|
|
state = ALTERNATE_VALUE;
|
|
else {
|
|
state = WORD;
|
|
break;
|
|
}
|
|
|
|
test_value = e+1;
|
|
break;
|
|
|
|
case DEFAULT_VALUE: /* fall through */
|
|
case ALTERNATE_VALUE:
|
|
assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
|
|
|
|
if (*e == '{') {
|
|
nest++;
|
|
break;
|
|
}
|
|
|
|
if (*e != '}')
|
|
break;
|
|
|
|
nest--;
|
|
if (nest == 0) {
|
|
_cleanup_strv_free_ char **u = NULL, **b = NULL;
|
|
_cleanup_free_ char *v = NULL;
|
|
char *t = NULL;
|
|
|
|
r = strv_env_get_n_validated(env, word+2, len, flags, &t, pu, pb);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (t && state == ALTERNATE_VALUE) {
|
|
r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
t = v;
|
|
} else if (!t && state == DEFAULT_VALUE) {
|
|
r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
t = v;
|
|
}
|
|
|
|
r = strv_extend_strv_consume(&unset_variables, TAKE_PTR(u), /* filter_duplicates= */ true);
|
|
if (r < 0)
|
|
return r;
|
|
r = strv_extend_strv_consume(&bad_variables, TAKE_PTR(b), /* filter_duplicates= */ true);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!strextend(&s, t))
|
|
return -ENOMEM;
|
|
|
|
word = e+1;
|
|
state = WORD;
|
|
}
|
|
break;
|
|
|
|
case VARIABLE_RAW:
|
|
assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
|
|
|
|
if (!strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
|
|
char *t = NULL;
|
|
|
|
r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!strextend(&s, t))
|
|
return -ENOMEM;
|
|
|
|
word = e--;
|
|
i--;
|
|
state = WORD;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (state == VARIABLE_RAW) {
|
|
char *t;
|
|
|
|
assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
|
|
|
|
r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!strextend(&s, t))
|
|
return -ENOMEM;
|
|
|
|
} else if (!strextendn(&s, word, e-word))
|
|
return -ENOMEM;
|
|
|
|
if (ret_unset_variables)
|
|
*ret_unset_variables = TAKE_PTR(unset_variables);
|
|
if (ret_bad_variables)
|
|
*ret_bad_variables = TAKE_PTR(bad_variables);
|
|
|
|
if (ret)
|
|
*ret = TAKE_PTR(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int replace_env_argv(
|
|
char **argv,
|
|
char **env,
|
|
char ***ret,
|
|
char ***ret_unset_variables,
|
|
char ***ret_bad_variables) {
|
|
|
|
_cleanup_strv_free_ char **n = NULL, **unset_variables = NULL, **bad_variables = NULL;
|
|
size_t k = 0, l = 0;
|
|
int r;
|
|
|
|
l = strv_length(argv);
|
|
|
|
n = new(char*, l+1);
|
|
if (!n)
|
|
return -ENOMEM;
|
|
|
|
STRV_FOREACH(i, argv) {
|
|
const char *word = *i;
|
|
|
|
/* If $FOO appears as single word, replace it by the split up variable */
|
|
if (word[0] == '$' && !IN_SET(word[1], '{', '$')) {
|
|
_cleanup_strv_free_ char **m = NULL;
|
|
const char *name = word + 1;
|
|
char *e, **w;
|
|
size_t q;
|
|
|
|
if (env_name_is_valid(name)) {
|
|
e = strv_env_get(env, name);
|
|
if (e)
|
|
r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
|
|
else if (ret_unset_variables)
|
|
r = strv_extend(&unset_variables, name);
|
|
else
|
|
r = 0;
|
|
} else if (ret_bad_variables)
|
|
r = strv_extend(&bad_variables, name);
|
|
else
|
|
r = 0;
|
|
if (r < 0)
|
|
return r;
|
|
|
|
q = strv_length(m);
|
|
l = l + q - 1;
|
|
|
|
w = reallocarray(n, l + 1, sizeof(char*));
|
|
if (!w)
|
|
return -ENOMEM;
|
|
|
|
n = w;
|
|
if (m) {
|
|
memcpy(n + k, m, (q + 1) * sizeof(char*));
|
|
m = mfree(m);
|
|
}
|
|
|
|
k += q;
|
|
continue;
|
|
}
|
|
|
|
_cleanup_strv_free_ char **u = NULL, **b = NULL;
|
|
|
|
/* If ${FOO} appears as part of a word, replace it by the variable as-is */
|
|
r = replace_env_full(
|
|
word,
|
|
/* length= */ SIZE_MAX,
|
|
env,
|
|
/* flags= */ 0,
|
|
n + k,
|
|
ret_unset_variables ? &u : NULL,
|
|
ret_bad_variables ? &b : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
n[++k] = NULL;
|
|
|
|
r = strv_extend_strv_consume(&unset_variables, TAKE_PTR(u), /* filter_duplicates= */ true);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = strv_extend_strv_consume(&bad_variables, TAKE_PTR(b), /* filter_duplicates= */ true);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (ret_unset_variables) {
|
|
strv_sort_uniq(unset_variables);
|
|
*ret_unset_variables = TAKE_PTR(unset_variables);
|
|
}
|
|
if (ret_bad_variables) {
|
|
strv_sort_uniq(bad_variables);
|
|
*ret_bad_variables = TAKE_PTR(bad_variables);
|
|
}
|
|
|
|
*ret = TAKE_PTR(n);
|
|
return 0;
|
|
}
|
|
|
|
int getenv_bool(const char *p) {
|
|
const char *e;
|
|
|
|
e = getenv(p);
|
|
if (!e)
|
|
return -ENXIO;
|
|
|
|
return parse_boolean(e);
|
|
}
|
|
|
|
int secure_getenv_bool(const char *p) {
|
|
const char *e;
|
|
|
|
e = secure_getenv(p);
|
|
if (!e)
|
|
return -ENXIO;
|
|
|
|
return parse_boolean(e);
|
|
}
|
|
|
|
int secure_getenv_uint64(const char *p, uint64_t *ret) {
|
|
const char *e;
|
|
|
|
assert(p);
|
|
|
|
e = secure_getenv(p);
|
|
if (!e)
|
|
return -ENXIO;
|
|
|
|
return safe_atou64(e, ret);
|
|
}
|
|
|
|
int set_unset_env(const char *name, const char *value, bool overwrite) {
|
|
assert(name);
|
|
|
|
if (value)
|
|
return RET_NERRNO(setenv(name, value, overwrite));
|
|
|
|
return RET_NERRNO(unsetenv(name));
|
|
}
|
|
|
|
int putenv_dup(const char *assignment, bool override) {
|
|
const char *e, *n;
|
|
|
|
e = strchr(assignment, '=');
|
|
if (!e)
|
|
return -EINVAL;
|
|
|
|
n = strndupa_safe(assignment, e - assignment);
|
|
|
|
/* This is like putenv(), but uses setenv() so that our memory doesn't become part of environ[]. */
|
|
return RET_NERRNO(setenv(n, e + 1, override));
|
|
}
|
|
|
|
int setenv_systemd_exec_pid(bool update_only) {
|
|
const char *e;
|
|
int r;
|
|
|
|
/* Update $SYSTEMD_EXEC_PID=pid except when '*' is set for the variable. */
|
|
|
|
e = secure_getenv("SYSTEMD_EXEC_PID");
|
|
if (!e && update_only)
|
|
return 0;
|
|
|
|
if (streq_ptr(e, "*"))
|
|
return 0;
|
|
|
|
r = setenvf("SYSTEMD_EXEC_PID", /* overwrite= */ 1, PID_FMT, getpid_cached());
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int setenv_systemd_log_level(void) {
|
|
_cleanup_free_ char *val = NULL;
|
|
int r;
|
|
|
|
r = log_level_to_string_alloc(log_get_max_level(), &val);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return RET_NERRNO(setenv("SYSTEMD_LOG_LEVEL", val, /* overwrite= */ true));
|
|
}
|
|
|
|
int getenv_path_list(const char *name, char ***ret_paths) {
|
|
_cleanup_strv_free_ char **l = NULL;
|
|
const char *e;
|
|
int r;
|
|
|
|
assert(name);
|
|
assert(ret_paths);
|
|
|
|
e = secure_getenv(name);
|
|
if (!e)
|
|
return -ENXIO;
|
|
|
|
r = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to parse $%s: %m", name);
|
|
|
|
STRV_FOREACH(p, l) {
|
|
if (!path_is_absolute(*p))
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"Path '%s' is not absolute, refusing.", *p);
|
|
|
|
if (!path_is_normalized(*p))
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"Path '%s' is not normalized, refusing.", *p);
|
|
|
|
if (path_equal(*p, "/"))
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"Path '%s' is the root fs, refusing.", *p);
|
|
}
|
|
|
|
if (strv_isempty(l))
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"No paths specified, refusing.");
|
|
|
|
*ret_paths = TAKE_PTR(l);
|
|
return 1;
|
|
}
|
|
|
|
int getenv_steal_erase(const char *name, char **ret) {
|
|
_cleanup_(erase_and_freep) char *a = NULL;
|
|
char *e;
|
|
|
|
assert(name);
|
|
|
|
/* Reads an environment variable, makes a copy of it, erases its memory in the environment block and removes
|
|
* it from there. Usecase: reading passwords from the env block (which is a bad idea, but useful for
|
|
* testing, and given that people are likely going to misuse this, be thorough) */
|
|
|
|
e = secure_getenv(name);
|
|
if (!e) {
|
|
if (ret)
|
|
*ret = NULL;
|
|
return 0;
|
|
}
|
|
|
|
if (ret) {
|
|
a = strdup(e);
|
|
if (!a)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
string_erase(e);
|
|
|
|
if (unsetenv(name) < 0)
|
|
return -errno;
|
|
|
|
if (ret)
|
|
*ret = TAKE_PTR(a);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int set_full_environment(char **env) {
|
|
int r;
|
|
|
|
clearenv();
|
|
|
|
STRV_FOREACH(e, env) {
|
|
_cleanup_free_ char *k = NULL, *v = NULL;
|
|
|
|
r = split_pair(*e, "=", &k, &v);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (setenv(k, v, /* overwrite= */ true) < 0)
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int setenvf(const char *name, bool overwrite, const char *valuef, ...) {
|
|
_cleanup_free_ char *value = NULL;
|
|
va_list ap;
|
|
int r;
|
|
|
|
assert(name);
|
|
|
|
if (!valuef)
|
|
return RET_NERRNO(unsetenv(name));
|
|
|
|
va_start(ap, valuef);
|
|
r = vasprintf(&value, valuef, ap);
|
|
va_end(ap);
|
|
|
|
if (r < 0)
|
|
return -ENOMEM;
|
|
|
|
/* Try to suppress writes if the value is already set correctly (simply because memory management of
|
|
* environment variables sucks a bit. */
|
|
if (streq_ptr(getenv(name), value))
|
|
return 0;
|
|
|
|
return RET_NERRNO(setenv(name, value, overwrite));
|
|
}
|