]>
Commit | Line | Data |
---|---|---|
663996b3 MS |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2010 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <unistd.h> | |
23 | #include "fileio.h" | |
24 | #include "util.h" | |
25 | #include "strv.h" | |
14228c0d MB |
26 | #include "utf8.h" |
27 | #include "ctype.h" | |
663996b3 MS |
28 | |
29 | int write_string_to_file(FILE *f, const char *line) { | |
30 | errno = 0; | |
31 | fputs(line, f); | |
32 | if (!endswith(line, "\n")) | |
33 | fputc('\n', f); | |
34 | ||
35 | fflush(f); | |
36 | ||
37 | if (ferror(f)) | |
38 | return errno ? -errno : -EIO; | |
39 | ||
40 | return 0; | |
41 | } | |
42 | ||
43 | int write_string_file(const char *fn, const char *line) { | |
44 | _cleanup_fclose_ FILE *f = NULL; | |
45 | ||
46 | assert(fn); | |
47 | assert(line); | |
48 | ||
49 | f = fopen(fn, "we"); | |
50 | if (!f) | |
51 | return -errno; | |
52 | ||
53 | return write_string_to_file(f, line); | |
54 | } | |
55 | ||
56 | int write_string_file_atomic(const char *fn, const char *line) { | |
57 | _cleanup_fclose_ FILE *f = NULL; | |
58 | _cleanup_free_ char *p = NULL; | |
59 | int r; | |
60 | ||
61 | assert(fn); | |
62 | assert(line); | |
63 | ||
64 | r = fopen_temporary(fn, &f, &p); | |
65 | if (r < 0) | |
66 | return r; | |
67 | ||
68 | fchmod_umask(fileno(f), 0644); | |
69 | ||
70 | errno = 0; | |
71 | fputs(line, f); | |
72 | if (!endswith(line, "\n")) | |
73 | fputc('\n', f); | |
74 | ||
75 | fflush(f); | |
76 | ||
77 | if (ferror(f)) | |
78 | r = errno ? -errno : -EIO; | |
79 | else { | |
80 | if (rename(p, fn) < 0) | |
81 | r = -errno; | |
82 | else | |
83 | r = 0; | |
84 | } | |
85 | ||
86 | if (r < 0) | |
87 | unlink(p); | |
88 | ||
89 | return r; | |
90 | } | |
91 | ||
92 | int read_one_line_file(const char *fn, char **line) { | |
93 | _cleanup_fclose_ FILE *f = NULL; | |
94 | char t[LINE_MAX], *c; | |
95 | ||
96 | assert(fn); | |
97 | assert(line); | |
98 | ||
99 | f = fopen(fn, "re"); | |
100 | if (!f) | |
101 | return -errno; | |
102 | ||
103 | if (!fgets(t, sizeof(t), f)) { | |
104 | ||
105 | if (ferror(f)) | |
106 | return errno ? -errno : -EIO; | |
107 | ||
108 | t[0] = 0; | |
109 | } | |
110 | ||
111 | c = strdup(t); | |
112 | if (!c) | |
113 | return -ENOMEM; | |
114 | truncate_nl(c); | |
115 | ||
116 | *line = c; | |
117 | return 0; | |
118 | } | |
119 | ||
120 | int read_full_file(const char *fn, char **contents, size_t *size) { | |
121 | _cleanup_fclose_ FILE *f = NULL; | |
122 | size_t n, l; | |
123 | _cleanup_free_ char *buf = NULL; | |
124 | struct stat st; | |
125 | ||
126 | assert(fn); | |
127 | assert(contents); | |
128 | ||
129 | f = fopen(fn, "re"); | |
130 | if (!f) | |
131 | return -errno; | |
132 | ||
133 | if (fstat(fileno(f), &st) < 0) | |
134 | return -errno; | |
135 | ||
136 | /* Safety check */ | |
137 | if (st.st_size > 4*1024*1024) | |
138 | return -E2BIG; | |
139 | ||
140 | n = st.st_size > 0 ? st.st_size : LINE_MAX; | |
141 | l = 0; | |
142 | ||
143 | for (;;) { | |
144 | char *t; | |
145 | size_t k; | |
146 | ||
147 | t = realloc(buf, n+1); | |
148 | if (!t) | |
149 | return -ENOMEM; | |
150 | ||
151 | buf = t; | |
152 | k = fread(buf + l, 1, n - l, f); | |
153 | ||
154 | if (k <= 0) { | |
155 | if (ferror(f)) | |
156 | return -errno; | |
157 | ||
158 | break; | |
159 | } | |
160 | ||
161 | l += k; | |
162 | n *= 2; | |
163 | ||
164 | /* Safety check */ | |
165 | if (n > 4*1024*1024) | |
166 | return -E2BIG; | |
167 | } | |
168 | ||
169 | buf[l] = 0; | |
170 | *contents = buf; | |
171 | buf = NULL; | |
172 | ||
173 | if (size) | |
174 | *size = l; | |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
179 | static int parse_env_file_internal( | |
180 | const char *fname, | |
181 | const char *newline, | |
14228c0d MB |
182 | int (*push) (const char *filename, unsigned line, |
183 | const char *key, char *value, void *userdata), | |
663996b3 MS |
184 | void *userdata) { |
185 | ||
186 | _cleanup_free_ char *contents = NULL, *key = NULL; | |
187 | 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; | |
188 | char *p, *value = NULL; | |
189 | int r; | |
14228c0d | 190 | unsigned line = 1; |
663996b3 MS |
191 | |
192 | enum { | |
193 | PRE_KEY, | |
194 | KEY, | |
195 | PRE_VALUE, | |
196 | VALUE, | |
197 | VALUE_ESCAPE, | |
198 | SINGLE_QUOTE_VALUE, | |
199 | SINGLE_QUOTE_VALUE_ESCAPE, | |
200 | DOUBLE_QUOTE_VALUE, | |
201 | DOUBLE_QUOTE_VALUE_ESCAPE, | |
202 | COMMENT, | |
203 | COMMENT_ESCAPE | |
204 | } state = PRE_KEY; | |
205 | ||
206 | assert(fname); | |
207 | assert(newline); | |
208 | ||
209 | r = read_full_file(fname, &contents, NULL); | |
210 | if (r < 0) | |
211 | return r; | |
212 | ||
213 | for (p = contents; *p; p++) { | |
214 | char c = *p; | |
215 | ||
216 | switch (state) { | |
217 | ||
218 | case PRE_KEY: | |
219 | if (strchr(COMMENTS, c)) | |
220 | state = COMMENT; | |
221 | else if (!strchr(WHITESPACE, c)) { | |
222 | state = KEY; | |
223 | last_key_whitespace = (size_t) -1; | |
224 | ||
225 | if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { | |
226 | r = -ENOMEM; | |
227 | goto fail; | |
228 | } | |
229 | ||
230 | key[n_key++] = c; | |
231 | } | |
232 | break; | |
233 | ||
234 | case KEY: | |
235 | if (strchr(newline, c)) { | |
236 | state = PRE_KEY; | |
14228c0d | 237 | line ++; |
663996b3 MS |
238 | n_key = 0; |
239 | } else if (c == '=') { | |
240 | state = PRE_VALUE; | |
241 | last_value_whitespace = (size_t) -1; | |
242 | } else { | |
243 | if (!strchr(WHITESPACE, c)) | |
244 | last_key_whitespace = (size_t) -1; | |
245 | else if (last_key_whitespace == (size_t) -1) | |
246 | last_key_whitespace = n_key; | |
247 | ||
248 | if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { | |
249 | r = -ENOMEM; | |
250 | goto fail; | |
251 | } | |
252 | ||
253 | key[n_key++] = c; | |
254 | } | |
255 | ||
256 | break; | |
257 | ||
258 | case PRE_VALUE: | |
259 | if (strchr(newline, c)) { | |
260 | state = PRE_KEY; | |
14228c0d | 261 | line ++; |
663996b3 MS |
262 | key[n_key] = 0; |
263 | ||
264 | if (value) | |
265 | value[n_value] = 0; | |
266 | ||
267 | /* strip trailing whitespace from key */ | |
268 | if (last_key_whitespace != (size_t) -1) | |
269 | key[last_key_whitespace] = 0; | |
270 | ||
14228c0d | 271 | r = push(fname, line, key, value, userdata); |
663996b3 MS |
272 | if (r < 0) |
273 | goto fail; | |
274 | ||
275 | n_key = 0; | |
276 | value = NULL; | |
277 | value_alloc = n_value = 0; | |
278 | ||
279 | } else if (c == '\'') | |
280 | state = SINGLE_QUOTE_VALUE; | |
281 | else if (c == '\"') | |
282 | state = DOUBLE_QUOTE_VALUE; | |
283 | else if (c == '\\') | |
284 | state = VALUE_ESCAPE; | |
285 | else if (!strchr(WHITESPACE, c)) { | |
286 | state = VALUE; | |
287 | ||
288 | if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { | |
289 | r = -ENOMEM; | |
290 | goto fail; | |
291 | } | |
292 | ||
293 | value[n_value++] = c; | |
294 | } | |
295 | ||
296 | break; | |
297 | ||
298 | case VALUE: | |
299 | if (strchr(newline, c)) { | |
300 | state = PRE_KEY; | |
14228c0d | 301 | line ++; |
663996b3 MS |
302 | |
303 | key[n_key] = 0; | |
304 | ||
305 | if (value) | |
306 | value[n_value] = 0; | |
307 | ||
308 | /* Chomp off trailing whitespace from value */ | |
309 | if (last_value_whitespace != (size_t) -1) | |
310 | value[last_value_whitespace] = 0; | |
311 | ||
312 | /* strip trailing whitespace from key */ | |
313 | if (last_key_whitespace != (size_t) -1) | |
314 | key[last_key_whitespace] = 0; | |
315 | ||
14228c0d | 316 | r = push(fname, line, key, value, userdata); |
663996b3 MS |
317 | if (r < 0) |
318 | goto fail; | |
319 | ||
320 | n_key = 0; | |
321 | value = NULL; | |
322 | value_alloc = n_value = 0; | |
323 | ||
324 | } else if (c == '\\') { | |
325 | state = VALUE_ESCAPE; | |
326 | last_value_whitespace = (size_t) -1; | |
327 | } else { | |
328 | if (!strchr(WHITESPACE, c)) | |
329 | last_value_whitespace = (size_t) -1; | |
330 | else if (last_value_whitespace == (size_t) -1) | |
331 | last_value_whitespace = n_value; | |
332 | ||
333 | if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { | |
334 | r = -ENOMEM; | |
335 | goto fail; | |
336 | } | |
337 | ||
338 | value[n_value++] = c; | |
339 | } | |
340 | ||
341 | break; | |
342 | ||
343 | case VALUE_ESCAPE: | |
344 | state = VALUE; | |
345 | ||
346 | if (!strchr(newline, c)) { | |
347 | /* Escaped newlines we eat up entirely */ | |
348 | if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { | |
349 | r = -ENOMEM; | |
350 | goto fail; | |
351 | } | |
352 | ||
353 | value[n_value++] = c; | |
354 | } | |
355 | break; | |
356 | ||
357 | case SINGLE_QUOTE_VALUE: | |
358 | if (c == '\'') | |
359 | state = PRE_VALUE; | |
360 | else if (c == '\\') | |
361 | state = SINGLE_QUOTE_VALUE_ESCAPE; | |
362 | else { | |
363 | if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { | |
364 | r = -ENOMEM; | |
365 | goto fail; | |
366 | } | |
367 | ||
368 | value[n_value++] = c; | |
369 | } | |
370 | ||
371 | break; | |
372 | ||
373 | case SINGLE_QUOTE_VALUE_ESCAPE: | |
374 | state = SINGLE_QUOTE_VALUE; | |
375 | ||
376 | if (!strchr(newline, c)) { | |
377 | if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { | |
378 | r = -ENOMEM; | |
379 | goto fail; | |
380 | } | |
381 | ||
382 | value[n_value++] = c; | |
383 | } | |
384 | break; | |
385 | ||
386 | case DOUBLE_QUOTE_VALUE: | |
387 | if (c == '\"') | |
388 | state = PRE_VALUE; | |
389 | else if (c == '\\') | |
390 | state = DOUBLE_QUOTE_VALUE_ESCAPE; | |
391 | else { | |
392 | if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { | |
393 | r = -ENOMEM; | |
394 | goto fail; | |
395 | } | |
396 | ||
397 | value[n_value++] = c; | |
398 | } | |
399 | ||
400 | break; | |
401 | ||
402 | case DOUBLE_QUOTE_VALUE_ESCAPE: | |
403 | state = DOUBLE_QUOTE_VALUE; | |
404 | ||
405 | if (!strchr(newline, c)) { | |
406 | if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { | |
407 | r = -ENOMEM; | |
408 | goto fail; | |
409 | } | |
410 | ||
411 | value[n_value++] = c; | |
412 | } | |
413 | break; | |
414 | ||
415 | case COMMENT: | |
416 | if (c == '\\') | |
417 | state = COMMENT_ESCAPE; | |
14228c0d | 418 | else if (strchr(newline, c)) { |
663996b3 | 419 | state = PRE_KEY; |
14228c0d MB |
420 | line ++; |
421 | } | |
663996b3 MS |
422 | break; |
423 | ||
424 | case COMMENT_ESCAPE: | |
425 | state = COMMENT; | |
426 | break; | |
427 | } | |
428 | } | |
429 | ||
430 | if (state == PRE_VALUE || | |
431 | state == VALUE || | |
432 | state == VALUE_ESCAPE || | |
433 | state == SINGLE_QUOTE_VALUE || | |
434 | state == SINGLE_QUOTE_VALUE_ESCAPE || | |
435 | state == DOUBLE_QUOTE_VALUE || | |
436 | state == DOUBLE_QUOTE_VALUE_ESCAPE) { | |
437 | ||
438 | key[n_key] = 0; | |
439 | ||
440 | if (value) | |
441 | value[n_value] = 0; | |
442 | ||
443 | if (state == VALUE) | |
444 | if (last_value_whitespace != (size_t) -1) | |
445 | value[last_value_whitespace] = 0; | |
446 | ||
447 | /* strip trailing whitespace from key */ | |
448 | if (last_key_whitespace != (size_t) -1) | |
449 | key[last_key_whitespace] = 0; | |
450 | ||
14228c0d | 451 | r = push(fname, line, key, value, userdata); |
663996b3 MS |
452 | if (r < 0) |
453 | goto fail; | |
454 | } | |
455 | ||
456 | return 0; | |
457 | ||
458 | fail: | |
459 | free(value); | |
460 | return r; | |
461 | } | |
462 | ||
14228c0d MB |
463 | static int parse_env_file_push(const char *filename, unsigned line, |
464 | const char *key, char *value, void *userdata) { | |
465 | assert(utf8_is_valid(key)); | |
466 | ||
467 | if (value && !utf8_is_valid(value)) | |
468 | /* FIXME: filter UTF-8 */ | |
469 | log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.", | |
470 | filename, line, key, value); | |
471 | else { | |
472 | const char *k; | |
473 | va_list* ap = (va_list*) userdata; | |
474 | va_list aq; | |
663996b3 | 475 | |
14228c0d | 476 | va_copy(aq, *ap); |
663996b3 | 477 | |
14228c0d MB |
478 | while ((k = va_arg(aq, const char *))) { |
479 | char **v; | |
663996b3 | 480 | |
14228c0d | 481 | v = va_arg(aq, char **); |
663996b3 | 482 | |
14228c0d MB |
483 | if (streq(key, k)) { |
484 | va_end(aq); | |
485 | free(*v); | |
486 | *v = value; | |
487 | return 1; | |
488 | } | |
663996b3 | 489 | } |
663996b3 | 490 | |
14228c0d MB |
491 | va_end(aq); |
492 | } | |
663996b3 MS |
493 | |
494 | free(value); | |
495 | return 0; | |
496 | } | |
497 | ||
498 | int parse_env_file( | |
499 | const char *fname, | |
500 | const char *newline, ...) { | |
501 | ||
502 | va_list ap; | |
503 | int r; | |
504 | ||
505 | if (!newline) | |
506 | newline = NEWLINE; | |
507 | ||
508 | va_start(ap, newline); | |
509 | r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap); | |
510 | va_end(ap); | |
511 | ||
512 | return r; | |
513 | } | |
514 | ||
14228c0d MB |
515 | static int load_env_file_push(const char *filename, unsigned line, |
516 | const char *key, char *value, void *userdata) { | |
517 | assert(utf8_is_valid(key)); | |
663996b3 | 518 | |
14228c0d MB |
519 | if (value && !utf8_is_valid(value)) |
520 | /* FIXME: filter UTF-8 */ | |
521 | log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.", | |
522 | filename, line, key, value); | |
523 | else { | |
524 | char ***m = userdata; | |
525 | char *p; | |
526 | int r; | |
663996b3 | 527 | |
14228c0d MB |
528 | p = strjoin(key, "=", strempty(value), NULL); |
529 | if (!p) | |
530 | return -ENOMEM; | |
531 | ||
532 | r = strv_push(m, p); | |
533 | if (r < 0) { | |
534 | free(p); | |
535 | return r; | |
536 | } | |
663996b3 MS |
537 | } |
538 | ||
539 | free(value); | |
540 | return 0; | |
541 | } | |
542 | ||
543 | int load_env_file(const char *fname, const char *newline, char ***rl) { | |
544 | char **m = NULL; | |
545 | int r; | |
546 | ||
547 | if (!newline) | |
548 | newline = NEWLINE; | |
549 | ||
550 | r = parse_env_file_internal(fname, newline, load_env_file_push, &m); | |
551 | if (r < 0) { | |
552 | strv_free(m); | |
553 | return r; | |
554 | } | |
555 | ||
556 | *rl = m; | |
557 | return 0; | |
558 | } | |
559 | ||
560 | static void write_env_var(FILE *f, const char *v) { | |
561 | const char *p; | |
562 | ||
563 | p = strchr(v, '='); | |
564 | if (!p) { | |
565 | /* Fallback */ | |
566 | fputs(v, f); | |
567 | fputc('\n', f); | |
568 | return; | |
569 | } | |
570 | ||
571 | p++; | |
572 | fwrite(v, 1, p-v, f); | |
573 | ||
574 | if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) { | |
575 | fputc('\"', f); | |
576 | ||
577 | for (; *p; p++) { | |
578 | if (strchr("\'\"\\`$", *p)) | |
579 | fputc('\\', f); | |
580 | ||
581 | fputc(*p, f); | |
582 | } | |
583 | ||
584 | fputc('\"', f); | |
585 | } else | |
586 | fputs(p, f); | |
587 | ||
588 | fputc('\n', f); | |
589 | } | |
590 | ||
591 | int write_env_file(const char *fname, char **l) { | |
592 | char **i; | |
593 | _cleanup_free_ char *p = NULL; | |
594 | _cleanup_fclose_ FILE *f = NULL; | |
595 | int r; | |
596 | ||
597 | r = fopen_temporary(fname, &f, &p); | |
598 | if (r < 0) | |
599 | return r; | |
600 | ||
601 | fchmod_umask(fileno(f), 0644); | |
602 | ||
603 | errno = 0; | |
604 | STRV_FOREACH(i, l) | |
605 | write_env_var(f, *i); | |
606 | ||
607 | fflush(f); | |
608 | ||
609 | if (ferror(f)) | |
610 | r = errno ? -errno : -EIO; | |
611 | else { | |
612 | if (rename(p, fname) < 0) | |
613 | r = -errno; | |
614 | else | |
615 | r = 0; | |
616 | } | |
617 | ||
618 | if (r < 0) | |
619 | unlink(p); | |
620 | ||
621 | return r; | |
622 | } | |
14228c0d MB |
623 | |
624 | int executable_is_script(const char *path, char **interpreter) { | |
625 | int r; | |
626 | char _cleanup_free_ *line = NULL; | |
627 | int len; | |
628 | char *ans; | |
629 | ||
630 | assert(path); | |
631 | ||
632 | r = read_one_line_file(path, &line); | |
633 | if (r < 0) | |
634 | return r; | |
635 | ||
636 | if (!startswith(line, "#!")) | |
637 | return 0; | |
638 | ||
639 | ans = strstrip(line + 2); | |
640 | len = strcspn(ans, " \t"); | |
641 | ||
642 | if (len == 0) | |
643 | return 0; | |
644 | ||
645 | ans = strndup(ans, len); | |
646 | if (!ans) | |
647 | return -ENOMEM; | |
648 | ||
649 | *interpreter = ans; | |
650 | return 1; | |
651 | } | |
652 | ||
653 | /** | |
654 | * Retrieve one field from a file like /proc/self/status. pattern | |
655 | * should start with '\n' and end with a ':'. Whitespace and zeros | |
656 | * after the ':' will be skipped. field must be freed afterwards. | |
657 | */ | |
658 | int get_status_field(const char *filename, const char *pattern, char **field) { | |
659 | _cleanup_free_ char *status = NULL; | |
660 | char *t; | |
661 | size_t len; | |
662 | int r; | |
663 | ||
664 | assert(filename); | |
665 | assert(field); | |
666 | ||
667 | r = read_full_file(filename, &status, NULL); | |
668 | if (r < 0) | |
669 | return r; | |
670 | ||
671 | t = strstr(status, pattern); | |
672 | if (!t) | |
673 | return -ENOENT; | |
674 | ||
675 | t += strlen(pattern); | |
676 | if (*t) { | |
677 | t += strspn(t, " \t"); | |
678 | ||
679 | /* Also skip zeros, because when this is used for | |
680 | * capabilities, we don't want the zeros. This way the | |
681 | * same capability set always maps to the same string, | |
682 | * irrespective of the total capability set size. For | |
683 | * other numbers it shouldn't matter. */ | |
684 | t += strspn(t, "0"); | |
685 | /* Back off one char if there's nothing but whitespace | |
686 | and zeros */ | |
687 | if (!*t || isspace(*t)) | |
688 | t --; | |
689 | } | |
690 | ||
691 | len = strcspn(t, WHITESPACE); | |
692 | ||
693 | *field = strndup(t, len); | |
694 | if (!*field) | |
695 | return -ENOMEM; | |
696 | ||
697 | return 0; | |
698 | } |