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 typedef int (*push_env_func_t
)(
22 static int parse_env_file_internal(
28 size_t n_key
= 0, n_value
= 0, last_value_whitespace
= SIZE_MAX
, last_key_whitespace
= SIZE_MAX
;
29 _cleanup_free_
char *contents
= NULL
, *key
= NULL
, *value
= NULL
;
41 DOUBLE_QUOTE_VALUE_ESCAPE
,
50 r
= read_full_stream(f
, &contents
, NULL
);
52 r
= read_full_file(fname
, &contents
, NULL
);
56 for (char *p
= contents
; *p
; p
++) {
62 if (strchr(COMMENTS
, c
))
64 else if (!strchr(WHITESPACE
, c
)) {
66 last_key_whitespace
= SIZE_MAX
;
68 if (!GREEDY_REALLOC(key
, n_key
+2))
76 if (strchr(NEWLINE
, c
)) {
80 } else if (c
== '=') {
82 last_value_whitespace
= SIZE_MAX
;
84 if (!strchr(WHITESPACE
, c
))
85 last_key_whitespace
= SIZE_MAX
;
86 else if (last_key_whitespace
== SIZE_MAX
)
87 last_key_whitespace
= n_key
;
89 if (!GREEDY_REALLOC(key
, n_key
+2))
98 if (strchr(NEWLINE
, c
)) {
106 /* strip trailing whitespace from key */
107 if (last_key_whitespace
!= SIZE_MAX
)
108 key
[last_key_whitespace
] = 0;
110 r
= push(fname
, line
, key
, value
, userdata
);
118 } else if (c
== '\'')
119 state
= SINGLE_QUOTE_VALUE
;
121 state
= DOUBLE_QUOTE_VALUE
;
123 state
= VALUE_ESCAPE
;
124 else if (!strchr(WHITESPACE
, c
)) {
127 if (!GREEDY_REALLOC(value
, n_value
+2))
130 value
[n_value
++] = c
;
136 if (strchr(NEWLINE
, c
)) {
145 /* Chomp off trailing whitespace from value */
146 if (last_value_whitespace
!= SIZE_MAX
)
147 value
[last_value_whitespace
] = 0;
149 /* strip trailing whitespace from key */
150 if (last_key_whitespace
!= SIZE_MAX
)
151 key
[last_key_whitespace
] = 0;
153 r
= push(fname
, line
, key
, value
, userdata
);
161 } else if (c
== '\\') {
162 state
= VALUE_ESCAPE
;
163 last_value_whitespace
= SIZE_MAX
;
165 if (!strchr(WHITESPACE
, c
))
166 last_value_whitespace
= SIZE_MAX
;
167 else if (last_value_whitespace
== SIZE_MAX
)
168 last_value_whitespace
= n_value
;
170 if (!GREEDY_REALLOC(value
, n_value
+2))
173 value
[n_value
++] = c
;
181 if (!strchr(NEWLINE
, c
)) {
182 /* Escaped newlines we eat up entirely */
183 if (!GREEDY_REALLOC(value
, n_value
+2))
186 value
[n_value
++] = c
;
190 case SINGLE_QUOTE_VALUE
:
194 if (!GREEDY_REALLOC(value
, n_value
+2))
197 value
[n_value
++] = c
;
202 case DOUBLE_QUOTE_VALUE
:
206 state
= DOUBLE_QUOTE_VALUE_ESCAPE
;
208 if (!GREEDY_REALLOC(value
, n_value
+2))
211 value
[n_value
++] = c
;
216 case DOUBLE_QUOTE_VALUE_ESCAPE
:
217 state
= DOUBLE_QUOTE_VALUE
;
219 if (strchr(SHELL_NEED_ESCAPE
, c
)) {
220 /* If this is a char that needs escaping, just unescape it. */
221 if (!GREEDY_REALLOC(value
, n_value
+2))
223 value
[n_value
++] = c
;
224 } else if (c
!= '\n') {
225 /* If other char than what needs escaping, keep the "\" in place, like the
226 * real shell does. */
227 if (!GREEDY_REALLOC(value
, n_value
+3))
229 value
[n_value
++] = '\\';
230 value
[n_value
++] = c
;
233 /* Escaped newlines (aka "continuation lines") are eaten up entirely */
238 state
= COMMENT_ESCAPE
;
239 else if (strchr(NEWLINE
, c
)) {
246 log_debug("The line which doesn't begin with \";\" or \"#\", but follows a comment" \
247 " line trailing with escape is now treated as a non comment line since v254.");
248 if (strchr(NEWLINE
, c
)) {
263 DOUBLE_QUOTE_VALUE_ESCAPE
)) {
271 if (last_value_whitespace
!= SIZE_MAX
)
272 value
[last_value_whitespace
] = 0;
274 /* strip trailing whitespace from key */
275 if (last_key_whitespace
!= SIZE_MAX
)
276 key
[last_key_whitespace
] = 0;
278 r
= push(fname
, line
, key
, value
, userdata
);
288 static int check_utf8ness_and_warn(
289 const char *filename
, unsigned line
,
290 const char *key
, char *value
) {
294 if (!utf8_is_valid(key
)) {
295 _cleanup_free_
char *p
= NULL
;
297 p
= utf8_escape_invalid(key
);
298 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
299 "%s:%u: invalid UTF-8 in key '%s', ignoring.",
300 strna(filename
), line
, p
);
303 if (value
&& !utf8_is_valid(value
)) {
304 _cleanup_free_
char *p
= NULL
;
306 p
= utf8_escape_invalid(value
);
307 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
308 "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
309 strna(filename
), line
, key
, p
);
315 static int parse_env_file_push(
316 const char *filename
, unsigned line
,
317 const char *key
, char *value
,
321 va_list aq
, *ap
= userdata
;
326 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
332 while ((k
= va_arg(aq
, const char *))) {
335 v
= va_arg(aq
, char **);
339 free_and_replace(*v
, value
);
362 r
= parse_env_file_internal(f
, fname
, parse_env_file_push
, &aq
);
367 int parse_env_file_fdv(int fd
, const char *fname
, va_list ap
) {
368 _cleanup_fclose_
FILE *f
= NULL
;
374 r
= fdopen_independent(fd
, "re", &f
);
379 r
= parse_env_file_internal(f
, fname
, parse_env_file_push
, &aq
);
384 int parse_env_file_sentinel(
395 r
= parse_env_filev(f
, fname
, ap
);
401 int parse_env_file_fd_sentinel(
403 const char *fname
, /* only used for logging */
412 r
= parse_env_file_fdv(fd
, fname
, ap
);
418 static int load_env_file_push(
419 const char *filename
, unsigned line
,
420 const char *key
, char *value
,
423 char ***m
= userdata
;
429 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
433 p
= strjoin(key
, "=", value
);
437 r
= strv_env_replace_consume(m
, p
);
445 int load_env_file(FILE *f
, const char *fname
, char ***ret
) {
446 _cleanup_strv_free_
char **m
= NULL
;
452 r
= parse_env_file_internal(f
, fname
, load_env_file_push
, &m
);
460 static int load_env_file_push_pairs(
461 const char *filename
, unsigned line
,
462 const char *key
, char *value
,
465 char ***m
= ASSERT_PTR(userdata
);
470 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
474 /* Check if the key is present */
475 for (char **t
= *m
; t
&& *t
; t
+= 2)
476 if (streq(t
[0], key
)) {
478 return free_and_replace(t
[1], value
);
480 return free_and_strdup(t
+1, "");
483 r
= strv_extend(m
, key
);
488 return strv_push(m
, value
);
490 return strv_extend(m
, "");
493 int load_env_file_pairs(FILE *f
, const char *fname
, char ***ret
) {
494 _cleanup_strv_free_
char **m
= NULL
;
500 r
= parse_env_file_internal(f
, fname
, load_env_file_push_pairs
, &m
);
508 int load_env_file_pairs_fd(int fd
, const char *fname
, char ***ret
) {
509 _cleanup_fclose_
FILE *f
= NULL
;
514 r
= fdopen_independent(fd
, "re", &f
);
518 return load_env_file_pairs(f
, fname
, ret
);
521 static int merge_env_file_push(
522 const char *filename
, unsigned line
,
523 const char *key
, char *value
,
526 char ***env
= ASSERT_PTR(userdata
);
527 char *expanded_value
;
533 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename
), line
, key
);
537 if (!env_name_is_valid(key
)) {
538 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename
), line
, key
);
543 r
= replace_env(value
,
545 REPLACE_ENV_USE_ENVIRONMENT
|REPLACE_ENV_ALLOW_BRACELESS
|REPLACE_ENV_ALLOW_EXTENDED
,
548 return log_error_errno(r
, "%s:%u: Failed to expand variable '%s': %m", strna(filename
), line
, value
);
550 free_and_replace(value
, expanded_value
);
552 log_debug("%s:%u: setting %s=%s", filename
, line
, key
, value
);
554 return load_env_file_push(filename
, line
, key
, value
, env
);
565 /* NOTE: this function supports braceful and braceless variable expansions,
566 * plus "extended" substitutions, unlike other exported parsing functions.
569 return parse_env_file_internal(f
, fname
, merge_env_file_push
, env
);
572 static void write_env_var(FILE *f
, const char *v
) {
581 fputs_unlocked(v
, f
);
582 fputc_unlocked('\n', f
);
587 fwrite_unlocked(v
, 1, p
-v
, f
);
589 if (string_has_cc(p
, NULL
) || chars_intersect(p
, WHITESPACE SHELL_NEED_QUOTES
)) {
590 fputc_unlocked('"', f
);
593 if (strchr(SHELL_NEED_ESCAPE
, *p
))
594 fputc_unlocked('\\', f
);
596 fputc_unlocked(*p
, f
);
599 fputc_unlocked('"', f
);
601 fputs_unlocked(p
, f
);
603 fputc_unlocked('\n', f
);
606 int write_env_file(int dir_fd
, const char *fname
, char **headers
, char **l
) {
607 _cleanup_fclose_
FILE *f
= NULL
;
608 _cleanup_free_
char *p
= NULL
;
611 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
614 r
= fopen_temporary_at(dir_fd
, fname
, &f
, &p
);
618 (void) fchmod_umask(fileno(f
), 0644);
620 STRV_FOREACH(i
, headers
) {
621 assert(isempty(*i
) || startswith(*i
, "#"));
622 fputs_unlocked(*i
, f
);
623 fputc_unlocked('\n', f
);
627 write_env_var(f
, *i
);
629 r
= fflush_and_check(f
);
631 if (renameat(dir_fd
, p
, dir_fd
, fname
) >= 0)
637 (void) unlinkat(dir_fd
, p
, 0);