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