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