]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/env-file.c
Merge pull request #13436 from systemd/hidden-units-are-good-units
[thirdparty/systemd.git] / src / basic / env-file.c
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 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 (c == '"') {
213 if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
214 return -ENOMEM;
215 value[n_value++] = '"';
216 } else if (!strchr(NEWLINE, c)) {
217 if (!GREEDY_REALLOC(value, value_alloc, n_value+3))
218 return -ENOMEM;
219 value[n_value++] = '\\';
220 value[n_value++] = c;
221 }
222
223 break;
224
225 case COMMENT:
226 if (c == '\\')
227 state = COMMENT_ESCAPE;
228 else if (strchr(NEWLINE, c)) {
229 state = PRE_KEY;
230 line++;
231 }
232 break;
233
234 case COMMENT_ESCAPE:
235 state = COMMENT;
236 break;
237 }
238 }
239
240 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 key[n_key] = 0;
249
250 if (value)
251 value[n_value] = 0;
252
253 if (state == VALUE)
254 if (last_value_whitespace != (size_t) -1)
255 value[last_value_whitespace] = 0;
256
257 /* strip trailing whitespace from key */
258 if (last_key_whitespace != (size_t) -1)
259 key[last_key_whitespace] = 0;
260
261 r = push(fname, line, key, value, userdata, n_pushed);
262 if (r < 0)
263 return r;
264
265 value = NULL;
266 }
267
268 return 0;
269 }
270
271 static int check_utf8ness_and_warn(
272 const char *filename, unsigned line,
273 const char *key, char *value) {
274
275 if (!utf8_is_valid(key)) {
276 _cleanup_free_ char *p = NULL;
277
278 p = utf8_escape_invalid(key);
279 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 if (value && !utf8_is_valid(value)) {
285 _cleanup_free_ char *p = NULL;
286
287 p = utf8_escape_invalid(value);
288 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 return 0;
294 }
295
296 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 va_list aq, *ap = userdata;
304 int r;
305
306 r = check_utf8ness_and_warn(filename, line, key, value);
307 if (r < 0)
308 return r;
309
310 va_copy(aq, *ap);
311
312 while ((k = va_arg(aq, const char *))) {
313 char **v;
314
315 v = va_arg(aq, char **);
316
317 if (streq(key, k)) {
318 va_end(aq);
319 free(*v);
320 *v = value;
321
322 if (n_pushed)
323 (*n_pushed)++;
324
325 return 1;
326 }
327 }
328
329 va_end(aq);
330 free(value);
331
332 return 0;
333 }
334
335 int parse_env_filev(
336 FILE *f,
337 const char *fname,
338 va_list ap) {
339
340 int r, n_pushed = 0;
341 va_list aq;
342
343 va_copy(aq, ap);
344 r = parse_env_file_internal(f, fname, parse_env_file_push, &aq, &n_pushed);
345 va_end(aq);
346 if (r < 0)
347 return r;
348
349 return n_pushed;
350 }
351
352 int parse_env_file_sentinel(
353 FILE *f,
354 const char *fname,
355 ...) {
356
357 va_list ap;
358 int r;
359
360 va_start(ap, fname);
361 r = parse_env_filev(f, fname, ap);
362 va_end(ap);
363
364 return r;
365 }
366
367 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 char ***m = userdata;
373 char *p;
374 int r;
375
376 r = check_utf8ness_and_warn(filename, line, key, value);
377 if (r < 0)
378 return r;
379
380 p = strjoin(key, "=", value);
381 if (!p)
382 return -ENOMEM;
383
384 r = strv_env_replace(m, p);
385 if (r < 0) {
386 free(p);
387 return r;
388 }
389
390 if (n_pushed)
391 (*n_pushed)++;
392
393 free(value);
394 return 0;
395 }
396
397 int load_env_file(FILE *f, const char *fname, char ***rl) {
398 char **m = NULL;
399 int r;
400
401 r = parse_env_file_internal(f, fname, load_env_file_push, &m, NULL);
402 if (r < 0) {
403 strv_free(m);
404 return r;
405 }
406
407 *rl = m;
408 return 0;
409 }
410
411 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 char ***m = userdata;
417 int r;
418
419 r = check_utf8ness_and_warn(filename, line, key, value);
420 if (r < 0)
421 return r;
422
423 r = strv_extend(m, key);
424 if (r < 0)
425 return -ENOMEM;
426
427 if (!value) {
428 r = strv_extend(m, "");
429 if (r < 0)
430 return -ENOMEM;
431 } else {
432 r = strv_push(m, value);
433 if (r < 0)
434 return r;
435 }
436
437 if (n_pushed)
438 (*n_pushed)++;
439
440 return 0;
441 }
442
443 int load_env_file_pairs(FILE *f, const char *fname, char ***rl) {
444 char **m = NULL;
445 int r;
446
447 r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m, NULL);
448 if (r < 0) {
449 strv_free(m);
450 return r;
451 }
452
453 *rl = m;
454 return 0;
455 }
456
457 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 char ***env = userdata;
464 char *expanded_value;
465
466 assert(env);
467
468 if (!value) {
469 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
470 return 0;
471 }
472
473 if (!env_name_is_valid(key)) {
474 log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key);
475 free(value);
476 return 0;
477 }
478
479 expanded_value = replace_env(value, *env,
480 REPLACE_ENV_USE_ENVIRONMENT|
481 REPLACE_ENV_ALLOW_BRACELESS|
482 REPLACE_ENV_ALLOW_EXTENDED);
483 if (!expanded_value)
484 return -ENOMEM;
485
486 free_and_replace(value, expanded_value);
487
488 log_debug("%s:%u: setting %s=%s", filename, line, key, 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) fchmod_umask(fileno(f), 0644);
549
550 STRV_FOREACH(i, l)
551 write_env_var(f, *i);
552
553 r = fflush_and_check(f);
554 if (r >= 0) {
555 if (rename(p, fname) >= 0)
556 return 0;
557
558 r = -errno;
559 }
560
561 (void) unlink(p);
562 return r;
563 }