X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fbasic%2Ffileio.c;h=7bafe9038cc139ad542182a937083033faa0f580;hp=bfb75608f887d26926886398f091c71ea0e6f2ff;hb=d93247127eb2e073a6d3b5bcc67bcc4048d674fe;hpb=da2587d5154e11d4e643e326793f3ce2cc48dee6 diff --git a/src/basic/fileio.c b/src/basic/fileio.c index bfb75608f..7bafe9038 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -19,6 +17,15 @@ along with systemd; If not, see . ***/ +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "alloc-util.h" @@ -28,17 +35,22 @@ #include "fileio.h" #include "fs-util.h" #include "hexdecoct.h" +//#include "log.h" +//#include "macro.h" +#include "missing.h" #include "parse-util.h" #include "path-util.h" #include "random-util.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" +//#include "time-util.h" #include "umask-util.h" #include "utf8.h" -#include "util.h" -int write_string_stream(FILE *f, const char *line, bool enforce_newline) { +#define READ_FULL_BYTES_MAX (4U*1024U*1024U) + +int write_string_stream_ts(FILE *f, const char *line, bool enforce_newline, struct timespec *ts) { assert(f); assert(line); @@ -47,6 +59,13 @@ int write_string_stream(FILE *f, const char *line, bool enforce_newline) { if (enforce_newline && !endswith(line, "\n")) fputc('\n', f); + if (ts) { + struct timespec twice[2] = {*ts, *ts}; + + if (futimens(fileno(f), twice) < 0) + return -errno; + } + return fflush_and_check(f); } @@ -76,7 +95,7 @@ static int write_string_file_atomic(const char *fn, const char *line, bool enfor return r; } -int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { +int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, struct timespec *ts) { _cleanup_fclose_ FILE *f = NULL; int q, r; @@ -91,7 +110,8 @@ int write_string_file(const char *fn, const char *line, WriteStringFileFlags fla goto fail; return r; - } + } else + assert(ts == NULL); if (flags & WRITE_STRING_FILE_CREATE) { f = fopen(fn, "we"); @@ -118,7 +138,7 @@ int write_string_file(const char *fn, const char *line, WriteStringFileFlags fla } } - r = write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + r = write_string_stream_ts(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE), ts); if (r < 0) goto fail; @@ -154,7 +174,7 @@ int read_one_line_file(const char *fn, char **line) { if (!fgets(t, sizeof(t), f)) { if (ferror(f)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; t[0] = 0; } @@ -221,7 +241,7 @@ int read_full_stream(FILE *f, char **contents, size_t *size) { if (S_ISREG(st.st_mode)) { /* Safety check */ - if (st.st_size > 4*1024*1024) + if (st.st_size > READ_FULL_BYTES_MAX) return -E2BIG; /* Start with the right file size, but be prepared for @@ -236,26 +256,31 @@ int read_full_stream(FILE *f, char **contents, size_t *size) { char *t; size_t k; - t = realloc(buf, n+1); + t = realloc(buf, n + 1); if (!t) return -ENOMEM; buf = t; k = fread(buf + l, 1, n - l, f); + if (k > 0) + l += k; - if (k <= 0) { - if (ferror(f)) - return -errno; + if (ferror(f)) + return -errno; + if (feof(f)) break; - } - l += k; - n *= 2; + /* We aren't expecting fread() to return a short read outside + * of (error && eof), assert buffer is full and enlarge buffer. + */ + assert(l == n); /* Safety check */ - if (n > 4*1024*1024) + if (n >= READ_FULL_BYTES_MAX) return -E2BIG; + + n = MIN(n * 2, READ_FULL_BYTES_MAX); } buf[l] = 0; @@ -343,7 +368,7 @@ static int parse_env_file_internal( case KEY: if (strchr(newline, c)) { state = PRE_KEY; - line ++; + line++; n_key = 0; } else if (c == '=') { state = PRE_VALUE; @@ -367,7 +392,7 @@ static int parse_env_file_internal( case PRE_VALUE: if (strchr(newline, c)) { state = PRE_KEY; - line ++; + line++; key[n_key] = 0; if (value) @@ -407,7 +432,7 @@ static int parse_env_file_internal( case VALUE: if (strchr(newline, c)) { state = PRE_KEY; - line ++; + line++; key[n_key] = 0; @@ -526,7 +551,7 @@ static int parse_env_file_internal( state = COMMENT_ESCAPE; else if (strchr(newline, c)) { state = PRE_KEY; - line ++; + line++; } break; @@ -536,13 +561,14 @@ static int parse_env_file_internal( } } - 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) { + if (IN_SET(state, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + SINGLE_QUOTE_VALUE_ESCAPE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE)) { key[n_key] = 0; @@ -569,17 +595,12 @@ fail: return r; } -static int parse_env_file_push( +static int check_utf8ness_and_warn( const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - - const char *k; - va_list aq, *ap = userdata; + const char *key, char *value) { if (!utf8_is_valid(key)) { - _cleanup_free_ char *p; + _cleanup_free_ char *p = NULL; p = utf8_escape_invalid(key); log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p); @@ -587,13 +608,30 @@ static int parse_env_file_push( } if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *p; + _cleanup_free_ char *p = NULL; 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; } + return 0; +} + +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; + int r; + + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; + va_copy(aq, *ap); while ((k = va_arg(aq, const char *))) { @@ -636,6 +674,7 @@ int parse_env_file( return r < 0 ? r : n_pushed; } +#if 0 /// UNNEEDED by elogind static int load_env_file_push( const char *filename, unsigned line, const char *key, char *value, @@ -645,27 +684,19 @@ static int load_env_file_push( 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; - } + r = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; - p = strjoin(key, "=", strempty(value), NULL); + p = strjoin(key, "=", value); if (!p) return -ENOMEM; - r = strv_consume(m, p); - if (r < 0) + r = strv_env_replace(m, p); + if (r < 0) { + free(p); return r; + } if (n_pushed) (*n_pushed)++; @@ -690,8 +721,8 @@ int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) { *rl = m; return 0; } +#endif // 0 -#if 0 /// UNNEDED by elogind static int load_env_file_push_pairs( const char *filename, unsigned line, const char *key, char *value, @@ -700,19 +731,9 @@ static int load_env_file_push_pairs( 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 = check_utf8ness_and_warn(filename, line, key, value); + if (r < 0) + return r; r = strv_extend(m, key); if (r < 0) @@ -750,7 +771,53 @@ int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ** *rl = m; return 0; } -#endif // 0 +#if 0 /// UNNEEDED by elogind + +static int merge_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + char ***env = userdata; + char *expanded_value; + + assert(env); + + if (!value) { + log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key); + return 0; + } + + if (!env_name_is_valid(key)) { + log_error("%s:%u: invalid variable name \"%s\", ignoring.", strna(filename), line, key); + free(value); + return 0; + } + + expanded_value = replace_env(value, *env, + REPLACE_ENV_USE_ENVIRONMENT| + REPLACE_ENV_ALLOW_BRACELESS| + REPLACE_ENV_ALLOW_EXTENDED); + if (!expanded_value) + return -ENOMEM; + + free_and_replace(value, expanded_value); + + return load_env_file_push(filename, line, key, value, env, n_pushed); +} + +int merge_env_file( + char ***env, + FILE *f, + const char *fname) { + + /* NOTE: this function supports braceful and braceless variable expansions, + * plus "extended" substitutions, unlike other exported parsing functions. + */ + + return parse_env_file_internal(f, fname, NEWLINE, merge_env_file_push, env, NULL); +} static void write_env_var(FILE *f, const char *v) { const char *p; @@ -812,7 +879,6 @@ int write_env_file(const char *fname, char **l) { return r; } -#if 0 /// UNNEEDED by elogind int executable_is_script(const char *path, char **interpreter) { int r; _cleanup_free_ char *line = NULL; @@ -873,13 +939,13 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin do { t = strstr(t, pattern); - if (!t) - return -ENOENT; + if (!t) + return -ENOENT; /* Check that pattern occurs in beginning of line. */ pattern_ok = (t == status || t[-1] == '\n'); - t += strlen(pattern); + t += strlen(pattern); } while (!pattern_ok); @@ -903,7 +969,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin /* Back off one char if there's nothing but whitespace and zeros */ if (!*t || isspace(*t)) - t --; + t--; } len = strcspn(t, terminator); @@ -951,9 +1017,9 @@ static int search_and_fopen_internal(const char *path, const char *mode, const c FILE *f; if (root) - p = strjoin(root, *i, "/", path, NULL); + p = strjoin(root, *i, "/", path); else - p = strjoin(*i, "/", path, NULL); + p = strjoin(*i, "/", path); if (!p) return -ENOMEM; @@ -1032,7 +1098,7 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { if (r < 0) return r; - fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); + fd = mkostemp_safe(t); if (fd < 0) { free(t); return -errno; @@ -1059,53 +1125,27 @@ int fflush_and_check(FILE *f) { fflush(f); if (ferror(f)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } -/* This is much like like mkostemp() but is subject to umask(). */ -int mkostemp_safe(char *pattern, int flags) { - _cleanup_umask_ mode_t u; +/* This is much like mkostemp() but is subject to umask(). */ +int mkostemp_safe(char *pattern) { + _cleanup_umask_ mode_t u = 0; int fd; assert(pattern); u = umask(077); - fd = mkostemp(pattern, flags); + fd = mkostemp(pattern, O_CLOEXEC); if (fd < 0) return -errno; return fd; } -#if 0 /// UNNEEDED by elogind -int open_tmpfile(const char *path, int flags) { - char *p; - int fd; - - assert(path); - -#ifdef O_TMPFILE - /* Try O_TMPFILE first, if it is supported */ - fd = open(path, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); - if (fd >= 0) - return fd; -#endif - - /* Fall back to unguessable name + unlinking */ - p = strjoina(path, "/systemd-tmp-XXXXXX"); - - fd = mkostemp_safe(p, flags); - if (fd < 0) - return fd; - - unlink(p); - return fd; -} -#endif // 0 - int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { const char *fn; char *t; @@ -1138,7 +1178,6 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { return 0; } -#if 0 /// UNNEEDED by elogind int tempfn_random(const char *p, const char *extra, char **ret) { const char *fn; char *t, *x; @@ -1181,12 +1220,13 @@ int tempfn_random(const char *p, const char *extra, char **ret) { return 0; } +#if 0 /// UNNEEDED by elogind int tempfn_random_child(const char *p, const char *extra, char **ret) { char *t, *x; uint64_t u; unsigned i; + int r; - assert(p); assert(ret); /* Turns this: @@ -1195,6 +1235,12 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) { * /foo/bar/waldo/.#3c2b6219aa75d7d0 */ + if (!p) { + r = tmp_dir(&p); + if (r < 0) + return r; + } + if (!extra) extra = ""; @@ -1249,4 +1295,216 @@ int read_timestamp_file(const char *fn, usec_t *ret) { *ret = (usec_t) t; return 0; } + +int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { + int r; + + assert(s); + + /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter + * when specified shall initially point to a boolean variable initialized to false. It is set to true after the + * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each + * element, but not before the first one. */ + + if (!f) + f = stdout; + + if (space) { + if (!separator) + separator = " "; + + if (*space) { + r = fputs(separator, f); + if (r < 0) + return r; + } + + *space = true; + } + + return fputs(s, f); +} +#endif // 0 + +int open_tmpfile_unlinkable(const char *directory, int flags) { + char *p; + int fd, r; + + if (!directory) { + r = tmp_dir(&directory); + if (r < 0) + return r; + } + + /* Returns an unlinked temporary file that cannot be linked into the file system anymore */ + + /* Try O_TMPFILE first, if it is supported */ + fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); + if (fd >= 0) + return fd; + + /* Fall back to unguessable name + unlinking */ + p = strjoina(directory, "/systemd-tmp-XXXXXX"); + + fd = mkostemp_safe(p); + if (fd < 0) + return fd; + + (void) unlink(p); + + return fd; +} + +#if 0 /// UNNEEDED by elogind +int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { + _cleanup_free_ char *tmp = NULL; + int r, fd; + + assert(target); + assert(ret_path); + + /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */ + assert((flags & O_EXCL) == 0); + + /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in + * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in + * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */ + + { + _cleanup_free_ char *dn = NULL; + + dn = dirname_malloc(target); + if (!dn) + return -ENOMEM; + + fd = open(dn, O_TMPFILE|flags, 0640); + if (fd >= 0) { + *ret_path = NULL; + return fd; + } + + log_debug_errno(errno, "Failed to use O_TMPFILE on %s: %m", dn); + } + + r = tempfn_random(target, NULL, &tmp); + if (r < 0) + return r; + + fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640); + if (fd < 0) + return -errno; + + *ret_path = tmp; + tmp = NULL; + + return fd; +} +#endif // 0 + +int open_serialization_fd(const char *ident) { + int fd = -1; + + fd = memfd_create(ident, MFD_CLOEXEC); + if (fd < 0) { + const char *path; + + path = getpid() == 1 ? "/run/systemd" : "/tmp"; + fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC); + if (fd < 0) + return fd; + + log_debug("Serializing %s to %s.", ident, path); + } else + log_debug("Serializing %s to memfd.", ident); + + return fd; +} + +#if 0 /// UNNEEDED by elogind +int link_tmpfile(int fd, const char *path, const char *target) { + + assert(fd >= 0); + assert(target); + + /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd + * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported + * on the directory, and renameat2() is used instead. + * + * Note that in both cases we will not replace existing files. This is because linkat() does not support this + * operation currently (renameat2() does), and there is no nice way to emulate this. */ + + if (path) { + if (rename_noreplace(AT_FDCWD, path, AT_FDCWD, target) < 0) + return -errno; + } else { + char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; + + xsprintf(proc_fd_path, "/proc/self/fd/%i", fd); + + if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0) + return -errno; + } + + return 0; +} + +int read_nul_string(FILE *f, char **ret) { + _cleanup_free_ char *x = NULL; + size_t allocated = 0, n = 0; + + assert(f); + assert(ret); + + /* Reads a NUL-terminated string from the specified file. */ + + for (;;) { + int c; + + if (!GREEDY_REALLOC(x, allocated, n+2)) + return -ENOMEM; + + c = fgetc(f); + if (c == 0) /* Terminate at NUL byte */ + break; + if (c == EOF) { + if (ferror(f)) + return -errno; + break; /* Terminate at EOF */ + } + + x[n++] = (char) c; + } + + if (x) + x[n] = 0; + else { + x = new0(char, 1); + if (!x) + return -ENOMEM; + } + + *ret = x; + x = NULL; + + return 0; +} + +int mkdtemp_malloc(const char *template, char **ret) { + char *p; + + assert(template); + assert(ret); + + p = strdup(template); + if (!p) + return -ENOMEM; + + if (!mkdtemp(p)) { + free(p); + return -errno; + } + + *ret = p; + return 0; +} #endif // 0