]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/env-file.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[thirdparty/systemd.git] / src / basic / env-file.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
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,
19 const char *key, char *value, void *userdata, int *n_pushed),
20 void *userdata,
21 int *n_pushed) {
22
23 size_t n_key = 0, n_value = 0, last_value_whitespace = SIZE_MAX, last_key_whitespace = SIZE_MAX;
24 _cleanup_free_ char *contents = NULL, *key = NULL, *value = NULL;
25 unsigned line = 1;
26 int r;
27
28 enum {
29 PRE_KEY,
30 KEY,
31 PRE_VALUE,
32 VALUE,
33 VALUE_ESCAPE,
34 SINGLE_QUOTE_VALUE,
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
48 for (char *p = contents; *p; p++) {
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;
58 last_key_whitespace = SIZE_MAX;
59
60 if (!GREEDY_REALLOC(key, n_key+2))
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;
74 last_value_whitespace = SIZE_MAX;
75 } else {
76 if (!strchr(WHITESPACE, c))
77 last_key_whitespace = SIZE_MAX;
78 else if (last_key_whitespace == SIZE_MAX)
79 last_key_whitespace = n_key;
80
81 if (!GREEDY_REALLOC(key, n_key+2))
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 */
99 if (last_key_whitespace != SIZE_MAX)
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;
108 n_value = 0;
109
110 } else if (c == '\'')
111 state = SINGLE_QUOTE_VALUE;
112 else if (c == '"')
113 state = DOUBLE_QUOTE_VALUE;
114 else if (c == '\\')
115 state = VALUE_ESCAPE;
116 else if (!strchr(WHITESPACE, c)) {
117 state = VALUE;
118
119 if (!GREEDY_REALLOC(value, n_value+2))
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 */
138 if (last_value_whitespace != SIZE_MAX)
139 value[last_value_whitespace] = 0;
140
141 /* strip trailing whitespace from key */
142 if (last_key_whitespace != SIZE_MAX)
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;
151 n_value = 0;
152
153 } else if (c == '\\') {
154 state = VALUE_ESCAPE;
155 last_value_whitespace = SIZE_MAX;
156 } else {
157 if (!strchr(WHITESPACE, c))
158 last_value_whitespace = SIZE_MAX;
159 else if (last_value_whitespace == SIZE_MAX)
160 last_value_whitespace = n_value;
161
162 if (!GREEDY_REALLOC(value, n_value+2))
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 */
175 if (!GREEDY_REALLOC(value, n_value+2))
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;
185 else {
186 if (!GREEDY_REALLOC(value, n_value+2))
187 return -ENOMEM;
188
189 value[n_value++] = c;
190 }
191
192 break;
193
194 case DOUBLE_QUOTE_VALUE:
195 if (c == '"')
196 state = PRE_VALUE;
197 else if (c == '\\')
198 state = DOUBLE_QUOTE_VALUE_ESCAPE;
199 else {
200 if (!GREEDY_REALLOC(value, n_value+2))
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
211 if (strchr(SHELL_NEED_ESCAPE, c)) {
212 /* If this is a char that needs escaping, just unescape it. */
213 if (!GREEDY_REALLOC(value, n_value+2))
214 return -ENOMEM;
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. */
219 if (!GREEDY_REALLOC(value, n_value+3))
220 return -ENOMEM;
221 value[n_value++] = '\\';
222 value[n_value++] = c;
223 }
224
225 /* Escaped newlines (aka "continuation lines") are eaten up entirely */
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,
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)
257 if (last_value_whitespace != SIZE_MAX)
258 value[last_value_whitespace] = 0;
259
260 /* strip trailing whitespace from key */
261 if (last_key_whitespace != SIZE_MAX)
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
274 static 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
299 static 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
338 int 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
355 int 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
370 static 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
387 r = strv_env_replace_consume(m, p);
388 if (r < 0)
389 return r;
390
391 if (n_pushed)
392 (*n_pushed)++;
393
394 free(value);
395 return 0;
396 }
397
398 int 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
412 static 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
444 int 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
458 static 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
489 log_debug("%s:%u: setting %s=%s", filename, line, key, value);
490
491 return load_env_file_push(filename, line, key, value, env, n_pushed);
492 }
493
494 int 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
506 static 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)) {
521 fputc_unlocked('"', f);
522
523 for (; *p; p++) {
524 if (strchr(SHELL_NEED_ESCAPE, *p))
525 fputc_unlocked('\\', f);
526
527 fputc_unlocked(*p, f);
528 }
529
530 fputc_unlocked('"', f);
531 } else
532 fputs_unlocked(p, f);
533
534 fputc_unlocked('\n', f);
535 }
536
537 int write_env_file(const char *fname, char **l) {
538 _cleanup_fclose_ FILE *f = NULL;
539 _cleanup_free_ char *p = NULL;
540 int r;
541
542 assert(fname);
543
544 r = fopen_temporary(fname, &f, &p);
545 if (r < 0)
546 return r;
547
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
561 (void) unlink(p);
562 return r;
563 }