1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
10 #include "string-util.h"
12 #include "tmpfile-util.h"
15 static int parse_env_file_internal(
18 int (*push
) (const char *filename
, unsigned line
,
19 const char *key
, char *value
, void *userdata
, int *n_pushed
),
23 size_t n_key
= 0, n_value
= 0, last_value_whitespace
= SIZE_MAX
, last_key_whitespace
= SIZE_MAX
;
24 _cleanup_free_
char *contents
= NULL
, *key
= NULL
, *value
= NULL
;
36 DOUBLE_QUOTE_VALUE_ESCAPE
,
42 r
= read_full_stream(f
, &contents
, NULL
);
44 r
= read_full_file(fname
, &contents
, NULL
);
48 for (char *p
= contents
; *p
; p
++) {
54 if (strchr(COMMENTS
, c
))
56 else if (!strchr(WHITESPACE
, c
)) {
58 last_key_whitespace
= SIZE_MAX
;
60 if (!GREEDY_REALLOC(key
, n_key
+2))
68 if (strchr(NEWLINE
, c
)) {
72 } else if (c
== '=') {
74 last_value_whitespace
= SIZE_MAX
;
76 if (!strchr(WHITESPACE
, c
))
77 last_key_whitespace
= SIZE_MAX
;
78 else if (last_key_whitespace
== SIZE_MAX
)
79 last_key_whitespace
= n_key
;
81 if (!GREEDY_REALLOC(key
, n_key
+2))
90 if (strchr(NEWLINE
, c
)) {
98 /* strip trailing whitespace from key */
99 if (last_key_whitespace
!= SIZE_MAX
)
100 key
[last_key_whitespace
] = 0;
102 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
110 } else if (c
== '\'')
111 state
= SINGLE_QUOTE_VALUE
;
113 state
= DOUBLE_QUOTE_VALUE
;
115 state
= VALUE_ESCAPE
;
116 else if (!strchr(WHITESPACE
, c
)) {
119 if (!GREEDY_REALLOC(value
, n_value
+2))
122 value
[n_value
++] = c
;
128 if (strchr(NEWLINE
, c
)) {
137 /* Chomp off trailing whitespace from value */
138 if (last_value_whitespace
!= SIZE_MAX
)
139 value
[last_value_whitespace
] = 0;
141 /* strip trailing whitespace from key */
142 if (last_key_whitespace
!= SIZE_MAX
)
143 key
[last_key_whitespace
] = 0;
145 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
153 } else if (c
== '\\') {
154 state
= VALUE_ESCAPE
;
155 last_value_whitespace
= SIZE_MAX
;
157 if (!strchr(WHITESPACE
, c
))
158 last_value_whitespace
= SIZE_MAX
;
159 else if (last_value_whitespace
== SIZE_MAX
)
160 last_value_whitespace
= n_value
;
162 if (!GREEDY_REALLOC(value
, n_value
+2))
165 value
[n_value
++] = c
;
173 if (!strchr(NEWLINE
, c
)) {
174 /* Escaped newlines we eat up entirely */
175 if (!GREEDY_REALLOC(value
, n_value
+2))
178 value
[n_value
++] = c
;
182 case SINGLE_QUOTE_VALUE
:
186 if (!GREEDY_REALLOC(value
, n_value
+2))
189 value
[n_value
++] = c
;
194 case DOUBLE_QUOTE_VALUE
:
198 state
= DOUBLE_QUOTE_VALUE_ESCAPE
;
200 if (!GREEDY_REALLOC(value
, n_value
+2))
203 value
[n_value
++] = c
;
208 case DOUBLE_QUOTE_VALUE_ESCAPE
:
209 state
= DOUBLE_QUOTE_VALUE
;
211 if (strchr(SHELL_NEED_ESCAPE
, c
)) {
212 /* If this is a char that needs escaping, just unescape it. */
213 if (!GREEDY_REALLOC(value
, n_value
+2))
215 value
[n_value
++] = c
;
216 } else if (c
!= '\n') {
217 /* If other char than what needs escaping, keep the "\" in place, like the
218 * real shell does. */
219 if (!GREEDY_REALLOC(value
, n_value
+3))
221 value
[n_value
++] = '\\';
222 value
[n_value
++] = c
;
225 /* Escaped newlines (aka "continuation lines") are eaten up entirely */
230 state
= COMMENT_ESCAPE
;
231 else if (strchr(NEWLINE
, c
)) {
249 DOUBLE_QUOTE_VALUE_ESCAPE
)) {
257 if (last_value_whitespace
!= SIZE_MAX
)
258 value
[last_value_whitespace
] = 0;
260 /* strip trailing whitespace from key */
261 if (last_key_whitespace
!= SIZE_MAX
)
262 key
[last_key_whitespace
] = 0;
264 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
274 static int check_utf8ness_and_warn(
275 const char *filename
, unsigned line
,
276 const char *key
, char *value
) {
278 if (!utf8_is_valid(key
)) {
279 _cleanup_free_
char *p
= NULL
;
281 p
= utf8_escape_invalid(key
);
282 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
283 "%s:%u: invalid UTF-8 in key '%s', ignoring.",
284 strna(filename
), line
, p
);
287 if (value
&& !utf8_is_valid(value
)) {
288 _cleanup_free_
char *p
= NULL
;
290 p
= utf8_escape_invalid(value
);
291 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
292 "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
293 strna(filename
), line
, key
, p
);
299 static int parse_env_file_push(
300 const char *filename
, unsigned line
,
301 const char *key
, char *value
,
306 va_list aq
, *ap
= userdata
;
309 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
315 while ((k
= va_arg(aq
, const char *))) {
318 v
= va_arg(aq
, char **);
347 r
= parse_env_file_internal(f
, fname
, parse_env_file_push
, &aq
, &n_pushed
);
355 int parse_env_file_sentinel(
364 r
= parse_env_filev(f
, fname
, ap
);
370 static int load_env_file_push(
371 const char *filename
, unsigned line
,
372 const char *key
, char *value
,
375 char ***m
= userdata
;
379 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
383 p
= strjoin(key
, "=", value
);
387 r
= strv_env_replace_consume(m
, p
);
398 int load_env_file(FILE *f
, const char *fname
, char ***rl
) {
402 r
= parse_env_file_internal(f
, fname
, load_env_file_push
, &m
, NULL
);
412 static int load_env_file_push_pairs(
413 const char *filename
, unsigned line
,
414 const char *key
, char *value
,
417 char ***m
= userdata
;
420 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
424 r
= strv_extend(m
, key
);
429 r
= strv_extend(m
, "");
433 r
= strv_push(m
, value
);
444 int load_env_file_pairs(FILE *f
, const char *fname
, char ***rl
) {
448 r
= parse_env_file_internal(f
, fname
, load_env_file_push_pairs
, &m
, NULL
);
458 static int merge_env_file_push(
459 const char *filename
, unsigned line
,
460 const char *key
, char *value
,
464 char ***env
= userdata
;
465 char *expanded_value
;
470 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename
), line
, key
);
474 if (!env_name_is_valid(key
)) {
475 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename
), line
, key
);
480 expanded_value
= replace_env(value
, *env
,
481 REPLACE_ENV_USE_ENVIRONMENT
|
482 REPLACE_ENV_ALLOW_BRACELESS
|
483 REPLACE_ENV_ALLOW_EXTENDED
);
487 free_and_replace(value
, expanded_value
);
489 log_debug("%s:%u: setting %s=%s", filename
, line
, key
, value
);
491 return load_env_file_push(filename
, line
, key
, value
, env
, n_pushed
);
499 /* NOTE: this function supports braceful and braceless variable expansions,
500 * plus "extended" substitutions, unlike other exported parsing functions.
503 return parse_env_file_internal(f
, fname
, merge_env_file_push
, env
, NULL
);
506 static void write_env_var(FILE *f
, const char *v
) {
512 fputs_unlocked(v
, f
);
513 fputc_unlocked('\n', f
);
518 fwrite_unlocked(v
, 1, p
-v
, f
);
520 if (string_has_cc(p
, NULL
) || chars_intersect(p
, WHITESPACE SHELL_NEED_QUOTES
)) {
521 fputc_unlocked('"', f
);
524 if (strchr(SHELL_NEED_ESCAPE
, *p
))
525 fputc_unlocked('\\', f
);
527 fputc_unlocked(*p
, f
);
530 fputc_unlocked('"', f
);
532 fputs_unlocked(p
, f
);
534 fputc_unlocked('\n', f
);
537 int write_env_file(const char *fname
, char **l
) {
538 _cleanup_fclose_
FILE *f
= NULL
;
539 _cleanup_free_
char *p
= NULL
;
545 r
= fopen_temporary(fname
, &f
, &p
);
549 (void) fchmod_umask(fileno(f
), 0644);
552 write_env_var(f
, *i
);
554 r
= fflush_and_check(f
);
556 if (rename(p
, fname
) >= 0)