1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "alloc-util.h"
12 #include "string-util.h"
14 #include "tmpfile-util.h"
17 static int parse_env_file_internal(
20 int (*push
) (const char *filename
, unsigned line
,
21 const char *key
, char *value
, void *userdata
, int *n_pushed
),
25 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;
26 _cleanup_free_
char *contents
= NULL
, *key
= NULL
, *value
= NULL
;
38 SINGLE_QUOTE_VALUE_ESCAPE
,
40 DOUBLE_QUOTE_VALUE_ESCAPE
,
46 r
= read_full_stream(f
, &contents
, NULL
);
48 r
= read_full_file(fname
, &contents
, NULL
);
52 for (p
= contents
; *p
; p
++) {
58 if (strchr(COMMENTS
, c
))
60 else if (!strchr(WHITESPACE
, c
)) {
62 last_key_whitespace
= (size_t) -1;
64 if (!GREEDY_REALLOC(key
, key_alloc
, n_key
+2))
72 if (strchr(NEWLINE
, c
)) {
76 } else if (c
== '=') {
78 last_value_whitespace
= (size_t) -1;
80 if (!strchr(WHITESPACE
, c
))
81 last_key_whitespace
= (size_t) -1;
82 else if (last_key_whitespace
== (size_t) -1)
83 last_key_whitespace
= n_key
;
85 if (!GREEDY_REALLOC(key
, key_alloc
, n_key
+2))
94 if (strchr(NEWLINE
, c
)) {
102 /* strip trailing whitespace from key */
103 if (last_key_whitespace
!= (size_t) -1)
104 key
[last_key_whitespace
] = 0;
106 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
112 value_alloc
= n_value
= 0;
114 } else if (c
== '\'')
115 state
= SINGLE_QUOTE_VALUE
;
117 state
= DOUBLE_QUOTE_VALUE
;
119 state
= VALUE_ESCAPE
;
120 else if (!strchr(WHITESPACE
, c
)) {
123 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
126 value
[n_value
++] = c
;
132 if (strchr(NEWLINE
, c
)) {
141 /* Chomp off trailing whitespace from value */
142 if (last_value_whitespace
!= (size_t) -1)
143 value
[last_value_whitespace
] = 0;
145 /* strip trailing whitespace from key */
146 if (last_key_whitespace
!= (size_t) -1)
147 key
[last_key_whitespace
] = 0;
149 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
155 value_alloc
= n_value
= 0;
157 } else if (c
== '\\') {
158 state
= VALUE_ESCAPE
;
159 last_value_whitespace
= (size_t) -1;
161 if (!strchr(WHITESPACE
, c
))
162 last_value_whitespace
= (size_t) -1;
163 else if (last_value_whitespace
== (size_t) -1)
164 last_value_whitespace
= n_value
;
166 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
169 value
[n_value
++] = c
;
177 if (!strchr(NEWLINE
, c
)) {
178 /* Escaped newlines we eat up entirely */
179 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
182 value
[n_value
++] = c
;
186 case SINGLE_QUOTE_VALUE
:
190 state
= SINGLE_QUOTE_VALUE_ESCAPE
;
192 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
195 value
[n_value
++] = c
;
200 case SINGLE_QUOTE_VALUE_ESCAPE
:
201 state
= SINGLE_QUOTE_VALUE
;
203 if (!strchr(NEWLINE
, c
)) {
204 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
207 value
[n_value
++] = c
;
211 case DOUBLE_QUOTE_VALUE
:
215 state
= DOUBLE_QUOTE_VALUE_ESCAPE
;
217 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
220 value
[n_value
++] = c
;
225 case DOUBLE_QUOTE_VALUE_ESCAPE
:
226 state
= DOUBLE_QUOTE_VALUE
;
228 if (!strchr(NEWLINE
, c
)) {
229 if (!GREEDY_REALLOC(value
, value_alloc
, n_value
+2))
232 value
[n_value
++] = c
;
238 state
= COMMENT_ESCAPE
;
239 else if (strchr(NEWLINE
, c
)) {
256 SINGLE_QUOTE_VALUE_ESCAPE
,
258 DOUBLE_QUOTE_VALUE_ESCAPE
)) {
266 if (last_value_whitespace
!= (size_t) -1)
267 value
[last_value_whitespace
] = 0;
269 /* strip trailing whitespace from key */
270 if (last_key_whitespace
!= (size_t) -1)
271 key
[last_key_whitespace
] = 0;
273 r
= push(fname
, line
, key
, value
, userdata
, n_pushed
);
283 static int check_utf8ness_and_warn(
284 const char *filename
, unsigned line
,
285 const char *key
, char *value
) {
287 if (!utf8_is_valid(key
)) {
288 _cleanup_free_
char *p
= NULL
;
290 p
= utf8_escape_invalid(key
);
291 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
292 "%s:%u: invalid UTF-8 in key '%s', ignoring.",
293 strna(filename
), line
, p
);
296 if (value
&& !utf8_is_valid(value
)) {
297 _cleanup_free_
char *p
= NULL
;
299 p
= utf8_escape_invalid(value
);
300 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
301 "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.",
302 strna(filename
), line
, key
, p
);
308 static int parse_env_file_push(
309 const char *filename
, unsigned line
,
310 const char *key
, char *value
,
315 va_list aq
, *ap
= userdata
;
318 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
324 while ((k
= va_arg(aq
, const char *))) {
327 v
= va_arg(aq
, char **);
356 r
= parse_env_file_internal(f
, fname
, parse_env_file_push
, &aq
, &n_pushed
);
364 int parse_env_file_sentinel(
373 r
= parse_env_filev(f
, fname
, ap
);
379 static int load_env_file_push(
380 const char *filename
, unsigned line
,
381 const char *key
, char *value
,
384 char ***m
= userdata
;
388 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
392 p
= strjoin(key
, "=", value
);
396 r
= strv_env_replace(m
, p
);
409 int load_env_file(FILE *f
, const char *fname
, char ***rl
) {
413 r
= parse_env_file_internal(f
, fname
, load_env_file_push
, &m
, NULL
);
423 static int load_env_file_push_pairs(
424 const char *filename
, unsigned line
,
425 const char *key
, char *value
,
428 char ***m
= userdata
;
431 r
= check_utf8ness_and_warn(filename
, line
, key
, value
);
435 r
= strv_extend(m
, key
);
440 r
= strv_extend(m
, "");
444 r
= strv_push(m
, value
);
455 int load_env_file_pairs(FILE *f
, const char *fname
, char ***rl
) {
459 r
= parse_env_file_internal(f
, fname
, load_env_file_push_pairs
, &m
, NULL
);
469 static int merge_env_file_push(
470 const char *filename
, unsigned line
,
471 const char *key
, char *value
,
475 char ***env
= userdata
;
476 char *expanded_value
;
481 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename
), line
, key
);
485 if (!env_name_is_valid(key
)) {
486 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename
), line
, key
);
491 expanded_value
= replace_env(value
, *env
,
492 REPLACE_ENV_USE_ENVIRONMENT
|
493 REPLACE_ENV_ALLOW_BRACELESS
|
494 REPLACE_ENV_ALLOW_EXTENDED
);
498 free_and_replace(value
, expanded_value
);
500 return load_env_file_push(filename
, line
, key
, value
, env
, n_pushed
);
508 /* NOTE: this function supports braceful and braceless variable expansions,
509 * plus "extended" substitutions, unlike other exported parsing functions.
512 return parse_env_file_internal(f
, fname
, merge_env_file_push
, env
, NULL
);
515 static void write_env_var(FILE *f
, const char *v
) {
521 fputs_unlocked(v
, f
);
522 fputc_unlocked('\n', f
);
527 fwrite_unlocked(v
, 1, p
-v
, f
);
529 if (string_has_cc(p
, NULL
) || chars_intersect(p
, WHITESPACE SHELL_NEED_QUOTES
)) {
530 fputc_unlocked('\"', f
);
533 if (strchr(SHELL_NEED_ESCAPE
, *p
))
534 fputc_unlocked('\\', f
);
536 fputc_unlocked(*p
, f
);
539 fputc_unlocked('\"', f
);
541 fputs_unlocked(p
, f
);
543 fputc_unlocked('\n', f
);
546 int write_env_file(const char *fname
, char **l
) {
547 _cleanup_fclose_
FILE *f
= NULL
;
548 _cleanup_free_
char *p
= NULL
;
554 r
= fopen_temporary(fname
, &f
, &p
);
558 (void) __fsetlocking(f
, FSETLOCKING_BYCALLER
);
559 (void) fchmod_umask(fileno(f
), 0644);
562 write_env_var(f
, *i
);
564 r
= fflush_and_check(f
);
566 if (rename(p
, fname
) >= 0)