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