chiark / gitweb /
fileio:parse_env_file_internal() fix environment file parsing
[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 (startswith(p, "export "))
213                                 p+=6;
214                         else if (strchr(COMMENTS, c))
215                                 state = COMMENT;
216                         else if (!strchr(WHITESPACE, c)) {
217                                 state = KEY;
218                                 if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
219                                         r = -ENOMEM;
220                                         goto fail;
221                                 }
222
223                                 key[n_key++] = c;
224                         }
225                         break;
226
227                 case KEY:
228                         if (strchr(newline, c)) {
229                                 state = PRE_KEY;
230                                 n_key = 0;
231                         } else if (strchr(WHITESPACE, c))
232                                 state = PRE_EQUAL;
233                         else if (c == '=')
234                                 state = PRE_VALUE;
235                         else {
236                                 if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
237                                         r = -ENOMEM;
238                                         goto fail;
239                                 }
240
241                                 key[n_key++] = c;
242                         }
243
244                         break;
245
246                 case PRE_EQUAL:
247                         if (strchr(newline, c)) {
248                                 state = PRE_KEY;
249                                 n_key = 0;
250                         } else if (c == '=')
251                                 state = PRE_VALUE;
252                         else if (!strchr(WHITESPACE, c)) {
253                                 n_key = 0;
254                                 state = COMMENT;
255                         }
256
257                         break;
258
259                 case PRE_VALUE:
260                         if (strchr(newline, c) || strchr(COMMENTS, c)) {
261                                 state = PRE_KEY;
262                                 key[n_key] = 0;
263
264                                 if (value)
265                                         value[n_value] = 0;
266
267                                 r = push(key, value, userdata);
268                                 if (r < 0)
269                                         goto fail;
270
271                                 n_key = 0;
272                                 value = NULL;
273                                 value_alloc = n_value = 0;
274                         } else if (c == '\'')
275                                 state = SINGLE_QUOTE_VALUE;
276                         else if (c == '\"')
277                                 state = DOUBLE_QUOTE_VALUE;
278                         else if (c == '\\')
279                                 state = VALUE_ESCAPE;
280                         else if (!strchr(WHITESPACE, c)) {
281                                 state = VALUE;
282
283                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
284                                         r = -ENOMEM;
285                                         goto fail;
286                                 }
287
288                                 value[n_value++] = c;
289                         }
290
291                         break;
292
293                 case VALUE:
294                         if (strchr(newline, c)) {
295                                 state = PRE_KEY;
296                                 key[n_key] = 0;
297
298                                 if (value)
299                                         value[n_value] = 0;
300
301                                 /* Chomp off trailing whitespace */
302                                 if (last_whitespace != (size_t) -1)
303                                         value[last_whitespace] = 0;
304
305                                 r = push(key, value, userdata);
306                                 if (r < 0)
307                                         goto fail;
308
309                                 n_key = 0;
310                                 value = NULL;
311                                 value_alloc = n_value = 0;
312                         } else if (c == '\\') {
313                                 state = VALUE_ESCAPE;
314                                 last_whitespace = (size_t) -1;
315                         } else {
316                                 if (!strchr(WHITESPACE, c))
317                                         last_whitespace = (size_t) -1;
318                                 else if (last_whitespace == (size_t) -1)
319                                         last_whitespace = n_value;
320
321                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
322                                         r = -ENOMEM;
323                                         goto fail;
324                                 }
325
326                                 value[n_value++] = c;
327                         }
328
329                         break;
330
331                 case VALUE_ESCAPE:
332                         state = VALUE;
333
334                         if (!strchr(newline, c)) {
335                                 /* Escaped newlines we eat up entirely */
336                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
337                                         r = -ENOMEM;
338                                         goto fail;
339                                 }
340
341                                 value[n_value++] = c;
342                         }
343                         break;
344
345                 case SINGLE_QUOTE_VALUE:
346                         if (c == '\'')
347                                 state = PRE_VALUE;
348                         else if (c == '\\')
349                                 state = SINGLE_QUOTE_VALUE_ESCAPE;
350                         else {
351                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
352                                         r = -ENOMEM;
353                                         goto fail;
354                                 }
355
356                                 value[n_value++] = c;
357                         }
358
359                         break;
360
361                 case SINGLE_QUOTE_VALUE_ESCAPE:
362                         state = SINGLE_QUOTE_VALUE;
363
364                         if (!strchr(newline, c)) {
365                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
366                                         r = -ENOMEM;
367                                         goto fail;
368                                 }
369
370                                 value[n_value++] = c;
371                         }
372                         break;
373
374                 case DOUBLE_QUOTE_VALUE:
375                         if (c == '\"')
376                                 state = PRE_VALUE;
377                         else if (c == '\\')
378                                 state = DOUBLE_QUOTE_VALUE_ESCAPE;
379                         else {
380                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
381                                         r = -ENOMEM;
382                                         goto fail;
383                                 }
384
385                                 value[n_value++] = c;
386                         }
387
388                         break;
389
390                 case DOUBLE_QUOTE_VALUE_ESCAPE:
391                         state = DOUBLE_QUOTE_VALUE;
392
393                         if (!strchr(newline, c)) {
394                                 if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
395                                         r = -ENOMEM;
396                                         goto fail;
397                                 }
398
399                                 value[n_value++] = c;
400                         }
401                         break;
402
403                 case COMMENT:
404                         if (c == '\\')
405                                 state = COMMENT_ESCAPE;
406                         else if (strchr(newline, c))
407                                 state = PRE_KEY;
408                         break;
409
410                 case COMMENT_ESCAPE:
411                         state = COMMENT;
412                         break;
413                 }
414         }
415
416         if (state == PRE_VALUE ||
417             state == VALUE ||
418             state == VALUE_ESCAPE ||
419             state == SINGLE_QUOTE_VALUE ||
420             state == SINGLE_QUOTE_VALUE_ESCAPE ||
421             state == DOUBLE_QUOTE_VALUE ||
422             state == DOUBLE_QUOTE_VALUE_ESCAPE) {
423
424                 key[n_key] = 0;
425
426                 if (value)
427                         value[n_value] = 0;
428
429                 r = push(key, value, userdata);
430                 if (r < 0)
431                         goto fail;
432         }
433
434         return 0;
435
436 fail:
437         free(value);
438         return r;
439 }
440
441 static int parse_env_file_push(const char *key, char *value, void *userdata) {
442         const char *k;
443         va_list* ap = (va_list*) userdata;
444         va_list aq;
445
446         va_copy(aq, *ap);
447
448         while ((k = va_arg(aq, const char *))) {
449                 char **v;
450
451                 v = va_arg(aq, char **);
452
453                 if (streq(key, k)) {
454                         va_end(aq);
455                         free(*v);
456                         *v = value;
457                         return 1;
458                 }
459         }
460
461         va_end(aq);
462
463         free(value);
464         return 0;
465 }
466
467 int parse_env_file(
468                 const char *fname,
469                 const char *newline, ...) {
470
471         va_list ap;
472         int r;
473
474         if (!newline)
475                 newline = NEWLINE;
476
477         va_start(ap, newline);
478         r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap);
479         va_end(ap);
480
481         return r;
482 }
483
484 static int load_env_file_push(const char *key, char *value, void *userdata) {
485         char ***m = userdata;
486         char *p;
487         int r;
488
489         p = strjoin(key, "=", strempty(value), NULL);
490         if (!p)
491                 return -ENOMEM;
492
493         r = strv_push(m, p);
494         if (r < 0) {
495                 free(p);
496                 return r;
497         }
498
499         free(value);
500         return 0;
501 }
502
503 int load_env_file(const char *fname, const char *newline, char ***rl) {
504         char **m = NULL;
505         int r;
506
507         if (!newline)
508                 newline = NEWLINE;
509
510         r = parse_env_file_internal(fname, newline, load_env_file_push, &m);
511         if (r < 0) {
512                 strv_free(m);
513                 return r;
514         }
515
516         *rl = m;
517         return 0;
518 }
519
520 static void write_env_var(FILE *f, const char *v) {
521         const char *p;
522
523         p = strchr(v, '=');
524         if (!p) {
525                 /* Fallback */
526                 fputs(v, f);
527                 fputc('\n', f);
528                 return;
529         }
530
531         p++;
532         fwrite(v, 1, p-v, f);
533
534         if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) {
535                 fputc('\"', f);
536
537                 for (; *p; p++) {
538                         if (strchr("\'\"\\`$", *p))
539                                 fputc('\\', f);
540
541                         fputc(*p, f);
542                 }
543
544                 fputc('\"', f);
545         } else
546                 fputs(p, f);
547
548         fputc('\n', f);
549 }
550
551 int write_env_file(const char *fname, char **l) {
552         char **i;
553         char _cleanup_free_ *p = NULL;
554         FILE _cleanup_fclose_ *f = NULL;
555         int r;
556
557         r = fopen_temporary(fname, &f, &p);
558         if (r < 0)
559                 return r;
560
561         fchmod_umask(fileno(f), 0644);
562
563         errno = 0;
564         STRV_FOREACH(i, l)
565                 write_env_var(f, *i);
566
567         fflush(f);
568
569         if (ferror(f))
570                 r = errno ? -errno : -EIO;
571         else {
572                 if (rename(p, fname) < 0)
573                         r = -errno;
574                 else
575                         r = 0;
576         }
577
578         if (r < 0)
579                 unlink(p);
580
581         return r;
582 }