Skip to content

Commit f331434

Browse files
committed
env-file: when resolving env vars in command lines, collect list of unset/invalid ones
When resolving environment variables we currently silently resolve unset and invalid environment variables to empty strings. Let's do this slightly less silently: log about unset and invalid env vars, but still resolve them to an empty string. Fixes: #27036
1 parent 7658139 commit f331434

File tree

6 files changed

+290
-87
lines changed

6 files changed

+290
-87
lines changed

src/basic/env-file.c

+7-6
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ static int merge_env_file_push(
525525

526526
char ***env = ASSERT_PTR(userdata);
527527
char *expanded_value;
528+
int r;
528529

529530
assert(key);
530531

@@ -539,12 +540,12 @@ static int merge_env_file_push(
539540
return 0;
540541
}
541542

542-
expanded_value = replace_env(value, *env,
543-
REPLACE_ENV_USE_ENVIRONMENT|
544-
REPLACE_ENV_ALLOW_BRACELESS|
545-
REPLACE_ENV_ALLOW_EXTENDED);
546-
if (!expanded_value)
547-
return -ENOMEM;
543+
r = replace_env(value,
544+
*env,
545+
REPLACE_ENV_USE_ENVIRONMENT|REPLACE_ENV_ALLOW_BRACELESS|REPLACE_ENV_ALLOW_EXTENDED,
546+
&expanded_value);
547+
if (r < 0)
548+
return log_error_errno(r, "%s:%u: Failed to expand variable '%s': %m", strna(filename), line, value);
548549

549550
free_and_replace(value, expanded_value);
550551

src/basic/env-util.c

+192-56
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,61 @@ char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const cha
574574
return e;
575575
}
576576

