]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/env-file.c
Make fopen_temporary and fopen_temporary_label unlocked
[thirdparty/systemd.git] / src / basic / env-file.c
CommitLineData
686d13b9
LP
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
686d13b9
LP
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
15static 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,
686d13b9
LP
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;
e768a4f0 113 else if (c == '"')
686d13b9
LP
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;
686d13b9
LP
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
686d13b9 195 case DOUBLE_QUOTE_VALUE:
e768a4f0 196 if (c == '"')
686d13b9
LP
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
e4a8db1f 212 if (c == '"') {
686d13b9
LP
213 if (!GREEDY_REALLOC(value, value_alloc, n_value+2))
214 return -ENOMEM;
e4a8db1f
LT
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++] = '\\';
686d13b9
LP
220 value[n_value++] = c;
221 }
e4a8db1f 222
686d13b9
LP
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,
686d13b9
LP
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
271static 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
296static 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
335int 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
352int 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
367static 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
397int 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
411static 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
443int 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
457static 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 return load_env_file_push(filename, line, key, value, env, n_pushed);
489}
490
491int 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 return parse_env_file_internal(f, fname, merge_env_file_push, env, NULL);
501}
502
503static void write_env_var(FILE *f, const char *v) {
504 const char *p;
505
506 p = strchr(v, '=');
507 if (!p) {
508 /* Fallback */
509 fputs_unlocked(v, f);
510 fputc_unlocked('\n', f);
511 return;
512 }
513
514 p++;
515 fwrite_unlocked(v, 1, p-v, f);
516
517 if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
e768a4f0 518 fputc_unlocked('"', f);
686d13b9
LP
519
520 for (; *p; p++) {
521 if (strchr(SHELL_NEED_ESCAPE, *p))
522 fputc_unlocked('\\', f);
523
524 fputc_unlocked(*p, f);
525 }
526
e768a4f0 527 fputc_unlocked('"', f);
686d13b9
LP
528 } else
529 fputs_unlocked(p, f);
530
531 fputc_unlocked('\n', f);
532}
533
534int write_env_file(const char *fname, char **l) {
535 _cleanup_fclose_ FILE *f = NULL;
536 _cleanup_free_ char *p = NULL;
537 char **i;
538 int r;
539
540 assert(fname);
541
542 r = fopen_temporary(fname, &f, &p);
543 if (r < 0)
544 return r;
545
686d13b9
LP
546 (void) fchmod_umask(fileno(f), 0644);
547
548 STRV_FOREACH(i, l)
549 write_env_var(f, *i);
550
551 r = fflush_and_check(f);
552 if (r >= 0) {
553 if (rename(p, fname) >= 0)
554 return 0;
555
556 r = -errno;
557 }
558
57d2db22 559 (void) unlink(p);
686d13b9
LP
560 return r;
561}