X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Ffileio.c;h=cbb40c237998438fac7ef27234bcb134866c44dc;hp=4e2b4442db1fb8756ff52cd8cdbf0e2098da848d;hb=9f1c19405a1ccaf59dcc8c32c13a1619541189ad;hpb=69ab80881552d5f79ca95f6b3be48ad122ab1ec2 diff --git a/src/shared/fileio.c b/src/shared/fileio.c index 4e2b4442d..cbb40c237 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -20,13 +20,19 @@ ***/ #include +#include #include "fileio.h" #include "util.h" #include "strv.h" #include "utf8.h" +#include "ctype.h" + +int write_string_stream(FILE *f, const char *line) { + assert(f); + assert(line); -int write_string_to_file(FILE *f, const char *line) { errno = 0; + fputs(line, f); if (!endswith(line, "\n")) fputc('\n', f); @@ -49,7 +55,7 @@ int write_string_file(const char *fn, const char *line) { if (!f) return -errno; - return write_string_to_file(f, line); + return write_string_stream(f, line); } int write_string_file_atomic(const char *fn, const char *line) { @@ -116,22 +122,37 @@ int read_one_line_file(const char *fn, char **line) { return 0; } -int read_full_file(const char *fn, char **contents, size_t *size) { - _cleanup_fclose_ FILE *f = NULL; +ssize_t sendfile_full(int out_fd, const char *fn) { + _cleanup_fclose_ FILE *f; + struct stat st; + int r; + ssize_t s; + size_t n, l; _cleanup_free_ char *buf = NULL; - struct stat st; + assert(out_fd > 0); assert(fn); - assert(contents); f = fopen(fn, "re"); if (!f) return -errno; - if (fstat(fileno(f), &st) < 0) + r = fstat(fileno(f), &st); + if (r < 0) return -errno; + s = sendfile(out_fd, fileno(f), NULL, st.st_size); + if (s < 0) + if (errno == EINVAL || errno == ENOSYS) { + /* continue below */ + } else + return -errno; + else + return s; + + /* sendfile() failed, fall back to read/write */ + /* Safety check */ if (st.st_size > 4*1024*1024) return -E2BIG; @@ -139,6 +160,66 @@ int read_full_file(const char *fn, char **contents, size_t *size) { n = st.st_size > 0 ? st.st_size : LINE_MAX; l = 0; + while (true) { + char *t; + size_t k; + + t = realloc(buf, n); + 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; + } + + r = write(out_fd, buf, l); + if (r < 0) + return -errno; + + return (ssize_t) l; +} + +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; @@ -167,7 +248,7 @@ int read_full_file(const char *fn, char **contents, size_t *size) { buf[l] = 0; *contents = buf; - buf = NULL; + buf = NULL; /* do not free */ if (size) *size = l; @@ -175,7 +256,21 @@ int read_full_file(const char *fn, char **contents, size_t *size) { 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, @@ -202,10 +297,12 @@ static int parse_env_file_internal( COMMENT_ESCAPE } state = PRE_KEY; - assert(fname); assert(newline); - r = read_full_file(fname, &contents, NULL); + if (f) + r = read_full_stream(f, &contents, NULL); + else + r = read_full_file(fname, &contents, NULL); if (r < 0) return r; @@ -221,7 +318,7 @@ static int parse_env_file_internal( state = KEY; last_key_whitespace = (size_t) -1; - if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { + if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { r = -ENOMEM; goto fail; } @@ -244,7 +341,7 @@ static int parse_env_file_internal( else if (last_key_whitespace == (size_t) -1) last_key_whitespace = n_key; - if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { + if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { r = -ENOMEM; goto fail; } @@ -284,7 +381,7 @@ static int parse_env_file_internal( else if (!strchr(WHITESPACE, c)) { state = VALUE; - if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; goto fail; } @@ -329,7 +426,7 @@ static int parse_env_file_internal( else if (last_value_whitespace == (size_t) -1) last_value_whitespace = n_value; - if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; goto fail; } @@ -344,7 +441,7 @@ static int parse_env_file_internal( if (!strchr(newline, c)) { /* Escaped newlines we eat up entirely */ - if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; goto fail; } @@ -359,7 +456,7 @@ static int parse_env_file_internal( else if (c == '\\') state = SINGLE_QUOTE_VALUE_ESCAPE; else { - if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; goto fail; } @@ -373,7 +470,7 @@ static int parse_env_file_internal( state = SINGLE_QUOTE_VALUE; if (!strchr(newline, c)) { - if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; goto fail; } @@ -388,7 +485,7 @@ static int parse_env_file_internal( else if (c == '\\') state = DOUBLE_QUOTE_VALUE_ESCAPE; else { - if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; goto fail; } @@ -402,7 +499,7 @@ static int parse_env_file_internal( state = DOUBLE_QUOTE_VALUE; if (!strchr(newline, c)) { - if (!greedy_realloc((void**) &value, &value_alloc, n_value+2)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; goto fail; } @@ -459,38 +556,48 @@ fail: return r; } -static int parse_env_file_push(const char *filename, unsigned line, - const char *key, char *value, void *userdata) { - assert(utf8_is_valid(key)); +static int parse_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata) { - if (value && !utf8_is_valid(value)) - /* FIXME: filter UTF-8 */ - log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.", - filename, line, key, value); - else { - const char *k; - va_list* ap = (va_list*) userdata; - va_list aq; + const char *k; + va_list aq, *ap = userdata; - va_copy(aq, *ap); + if (!utf8_is_valid(key)) { + _cleanup_free_ char *p; - while ((k = va_arg(aq, const char *))) { - char **v; + p = utf8_escape_invalid(key); + log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p); + return -EINVAL; + } - v = va_arg(aq, char **); + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *p; - if (streq(key, k)) { - va_end(aq); - free(*v); - *v = value; - return 1; - } - } + 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; - va_end(aq); + 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; } @@ -505,48 +612,109 @@ int parse_env_file( newline = NEWLINE; va_start(ap, newline); - r = parse_env_file_internal(fname, newline, parse_env_file_push, &ap); + r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap); va_end(ap); return r; } -static int load_env_file_push(const char *filename, unsigned line, - const char *key, char *value, void *userdata) { - assert(utf8_is_valid(key)); +static int load_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata) { + char ***m = userdata; + char *p; + int r; - if (value && !utf8_is_valid(value)) - /* FIXME: filter UTF-8 */ - log_error("%s:%u: invalid UTF-8 for key %s: '%s', ignoring.", - filename, line, key, value); - else { - char ***m = userdata; - char *p; - int r; + if (!utf8_is_valid(key)) { + _cleanup_free_ char *t = utf8_escape_invalid(key); - p = strjoin(key, "=", strempty(value), NULL); - if (!p) - return -ENOMEM; + log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); + return -EINVAL; + } - r = strv_push(m, p); - if (r < 0) { - free(p); - return r; - } + 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; + free(value); return 0; } -int load_env_file(const char *fname, const char *newline, char ***rl) { +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(fname, newline, load_env_file_push, &m); + r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m); + 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) { + 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; + } + + 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); if (r < 0) { strv_free(m); return r; @@ -570,11 +738,11 @@ static void write_env_var(FILE *f, const char *v) { p++; fwrite(v, 1, p-v, f); - if (string_has_cc(p) || chars_intersect(p, WHITESPACE "\'\"\\`$")) { + if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) { fputc('\"', f); for (; *p; p++) { - if (strchr("\'\"\\`$", *p)) + if (strchr(SHELL_NEED_ESCAPE, *p)) fputc('\\', f); fputc(*p, f); @@ -588,41 +756,37 @@ static void write_env_var(FILE *f, const char *v) { } int write_env_file(const char *fname, char **l) { - char **i; - _cleanup_free_ char *p = NULL; _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); - errno = 0; STRV_FOREACH(i, l) write_env_var(f, *i); - fflush(f); + r = fflush_and_check(f); + if (r >= 0) { + if (rename(p, fname) >= 0) + return 0; - if (ferror(f)) - r = errno ? -errno : -EIO; - else { - if (rename(p, fname) < 0) - r = -errno; - else - r = 0; + r = -errno; } - if (r < 0) - unlink(p); - + unlink(p); return r; } int executable_is_script(const char *path, char **interpreter) { int r; - char _cleanup_free_ *line = NULL; + _cleanup_free_ char *line = NULL; int len; char *ans; @@ -650,9 +814,9 @@ int executable_is_script(const char *path, char **interpreter) { } /** - * Retrieve one field from a file like /proc/self/status. - * pattern should start with '\n' and end with ':'. Whitespace - * after ':' will be skipped. field must be freed afterwards. + * 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; @@ -661,6 +825,7 @@ int get_status_field(const char *filename, const char *pattern, char **field) { int r; assert(filename); + assert(pattern); assert(field); r = read_full_file(filename, &status, NULL); @@ -672,7 +837,20 @@ int get_status_field(const char *filename, const char *pattern, char **field) { return -ENOENT; t += strlen(pattern); - t += strspn(t, WHITESPACE); + 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);