577-
char *replace_env_n(const char *format, size_t n, char **env, ReplaceEnvFlags flags) {
577+
static int strv_extend_with_length(char ***l, const char *s, size_t n) {
578+
char *c;
579+
580+
c = strndup(s, n);
581+
if (!c)
582+
return -ENOMEM;
583+
584+
return strv_consume(l, c);
585+
}
586+
587+
static int strv_env_get_n_validated(
588+
char **env,
589+
const char *name,
590+
size_t l,
591+
ReplaceEnvFlags flags,
592+
char **ret, /* points into the env block! do not free! */
593+
char ***unset_variables, /* updated in place */
594+
char ***bad_variables) { /* ditto */
595+
596+
char *e;
597+
int r;
598+
599+
assert(l == 0 || name);
600+
assert(ret);
601+
602+
if (env_name_is_valid_n(name, l)) {
603+
e = strv_env_get_n(env, name, l, flags);
604+
if (!e && unset_variables) {
605+
r = strv_extend_with_length(unset_variables, name, l);
606+
if (r < 0)
607+
return r;
608+
}
609+
} else {
610+
e = NULL; /* Resolve invalid variable names the same way as unset ones */
611+
612+
if (bad_variables) {
613+
r = strv_extend_with_length(bad_variables, name, l);
614+
if (r < 0)
615+
return r;
616+
}
617+
}
618+
619+
*ret = e;
620+
return !!e;
621+
}
622+
623+
int replace_env_full(
624+
const char *format,
625+
size_t n,
626+
char **env,
627+
ReplaceEnvFlags flags,
628+
char **ret,
629+
char ***ret_unset_variables,
630+
char ***ret_bad_variables) {
631+
578632
enum {
579633
WORD,
580634
CURLY,
@@ -585,14 +639,21 @@ char *replace_env_n(const char *format, size_t n, char **env, ReplaceEnvFlags fl
585639
ALTERNATE_VALUE,
586640
} state = WORD;
587641

642+
_cleanup_strv_free_ char **unset_variables = NULL, **bad_variables = NULL;
588643
const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */
589-
char *k;
590644
_cleanup_free_ char *s = NULL;
645+
char ***pu, ***pb, *k;
591646
size_t i, len = 0; /* len is initialized to appease gcc */
592-
int nest = 0;
647+
int nest = 0, r;
593648

594649
assert(format);
595650

651+
if (n == SIZE_MAX)
652+
n = strlen(format);
653+
654+
pu = ret_unset_variables ? &unset_variables : NULL;
655+
pb = ret_bad_variables ? &bad_variables : NULL;
656+
596657
for (e = format, i = 0; *e && i < n; e ++, i ++)
597658
switch (state) {
598659

@@ -605,27 +666,28 @@ char *replace_env_n(const char *format, size_t n, char **env, ReplaceEnvFlags fl
605666
if (*e == '{') {
606667
k = strnappend(s, word, e-word-1);
607668
if (!k)
608-
return NULL;
669+
return -ENOMEM;
609670

610671
free_and_replace(s, k);
611672

612673
word = e-1;
613674
state = VARIABLE;
614675
nest++;
676+
615677
} else if (*e == '$') {
616678
k = strnappend(s, word, e-word);
617679
if (!k)
618-
return NULL;
680+
return -ENOMEM;
619681

620682
free_and_replace(s, k);
621683

622684
word = e+1;
623685
state = WORD;
624686

625-
} else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
687+
} else if (FLAGS_SET(flags, REPLACE_ENV_ALLOW_BRACELESS) && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
626688
k = strnappend(s, word, e-word-1);
627689
if (!k)
628-
return NULL;
690+
return -ENOMEM;
629691

630692
free_and_replace(s, k);
631693

@@ -638,12 +700,14 @@ char *replace_env_n(const char *format, size_t n, char **env, ReplaceEnvFlags fl
638700

639701
case VARIABLE:
640702
if (*e == '}') {
641-
const char *t;
703+
char *t;
642704

643-
t = strv_env_get_n(env, word+2, e-word-2, flags);
705+
r = strv_env_get_n_validated(env, word+2, e-word-2, flags, &t, pu, pb);
706+
if (r < 0)
707+
return r;
644708

645709
if (!strextend(&s, t))
646-
return NULL;
710+
return -ENOMEM;
647711

648712
word = e+1;
649713
state = WORD;
@@ -685,18 +749,37 @@ char *replace_env_n(const char *format, size_t n, char **env, ReplaceEnvFlags fl
685749

686750
nest--;
687751
if (nest == 0) {
688-
const char *t;
752+
_cleanup_strv_free_ char **u = NULL, **b = NULL;
689753
_cleanup_free_ char *v = NULL;
754+
char *t = NULL;
755+
756+
r = strv_env_get_n_validated(env, word+2, len, flags, &t, pu, pb);
757+
if (r < 0)
758+
return r;
690759

691-
t = strv_env_get_n(env, word+2, len, flags);
760+
if (t && state == ALTERNATE_VALUE) {
761+
r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL);
762+
if (r < 0)
763+
return r;
692764

693-
if (t && state == ALTERNATE_VALUE)
694-
t = v = replace_env_n(test_value, e-test_value, env, flags);
695-
else if (!t && state == DEFAULT_VALUE)
696-
t = v = replace_env_n(test_value, e-test_value, env, flags);
765+
t = v;
766+
} else if (!t && state == DEFAULT_VALUE) {
767+
r = replace_env_full(test_value, e-test_value, env, flags, &v, pu ? &u : NULL, pb ? &b : NULL);
768+
if (r < 0)
769+
return r;
770+
771+
t = v;
772+
}
773+
774+
r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true);
775+
if (r < 0)
776+
return r;
777+
r = strv_extend_strv(&bad_variables, b, /* filter_duplicates= */ true);
778+
if (r < 0)
779+
return r;
697780

698781
if (!strextend(&s, t))
699-
return NULL;
782+
return -ENOMEM;
700783

701784
word = e+1;
702785
state = WORD;
@@ -707,12 +790,14 @@ char *replace_env_n(const char *format, size_t n, char **env, ReplaceEnvFlags fl
707790
assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
708791

709792
if (!strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
710-
const char *t;
793+
char *t = NULL;
711794

712-
t = strv_env_get_n(env, word+1, e-word-1, flags);
795+
r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables);
796+
if (r < 0)
797+
return r;
713798

714799
if (!strextend(&s, t))
715-
return NULL;
800+
return -ENOMEM;
716801

717802
word = e--;
718803
i--;
@@ -722,74 +807,125 @@ char *replace_env_n(const char *format, size_t n, char **env, ReplaceEnvFlags fl
722807
}
723808

724809
if (state == VARIABLE_RAW) {
725-
const char *t;
810+
char *t;
726811

727812
assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
728813

729-
t = strv_env_get_n(env, word+1, e-word-1, flags);
730-
return strjoin(s, t);
731-
} else
732-
return strnappend(s, word, e-word);
814+
r = strv_env_get_n_validated(env, word+1, e-word-1, flags, &t, &unset_variables, &bad_variables);
815+
if (r < 0)
816+
return r;
817+
818+
if (!strextend(&s, t))
819+
return -ENOMEM;
820+
821+
} else if (!strextendn(&s, word, e-word))
822+
return -ENOMEM;
823+
824+
if (ret_unset_variables)
825+
*ret_unset_variables = TAKE_PTR(unset_variables);
826+
if (ret_bad_variables)
827+
*ret_bad_variables = TAKE_PTR(bad_variables);
828+
829+
if (ret)
830+
*ret = TAKE_PTR(s);
831+
832+
return 0;
733833
}
734834

735-
char **replace_env_argv(char **argv, char **env) {
736-
_cleanup_strv_free_ char **ret = NULL;
835+
int replace_env_argv(
836+
char **argv,
837+
char **env,
838+
char ***ret,
839+
char ***ret_unset_variables,
840+
char ***ret_bad_variables) {
841+
842+
_cleanup_strv_free_ char **n = NULL, **unset_variables = NULL, **bad_variables = NULL;
737843
size_t k = 0, l = 0;
844+
int r;
738845

739846
l = strv_length(argv);
740847

741-
ret = new(char*, l+1);
742-
if (!ret)
743-
return NULL;
848+
n = new(char*, l+1);
849+
if (!n)
850+
return -ENOMEM;
744851

745852
STRV_FOREACH(i, argv) {
853+
const char *word = *i;
746854

747855
/* If $FOO appears as single word, replace it by the split up variable */
748-
if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
749-
char *e;
750-
char **w;
856+
if (word[0] == '$' && !IN_SET(word[1], '{', '$')) {
751857
_cleanup_strv_free_ char **m = NULL;
858+
const char *name = word + 1;
859+
char *e, **w;
752860
size_t q;
753861

754-
e = strv_env_get(env, *i+1);
755-
if (e) {
756-
int r;
757-
758-
r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
759-
if (r < 0) {
760-
ret[k] = NULL;
761-
return NULL;
762-
}
763-
}
862+
if (env_name_is_valid(name)) {
863+
e = strv_env_get(env, name);
864+
if (e)
865+
r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
866+
else if (ret_unset_variables)
867+
r = strv_extend(&unset_variables, name);
868+
else
869+
r = 0;
870+
} else if (ret_bad_variables)
871+
r = strv_extend(&bad_variables, name);
872+
else
873+
r = 0;
874+
if (r < 0)
875+
return r;
764876

765877
q = strv_length(m);
766878
l = l + q - 1;
767879

768-
w = reallocarray(ret, l + 1, sizeof(char *));
769-
if (!w) {
770-
ret[k] = NULL;
771-
return NULL;
772-
}
880+
w = reallocarray(n, l + 1, sizeof(char*));
881+
if (!w)
882+
return -ENOMEM;
773883

774-
ret = w;
884+
n = w;
775885
if (m) {
776-
memcpy(ret + k, m, q * sizeof(char*));
886+
memcpy(n + k, m, (q + 1) * sizeof(char*));
777887
m = mfree(m);
778888
}
779889

780890
k += q;
781891
continue;
782892
}
783893

894+
_cleanup_strv_free_ char **u = NULL, **b = NULL;
895+
784896
/* If ${FOO} appears as part of a word, replace it by the variable as-is */
785-
ret[k] = replace_env(*i, env, 0);
786-
if (!ret[k])
787-
return NULL;
788-
k++;
897+
r = replace_env_full(
898+
word,
899+
/* length= */ SIZE_MAX,
900+
env,
901+
/* flags= */ 0,
902+
n + k,
903+
ret_unset_variables ? &u : NULL,
904+
ret_bad_variables ? &b : NULL);
905+
if (r < 0)
906+
return r;
907+
n[++k] = NULL;
908+
909+
r = strv_extend_strv(&unset_variables, u, /* filter_duplicates= */ true);
910+
if (r < 0)
911+
return r;
912+
913+
r = strv_extend_strv(&bad_variables, b, /*filter_duplicates= */ true);
914+
if (r < 0)
915+
return r;
789916
}
790917

791-
ret[k] = NULL;
792-
return TAKE_PTR(ret);
918+
if (ret_unset_variables) {
919+
strv_uniq(strv_sort(unset_variables));
920+
*ret_unset_variables = TAKE_PTR(unset_variables);
921+
}
922+
if (ret_bad_variables) {
923+
strv_uniq(strv_sort(bad_variables));
924+
*ret_bad_variables = TAKE_PTR(bad_variables);
925+
}
926+
927+
*ret = TAKE_PTR(n);
928+
return 0;
793929
}
794930

795931
int getenv_bool(const char *p) {

0 commit comments

Comments
 (0)