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