]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/env-file.c
tree-wide: use ASSERT_PTR more
[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,
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
273static 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
298static 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
333int 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
347int 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
362static 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
386int 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
398static 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
429int 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
441static 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
474int 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
486static 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
517int 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}