chiark / gitweb /
718ed3642dca88c5dfe18fe32566940671a30d47
[elogind.git] / src / basic / fileio.c
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
24 #include "util.h"
25 #include "strv.h"
26 #include "utf8.h"
27 #include "ctype.h"
28 #include "fileio.h"
29
30 int write_string_stream(FILE *f, const char *line, bool enforce_newline) {
31         assert(f);
32         assert(line);
33
34         errno = 0;
35
36         fputs(line, f);
37         if (enforce_newline && !endswith(line, "\n"))
38                 fputc('\n', f);
39
40         fflush(f);
41
42         if (ferror(f))
43                 return errno ? -errno : -EIO;
44
45         return 0;
46 }
47
48 static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) {
49         _cleanup_fclose_ FILE *f = NULL;
50         _cleanup_free_ char *p = NULL;
51         int r;
52
53         assert(fn);
54         assert(line);
55
56         r = fopen_temporary(fn, &f, &p);
57         if (r < 0)
58                 return r;
59
60         fchmod_umask(fileno(f), 0644);
61
62         r = write_string_stream(f, line, enforce_newline);
63         if (r >= 0) {
64                 if (rename(p, fn) < 0)
65                         r = -errno;
66         }
67
68         if (r < 0)
69                 unlink(p);
70
71         return r;
72 }
73
74 int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
75         _cleanup_fclose_ FILE *f = NULL;
76
77         assert(fn);
78         assert(line);
79
80         if (flags & WRITE_STRING_FILE_ATOMIC) {
81                 assert(flags & WRITE_STRING_FILE_CREATE);
82
83                 return write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
84         }
85
86         if (flags & WRITE_STRING_FILE_CREATE) {
87                 f = fopen(fn, "we");
88                 if (!f)
89                         return -errno;
90         } else {
91                 int fd;
92
93                 /* We manually build our own version of fopen(..., "we") that
94                  * works without O_CREAT */
95                 fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY);
96                 if (fd < 0)
97                         return -errno;
98
99                 f = fdopen(fd, "we");
100                 if (!f) {
101                         safe_close(fd);
102                         return -errno;
103                 }
104         }
105
106         return write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
107 }
108
109 int read_one_line_file(const char *fn, char **line) {
110         _cleanup_fclose_ FILE *f = NULL;
111         char t[LINE_MAX], *c;
112
113         assert(fn);
114         assert(line);
115
116         f = fopen(fn, "re");
117         if (!f)
118                 return -errno;
119
120         if (!fgets(t, sizeof(t), f)) {
121
122                 if (ferror(f))
123                         return errno ? -errno : -EIO;
124
125                 t[0] = 0;
126         }
127
128         c = strdup(t);
129         if (!c)
130                 return -ENOMEM;
131         truncate_nl(c);
132
133         *line = c;
134         return 0;
135 }
136
137 int verify_one_line_file(const char *fn, const char *line) {
138         _cleanup_free_ char *value = NULL;
139         int r;
140
141         r = read_one_line_file(fn, &value);
142         if (r < 0)
143                 return r;
144
145         return streq(value, line);
146 }
147
148 int read_full_stream(FILE *f, char **contents, size_t *size) {
149         size_t n, l;
150         _cleanup_free_ char *buf = NULL;
151         struct stat st;
152
153         assert(f);
154         assert(contents);
155
156         if (fstat(fileno(f), &st) < 0)
157                 return -errno;
158
159         n = LINE_MAX;
160
161         if (S_ISREG(st.st_mode)) {
162
163                 /* Safety check */
164                 if (st.st_size > 4*1024*1024)
165                         return -E2BIG;
166
167                 /* Start with the right file size, but be prepared for
168                  * files from /proc which generally report a file size
169                  * of 0 */
170                 if (st.st_size > 0)
171                         n = st.st_size;
172         }
173
174         l = 0;
175         for (;;) {
176                 char *t;
177                 size_t k;
178
179                 t = realloc(buf, n+1);
180                 if (!t)
181                         return -ENOMEM;
182
183                 buf = t;
184                 k = fread(buf + l, 1, n - l, f);
185
186                 if (k <= 0) {
187                         if (ferror(f))
188                                 return -errno;
189
190                         break;
191                 }
192
193                 l += k;
194                 n *= 2;
195
196                 /* Safety check */
197                 if (n > 4*1024*1024)
198                         return -E2BIG;
199         }
200
201         buf[l] = 0;
202         *contents = buf;
203         buf = NULL; /* do not free */
204
205         if (size)
206                 *size = l;
207
208         return 0;
209 }
210
211 int read_full_file(const char *fn, char **contents, size_t *size) {
212         _cleanup_fclose_ FILE *f = NULL;
213
214         assert(fn);
215         assert(contents);
216
217         f = fopen(fn, "re");
218         if (!f)
219                 return -errno;
220
221         return read_full_stream(f, contents, size);
222 }
223
224 static int parse_env_file_internal(
225                 FILE *f,
226                 const char *fname,
227                 const char *newline,
228                 int (*push) (const char *filename, unsigned line,
229                              const char *key, char *value, void *userdata, int *n_pushed),
230                 void *userdata,
231                 int *n_pushed) {
232
233         _cleanup_free_ char *contents = NULL, *key = NULL;
234         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;
235         char *p, *value = NULL;
236         int r;
237         unsigned line = 1;
238
239         enum {
240                 PRE_KEY,
241                 KEY,
242                 PRE_VALUE,
243                 VALUE,
244                 VALUE_ESCAPE,
245                 SINGLE_QUOTE_VALUE,
246                 SINGLE_QUOTE_VALUE_ESCAPE,
247                 DOUBLE_QUOTE_VALUE,
248                 DOUBLE_QUOTE_VALUE_ESCAPE,
249                 COMMENT,
250                 COMMENT_ESCAPE
251         } state = PRE_KEY;
252
253         assert(newline);
254
255         if (f)
256                 r = read_full_stream(f, &contents, NULL);
257         else
258                 r = read_full_file(fname, &contents, NULL);
259         if (r < 0)
260                 return r;
261
262         for (p = contents; *p; p++) {
263                 char c = *p;
264
265                 switch (state) {
266
267                 case PRE_KEY:
268                         if (strchr(COMMENTS, c))
269                                 state = COMMENT;
270                         else if (!strchr(WHITESPACE, c)) {
271                                 state = KEY;
272                                 last_key_whitespace = (size_t) -1;
273
274                                 if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
275                                         r = -ENOMEM;
276                                         goto fail;
277                                 }
278
279                                 key[n_key++] = c;
280                         }
281                         break;
282
283                 case KEY:
284                         if (strchr(newline, c)) {
285                                 state = PRE_KEY;
286                                 line ++;
287                                 n_key = 0;
288                         } else if (c == '=') {
289                                 state = PRE_VALUE;
290                                 last_value_whitespace = (size_t) -1;
291                         } else {
292                                 if (!strchr(WHITESPACE, c))
293                                         last_key_whitespace = (size_t) -1;
294                                 else if (last_key_whitespace == (size_t) -1)
295                                          last_key_whitespace = n_key;
296
297                                 if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
298                                         r = -ENOMEM;
299                                         goto fail;
300                                 }
301
302                                 key[n_key++] = c;
303                         }
304
305                         break;
306
307                 case PRE_VALUE:
308                         if (strchr(newline, c)) {
309                                 state = PRE_KEY;
310                                 line ++;
311                                 key[n_key] = 0;
312
313                                 if (value)
314                                         value[n_value] = 0;
315
316                                 /* strip trailing whitespace from key */
317                                 if (last_key_whitespace != (size_t) -1)
318                                         key[last_key_whitespace] = 0;
319
320                                 r = push(fname, line, key, value, userdata, n_pushed);
321                                 if (r < 0)
322                                         goto fail;
323
324                                 n_key = 0;
325                                 value = NULL;
326                                 value_alloc = n_value = 0;
327
328                         } else if (c == '\'')
329                                 state = SINGLE_QUOTE_VALUE;
330                         else if (c == '\"')
331                                 state = DOUBLE_QUOTE_VALUE;
332                         else if (c == '\\')
333                                 state = VALUE_ESCAPE;
334                         else if (!strchr(WHITESPACE, c)) {
335                                 state = VALUE;
336
337                                 if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
338                                         r = -ENOMEM;
339                                         goto fail;
340                                 }
341
342                                 value[n_value++] = c;
343                         }
344
345                         break;
346
347                 case VALUE:
348                         if (strchr(newline, c)) {
349                                 state = PRE_KEY;
350                                 line ++;
351
352                                 key[n_key] = 0;
353
354                                 if (value)
355                                         value[n_value] = 0;
356
357                                 /* Chomp off trailing whitespace from value */
358                                 if (last_value_whitespace != (size_t) -1)
359                                         value[last_value_whitespace] = 0;
360
361                                 /* strip trailing whitespace from key */
362                                 if (last_key_whitespace != (size_t) -1)
363                                         key[last_key_whitespace] = 0;
364
365                                 r = push(fname, line, key, value, userdata, n_pushed);
366                                 if (r < 0)
367                                         goto fail;
368
369                                 n_key = 0;
370                                 value = NULL;
371                                 value_alloc = n_value = 0;
372
373                         } else if (c == '\\') {
374                                 state = VALUE_ESCAPE;
375                                 last_value_whitespace = (size_t) -1;
376                         } else {
377                                 if (!strchr(WHITESPACE, c))
378                                         last_value_whitespace = (size_t) -1;
379                                 else if (last_value_whitespace == (size_t) -1)
380                                         last_value_whitespace = n_value;
381
382                                 if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
383                                         r = -ENOMEM;
384                                         goto fail;
385                                 }
386
387                                 value[n_value++] = c;
388                         }
389
390                         break;
391
392                 case VALUE_ESCAPE:
393                         state = VALUE;
394
395                         if (!strchr(newline, c)) {
396                                 /* Escaped newlines we eat up entirely */
397                                 if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
398                                         r = -ENOMEM;
399                                         goto fail;
400                                 }
401
402                                 value[n_value++] = c;
403                         }
404                         break;
405
406                 case SINGLE_QUOTE_VALUE:
407                         if (c == '\'')
408                                 state = PRE_VALUE;
409                         else if (c == '\\')
410                                 state = SINGLE_QUOTE_VALUE_ESCAPE;
411                         else {
412                                 if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
413                                         r = -ENOMEM;
414                                         goto fail;
415                                 }
416
417                                 value[n_value++] = c;
418                         }
419
420                         break;
421
422                 case SINGLE_QUOTE_VALUE_ESCAPE:
423                         state = SINGLE_QUOTE_VALUE;
424
425                         if (!strchr(newline, c)) {
426                                 if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
427                                         r = -ENOMEM;
428                                         goto fail;
429                                 }
430
431                                 value[n_value++] = c;
432                         }
433                         break;
434
435                 case DOUBLE_QUOTE_VALUE:
436                         if (c == '\"')
437                                 state = PRE_VALUE;
438                         else if (c == '\\')
439                                 state = DOUBLE_QUOTE_VALUE_ESCAPE;
440                         else {
441                                 if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
442                                         r = -ENOMEM;
443                                         goto fail;
444                                 }
445
446                                 value[n_value++] = c;
447                         }
448
449                         break;
450
451                 case DOUBLE_QUOTE_VALUE_ESCAPE:
452                         state = DOUBLE_QUOTE_VALUE;
453
454                         if (!strchr(newline, c)) {
455                                 if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) {
456                                         r = -ENOMEM;
457                                         goto fail;
458                                 }
459
460                                 value[n_value++] = c;
461                         }
462                         break;
463
464                 case COMMENT:
465                         if (c == '\\')
466                                 state = COMMENT_ESCAPE;
467                         else if (strchr(newline, c)) {
468                                 state = PRE_KEY;
469                                 line ++;
470                         }
471                         break;
472
473                 case COMMENT_ESCAPE:
474                         state = COMMENT;
475                         break;
476                 }
477         }
478
479         if (state == PRE_VALUE ||
480             state == VALUE ||
481             state == VALUE_ESCAPE ||
482             state == SINGLE_QUOTE_VALUE ||
483             state == SINGLE_QUOTE_VALUE_ESCAPE ||
484             state == DOUBLE_QUOTE_VALUE ||
485             state == DOUBLE_QUOTE_VALUE_ESCAPE) {
486
487                 key[n_key] = 0;
488
489                 if (value)
490                         value[n_value] = 0;
491
492                 if (state == VALUE)
493                         if (last_value_whitespace != (size_t) -1)
494                                 value[last_value_whitespace] = 0;
495
496                 /* strip trailing whitespace from key */
497                 if (last_key_whitespace != (size_t) -1)
498                         key[last_key_whitespace] = 0;
499
500                 r = push(fname, line, key, value, userdata, n_pushed);
501                 if (r < 0)
502                         goto fail;
503         }
504
505         return 0;
506
507 fail:
508         free(value);
509         return r;
510 }
511
512 static int parse_env_file_push(
513                 const char *filename, unsigned line,
514                 const char *key, char *value,
515                 void *userdata,
516                 int *n_pushed) {
517
518         const char *k;
519         va_list aq, *ap = userdata;
520
521         if (!utf8_is_valid(key)) {
522                 _cleanup_free_ char *p;
523
524                 p = utf8_escape_invalid(key);
525                 log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p);
526                 return -EINVAL;
527         }
528
529         if (value && !utf8_is_valid(value)) {
530                 _cleanup_free_ char *p;
531
532                 p = utf8_escape_invalid(value);
533                 log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p);
534                 return -EINVAL;
535         }
536
537         va_copy(aq, *ap);
538
539         while ((k = va_arg(aq, const char *))) {
540                 char **v;
541
542                 v = va_arg(aq, char **);
543
544                 if (streq(key, k)) {
545                         va_end(aq);
546                         free(*v);
547                         *v = value;
548
549                         if (n_pushed)
550                                 (*n_pushed)++;
551
552                         return 1;
553                 }
554         }
555
556         va_end(aq);
557         free(value);
558
559         return 0;
560 }
561
562 int parse_env_file(
563                 const char *fname,
564                 const char *newline, ...) {
565
566         va_list ap;
567         int r, n_pushed = 0;
568
569         if (!newline)
570                 newline = NEWLINE;
571
572         va_start(ap, newline);
573         r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed);
574         va_end(ap);
575
576         return r < 0 ? r : n_pushed;
577 }
578
579 static int load_env_file_push(
580                 const char *filename, unsigned line,
581                 const char *key, char *value,
582                 void *userdata,
583                 int *n_pushed) {
584         char ***m = userdata;
585         char *p;
586         int r;
587
588         if (!utf8_is_valid(key)) {
589                 _cleanup_free_ char *t = utf8_escape_invalid(key);
590
591                 log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t);
592                 return -EINVAL;
593         }
594
595         if (value && !utf8_is_valid(value)) {
596                 _cleanup_free_ char *t = utf8_escape_invalid(value);
597
598                 log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t);
599                 return -EINVAL;
600         }
601
602         p = strjoin(key, "=", strempty(value), NULL);
603         if (!p)
604                 return -ENOMEM;
605
606         r = strv_consume(m, p);
607         if (r < 0)
608                 return r;
609
610         if (n_pushed)
611                 (*n_pushed)++;
612
613         free(value);
614         return 0;
615 }
616
617 int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) {
618         char **m = NULL;
619         int r;
620
621         if (!newline)
622                 newline = NEWLINE;
623
624         r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m, NULL);
625         if (r < 0) {
626                 strv_free(m);
627                 return r;
628         }
629
630         *rl = m;
631         return 0;
632 }
633
634 /// UNNEDED by elogind
635 #if 0
636 static int load_env_file_push_pairs(
637                 const char *filename, unsigned line,
638                 const char *key, char *value,
639                 void *userdata,
640                 int *n_pushed) {
641         char ***m = userdata;
642         int r;
643
644         if (!utf8_is_valid(key)) {
645                 _cleanup_free_ char *t = utf8_escape_invalid(key);
646
647                 log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t);
648                 return -EINVAL;
649         }
650
651         if (value && !utf8_is_valid(value)) {
652                 _cleanup_free_ char *t = utf8_escape_invalid(value);
653
654                 log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t);
655                 return -EINVAL;
656         }
657
658         r = strv_extend(m, key);
659         if (r < 0)
660                 return -ENOMEM;
661
662         if (!value) {
663                 r = strv_extend(m, "");
664                 if (r < 0)
665                         return -ENOMEM;
666         } else {
667                 r = strv_push(m, value);
668                 if (r < 0)
669                         return r;
670         }
671
672         if (n_pushed)
673                 (*n_pushed)++;
674
675         return 0;
676 }
677
678 int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) {
679         char **m = NULL;
680         int r;
681
682         if (!newline)
683                 newline = NEWLINE;
684
685         r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL);
686         if (r < 0) {
687                 strv_free(m);
688                 return r;
689         }
690
691         *rl = m;
692         return 0;
693 }
694 #endif // 0
695
696 static void write_env_var(FILE *f, const char *v) {
697         const char *p;
698
699         p = strchr(v, '=');
700         if (!p) {
701                 /* Fallback */
702                 fputs(v, f);
703                 fputc('\n', f);
704                 return;
705         }
706
707         p++;
708         fwrite(v, 1, p-v, f);
709
710         if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
711                 fputc('\"', f);
712
713                 for (; *p; p++) {
714                         if (strchr(SHELL_NEED_ESCAPE, *p))
715                                 fputc('\\', f);
716
717                         fputc(*p, f);
718                 }
719
720                 fputc('\"', f);
721         } else
722                 fputs(p, f);
723
724         fputc('\n', f);
725 }
726
727 int write_env_file(const char *fname, char **l) {
728         _cleanup_fclose_ FILE *f = NULL;
729         _cleanup_free_ char *p = NULL;
730         char **i;
731         int r;
732
733         assert(fname);
734
735         r = fopen_temporary(fname, &f, &p);
736         if (r < 0)
737                 return r;
738
739         fchmod_umask(fileno(f), 0644);
740
741         STRV_FOREACH(i, l)
742                 write_env_var(f, *i);
743
744         r = fflush_and_check(f);
745         if (r >= 0) {
746                 if (rename(p, fname) >= 0)
747                         return 0;
748
749                 r = -errno;
750         }
751
752         unlink(p);
753         return r;
754 }
755
756 /// UNNEEDED by elogind
757 #if 0
758 int executable_is_script(const char *path, char **interpreter) {
759         int r;
760         _cleanup_free_ char *line = NULL;
761         int len;
762         char *ans;
763
764         assert(path);
765
766         r = read_one_line_file(path, &line);
767         if (r < 0)
768                 return r;
769
770         if (!startswith(line, "#!"))
771                 return 0;
772
773         ans = strstrip(line + 2);
774         len = strcspn(ans, " \t");
775
776         if (len == 0)
777                 return 0;
778
779         ans = strndup(ans, len);
780         if (!ans)
781                 return -ENOMEM;
782
783         *interpreter = ans;
784         return 1;
785 }
786 #endif // 0
787
788 /**
789  * Retrieve one field from a file like /proc/self/status.  pattern
790  * should start with '\n' and end with a ':'. Whitespace and zeros
791  * after the ':' will be skipped. field must be freed afterwards.
792  */
793 int get_status_field(const char *filename, const char *pattern, char **field) {
794         _cleanup_free_ char *status = NULL;
795         char *t;
796         size_t len;
797         int r;
798
799         assert(filename);
800         assert(pattern);
801         assert(field);
802
803         r = read_full_file(filename, &status, NULL);
804         if (r < 0)
805                 return r;
806
807         t = strstr(status, pattern);
808         if (!t)
809                 return -ENOENT;
810
811         t += strlen(pattern);
812         if (*t) {
813                 t += strspn(t, " \t");
814
815                 /* Also skip zeros, because when this is used for
816                  * capabilities, we don't want the zeros. This way the
817                  * same capability set always maps to the same string,
818                  * irrespective of the total capability set size. For
819                  * other numbers it shouldn't matter. */
820                 t += strspn(t, "0");
821                 /* Back off one char if there's nothing but whitespace
822                    and zeros */
823                 if (!*t || isspace(*t))
824                         t --;
825         }
826
827         len = strcspn(t, WHITESPACE);
828
829         *field = strndup(t, len);
830         if (!*field)
831                 return -ENOMEM;
832
833         return 0;
834 }