fileio.c revision 768100efd57ffbbefe9beaa33d1dd5ecc7f69173
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers/***
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers This file is part of systemd.
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers Copyright 2010 Lennart Poettering
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers systemd is free software; you can redistribute it and/or modify it
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers under the terms of the GNU Lesser General Public License as published by
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers the Free Software Foundation; either version 2.1 of the License, or
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers (at your option) any later version.
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers systemd is distributed in the hope that it will be useful, but
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers WITHOUT ANY WARRANTY; without even the implied warranty of
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers Lesser General Public License for more details.
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers You should have received a copy of the GNU Lesser General Public License
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers along with systemd; If not, see <http://www.gnu.org/licenses/>.
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers***/
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers#include <unistd.h>
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers#include "fileio.h"
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers#include "util.h"
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers#include "strv.h"
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sieversint write_string_file(const char *fn, const char *line) {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers _cleanup_fclose_ FILE *f = NULL;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(fn);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(line);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers f = fopen(fn, "we");
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!f)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return -errno;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers errno = 0;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (fputs(line, f) < 0)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return errno ? -errno : -EIO;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!endswith(line, "\n"))
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers fputc('\n', f);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers fflush(f);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (ferror(f))
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return errno ? -errno : -EIO;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return 0;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers}
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sieversint write_string_file_atomic(const char *fn, const char *line) {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers _cleanup_fclose_ FILE *f = NULL;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers _cleanup_free_ char *p = NULL;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers int r;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(fn);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(line);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers r = fopen_temporary(fn, &f, &p);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (r < 0)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return r;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers fchmod_umask(fileno(f), 0644);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers errno = 0;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (fputs(line, f) < 0) {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers r = -errno;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers goto finish;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers }
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!endswith(line, "\n"))
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers fputc('\n', f);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers fflush(f);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (ferror(f))
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers r = errno ? -errno : -EIO;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers else {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (rename(p, fn) < 0)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers r = -errno;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers else
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers r = 0;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers }
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sieversfinish:
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (r < 0)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers unlink(p);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return r;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers}
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sieversint read_one_line_file(const char *fn, char **line) {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers _cleanup_fclose_ FILE *f = NULL;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers char t[LINE_MAX], *c;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(fn);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(line);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers f = fopen(fn, "re");
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!f)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return -errno;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!fgets(t, sizeof(t), f)) {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (ferror(f))
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return errno ? -errno : -EIO;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers t[0] = 0;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers }
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers c = strdup(t);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!c)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return -ENOMEM;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers truncate_nl(c);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers *line = c;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return 0;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers}
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sieversint read_full_file(const char *fn, char **contents, size_t *size) {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers _cleanup_fclose_ FILE *f = NULL;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers size_t n, l;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers _cleanup_free_ char *buf = NULL;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers struct stat st;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(fn);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers assert(contents);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers f = fopen(fn, "re");
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!f)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return -errno;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (fstat(fileno(f), &st) < 0)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return -errno;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers /* Safety check */
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (st.st_size > 4*1024*1024)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return -E2BIG;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers n = st.st_size > 0 ? st.st_size : LINE_MAX;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers l = 0;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers for (;;) {
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers char *t;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers size_t k;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers t = realloc(buf, n+1);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (!t)
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers return -ENOMEM;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers buf = t;
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers k = fread(buf + l, 1, n - l, f);
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers
c51d84dc09476d9c06b8aac726220bf3c7d62e8dKay Sievers if (k <= 0) {
if (ferror(f))
return -errno;
break;
}
l += k;
n *= 2;
/* Safety check */
if (n > 4*1024*1024)
return -E2BIG;
}
buf[l] = 0;
*contents = buf;
buf = NULL;
if (size)
*size = l;
return 0;
}
static int parse_env_file_internal(
const char *fname,
const char *newline,
int (*push) (const char *key, char *value, void *userdata),
void *userdata) {
_cleanup_free_ char *contents = NULL, *key = NULL;
size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_whitespace = (size_t) -1;
char *p, *value = NULL;
int r;
enum {
PRE_KEY,
KEY,
PRE_EQUAL,
PRE_VALUE,
VALUE,
VALUE_ESCAPE,
SINGLE_QUOTE_VALUE,
SINGLE_QUOTE_VALUE_ESCAPE,
DOUBLE_QUOTE_VALUE,
DOUBLE_QUOTE_VALUE_ESCAPE,
COMMENT,
COMMENT_ESCAPE
} state = PRE_KEY;
assert(fname);
assert(newline);
r = read_full_file(fname, &contents, NULL);
if (r < 0)
return r;
for (p = contents; *p; p++) {
char c = *p;
switch (state) {
case PRE_KEY:
if (strchr(COMMENTS, c))
state = COMMENT;
else if (!strchr(WHITESPACE, c)) {
state = KEY;
if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
r = -ENOMEM;
goto fail;
}
key[n_key++] = c;
}
break;
case KEY:
if (strchr(newline, c)) {
state = PRE_KEY;
n_key = 0;
} else if (strchr(WHITESPACE, c))
state = PRE_EQUAL;
else if (c == '=')
state = PRE_VALUE;
else {
if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) {
r = -ENOMEM;
goto fail;
}
key[n_key++] = c;
}
break;
case PRE_EQUAL:
if (strchr(newline, c)) {
state = PRE_KEY;
n_key = 0;
} else if (c == '=')
state = PRE_VALUE;
else if (!strchr(WHITESPACE, c)) {
n_key = 0;
state = COMMENT;
}
break;
case PRE_VALUE:
if (strchr(newline, c)) {
state = PRE_KEY;
key[n_key] = 0;
if (value)
value[n_value] = 0;
r = push(key, value, userdata);
if (r < 0)
goto fail;
n_key = 0;
value = NULL;
value_alloc = n_value = 0;
} else if (c == '\'')
state = SINGLE_QUOTE_VALUE;
else if (c == '\"')
state = DOUBLE_QUOTE_VALUE;
else if (c == '\\')
state = VALUE_ESCAPE;
else if (!strchr(WHITESPACE, c)) {
state = VALUE;
if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case VALUE:
if (strchr(newline, c)) {
state = PRE_KEY;
key[n_key] = 0;
if (value)
value[n_value] = 0;
/* Chomp off trailing whitespace */
if (last_whitespace != (size_t) -1)
value[last_whitespace] = 0;
r = push(key, value, userdata);
if (r < 0)
goto fail;
n_key = 0;
value = NULL;
value_alloc = n_value = 0;
} else if (c == '\\') {
state = VALUE_ESCAPE;
last_whitespace = (size_t) -1;
} else {
if (!strchr(WHITESPACE, c))
last_whitespace = (size_t) -1;
else if (last_whitespace == (size_t) -1)
last_whitespace = n_value;
if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case VALUE_ESCAPE:
state = VALUE;
if (!strchr(newline, c)) {
/* Escaped newlines we eat up entirely */
if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case SINGLE_QUOTE_VALUE:
if (c == '\'')
state = PRE_VALUE;
else if (c == '\\')
state = SINGLE_QUOTE_VALUE_ESCAPE;
else {
if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case SINGLE_QUOTE_VALUE_ESCAPE:
state = SINGLE_QUOTE_VALUE;
if (!strchr(newline, c)) {
if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case DOUBLE_QUOTE_VALUE:
if (c == '\"')
state = PRE_VALUE;
else if (c == '\\')
state = DOUBLE_QUOTE_VALUE_ESCAPE;
else {
if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case DOUBLE_QUOTE_VALUE_ESCAPE:
state = DOUBLE_QUOTE_VALUE;
if (!strchr(newline, c)) {
if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case COMMENT:
if (c == '\\')
state = COMMENT_ESCAPE;
else if (strchr(newline, c))
state = PRE_KEY;
break;
case COMMENT_ESCAPE:
state = COMMENT;
break;
}
}
if (state == PRE_VALUE ||
state == VALUE ||
state == VALUE_ESCAPE ||
state == SINGLE_QUOTE_VALUE ||
state == SINGLE_QUOTE_VALUE_ESCAPE ||
state == DOUBLE_QUOTE_VALUE ||
state == DOUBLE_QUOTE_VALUE_ESCAPE) {
key[n_key] = 0;
if (value)
value[n_value] = 0;
r = push(key, value, userdata);
if (r < 0)
goto fail;
}
return 0;
fail:
free(value);
return r;
}
static int parse_env_file_push(const char *key, char *value, void *userdata) {
const char *k;
va_list* ap = (va_list*) userdata;
va_list aq;
va_copy(aq, *ap);
while ((k = va_arg(aq, const char *))) {
char **v;
v = va_arg(aq, char **);
if (streq(key, k)) {
va_end(aq);
free(*v);
*v = value;
return 1;
}
}
va_end(aq);
free(value);
return 0;
}
int parse_env_file(
const char *fname,
const char *newline, ...) {
va_list ap;
int r;
if (!newline)
newline = NEWLINE;
va_start(ap, newline);
r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap);
va_end(ap);
return r;
}
static int load_env_file_push(const char *key, char *value, void *userdata) {
char ***m = userdata;
char *p;
int r;
p = strjoin(key, "=", strempty(value), NULL);
if (!p)
return -ENOMEM;
r = strv_push(m, p);
if (r < 0) {
free(p);
return r;
}
free(value);
return 0;
}
int load_env_file(const char *fname, const char *newline, char ***rl) {
char **m = NULL;
int r;
if (!newline)
newline = NEWLINE;
r = parse_env_file_internal(fname, newline, load_env_file_push, &m);
if (r < 0) {
strv_free(m);
return r;
}
*rl = m;
return 0;
}
static void write_env_var(FILE *f, const char *v) {
const char *p;
p = strchr(v, '=');
if (!p) {
/* Fallback */
fputs(v, f);
fputc('\n', f);
return;
}
p++;
fwrite(v, 1, p-v, f);
if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\")) {
fputc('\"', f);
for (; *p; p++) {
if (strchr("\'\"\\", *p))
fputc('\\', f);
fputc(*p, f);
}
fputc('\"', f);
} else
fputs(p, f);
fputc('\n', f);
}
int write_env_file(const char *fname, char **l) {
char **i;
char _cleanup_free_ *p = NULL;
FILE _cleanup_fclose_ *f = NULL;
int r;
r = fopen_temporary(fname, &f, &p);
if (r < 0)
return r;
fchmod_umask(fileno(f), 0644);
errno = 0;
STRV_FOREACH(i, l)
write_env_var(f, *i);
fflush(f);
if (ferror(f)) {
if (errno > 0)
r = -errno;
else
r = -EIO;
} else {
if (rename(p, fname) < 0)
r = -errno;
else
r = 0;
}
if (r < 0)
unlink(p);
return r;
}