1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "alloc-util.h"
9 #include "errno-util.h"
16 #include "string-util.h"
18 #include "tmpfile-util.h"
21 typedef int (*push_env_func_t
)(
28 static int parse_env_file_internal(
34 size_t n_key
= 0, n_value
= 0, last_value_whitespace
= SIZE_MAX
, last_key_whitespace
= SIZE_MAX
;
35 _cleanup_free_
char *contents
= NULL
, *key
= NULL
, *value
= NULL
;
47 DOUBLE_QUOTE_VALUE_ESCAPE
,
56 r
= read_full_stream(f
, &contents
, NULL
);
58 r
= read_full_file(fname
, &contents
, NULL
);
62 for (char *p
= contents
; *p
; p
++) {
68 if (strchr(COMMENTS
, c
))
70 else if (!strchr(WHITESPACE
, c
)) {
72 last_key_whitespace
= SIZE_MAX
;
74 if (!GREEDY_REALLOC(key
, n_key
+2))
82 if (strchr(NEWLINE
, c
)) {
86 } else if (c
== '=') {
88 last_value_whitespace
= SIZE_MAX
;
90 if (!strchr(WHITESPACE
, c
))
91 last_key_whitespace
= SIZE_MAX
;
92 else if (last_key_whitespace
== SIZE_MAX
)
93 last_key_whitespace
= n_key
;
95 if (!GREEDY_REALLOC(key
, n_key
+2))
104 if (strchr(NEWLINE
, c
)) {
112 /* strip trailing whitespace from key */
113 if (last_key_whitespace
!= SIZE_MAX
)
114 key
[last_key_whitespace
] = 0;
116 r
= push(fname
, line
, key
, value
, userdata
);
124 } else if (c
== '\'')
125 state
= SINGLE_QUOTE_VALUE
;
127 state
= DOUBLE_QUOTE_VALUE
;
129 state
= VALUE_ESCAPE
;
130 else if (!strchr(WHITESPACE
, c
)) {
133 if (!GREEDY_REALLOC(value
, n_value
+2))
136 value
[n_value
++] = c
;
142 if (strchr(NEWLINE
, c
)) {
151 /* Chomp off trailing whitespace from value */
152 if (last_value_whitespace
!= SIZE_MAX
)
153 value
[last_value_whitespace
] = 0;
155 /* strip trailing whitespace from key */
156 if (last_key_whitespace
!= SIZE_MAX
)
157 key
[last_key_whitespace
] = 0;
159 r
= push(fname
, line
, key
, value
, userdata
);
167 } else if (c
== '\\') {
168 state
= VALUE_ESCAPE
;
169 last_value_whitespace
= SIZE_MAX
;
171 if (!strchr(WHITESPACE
, c
))
172 last_value_whitespace
= SIZE_MAX
;
173 else if (last_value_whitespace
== SIZE_MAX
)
174 last_value_whitespace
= n_value
;
176 if (!GREEDY_REALLOC(value
, n_value
+2))
179 value
[n_value
++] = c
;
187 if (!strchr(NEWLINE
, c
)) {
188 /* Escaped newlines we eat up entirely */
189 if (!GREEDY_REALLOC(value
, n_value
+2))
192 value
[n_value
++] = c
;
196 case SINGLE_QUOTE_VALUE
:
200 if (!GREEDY_REALLOC(value
, n_value
+2))
203 value
[n_value
++] = c
;
208 case DOUBLE_QUOTE_VALUE
:
212 state
= DOUBLE_QUOTE_VALUE_ESCAPE
;
214 if (!GREEDY_REALLOC(value
, n_value
+2))
217 value
[n_value
++] = c
;
222 case DOUBLE_QUOTE_VALUE_ESCAPE
:
223 state
= DOUBLE_QUOTE_VALUE
;
225 if (strchr(SHELL_NEED_ESCAPE
, c
)) {
226 /* If this is a char that needs escaping, just unescape it. */
227 if (!GREEDY_REALLOC(value
, n_value
+2))
229 value
[n_value
++] = c
;
230 } else if (c
!= '\n') {
231 /* If other char than what needs escaping, keep the "\" in place, like the
232 * real shell does. */
233 if (!GREEDY_REALLOC(value
, n_value
+3))
235 value
[n_value
++] = '\\';
236 value
[n_value
++] = c
;
239 /* Escaped newlines (aka "continuation lines") are eaten up entirely */
244 state
= COMMENT_ESCAPE
;
245 else if (strchr(NEWLINE
, c
)) {
252 log_debug("The line which doesn't begin with \";\" or \"#\", but follows a comment" \
253 " line trailing with escape is now treated as a non comment line since v254.");
254 if (strchr(NEWLINE
, c
)) {
269 DOUBLE_QUOTE_VALUE_ESCAPE
)) {
277 if (last_value_whitespace
!= SIZE_MAX
)
278 value
[last_value_whitespace
] = 0;
280 /* strip trailing whitespace from key */
281 if (last_key_whitespace
!= SIZE_MAX
)
282 key
[last_key_whitespace
] = 0;
284 r
= push(fname
, line
, key
, value
, userdata
);
294 static int check_utf8ness_and_warn(
295 const char *filename
, unsigned line
,
296 const char *key
, char *value
) {
300 if (!utf8_is_valid(key
)) {
301 _cleanup_free_
char *p
= NULL
;
303 p
= utf8_escape_invalid(key
);
304 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
305 "%s:%u: invalid UTF-8 in key '%s', ignoring.",
306 strna(filename
), line
, p
);
309 if (value
&& !utf8_is_valid(value
)) {
310 _cleanup_free_
char *p
= NULL
;
312 p
= utf8_escape_invalid(value
);
313 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
314 "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
315 strna(filename
), line
, key
, p
);
321 static int parse_env_file_push(
322 const char *filename
, unsigned line
,
323 const char *key
, char *value
,
327 va_list aq
, *ap
= userdata
;
332 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
338 while ((k
= va_arg(aq
, const char *))) {
341 v
= va_arg(aq
, char **);
345 free_and_replace(*v
, value
);
368 r
= parse_env_file_internal(f
, fname
, parse_env_file_push
, &aq
);
373 int parse_env_file_fdv(int fd
, const char *fname
, va_list ap
) {
374 _cleanup_fclose_
FILE *f
= NULL
;
380 r
= fdopen_independent(fd
, "re", &f
);
385 r
= parse_env_file_internal(f
, fname
, parse_env_file_push
, &aq
);
390 int parse_env_file_sentinel(
401 r
= parse_env_filev(f
, fname
, ap
);
407 int parse_env_file_fd_sentinel(
409 const char *fname
, /* only used for logging */
418 r
= parse_env_file_fdv(fd
, fname
, ap
);
424 static int load_env_file_push(
425 const char *filename
, unsigned line
,
426 const char *key
, char *value
,
429 char ***m
= userdata
;
435 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
439 p
= strjoin(key
, "=", value
);
443 r
= strv_env_replace_consume(m
, p
);
451 int load_env_file(FILE *f
, const char *fname
, char ***ret
) {
452 _cleanup_strv_free_
char **m
= NULL
;
458 r
= parse_env_file_internal(f
, fname
, load_env_file_push
, &m
);
466 static int load_env_file_push_pairs(
467 const char *filename
, unsigned line
,
468 const char *key
, char *value
,
471 char ***m
= ASSERT_PTR(userdata
);
476 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
480 /* Check if the key is present */
481 for (char **t
= *m
; t
&& *t
; t
+= 2)
482 if (streq(t
[0], key
)) {
484 return free_and_replace(t
[1], value
);
486 return free_and_strdup(t
+1, "");
489 r
= strv_extend(m
, key
);
494 return strv_push(m
, value
);
496 return strv_extend(m
, "");
499 int load_env_file_pairs(FILE *f
, const char *fname
, char ***ret
) {
500 _cleanup_strv_free_
char **m
= NULL
;
506 r
= parse_env_file_internal(f
, fname
, load_env_file_push_pairs
, &m
);
514 int load_env_file_pairs_fd(int fd
, const char *fname
, char ***ret
) {
515 _cleanup_fclose_
FILE *f
= NULL
;
520 r
= fdopen_independent(fd
, "re", &f
);
524 return load_env_file_pairs(f
, fname
, ret
);
527 static int merge_env_file_push(
528 const char *filename
, unsigned line
,
529 const char *key
, char *value
,
532 char ***env
= ASSERT_PTR(userdata
);
533 char *expanded_value
;
539 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename
), line
, key
);
543 if (!env_name_is_valid(key
)) {
544 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename
), line
, key
);
549 r
= replace_env(value
,
551 REPLACE_ENV_USE_ENVIRONMENT
|REPLACE_ENV_ALLOW_BRACELESS
|REPLACE_ENV_ALLOW_EXTENDED
,
554 return log_error_errno(r
, "%s:%u: Failed to expand variable '%s': %m", strna(filename
), line
, value
);
556 free_and_replace(value
, expanded_value
);
558 log_debug("%s:%u: setting %s=%s", filename
, line
, key
, value
);
560 return load_env_file_push(filename
, line
, key
, value
, env
);
571 /* NOTE: this function supports braceful and braceless variable expansions,
572 * plus "extended" substitutions, unlike other exported parsing functions.
575 return parse_env_file_internal(f
, fname
, merge_env_file_push
, env
);
578 static void env_file_fputs_escaped(FILE *f
, const char *p
) {
584 if (string_has_cc(p
, NULL
) || chars_intersect(p
, WHITESPACE SHELL_NEED_QUOTES
)) {
585 fputc_unlocked('"', f
);
588 if (strchr(SHELL_NEED_ESCAPE
, *p
))
589 fputc_unlocked('\\', f
);
591 fputc_unlocked(*p
, f
);
594 fputc_unlocked('"', f
);
596 fputs_unlocked(p
, f
);
601 void env_file_fputs_assignment(FILE *f
, const char *k
, const char *v
) {
609 env_file_fputs_escaped(f
, v
);
613 static void write_env_var(FILE *f
, const char *v
) {
622 fputs_unlocked(v
, f
);
623 fputc_unlocked('\n', f
);
628 fwrite_unlocked(v
, 1, p
-v
, f
);
630 env_file_fputs_escaped(f
, p
);
632 fputc_unlocked('\n', f
);
635 int write_env_file(int dir_fd
, const char *fname
, char **headers
, char **l
, WriteEnvFileFlags flags
) {
636 _cleanup_fclose_
FILE *f
= NULL
;
637 _cleanup_free_
char *p
= NULL
;
640 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
643 bool call_label_ops_post
= false;
644 if (FLAGS_SET(flags
, WRITE_ENV_FILE_LABEL
)) {
645 r
= label_ops_pre(dir_fd
, fname
, S_IFREG
);
649 call_label_ops_post
= true;
652 r
= fopen_tmpfile_linkable_at(dir_fd
, fname
, O_WRONLY
|O_CLOEXEC
, &p
, &f
);
653 if (call_label_ops_post
)
654 RET_GATHER(r
, label_ops_post(f
? fileno(f
) : dir_fd
, f
? NULL
: fname
, /* created= */ !!f
));
658 r
= fchmod_umask(fileno(f
), 0644);
662 STRV_FOREACH(i
, headers
) {
663 assert(isempty(*i
) || startswith(*i
, "#"));
664 fputs_unlocked(*i
, f
);
665 fputc_unlocked('\n', f
);
669 write_env_var(f
, *i
);
671 r
= flink_tmpfile_at(f
, dir_fd
, p
, fname
, LINK_TMPFILE_REPLACE
|LINK_TMPFILE_SYNC
);
679 (void) unlinkat(dir_fd
, p
, 0);
684 int write_vconsole_conf(int dir_fd
, const char *fname
, char **l
) {
685 char **headers
= STRV_MAKE(
686 "# Written by systemd-localed(8) or systemd-firstboot(1), read by systemd-localed",
687 "# and systemd-vconsole-setup(8). Use localectl(1) to update this file.");
689 return write_env_file(dir_fd
, fname
, headers
, l
, WRITE_ENV_FILE_LABEL
);