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