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