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