Files
systemd/src/basic/fileio.c
T
Lennart Poettering 15dee3f07c networkd: be more defensive when writing to ipv4/ipv6 forwarding settings
1) never bother with setting the flag for loopback devices

2) if we fail to write the flag due to EROFS (which is likely to happen
   in containers where /proc/sys is read-only) or any other error, check
   if the flag already has the right value. If so, don't complain.

Closes #469
2015-07-06 13:41:51 +02:00

829 lines
23 KiB
C

/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <unistd.h>
#include "util.h"
#include "strv.h"
#include "utf8.h"
#include "ctype.h"
#include "fileio.h"
int write_string_stream(FILE *f, const char *line) {
assert(f);
assert(line);
errno = 0;
fputs(line, f);
if (!endswith(line, "\n"))
fputc('\n', f);
fflush(f);
if (ferror(f))
return errno ? -errno : -EIO;
return 0;
}
int write_string_file(const char *fn, const char *line) {
_cleanup_fclose_ FILE *f = NULL;
assert(fn);
assert(line);
f = fopen(fn, "we");
if (!f)
return -errno;
return write_string_stream(f, line);
}
int write_string_file_no_create(const char *fn, const char *line) {
_cleanup_fclose_ FILE *f = NULL;
int fd;
assert(fn);
assert(line);
/* We manually build our own version of fopen(..., "we") that
* works without O_CREAT */
fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0)
return -errno;
f = fdopen(fd, "we");
if (!f) {
safe_close(fd);
return -errno;
}
return write_string_stream(f, line);
}
int write_string_file_atomic(const char *fn, const char *line) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
int r;
assert(fn);
assert(line);
r = fopen_temporary(fn, &f, &p);
if (r < 0)
return r;
fchmod_umask(fileno(f), 0644);
r = write_string_stream(f, line);
if (r >= 0) {
if (rename(p, fn) < 0)
r = -errno;
}
if (r < 0)
unlink(p);
return r;
}
int read_one_line_file(const char *fn, char **line) {
_cleanup_fclose_ FILE *f = NULL;
char t[LINE_MAX], *c;
assert(fn);
assert(line);
f = fopen(fn, "re");
if (!f)
return -errno;
if (!fgets(t, sizeof(t), f)) {
if (ferror(f))
return errno ? -errno : -EIO;
t[0] = 0;
}
c = strdup(t);
if (!c)
return -ENOMEM;
truncate_nl(c);
*line = c;
return 0;
}
int verify_one_line_file(const char *fn, const char *line) {
_cleanup_free_ char *value = NULL;
int r;
r = read_one_line_file(fn, &value);
if (r < 0)
return r;
return streq(value, line);
}
int read_full_stream(FILE *f, char **contents, size_t *size) {
size_t n, l;
_cleanup_free_ char *buf = NULL;
struct stat st;
assert(f);
assert(contents);
if (fstat(fileno(f), &st) < 0)
return -errno;
n = LINE_MAX;
if (S_ISREG(st.st_mode)) {
/* Safety check */
if (st.st_size > 4*1024*1024)
return -E2BIG;
/* Start with the right file size, but be prepared for
* files from /proc which generally report a file size
* of 0 */
if (st.st_size > 0)
n = st.st_size;
}
l = 0;
for (;;) {
char *t;
size_t k;
t = realloc(buf, n+1);
if (!t)
return -ENOMEM;
buf = t;
k = fread(buf + l, 1, n - l, f);
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; /* do not free */
if (size)
*size = l;
return 0;
}
int read_full_file(const char *fn, char **contents, size_t *size) {
_cleanup_fclose_ FILE *f = NULL;
assert(fn);
assert(contents);
f = fopen(fn, "re");
if (!f)
return -errno;
return read_full_stream(f, contents, size);
}
static int parse_env_file_internal(
FILE *f,
const char *fname,
const char *newline,
int (*push) (const char *filename, unsigned line,
const char *key, char *value, void *userdata, int *n_pushed),
void *userdata,
int *n_pushed) {
_cleanup_free_ char *contents = NULL, *key = NULL;
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;
char *p, *value = NULL;
int r;
unsigned line = 1;
enum {
PRE_KEY,
KEY,
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(newline);
if (f)
r = read_full_stream(f, &contents, NULL);
else
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;
last_key_whitespace = (size_t) -1;
if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
r = -ENOMEM;
goto fail;
}
key[n_key++] = c;
}
break;
case KEY:
if (strchr(newline, c)) {
state = PRE_KEY;
line ++;
n_key = 0;
} else if (c == '=') {
state = PRE_VALUE;
last_value_whitespace = (size_t) -1;
} else {
if (!strchr(WHITESPACE, c))
last_key_whitespace = (size_t) -1;
else if (last_key_whitespace == (size_t) -1)
last_key_whitespace = n_key;
if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) {
r = -ENOMEM;
goto fail;
}
key[n_key++] = c;
}
break;
case PRE_VALUE:
if (strchr(newline, c)) {
state = PRE_KEY;
line ++;
key[n_key] = 0;
if (value)
value[n_value] = 0;
/* strip trailing whitespace from key */
if (last_key_whitespace != (size_t) -1)
key[last_key_whitespace] = 0;
r = push(fname, line, key, value, userdata, n_pushed);
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(value, value_alloc, n_value+2)) {
r = -ENOMEM;
goto fail;
}
value[n_value++] = c;
}
break;
case VALUE:
if (strchr(newline, c)) {
state = PRE_KEY;
line ++;
key[n_key] = 0;
if (value)
value[n_value] = 0;
/* Chomp off trailing whitespace from value */
if (last_value_whitespace != (size_t) -1)
value[last_value_whitespace] = 0;
/* strip trailing whitespace from key */
if (last_key_whitespace != (size_t) -1)
key[last_key_whitespace] = 0;
r = push(fname, line, key, value, userdata, n_pushed);
if (r < 0)
goto fail;
n_key = 0;
value = NULL;
value_alloc = n_value = 0;
} else if (c == '\\') {
state = VALUE_ESCAPE;
last_value_whitespace = (size_t) -1;
} else {
if (!strchr(WHITESPACE, c))
last_value_whitespace = (size_t) -1;
else if (last_value_whitespace == (size_t) -1)
last_value_whitespace = n_value;
if (!GREEDY_REALLOC(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(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(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(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(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(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;
line ++;
}
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;
if (state == VALUE)
if (last_value_whitespace != (size_t) -1)
value[last_value_whitespace] = 0;
/* strip trailing whitespace from key */
if (last_key_whitespace != (size_t) -1)
key[last_key_whitespace] = 0;
r = push(fname, line, key, value, userdata, n_pushed);
if (r < 0)
goto fail;
}
return 0;
fail:
free(value);
return r;
}
static int parse_env_file_push(
const char *filename, unsigned line,
const char *key, char *value,
void *userdata,
int *n_pushed) {
const char *k;
va_list aq, *ap = userdata;
if (!utf8_is_valid(key)) {
_cleanup_free_ char *p;
p = utf8_escape_invalid(key);
log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p);
return -EINVAL;
}
if (value && !utf8_is_valid(value)) {
_cleanup_free_ char *p;
p = utf8_escape_invalid(value);
log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p);
return -EINVAL;
}
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;
if (n_pushed)
(*n_pushed)++;
return 1;
}
}
va_end(aq);
free(value);
return 0;
}
int parse_env_file(
const char *fname,
const char *newline, ...) {
va_list ap;
int r, n_pushed = 0;
if (!newline)
newline = NEWLINE;
va_start(ap, newline);
r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed);
va_end(ap);
return r < 0 ? r : n_pushed;
}
static int load_env_file_push(
const char *filename, unsigned line,
const char *key, char *value,
void *userdata,
int *n_pushed) {
char ***m = userdata;
char *p;
int r;
if (!utf8_is_valid(key)) {
_cleanup_free_ char *t = utf8_escape_invalid(key);
log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t);
return -EINVAL;
}
if (value && !utf8_is_valid(value)) {
_cleanup_free_ char *t = utf8_escape_invalid(value);
log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t);
return -EINVAL;
}
p = strjoin(key, "=", strempty(value), NULL);
if (!p)
return -ENOMEM;
r = strv_consume(m, p);
if (r < 0)
return r;
if (n_pushed)
(*n_pushed)++;
free(value);
return 0;
}
int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) {
char **m = NULL;
int r;
if (!newline)
newline = NEWLINE;
r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m, NULL);
if (r < 0) {
strv_free(m);
return r;
}
*rl = m;
return 0;
}
static int load_env_file_push_pairs(
const char *filename, unsigned line,
const char *key, char *value,
void *userdata,
int *n_pushed) {
char ***m = userdata;
int r;
if (!utf8_is_valid(key)) {
_cleanup_free_ char *t = utf8_escape_invalid(key);
log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t);
return -EINVAL;
}
if (value && !utf8_is_valid(value)) {
_cleanup_free_ char *t = utf8_escape_invalid(value);
log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t);
return -EINVAL;
}
r = strv_extend(m, key);
if (r < 0)
return -ENOMEM;
if (!value) {
r = strv_extend(m, "");
if (r < 0)
return -ENOMEM;
} else {
r = strv_push(m, value);
if (r < 0)
return r;
}
if (n_pushed)
(*n_pushed)++;
return 0;
}
int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) {
char **m = NULL;
int r;
if (!newline)
newline = NEWLINE;
r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL);
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, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
fputc('\"', f);
for (; *p; p++) {
if (strchr(SHELL_NEED_ESCAPE, *p))
fputc('\\', f);
fputc(*p, f);
}
fputc('\"', f);
} else
fputs(p, f);
fputc('\n', f);
}
int write_env_file(const char *fname, char **l) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
char **i;
int r;
assert(fname);
r = fopen_temporary(fname, &f, &p);
if (r < 0)
return r;
fchmod_umask(fileno(f), 0644);
STRV_FOREACH(i, l)
write_env_var(f, *i);
r = fflush_and_check(f);
if (r >= 0) {
if (rename(p, fname) >= 0)
return 0;
r = -errno;
}
unlink(p);
return r;
}
int executable_is_script(const char *path, char **interpreter) {
int r;
_cleanup_free_ char *line = NULL;
int len;
char *ans;
assert(path);
r = read_one_line_file(path, &line);
if (r < 0)
return r;
if (!startswith(line, "#!"))
return 0;
ans = strstrip(line + 2);
len = strcspn(ans, " \t");
if (len == 0)
return 0;
ans = strndup(ans, len);
if (!ans)
return -ENOMEM;
*interpreter = ans;
return 1;
}
/**
* Retrieve one field from a file like /proc/self/status. pattern
* should start with '\n' and end with a ':'. Whitespace and zeros
* after the ':' will be skipped. field must be freed afterwards.
*/
int get_status_field(const char *filename, const char *pattern, char **field) {
_cleanup_free_ char *status = NULL;
char *t;
size_t len;
int r;
assert(filename);
assert(pattern);
assert(field);
r = read_full_file(filename, &status, NULL);
if (r < 0)
return r;
t = strstr(status, pattern);
if (!t)
return -ENOENT;
t += strlen(pattern);
if (*t) {
t += strspn(t, " \t");
/* Also skip zeros, because when this is used for
* capabilities, we don't want the zeros. This way the
* same capability set always maps to the same string,
* irrespective of the total capability set size. For
* other numbers it shouldn't matter. */
t += strspn(t, "0");
/* Back off one char if there's nothing but whitespace
and zeros */
if (!*t || isspace(*t))
t --;
}
len = strcspn(t, WHITESPACE);
*field = strndup(t, len);
if (!*field)
return -ENOMEM;
return 0;
}