1 /* SPDX-License-Identifier: LGPL-2.1+ */
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 key_alloc
= 0, n_key
= 0, value_alloc
= 0, n_value
= 0, last_value_whitespace
= (size_t) -1, last_key_whitespace
= (size_t) -1;
24 _cleanup_free_
char *contents
= NULL
, *key
= NULL
, *value
= NULL
;
37 DOUBLE_QUOTE_VALUE_ESCAPE
,
43 r
= read_full_stream(f
, &contents
, NULL
);
45 r
= read_full_file(fname
, &contents
, NULL
);
49 for (p
= contents
; *p
; p
++) {
55 if (strchr(COMMENTS
, c
))
57 else if (!strchr(WHITESPACE
, c
)) {
59 last_key_whitespace
= (size_t) -1;
61 if (!GREEDY_REALLOC(key
, key_alloc
, n_key
+2))
69 if (strchr(NEWLINE
, c
)) {
73 } else if (c
== '=') {
75 last_value_whitespace
= (size_t) -1;
77 if (!strchr(WHITESPACE
, c
))
78 last_key_whitespace
= (size_t) -1;
79 else if (last_key_whitespace
== (size_t) -1)
80 last_key_whitespace
= n_key
;
82 if (!GREEDY_REALLOC(key
, key_alloc
, n_key
+2))
91 if (strchr(NEWLINE
, c
)) {
99 /* strip trailing whitespace from key */
100 if (last_key_whitespace
!= (size_t) -1)
101 key
[last_key_whitespace
] = 0;
103 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
109 value_alloc
= n_value
= 0;
111 } else if (c
== '\'')
112 state
= SINGLE_QUOTE_VALUE
;
114 state
= DOUBLE_QUOTE_VALUE
;
116 state
= VALUE_ESCAPE
;
117 else if (!strchr(WHITESPACE
, c
)) {
120 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
123 value
[n_value
++] = c
;
129 if (strchr(NEWLINE
, c
)) {
138 /* Chomp off trailing whitespace from value */
139 if (last_value_whitespace
!= (size_t) -1)
140 value
[last_value_whitespace
] = 0;
142 /* strip trailing whitespace from key */
143 if (last_key_whitespace
!= (size_t) -1)
144 key
[last_key_whitespace
] = 0;
146 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
152 value_alloc
= n_value
= 0;
154 } else if (c
== '\\') {
155 state
= VALUE_ESCAPE
;
156 last_value_whitespace
= (size_t) -1;
158 if (!strchr(WHITESPACE
, c
))
159 last_value_whitespace
= (size_t) -1;
160 else if (last_value_whitespace
== (size_t) -1)
161 last_value_whitespace
= n_value
;
163 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
166 value
[n_value
++] = c
;
174 if (!strchr(NEWLINE
, c
)) {
175 /* Escaped newlines we eat up entirely */
176 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
179 value
[n_value
++] = c
;
183 case SINGLE_QUOTE_VALUE
:
187 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
190 value
[n_value
++] = c
;
195 case DOUBLE_QUOTE_VALUE
:
199 state
= DOUBLE_QUOTE_VALUE_ESCAPE
;
201 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
204 value
[n_value
++] = c
;
209 case DOUBLE_QUOTE_VALUE_ESCAPE
:
210 state
= DOUBLE_QUOTE_VALUE
;
212 if (strchr(SHELL_NEED_ESCAPE
, c
)) {
213 /* If this is a char that needs escaping, just unescape it. */
214 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
216 value
[n_value
++] = c
;
217 } else if (c
!= '\n') {
218 /* If other char than what needs escaping, keep the "\" in place, like the
219 * real shell does. */
220 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+3))
222 value
[n_value
++] = '\\';
223 value
[n_value
++] = c
;
226 /* Escaped newlines (aka "continuation lines") are eaten up entirely */
231 state
= COMMENT_ESCAPE
;
232 else if (strchr(NEWLINE
, c
)) {
250 DOUBLE_QUOTE_VALUE_ESCAPE
)) {
258 if (last_value_whitespace
!= (size_t) -1)
259 value
[last_value_whitespace
] = 0;
261 /* strip trailing whitespace from key */
262 if (last_key_whitespace
!= (size_t) -1)
263 key
[last_key_whitespace
] = 0;
265 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
275 static int check_utf8ness_and_warn(
276 const char *filename
, unsigned line
,
277 const char *key
, char *value
) {
279 if (!utf8_is_valid(key
)) {
280 _cleanup_free_
char *p
= NULL
;
282 p
= utf8_escape_invalid(key
);
283 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
284 "%s:%u: invalid UTF-8 in key '%s', ignoring.",
285 strna(filename
), line
, p
);
288 if (value
&& !utf8_is_valid(value
)) {
289 _cleanup_free_
char *p
= NULL
;
291 p
= utf8_escape_invalid(value
);
292 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
293 "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
294 strna(filename
), line
, key
, p
);
300 static int parse_env_file_push(
301 const char *filename
, unsigned line
,
302 const char *key
, char *value
,
307 va_list aq
, *ap
= userdata
;
310 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
316 while ((k
= va_arg(aq
, const char *))) {
319 v
= va_arg(aq
, char **);
348 r
= parse_env_file_internal(f
, fname
, parse_env_file_push
, &aq
, &n_pushed
);
356 int parse_env_file_sentinel(
365 r
= parse_env_filev(f
, fname
, ap
);
371 static int load_env_file_push(
372 const char *filename
, unsigned line
,
373 const char *key
, char *value
,
376 char ***m
= userdata
;
380 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
384 p
= strjoin(key
, "=", value
);
388 r
= strv_env_replace(m
, p
);
401 int load_env_file(FILE *f
, const char *fname
, char ***rl
) {
405 r
= parse_env_file_internal(f
, fname
, load_env_file_push
, &m
, NULL
);
415 static int load_env_file_push_pairs(
416 const char *filename
, unsigned line
,
417 const char *key
, char *value
,
420 char ***m
= userdata
;
423 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
427 r
= strv_extend(m
, key
);
432 r
= strv_extend(m
, "");
436 r
= strv_push(m
, value
);
447 int load_env_file_pairs(FILE *f
, const char *fname
, char ***rl
) {
451 r
= parse_env_file_internal(f
, fname
, load_env_file_push_pairs
, &m
, NULL
);
461 static int merge_env_file_push(
462 const char *filename
, unsigned line
,
463 const char *key
, char *value
,
467 char ***env
= userdata
;
468 char *expanded_value
;
473 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename
), line
, key
);
477 if (!env_name_is_valid(key
)) {
478 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename
), line
, key
);
483 expanded_value
= replace_env(value
, *env
,
484 REPLACE_ENV_USE_ENVIRONMENT
|
485 REPLACE_ENV_ALLOW_BRACELESS
|
486 REPLACE_ENV_ALLOW_EXTENDED
);
490 free_and_replace(value
, expanded_value
);
492 log_debug("%s:%u: setting %s=%s", filename
, line
, key
, value
);
494 return load_env_file_push(filename
, line
, key
, value
, env
, n_pushed
);
502 /* NOTE: this function supports braceful and braceless variable expansions,
503 * plus "extended" substitutions, unlike other exported parsing functions.
506 return parse_env_file_internal(f
, fname
, merge_env_file_push
, env
, NULL
);
509 static void write_env_var(FILE *f
, const char *v
) {
515 fputs_unlocked(v
, f
);
516 fputc_unlocked('\n', f
);
521 fwrite_unlocked(v
, 1, p
-v
, f
);
523 if (string_has_cc(p
, NULL
) || chars_intersect(p
, WHITESPACE SHELL_NEED_QUOTES
)) {
524 fputc_unlocked('"', f
);
527 if (strchr(SHELL_NEED_ESCAPE
, *p
))
528 fputc_unlocked('\\', f
);
530 fputc_unlocked(*p
, f
);
533 fputc_unlocked('"', f
);
535 fputs_unlocked(p
, f
);
537 fputc_unlocked('\n', f
);
540 int write_env_file(const char *fname
, char **l
) {
541 _cleanup_fclose_
FILE *f
= NULL
;
542 _cleanup_free_
char *p
= NULL
;
548 r
= fopen_temporary(fname
, &f
, &p
);
552 (void) fchmod_umask(fileno(f
), 0644);
555 write_env_var(f
, *i
);
557 r
= fflush_and_check(f
);
559 if (rename(p
, fname
) >= 0)