chiark / gitweb /
fileio: don't check for errors more often than really necessary
[elogind.git] / src / shared / 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 #include "fileio.h"
24 #include "util.h"
25 #include "strv.h"
26
27 int write_string_file(const char *fn, const char *line) {
28         _cleanup_fclose_ FILE *f = NULL;
29
30         assert(fn);
31         assert(line);
32
33         f = fopen(fn, "we");
34         if (!f)
35                 return -errno;
36
37         errno = 0;
38         fputs(line, f);
39         if (!endswith(line, "\n"))
40                 fputc('\n', f);
41
42         fflush(f);
43
44         if (ferror(f))
45                 return errno ? -errno : -EIO;
46
47         return 0;
48 }
49
50 int write_string_file_atomic(const char *fn, const char *line) {
51         _cleanup_fclose_ FILE *f = NULL;
52         _cleanup_free_ char *p = NULL;
53         int r;
54
55         assert(fn);
56         assert(line);
57
58         r = fopen_temporary(fn, &f, &p);
59         if (r < 0)
60                 return r;
61
62         fchmod_umask(fileno(f), 0644);
63
64         errno = 0;
65         fputs(line, f);
66         if (!endswith(line, "\n"))
67                 fputc('\n', f);
68
69         fflush(f);
70
71         if (ferror(f))
72                 r = errno ? -errno : -EIO;
73         else {
74                 if (rename(p, fn) < 0)
75                         r = -errno;
76                 else
77                         r = 0;
78         }
79
80         if (r < 0)
81                 unlink(p);
82
83         return r;
84 }
85
86 int read_one_line_file(const char *fn, char **line) {
87         _cleanup_fclose_ FILE *f = NULL;
88         char t[LINE_MAX], *c;
89
90         assert(fn);
91         assert(line);
92
93         f = fopen(fn, "re");
94         if (!f)
95                 return -errno;
96
97         if (!fgets(t, sizeof(t), f)) {
98
99                 if (ferror(f))
100                         return errno ? -errno : -EIO;
101
102                 t[0] = 0;
103         }
104
105         c = strdup(t);
106         if (!c)
107                 return -ENOMEM;
108         truncate_nl(c);
109
110         *line = c;
111         return 0;
112 }
113
114 int read_full_file(const char *fn, char **contents, size_t *size) {
115         _cleanup_fclose_ FILE *f = NULL;
116         size_t n, l;
117         _cleanup_free_ char *buf = NULL;
118         struct stat st;
119
120         assert(fn);
121         assert(contents);
122
123         f = fopen(fn, "re");
124         if (!f)
125                 return -errno;
126
127         if (fstat(fileno(f), &st) < 0)
128                 return -errno;
129
130         /* Safety check */
131         if (st.st_size > 4*1024*1024)
132                 return -E2BIG;
133
134         n = st.st_size > 0 ? st.st_size : LINE_MAX;
135         l = 0;
136
137         for (;;) {
138                 char *t;
139                 size_t k;
140
141                 t = realloc(buf, n+1);
142                 if (!t)
143                         return -ENOMEM;
144
145                 buf = t;
146                 k = fread(buf + l, 1, n - l, f);
147
148                 if (k <= 0) {
149                         if (ferror(f))
150                                 return -errno;
151
152                         break;
153                 }
154
155                 l += k;
156                 n *= 2;
157
158                 /* Safety check */
159                 if (n > 4*1024*1024)
160                         return -E2BIG;
161         }
162
163         buf[l] = 0;
164         *contents = buf;
165         buf = NULL;
166
167         if (size)
168                 *size = l;
169
170         return 0;
171 }
172
173 static int parse_env_file_internal(
174                 const char *fname,
175                 const char *newline,
176                 int (*push) (const char *key, char *value, void *userdata),
177                 void *userdata) {
178
179         _cleanup_free_ char *contents = NULL, *key = NULL;
180         size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_whitespace = (size_t) -1;
181         char *p, *value = NULL;
182         int r;
183
184         enum {
185                 PRE_KEY,
186                 KEY,
187                 PRE_EQUAL,
188                 PRE_VALUE,
189                 VALUE,
190                 VALUE_ESCAPE,
191                 SINGLE_QUOTE_VALUE,
192                 SINGLE_QUOTE_VALUE_ESCAPE,
193                 DOUBLE_QUOTE_VALUE,
194                 DOUBLE_QUOTE_VALUE_ESCAPE,
195                 COMMENT,
196                 COMMENT_ESCAPE
197         } state = PRE_KEY;
198
199         assert(fname);
200         assert(newline);
201
202         r = read_full_file(fname, &contents, NULL);
203         if (r < 0)
204                 return r;
205
206         for (p = contents; *p; p++) {
207                 char c = *p;
208
209                 switch (state) {
210
211                 case PRE_KEY:
212                         if (strchr(COMMENTS, c))
213                                 state = COMMENT;
214                         else if (!strchr(WHITESPACE, c)) {
215                                 state = KEY;
216                                 if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
217                                         r = -ENOMEM;
218                                         goto fail;
219                                 }
220
221                                 key[n_key++] = c;
222                         }
223                         break;
224
225                 case KEY:
226                         if (strchr(newline, c)) {
227                                 state = PRE_KEY;
228                                 n_key = 0;
229                         } else if (strchr(WHITESPACE, c))
230                                 state = PRE_EQUAL;
231                         else if (c == '=')
232                                 state = PRE_VALUE;
233                         else {
234                                 if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
235                                         r = -ENOMEM;
236                                         goto fail;
237                                 }
238
239                                 key[n_key++] = c;
240                         }
241
242                         break;
243
244                 case PRE_EQUAL:
245                         if (strchr(newline, c)) {
246                                 state = PRE_KEY;
247                                 n_key = 0;
248                         } else if (c == '=')
249                                 state = PRE_VALUE;
250                         else if (!strchr(WHITESPACE, c)) {
251                                 n_key = 0;
252                                 state = COMMENT;
253                         }
254
255                         break;
256
257                 case PRE_VALUE:
258                         if (strchr(newline, c)) {
259                                 state = PRE_KEY;
260                                 key[n_key] = 0;
261
262                                 if (value)
263                                         value[n_value] = 0;
264
265                                 r = push(key, value, userdata);
266                                 if (r < 0)
267                                         goto fail;
268
269                                 n_key = 0;
270                                 value = NULL;
271                                 value_alloc = n_value = 0;
272                         } else if (c == '\'')
273                                 state = SINGLE_QUOTE_VALUE;
274                         else if (c == '\"')
275                                 state = DOUBLE_QUOTE_VALUE;
276                         else if (c == '\\')
277                                 state = VALUE_ESCAPE;
278                         else if (!strchr(WHITESPACE, c)) {
279                                 state = VALUE;
280
281                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
282                                         r = -ENOMEM;
283                                         goto fail;
284                                 }
285
286                                 value[n_value++] = c;
287                         }
288
289                         break;
290
291                 case VALUE:
292                         if (strchr(newline, c)) {
293                                 state = PRE_KEY;
294                                 key[n_key] = 0;
295
296                                 if (value)
297                                         value[n_value] = 0;
298
299                                 /* Chomp off trailing whitespace */
300                                 if (last_whitespace != (size_t) -1)
301                                         value[last_whitespace] = 0;
302
303                                 r = push(key, value, userdata);
304                                 if (r < 0)
305                                         goto fail;
306
307                                 n_key = 0;
308                                 value = NULL;
309                                 value_alloc = n_value = 0;
310                         } else if (c == '\\') {
311                                 state = VALUE_ESCAPE;
312                                 last_whitespace = (size_t) -1;
313                         } else {
314                                 if (!strchr(WHITESPACE, c))
315                                         last_whitespace = (size_t) -1;
316                                 else if (last_whitespace == (size_t) -1)
317                                         last_whitespace = n_value;
318
319                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
320                                         r = -ENOMEM;
321                                         goto fail;
322                                 }
323
324                                 value[n_value++] = c;
325                         }
326
327                         break;
328
329                 case VALUE_ESCAPE:
330                         state = VALUE;
331
332                         if (!strchr(newline, c)) {
333                                 /* Escaped newlines we eat up entirely */
334                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
335                                         r = -ENOMEM;
336                                         goto fail;
337                                 }
338
339                                 value[n_value++] = c;
340                         }
341                         break;
342
343                 case SINGLE_QUOTE_VALUE:
344                         if (c == '\'')
345                                 state = PRE_VALUE;
346                         else if (c == '\\')
347                                 state = SINGLE_QUOTE_VALUE_ESCAPE;
348                         else {
349                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
350                                         r = -ENOMEM;
351                                         goto fail;
352                                 }
353
354                                 value[n_value++] = c;
355                         }
356
357                         break;
358
359                 case SINGLE_QUOTE_VALUE_ESCAPE:
360                         state = SINGLE_QUOTE_VALUE;
361
362                         if (!strchr(newline, c)) {
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                         break;
371
372                 case DOUBLE_QUOTE_VALUE:
373                         if (c == '\"')
374                                 state = PRE_VALUE;
375                         else if (c == '\\')
376                                 state = DOUBLE_QUOTE_VALUE_ESCAPE;
377                         else {
378                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
379                                         r = -ENOMEM;
380                                         goto fail;
381                                 }
382
383                                 value[n_value++] = c;
384                         }
385
386                         break;
387
388                 case DOUBLE_QUOTE_VALUE_ESCAPE:
389                         state = DOUBLE_QUOTE_VALUE;
390
391                         if (!strchr(newline, c)) {
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                         break;
400
401                 case COMMENT:
402                         if (c == '\\')
403                                 state = COMMENT_ESCAPE;
404                         else if (strchr(newline, c))
405                                 state = PRE_KEY;
406                         break;
407
408                 case COMMENT_ESCAPE:
409                         state = COMMENT;
410                         break;
411                 }
412         }
413
414         if (state == PRE_VALUE ||
415             state == VALUE ||
416             state == VALUE_ESCAPE ||
417             state == SINGLE_QUOTE_VALUE ||
418             state == SINGLE_QUOTE_VALUE_ESCAPE ||
419             state == DOUBLE_QUOTE_VALUE ||
420             state == DOUBLE_QUOTE_VALUE_ESCAPE) {
421
422                 key[n_key] = 0;
423
424                 if (value)
425                         value[n_value] = 0;
426
427                 r = push(key, value, userdata);
428                 if (r < 0)
429                         goto fail;
430         }
431
432         return 0;
433
434 fail:
435         free(value);
436         return r;
437 }
438
439 static int parse_env_file_push(const char *key, char *value, void *userdata) {
440         const char *k;
441         va_list* ap = (va_list*) userdata;
442         va_list aq;
443
444         va_copy(aq, *ap);
445
446         while ((k = va_arg(aq, const char *))) {
447                 char **v;
448
449                 v = va_arg(aq, char **);
450
451                 if (streq(key, k)) {
452                         va_end(aq);
453                         free(*v);
454                         *v = value;
455                         return 1;
456                 }
457         }
458
459         va_end(aq);
460
461         free(value);
462         return 0;
463 }
464
465 int parse_env_file(
466                 const char *fname,
467                 const char *newline, ...) {
468
469         va_list ap;
470         int r;
471
472         if (!newline)
473                 newline = NEWLINE;
474
475         va_start(ap, newline);
476         r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap);
477         va_end(ap);
478
479         return r;
480 }
481
482 static int load_env_file_push(const char *key, char *value, void *userdata) {
483         char ***m = userdata;
484         char *p;
485         int r;
486
487         p = strjoin(key, "=", strempty(value), NULL);
488         if (!p)
489                 return -ENOMEM;
490
491         r = strv_push(m, p);
492         if (r < 0) {
493                 free(p);
494                 return r;
495         }
496
497         free(value);
498         return 0;
499 }
500
501 int load_env_file(const char *fname, const char *newline, char ***rl) {
502         char **m = NULL;
503         int r;
504
505         if (!newline)
506                 newline = NEWLINE;
507
508         r = parse_env_file_internal(fname, newline, load_env_file_push, &m);
509         if (r < 0) {
510                 strv_free(m);
511                 return r;
512         }
513
514         *rl = m;
515         return 0;
516 }
517
518 static void write_env_var(FILE *f, const char *v) {
519         const char *p;
520
521         p = strchr(v, '=');
522         if (!p) {
523                 /* Fallback */
524                 fputs(v, f);
525                 fputc('\n', f);
526                 return;
527         }
528
529         p++;
530         fwrite(v, 1, p-v, f);
531
532         if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\")) {
533                 fputc('\"', f);
534
535                 for (; *p; p++) {
536                         if (strchr("\'\"\\", *p))
537                                 fputc('\\', f);
538
539                         fputc(*p, f);
540                 }
541
542                 fputc('\"', f);
543         } else
544                 fputs(p, f);
545
546         fputc('\n', f);
547 }
548
549 int write_env_file(const char *fname, char **l) {
550         char **i;
551         char _cleanup_free_ *p = NULL;
552         FILE _cleanup_fclose_ *f = NULL;
553         int r;
554
555         r = fopen_temporary(fname, &f, &p);
556         if (r < 0)
557                 return r;
558
559         fchmod_umask(fileno(f), 0644);
560
561         errno = 0;
562         STRV_FOREACH(i, l)
563                 write_env_var(f, *i);
564
565         fflush(f);
566
567         if (ferror(f))
568                 r = errno ? -errno : -EIO;
569         else {
570                 if (rename(p, fname) < 0)
571                         r = -errno;
572                 else
573                         r = 0;
574         }
575
576         if (r < 0)
577                 unlink(p);
578
579         return r;
580 }