]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/env-file.c
shared/install: return failure when enablement fails, but process as much as possible
[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) {
25407ad2
ZJS
417 char ***m = ASSERT_PTR(userdata);
418 bool added = false;
686d13b9
LP
419 int r;
420
421 r = check_utf8ness_and_warn(filename, line, key, value);
422 if (r < 0)
423 return r;
424
25407ad2
ZJS
425 /* Check if the key is present */
426 for (char **t = *m; t && *t; t += 2)
427 if (streq(t[0], key)) {
428 if (value)
429 r = free_and_replace(t[1], value);
430 else
431 r = free_and_strdup(t+1, "");
432 goto finish;
433 }
434
686d13b9
LP
435 r = strv_extend(m, key);
436 if (r < 0)
437 return -ENOMEM;
438
25407ad2 439 if (value)
686d13b9 440 r = strv_push(m, value);
25407ad2
ZJS
441 else
442 r = strv_extend(m, "");
443 added = true;
444 finish:
445 if (r < 0)
446 return r;
686d13b9 447
25407ad2 448 if (n_pushed && added)
686d13b9 449 (*n_pushed)++;
686d13b9
LP
450 return 0;
451}
452
453int load_env_file_pairs(FILE *f, const char *fname, char ***rl) {
454 char **m = NULL;
455 int r;
456
457 r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m, NULL);
458 if (r < 0) {
459 strv_free(m);
460 return r;
461 }
462
463 *rl = m;
464 return 0;
465}
466
467static int merge_env_file_push(
468 const char *filename, unsigned line,
469 const char *key, char *value,
470 void *userdata,
471 int *n_pushed) {
472
473 char ***env = userdata;
474 char *expanded_value;
475
476 assert(env);
477
478 if (!value) {
479 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
480 return 0;
481 }
482
483 if (!env_name_is_valid(key)) {
484 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key);
485 free(value);
486 return 0;
487 }
488
489 expanded_value = replace_env(value, *env,
490 REPLACE_ENV_USE_ENVIRONMENT|
491 REPLACE_ENV_ALLOW_BRACELESS|
492 REPLACE_ENV_ALLOW_EXTENDED);
493 if (!expanded_value)
494 return -ENOMEM;
495
496 free_and_replace(value, expanded_value);
497
f6301bdc
ZJS
498 log_debug("%s:%u: setting %s=%s", filename, line, key, value);
499
686d13b9
LP
500 return load_env_file_push(filename, line, key, value, env, n_pushed);
501}
502
503int merge_env_file(
504 char ***env,
505 FILE *f,
506 const char *fname) {
507
508 /* NOTE: this function supports braceful and braceless variable expansions,
509 * plus "extended" substitutions, unlike other exported parsing functions.
510 */
511
512 return parse_env_file_internal(f, fname, merge_env_file_push, env, NULL);
513}
514
515static void write_env_var(FILE *f, const char *v) {
516 const char *p;
517
518 p = strchr(v, '=');
519 if (!p) {
520 /* Fallback */
521 fputs_unlocked(v, f);
522 fputc_unlocked('\n', f);
523 return;
524 }
525
526 p++;
527 fwrite_unlocked(v, 1, p-v, f);
528
529 if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
e768a4f0 530 fputc_unlocked('"', f);
686d13b9
LP
531
532 for (; *p; p++) {
533 if (strchr(SHELL_NEED_ESCAPE, *p))
534 fputc_unlocked('\\', f);
535
536 fputc_unlocked(*p, f);
537 }
538
e768a4f0 539 fputc_unlocked('"', f);
686d13b9
LP
540 } else
541 fputs_unlocked(p, f);
542
543 fputc_unlocked('\n', f);
544}
545
546int write_env_file(const char *fname, char **l) {
547 _cleanup_fclose_ FILE *f = NULL;
548 _cleanup_free_ char *p = NULL;
686d13b9
LP
549 int r;
550
551 assert(fname);
552
553 r = fopen_temporary(fname, &f, &p);
554 if (r < 0)
555 return r;
556
686d13b9
LP
557 (void) fchmod_umask(fileno(f), 0644);
558
559 STRV_FOREACH(i, l)
560 write_env_var(f, *i);
561
562 r = fflush_and_check(f);
563 if (r >= 0) {
564 if (rename(p, fname) >= 0)
565 return 0;
566
567 r = -errno;
568 }
569
57d2db22 570 (void) unlink(p);
686d13b9
LP
571 return r;
572}