]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
686d13b9 | 2 | |
686d13b9 LP |
3 | #include "alloc-util.h" |
4 | #include "env-file.h" | |
5 | #include "env-util.h" | |
6 | #include "escape.h" | |
7 | #include "fd-util.h" | |
8 | #include "fileio.h" | |
9 | #include "fs-util.h" | |
10 | #include "string-util.h" | |
11 | #include "strv.h" | |
12 | #include "tmpfile-util.h" | |
13 | #include "utf8.h" | |
14 | ||
15 | static int parse_env_file_internal( | |
16 | FILE *f, | |
17 | const char *fname, | |
18 | int (*push) (const char *filename, unsigned line, | |
99aad9a2 ZJS |
19 | const char *key, char *value, void *userdata), |
20 | void *userdata) { | |
686d13b9 | 21 | |
319a4f4b | 22 | size_t n_key = 0, n_value = 0, last_value_whitespace = SIZE_MAX, last_key_whitespace = SIZE_MAX; |
686d13b9 LP |
23 | _cleanup_free_ char *contents = NULL, *key = NULL, *value = NULL; |
24 | unsigned line = 1; | |
686d13b9 LP |
25 | int r; |
26 | ||
27 | enum { | |
28 | PRE_KEY, | |
29 | KEY, | |
30 | PRE_VALUE, | |
31 | VALUE, | |
32 | VALUE_ESCAPE, | |
33 | SINGLE_QUOTE_VALUE, | |
686d13b9 LP |
34 | DOUBLE_QUOTE_VALUE, |
35 | DOUBLE_QUOTE_VALUE_ESCAPE, | |
36 | COMMENT, | |
37 | COMMENT_ESCAPE | |
38 | } state = PRE_KEY; | |
39 | ||
40 | if (f) | |
41 | r = read_full_stream(f, &contents, NULL); | |
42 | else | |
43 | r = read_full_file(fname, &contents, NULL); | |
44 | if (r < 0) | |
45 | return r; | |
46 | ||
09f5fc66 | 47 | for (char *p = contents; *p; p++) { |
686d13b9 LP |
48 | char c = *p; |
49 | ||
50 | switch (state) { | |
51 | ||
52 | case PRE_KEY: | |
53 | if (strchr(COMMENTS, c)) | |
54 | state = COMMENT; | |
55 | else if (!strchr(WHITESPACE, c)) { | |
56 | state = KEY; | |
f5fbe71d | 57 | last_key_whitespace = SIZE_MAX; |
686d13b9 | 58 | |
319a4f4b | 59 | if (!GREEDY_REALLOC(key, n_key+2)) |
686d13b9 LP |
60 | return -ENOMEM; |
61 | ||
62 | key[n_key++] = c; | |
63 | } | |
64 | break; | |
65 | ||
66 | case KEY: | |
67 | if (strchr(NEWLINE, c)) { | |
68 | state = PRE_KEY; | |
69 | line++; | |
70 | n_key = 0; | |
71 | } else if (c == '=') { | |
72 | state = PRE_VALUE; | |
f5fbe71d | 73 | last_value_whitespace = SIZE_MAX; |
686d13b9 LP |
74 | } else { |
75 | if (!strchr(WHITESPACE, c)) | |
f5fbe71d YW |
76 | last_key_whitespace = SIZE_MAX; |
77 | else if (last_key_whitespace == SIZE_MAX) | |
686d13b9 LP |
78 | last_key_whitespace = n_key; |
79 | ||
319a4f4b | 80 | if (!GREEDY_REALLOC(key, n_key+2)) |
686d13b9 LP |
81 | return -ENOMEM; |
82 | ||
83 | key[n_key++] = c; | |
84 | } | |
85 | ||
86 | break; | |
87 | ||
88 | case PRE_VALUE: | |
89 | if (strchr(NEWLINE, c)) { | |
90 | state = PRE_KEY; | |
91 | line++; | |
92 | key[n_key] = 0; | |
93 | ||
94 | if (value) | |
95 | value[n_value] = 0; | |
96 | ||
97 | /* strip trailing whitespace from key */ | |
f5fbe71d | 98 | if (last_key_whitespace != SIZE_MAX) |
686d13b9 LP |
99 | key[last_key_whitespace] = 0; |
100 | ||
99aad9a2 | 101 | r = push(fname, line, key, value, userdata); |
686d13b9 LP |
102 | if (r < 0) |
103 | return r; | |
104 | ||
105 | n_key = 0; | |
106 | value = NULL; | |
319a4f4b | 107 | n_value = 0; |
686d13b9 LP |
108 | |
109 | } else if (c == '\'') | |
110 | state = SINGLE_QUOTE_VALUE; | |
e768a4f0 | 111 | else if (c == '"') |
686d13b9 LP |
112 | state = DOUBLE_QUOTE_VALUE; |
113 | else if (c == '\\') | |
114 | state = VALUE_ESCAPE; | |
115 | else if (!strchr(WHITESPACE, c)) { | |
116 | state = VALUE; | |
117 | ||
319a4f4b | 118 | if (!GREEDY_REALLOC(value, n_value+2)) |
686d13b9 LP |
119 | return -ENOMEM; |
120 | ||
121 | value[n_value++] = c; | |
122 | } | |
123 | ||
124 | break; | |
125 | ||
126 | case VALUE: | |
127 | if (strchr(NEWLINE, c)) { | |
128 | state = PRE_KEY; | |
129 | line++; | |
130 | ||
131 | key[n_key] = 0; | |
132 | ||
133 | if (value) | |
134 | value[n_value] = 0; | |
135 | ||
136 | /* Chomp off trailing whitespace from value */ | |
f5fbe71d | 137 | if (last_value_whitespace != SIZE_MAX) |
686d13b9 LP |
138 | value[last_value_whitespace] = 0; |
139 | ||
140 | /* strip trailing whitespace from key */ | |
f5fbe71d | 141 | if (last_key_whitespace != SIZE_MAX) |
686d13b9 LP |
142 | key[last_key_whitespace] = 0; |
143 | ||
99aad9a2 | 144 | r = push(fname, line, key, value, userdata); |
686d13b9 LP |
145 | if (r < 0) |
146 | return r; | |
147 | ||
148 | n_key = 0; | |
149 | value = NULL; | |
319a4f4b | 150 | n_value = 0; |
686d13b9 LP |
151 | |
152 | } else if (c == '\\') { | |
153 | state = VALUE_ESCAPE; | |
f5fbe71d | 154 | last_value_whitespace = SIZE_MAX; |
686d13b9 LP |
155 | } else { |
156 | if (!strchr(WHITESPACE, c)) | |
f5fbe71d YW |
157 | last_value_whitespace = SIZE_MAX; |
158 | else if (last_value_whitespace == SIZE_MAX) | |
686d13b9 LP |
159 | last_value_whitespace = n_value; |
160 | ||
319a4f4b | 161 | if (!GREEDY_REALLOC(value, n_value+2)) |
686d13b9 LP |
162 | return -ENOMEM; |
163 | ||
164 | value[n_value++] = c; | |
165 | } | |
166 | ||
167 | break; | |
168 | ||
169 | case VALUE_ESCAPE: | |
170 | state = VALUE; | |
171 | ||
172 | if (!strchr(NEWLINE, c)) { | |
173 | /* Escaped newlines we eat up entirely */ | |
319a4f4b | 174 | if (!GREEDY_REALLOC(value, n_value+2)) |
686d13b9 LP |
175 | return -ENOMEM; |
176 | ||
177 | value[n_value++] = c; | |
178 | } | |
179 | break; | |
180 | ||
181 | case SINGLE_QUOTE_VALUE: | |
182 | if (c == '\'') | |
183 | state = PRE_VALUE; | |
686d13b9 | 184 | else { |
319a4f4b | 185 | if (!GREEDY_REALLOC(value, n_value+2)) |
686d13b9 LP |
186 | return -ENOMEM; |
187 | ||
188 | value[n_value++] = c; | |
189 | } | |
190 | ||
191 | break; | |
192 | ||
686d13b9 | 193 | case DOUBLE_QUOTE_VALUE: |
e768a4f0 | 194 | if (c == '"') |
686d13b9 LP |
195 | state = PRE_VALUE; |
196 | else if (c == '\\') | |
197 | state = DOUBLE_QUOTE_VALUE_ESCAPE; | |
198 | else { | |
319a4f4b | 199 | if (!GREEDY_REALLOC(value, n_value+2)) |
686d13b9 LP |
200 | return -ENOMEM; |
201 | ||
202 | value[n_value++] = c; | |
203 | } | |
204 | ||
205 | break; | |
206 | ||
207 | case DOUBLE_QUOTE_VALUE_ESCAPE: | |
208 | state = DOUBLE_QUOTE_VALUE; | |
209 | ||
de008e53 LP |
210 | if (strchr(SHELL_NEED_ESCAPE, c)) { |
211 | /* If this is a char that needs escaping, just unescape it. */ | |
319a4f4b | 212 | if (!GREEDY_REALLOC(value, n_value+2)) |
686d13b9 | 213 | return -ENOMEM; |
de008e53 LP |
214 | value[n_value++] = c; |
215 | } else if (c != '\n') { | |
216 | /* If other char than what needs escaping, keep the "\" in place, like the | |
217 | * real shell does. */ | |
319a4f4b | 218 | if (!GREEDY_REALLOC(value, n_value+3)) |
e4a8db1f LT |
219 | return -ENOMEM; |
220 | value[n_value++] = '\\'; | |
686d13b9 LP |
221 | value[n_value++] = c; |
222 | } | |
e4a8db1f | 223 | |
de008e53 | 224 | /* Escaped newlines (aka "continuation lines") are eaten up entirely */ |
686d13b9 LP |
225 | break; |
226 | ||
227 | case COMMENT: | |
228 | if (c == '\\') | |
229 | state = COMMENT_ESCAPE; | |
230 | else if (strchr(NEWLINE, c)) { | |
231 | state = PRE_KEY; | |
232 | line++; | |
233 | } | |
234 | break; | |
235 | ||
236 | case COMMENT_ESCAPE: | |
237 | state = COMMENT; | |
238 | break; | |
239 | } | |
240 | } | |
241 | ||
242 | if (IN_SET(state, | |
243 | PRE_VALUE, | |
244 | VALUE, | |
245 | VALUE_ESCAPE, | |
246 | SINGLE_QUOTE_VALUE, | |
686d13b9 LP |
247 | DOUBLE_QUOTE_VALUE, |
248 | DOUBLE_QUOTE_VALUE_ESCAPE)) { | |
249 | ||
250 | key[n_key] = 0; | |
251 | ||
252 | if (value) | |
253 | value[n_value] = 0; | |
254 | ||
255 | if (state == VALUE) | |
f5fbe71d | 256 | if (last_value_whitespace != SIZE_MAX) |
686d13b9 LP |
257 | value[last_value_whitespace] = 0; |
258 | ||
259 | /* strip trailing whitespace from key */ | |
f5fbe71d | 260 | if (last_key_whitespace != SIZE_MAX) |
686d13b9 LP |
261 | key[last_key_whitespace] = 0; |
262 | ||
99aad9a2 | 263 | r = push(fname, line, key, value, userdata); |
686d13b9 LP |
264 | if (r < 0) |
265 | return r; | |
266 | ||
267 | value = NULL; | |
268 | } | |
269 | ||
270 | return 0; | |
271 | } | |
272 | ||
273 | static int check_utf8ness_and_warn( | |
274 | const char *filename, unsigned line, | |
275 | const char *key, char *value) { | |
276 | ||
277 | if (!utf8_is_valid(key)) { | |
278 | _cleanup_free_ char *p = NULL; | |
279 | ||
280 | p = utf8_escape_invalid(key); | |
281 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
282 | "%s:%u: invalid UTF-8 in key '%s', ignoring.", | |
283 | strna(filename), line, p); | |
284 | } | |
285 | ||
286 | if (value && !utf8_is_valid(value)) { | |
287 | _cleanup_free_ char *p = NULL; | |
288 | ||
289 | p = utf8_escape_invalid(value); | |
290 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
291 | "%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", | |
292 | strna(filename), line, key, p); | |
293 | } | |
294 | ||
295 | return 0; | |
296 | } | |
297 | ||
298 | static int parse_env_file_push( | |
299 | const char *filename, unsigned line, | |
300 | const char *key, char *value, | |
99aad9a2 | 301 | void *userdata) { |
686d13b9 LP |
302 | |
303 | const char *k; | |
304 | va_list aq, *ap = userdata; | |
305 | int r; | |
306 | ||
307 | r = check_utf8ness_and_warn(filename, line, key, value); | |
308 | if (r < 0) | |
309 | return r; | |
310 | ||
311 | va_copy(aq, *ap); | |
312 | ||
313 | while ((k = va_arg(aq, const char *))) { | |
314 | char **v; | |
315 | ||
316 | v = va_arg(aq, char **); | |
317 | ||
318 | if (streq(key, k)) { | |
319 | va_end(aq); | |
320 | free(*v); | |
321 | *v = value; | |
322 | ||
686d13b9 LP |
323 | return 1; |
324 | } | |
325 | } | |
326 | ||
327 | va_end(aq); | |
328 | free(value); | |
329 | ||
330 | return 0; | |
331 | } | |
332 | ||
333 | int parse_env_filev( | |
334 | FILE *f, | |
335 | const char *fname, | |
336 | va_list ap) { | |
337 | ||
99aad9a2 | 338 | int r; |
686d13b9 LP |
339 | va_list aq; |
340 | ||
341 | va_copy(aq, ap); | |
99aad9a2 | 342 | r = parse_env_file_internal(f, fname, parse_env_file_push, &aq); |
686d13b9 | 343 | va_end(aq); |
99aad9a2 | 344 | return r; |
686d13b9 LP |
345 | } |
346 | ||
347 | int parse_env_file_sentinel( | |
348 | FILE *f, | |
349 | const char *fname, | |
350 | ...) { | |
351 | ||
352 | va_list ap; | |
353 | int r; | |
354 | ||
355 | va_start(ap, fname); | |
356 | r = parse_env_filev(f, fname, ap); | |
357 | va_end(ap); | |
358 | ||
359 | return r; | |
360 | } | |
361 | ||
362 | static int load_env_file_push( | |
363 | const char *filename, unsigned line, | |
364 | const char *key, char *value, | |
99aad9a2 | 365 | void *userdata) { |
686d13b9 LP |
366 | char ***m = userdata; |
367 | char *p; | |
368 | int r; | |
369 | ||
370 | r = check_utf8ness_and_warn(filename, line, key, value); | |
371 | if (r < 0) | |
372 | return r; | |
373 | ||
374 | p = strjoin(key, "=", value); | |
375 | if (!p) | |
376 | return -ENOMEM; | |
377 | ||
13734c75 ZJS |
378 | r = strv_env_replace_consume(m, p); |
379 | if (r < 0) | |
686d13b9 | 380 | return r; |
686d13b9 | 381 | |
686d13b9 LP |
382 | free(value); |
383 | return 0; | |
384 | } | |
385 | ||
386 | int load_env_file(FILE *f, const char *fname, char ***rl) { | |
99aad9a2 | 387 | _cleanup_strv_free_ char **m = NULL; |
686d13b9 LP |
388 | int r; |
389 | ||
99aad9a2 ZJS |
390 | r = parse_env_file_internal(f, fname, load_env_file_push, &m); |
391 | if (r < 0) | |
686d13b9 | 392 | return r; |
686d13b9 | 393 | |
99aad9a2 | 394 | *rl = TAKE_PTR(m); |
686d13b9 LP |
395 | return 0; |
396 | } | |
397 | ||
398 | static int load_env_file_push_pairs( | |
399 | const char *filename, unsigned line, | |
400 | const char *key, char *value, | |
99aad9a2 ZJS |
401 | void *userdata) { |
402 | ||
25407ad2 | 403 | char ***m = ASSERT_PTR(userdata); |
686d13b9 LP |
404 | int r; |
405 | ||
406 | r = check_utf8ness_and_warn(filename, line, key, value); | |
407 | if (r < 0) | |
408 | return r; | |
409 | ||
25407ad2 ZJS |
410 | /* Check if the key is present */ |
411 | for (char **t = *m; t && *t; t += 2) | |
412 | if (streq(t[0], key)) { | |
413 | if (value) | |
99aad9a2 | 414 | return free_and_replace(t[1], value); |
25407ad2 | 415 | else |
99aad9a2 | 416 | return free_and_strdup(t+1, ""); |
25407ad2 ZJS |
417 | } |
418 | ||
686d13b9 LP |
419 | r = strv_extend(m, key); |
420 | if (r < 0) | |
99aad9a2 | 421 | return r; |
686d13b9 | 422 | |
25407ad2 | 423 | if (value) |
99aad9a2 | 424 | return strv_push(m, value); |
25407ad2 | 425 | else |
99aad9a2 | 426 | return strv_extend(m, ""); |
686d13b9 LP |
427 | } |
428 | ||
429 | int load_env_file_pairs(FILE *f, const char *fname, char ***rl) { | |
99aad9a2 | 430 | _cleanup_strv_free_ char **m = NULL; |
686d13b9 LP |
431 | int r; |
432 | ||
99aad9a2 ZJS |
433 | r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m); |
434 | if (r < 0) | |
686d13b9 | 435 | return r; |
686d13b9 | 436 | |
99aad9a2 | 437 | *rl = TAKE_PTR(m); |
686d13b9 LP |
438 | return 0; |
439 | } | |
440 | ||
441 | static int merge_env_file_push( | |
442 | const char *filename, unsigned line, | |
443 | const char *key, char *value, | |
99aad9a2 | 444 | void *userdata) { |
686d13b9 | 445 | |
99534007 | 446 | char ***env = ASSERT_PTR(userdata); |
686d13b9 LP |
447 | char *expanded_value; |
448 | ||
686d13b9 LP |
449 | if (!value) { |
450 | log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key); | |
451 | return 0; | |
452 | } | |
453 | ||
454 | if (!env_name_is_valid(key)) { | |
455 | log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key); | |
456 | free(value); | |
457 | return 0; | |
458 | } | |
459 | ||
460 | expanded_value = replace_env(value, *env, | |
461 | REPLACE_ENV_USE_ENVIRONMENT| | |
462 | REPLACE_ENV_ALLOW_BRACELESS| | |
463 | REPLACE_ENV_ALLOW_EXTENDED); | |
464 | if (!expanded_value) | |
465 | return -ENOMEM; | |
466 | ||
467 | free_and_replace(value, expanded_value); | |
468 | ||
f6301bdc ZJS |
469 | log_debug("%s:%u: setting %s=%s", filename, line, key, value); |
470 | ||
99aad9a2 | 471 | return load_env_file_push(filename, line, key, value, env); |
686d13b9 LP |
472 | } |
473 | ||
474 | int merge_env_file( | |
475 | char ***env, | |
476 | FILE *f, | |
477 | const char *fname) { | |
478 | ||
479 | /* NOTE: this function supports braceful and braceless variable expansions, | |
480 | * plus "extended" substitutions, unlike other exported parsing functions. | |
481 | */ | |
482 | ||
99aad9a2 | 483 | return parse_env_file_internal(f, fname, merge_env_file_push, env); |
686d13b9 LP |
484 | } |
485 | ||
486 | static void write_env_var(FILE *f, const char *v) { | |
487 | const char *p; | |
488 | ||
489 | p = strchr(v, '='); | |
490 | if (!p) { | |
491 | /* Fallback */ | |
492 | fputs_unlocked(v, f); | |
493 | fputc_unlocked('\n', f); | |
494 | return; | |
495 | } | |
496 | ||
497 | p++; | |
498 | fwrite_unlocked(v, 1, p-v, f); | |
499 | ||
500 | if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) { | |
e768a4f0 | 501 | fputc_unlocked('"', f); |
686d13b9 LP |
502 | |
503 | for (; *p; p++) { | |
504 | if (strchr(SHELL_NEED_ESCAPE, *p)) | |
505 | fputc_unlocked('\\', f); | |
506 | ||
507 | fputc_unlocked(*p, f); | |
508 | } | |
509 | ||
e768a4f0 | 510 | fputc_unlocked('"', f); |
686d13b9 LP |
511 | } else |
512 | fputs_unlocked(p, f); | |
513 | ||
514 | fputc_unlocked('\n', f); | |
515 | } | |
516 | ||
517 | int write_env_file(const char *fname, char **l) { | |
518 | _cleanup_fclose_ FILE *f = NULL; | |
519 | _cleanup_free_ char *p = NULL; | |
686d13b9 LP |
520 | int r; |
521 | ||
522 | assert(fname); | |
523 | ||
524 | r = fopen_temporary(fname, &f, &p); | |
525 | if (r < 0) | |
526 | return r; | |
527 | ||
686d13b9 LP |
528 | (void) fchmod_umask(fileno(f), 0644); |
529 | ||
530 | STRV_FOREACH(i, l) | |
531 | write_env_var(f, *i); | |
532 | ||
533 | r = fflush_and_check(f); | |
534 | if (r >= 0) { | |
535 | if (rename(p, fname) >= 0) | |
536 | return 0; | |
537 | ||
538 | r = -errno; | |
539 | } | |
540 | ||
57d2db22 | 541 | (void) unlink(p); |
686d13b9 LP |
542 | return r; |
543 | } |