X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Ffileio.c;h=ff6b1a7ed776db2c6b53a41b09fec1ed43466396;hp=2ccb9e7edc70453b6e027e758475881431a05357;hb=a60e9f7fc81558345c59bf203ace357223f208ef;hpb=768100efd57ffbbefe9beaa33d1dd5ecc7f69173 diff --git a/src/shared/fileio.c b/src/shared/fileio.c index 2ccb9e7ed..ff6b1a7ed 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -20,9 +20,30 @@ ***/ #include -#include "fileio.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; @@ -34,19 +55,29 @@ int write_string_file(const char *fn, const char *line) { if (!f) return -errno; - errno = 0; - if (fputs(line, f) < 0) - return errno ? -errno : -EIO; + return write_string_stream(f, line); +} - if (!endswith(line, "\n")) - fputc('\n', f); +int write_string_file_no_create(const char *fn, const char *line) { + _cleanup_fclose_ FILE *f = NULL; + int fd; - fflush(f); + assert(fn); + assert(line); - if (ferror(f)) - return errno ? -errno : -EIO; + /* 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; - return 0; + 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) { @@ -63,27 +94,12 @@ int write_string_file_atomic(const char *fn, const char *line) { fchmod_umask(fileno(f), 0644); - errno = 0; - if (fputs(line, f) < 0) { - r = -errno; - goto finish; - } - - if (!endswith(line, "\n")) - fputc('\n', f); - - fflush(f); - - if (ferror(f)) - r = errno ? -errno : -EIO; - else { + r = write_string_stream(f, line); + if (r >= 0) { if (rename(p, fn) < 0) r = -errno; - else - r = 0; } -finish: if (r < 0) unlink(p); @@ -118,29 +134,33 @@ 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; +int read_full_stream(FILE *f, char **contents, size_t *size) { size_t n, l; _cleanup_free_ char *buf = NULL; struct stat st; - assert(fn); + assert(f); assert(contents); - f = fopen(fn, "re"); - if (!f) - return -errno; - if (fstat(fileno(f), &st) < 0) return -errno; - /* Safety check */ - if (st.st_size > 4*1024*1024) - return -E2BIG; + n = LINE_MAX; - n = st.st_size > 0 ? st.st_size : LINE_MAX; - l = 0; + 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 +189,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,21 +197,37 @@ 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 *key, char *value, void *userdata), - void *userdata) { + 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_whitespace = (size_t) -1; + 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_EQUAL, PRE_VALUE, VALUE, VALUE_ESCAPE, @@ -203,10 +239,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; @@ -220,7 +258,9 @@ static int parse_env_file_internal( state = COMMENT; else if (!strchr(WHITESPACE, c)) { state = KEY; - if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { + last_key_whitespace = (size_t) -1; + + if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { r = -ENOMEM; goto fail; } @@ -232,13 +272,18 @@ static int parse_env_file_internal( case KEY: if (strchr(newline, c)) { state = PRE_KEY; + line ++; n_key = 0; - } else if (strchr(WHITESPACE, c)) - state = PRE_EQUAL; - else if (c == '=') + } else if (c == '=') { state = PRE_VALUE; - else { - if (!greedy_realloc((void**) &key, &key_alloc, n_key+2)) { + 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; } @@ -248,34 +293,27 @@ static int parse_env_file_internal( 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; + line ++; key[n_key] = 0; if (value) value[n_value] = 0; - r = push(key, value, userdata); + /* 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 == '\"') @@ -285,7 +323,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; } @@ -298,32 +336,39 @@ static int parse_env_file_internal( case VALUE: if (strchr(newline, c)) { state = PRE_KEY; + line ++; + key[n_key] = 0; if (value) value[n_value] = 0; - /* Chomp off trailing whitespace */ - if (last_whitespace != (size_t) -1) - value[last_whitespace] = 0; + /* Chomp off trailing whitespace from value */ + if (last_value_whitespace != (size_t) -1) + value[last_value_whitespace] = 0; - r = push(key, value, userdata); + /* 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_whitespace = (size_t) -1; + last_value_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; + last_value_whitespace = (size_t) -1; + 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; } @@ -338,7 +383,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; } @@ -353,7 +398,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; } @@ -367,7 +412,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; } @@ -382,7 +427,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; } @@ -396,7 +441,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; } @@ -408,8 +453,10 @@ static int parse_env_file_internal( case COMMENT: if (c == '\\') state = COMMENT_ESCAPE; - else if (strchr(newline, c)) + else if (strchr(newline, c)) { state = PRE_KEY; + line ++; + } break; case COMMENT_ESCAPE: @@ -431,7 +478,15 @@ static int parse_env_file_internal( if (value) value[n_value] = 0; - r = push(key, value, userdata); + 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; } @@ -443,10 +498,30 @@ fail: return r; } -static int parse_env_file_push(const char *key, char *value, void *userdata) { +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* ap = (va_list*) userdata; - va_list aq; + 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); @@ -459,13 +534,17 @@ static int parse_env_file_push(const char *key, char *value, void *userdata) { va_end(aq); free(*v); *v = value; + + if (n_pushed) + (*n_pushed)++; + return 1; } } va_end(aq); - free(value); + return 0; } @@ -474,45 +553,123 @@ int parse_env_file( const char *newline, ...) { va_list ap; - int r; + int r, n_pushed = 0; if (!newline) 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, &n_pushed); va_end(ap); - return r; + return r < 0 ? r : n_pushed; } -static int load_env_file_push(const char *key, char *value, void *userdata) { +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_push(m, p); + 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) { - free(p); + strv_free(m); return r; } - free(value); + *rl = m; return 0; } -int load_env_file(const char *fname, const char *newline, char ***rl) { +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(fname, newline, load_env_file_push, &m); + r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL); if (r < 0) { strv_free(m); return r; @@ -536,11 +693,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); @@ -554,37 +711,107 @@ static void write_env_var(FILE *f, const char *v) { } 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) 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; }