X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Ffileio.c;h=cbb40c237998438fac7ef27234bcb134866c44dc;hp=1c7d4851303af7ba9caae54809dc83207c8d55a5;hb=0ce5a80601597fe4d1a715a8f70ce8d5ccaa2d86;hpb=8333c77edf8fd1654cd96f3f6ee0f078dd64b58b diff --git a/src/shared/fileio.c b/src/shared/fileio.c index 1c7d48513..cbb40c237 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -20,24 +20,20 @@ ***/ #include +#include #include "fileio.h" #include "util.h" #include "strv.h" +#include "utf8.h" +#include "ctype.h" -int write_one_line_file(const char *fn, const char *line) { - _cleanup_fclose_ FILE *f = NULL; - - assert(fn); +int write_string_stream(FILE *f, const char *line) { + assert(f); assert(line); - f = fopen(fn, "we"); - if (!f) - return -errno; - errno = 0; - if (fputs(line, f) < 0) - return errno ? -errno : -EIO; + fputs(line, f); if (!endswith(line, "\n")) fputc('\n', f); @@ -49,7 +45,20 @@ int write_one_line_file(const char *fn, const char *line) { return 0; } -int write_one_line_file_atomic(const char *fn, const char *line) { +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_atomic(const char *fn, const char *line) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; int r; @@ -64,11 +73,7 @@ int write_one_line_file_atomic(const char *fn, const char *line) { fchmod_umask(fileno(f), 0644); errno = 0; - if (fputs(line, f) < 0) { - r = -errno; - goto finish; - } - + fputs(line, f); if (!endswith(line, "\n")) fputc('\n', f); @@ -83,7 +88,6 @@ int write_one_line_file_atomic(const char *fn, const char *line) { r = 0; } -finish: if (r < 0) unlink(p); @@ -118,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; @@ -141,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; @@ -169,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; @@ -177,206 +256,607 @@ int read_full_file(const char *fn, char **contents, size_t *size) { return 0; } -int parse_env_file( - const char *fname, - const char *separator, ...) { +int read_full_file(const char *fn, char **contents, size_t *size) { + _cleanup_fclose_ FILE *f = NULL; - int r = 0; - char *contents = NULL, *p; + assert(fn); + assert(contents); - assert(fname); - assert(separator); + f = fopen(fn, "re"); + if (!f) + return -errno; - r = read_full_file(fname, &contents, NULL); + 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), + void *userdata) { + + _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; - p = contents; - for (;;) { - const char *key = NULL; + for (p = contents; *p; p++) { + char c = *p; - p += strspn(p, separator); - p += strspn(p, WHITESPACE); + switch (state) { - if (!*p) + 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; - if (!strchr(COMMENTS, *p)) { - va_list ap; - char **value; + 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; + } - va_start(ap, separator); - while ((key = va_arg(ap, char *))) { - size_t n; - char *v; + key[n_key++] = c; + } - value = va_arg(ap, char **); + break; + + case PRE_VALUE: + if (strchr(newline, c)) { + state = PRE_KEY; + line ++; + key[n_key] = 0; + + if (value) + value[n_value] = 0; - n = strlen(key); - if (!strneq(p, key, n) || - p[n] != '=') - continue; + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata); + if (r < 0) + goto fail; - p += n + 1; - n = strcspn(p, separator); + n_key = 0; + value = NULL; + value_alloc = n_value = 0; - if (n >= 2 && - strchr(QUOTES, p[0]) && - p[n-1] == p[0]) - v = strndup(p+1, n-2); - else - v = strndup(p, n); + } 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 (!v) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { r = -ENOMEM; - va_end(ap); goto fail; } - if (v[0] == '\0') { - /* return empty value strings as NULL */ - free(v); - v = NULL; + 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); + 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; } - free(*value); - *value = v; + value[n_value++] = c; + } - p += n; + break; - r ++; - 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; } - va_end(ap); + 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; - if (!key) - p += strcspn(p, separator); + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata); + if (r < 0) + goto fail; } + return 0; + fail: - free(contents); + free(value); return r; } -int load_env_file(const char *fname, char ***rl) { +static int parse_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata) { - _cleanup_fclose_ FILE *f; - _cleanup_strv_free_ char **m = NULL; - _cleanup_free_ char *c = NULL; + const char *k; + va_list aq, *ap = userdata; - assert(fname); - assert(rl); + if (!utf8_is_valid(key)) { + _cleanup_free_ char *p; - /* This reads an environment file, but will not complain about - * any invalid assignments, that needs to be done by the - * caller */ + p = utf8_escape_invalid(key); + log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p); + return -EINVAL; + } - f = fopen(fname, "re"); - if (!f) - return -errno; + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *p; - while (!feof(f)) { - char l[LINE_MAX], *p, *cs, *b; + 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; + } - if (!fgets(l, sizeof(l), f)) { - if (ferror(f)) - return -errno; + va_copy(aq, *ap); - /* The previous line was a continuation line? - * Let's process it now, before we leave the - * loop */ - if (c) - goto process; + while ((k = va_arg(aq, const char *))) { + char **v; - break; - } + v = va_arg(aq, char **); - /* Is this a continuation line? If so, just append - * this to c, and go to next line right-away */ - cs = endswith(l, "\\\n"); - if (cs) { - *cs = '\0'; - b = strappend(c, l); - if (!b) - return -ENOMEM; - - free(c); - c = b; - continue; + if (streq(key, k)) { + va_end(aq); + free(*v); + *v = value; + return 1; } + } - /* If the previous line was a continuation line, - * append the current line to it */ - if (c) { - b = strappend(c, l); - if (!b) - return -ENOMEM; + va_end(aq); + free(value); - free(c); - c = b; - } + return 0; +} + +int parse_env_file( + const char *fname, + const char *newline, ...) { - process: - p = strstrip(c ? c : l); + va_list ap; + int r; - if (*p && !strchr(COMMENTS, *p)) { - _cleanup_free_ char *u; - int k; + if (!newline) + newline = NEWLINE; - u = normalize_env_assignment(p); - if (!u) - return -ENOMEM; + va_start(ap, newline); + r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap); + va_end(ap); - k = strv_extend(&m, u); - if (k < 0) - return -ENOMEM; - } + return r; +} - free(c); - c = NULL; +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 (!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; + + 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); + if (r < 0) { + strv_free(m); + return r; } *rl = m; - m = NULL; + 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; + } + + *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; - char _cleanup_free_ *p = NULL; - FILE _cleanup_fclose_ *f = NULL; 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) { - fputs(*i, f); - fputc('\n', f); - } + 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)) { - if (errno > 0) - r = -errno; - else - r = -EIO; - } else { - if (rename(p, fname) < 0) - r = -errno; - else - r = 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) - unlink(p); + return r; - 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; }