X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=src%2Fbasic%2Ffileio.c;h=e1600a1e6ae89103ab386aa0a96ce8d04cb83c4a;hb=21b40f16622f171a9969dc334d74fb5eb2f575c2;hp=e18b842999d22b51e55484d619c4a4c9d5add5b7;hpb=a92f2af28a5875dd15c85212553857ba893bfc3f;p=thirdparty%2Fsystemd.git diff --git a/src/basic/fileio.c b/src/basic/fileio.c index e18b842999d..e1600a1e6ae 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -17,9 +17,11 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "hexdecoct.h" #include "log.h" #include "macro.h" #include "missing.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "stdio-util.h" @@ -28,6 +30,52 @@ #define READ_FULL_BYTES_MAX (4U*1024U*1024U) +int fopen_unlocked(const char *path, const char *options, FILE **ret) { + assert(ret); + + FILE *f = fopen(path, options); + if (!f) + return -errno; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + *ret = f; + return 0; +} + +int fdopen_unlocked(int fd, const char *options, FILE **ret) { + assert(ret); + + FILE *f = fdopen(fd, options); + if (!f) + return -errno; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + *ret = f; + return 0; +} + +FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc) { + FILE *f = open_memstream(ptr, sizeloc); + if (!f) + return NULL; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + return f; +} + +FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode) { + FILE *f = fmemopen(buf, size, mode); + if (!f) + return NULL; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + return f; +} + int write_string_stream_ts( FILE *f, const char *line, @@ -94,7 +142,6 @@ static int write_string_file_atomic( if (r < 0) return r; - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); (void) fchmod_umask(fileno(f), 0644); r = write_string_stream_ts(f, line, flags, ts); @@ -128,6 +175,12 @@ int write_string_file_ts( /* We don't know how to verify whether the file contents was already on-disk. */ assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC))); + if (flags & WRITE_STRING_FILE_MKDIR_0755) { + r = mkdir_parents(fn, 0755); + if (r < 0) + return r; + } + if (flags & WRITE_STRING_FILE_ATOMIC) { assert(flags & WRITE_STRING_FILE_CREATE); @@ -140,11 +193,9 @@ int write_string_file_ts( assert(!ts); if (flags & WRITE_STRING_FILE_CREATE) { - f = fopen(fn, "we"); - if (!f) { - r = -errno; + r = fopen_unlocked(fn, "we", &f); + if (r < 0) goto fail; - } } else { int fd; @@ -156,16 +207,13 @@ int write_string_file_ts( goto fail; } - f = fdopen(fd, "w"); - if (!f) { - r = -errno; + r = fdopen_unlocked(fd, "w", &f); + if (r < 0) { safe_close(fd); goto fail; } } - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - if (flags & WRITE_STRING_FILE_DISABLE_BUFFER) setvbuf(f, NULL, _IONBF, 0); @@ -217,20 +265,18 @@ int read_one_line_file(const char *fn, char **line) { assert(fn); assert(line); - f = fopen(fn, "re"); - if (!f) - return -errno; - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(fn, "re", &f); + if (r < 0) + return r; - r = read_line(f, LONG_LINE_MAX, line); - return r < 0 ? r : 0; + return read_line(f, LONG_LINE_MAX, line); } int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *buf = NULL; size_t l, k; + int r; assert(fn); assert(blob); @@ -244,17 +290,15 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { if (!buf) return -ENOMEM; - f = fopen(fn, "re"); - if (!f) - return -errno; - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(fn, "re", &f); + if (r < 0) + return r; /* We try to read one byte more than we need, so that we know whether we hit eof */ errno = 0; k = fread(buf, 1, l + accept_extra_nl + 1, f); if (ferror(f)) - return errno > 0 ? -errno : -EIO; + return errno_or_else(EIO); if (k != l && k != l + accept_extra_nl) return 0; @@ -266,26 +310,137 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { return 1; } -int read_full_stream( +int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) { + _cleanup_free_ char *buf = NULL; + _cleanup_close_ int fd = -1; + struct stat st; + size_t n, size; + int n_retries; + char *p; + + assert(ret_contents); + + /* Virtual filesystems such as sysfs or procfs use kernfs, and kernfs can work + * with two sorts of virtual files. One sort uses "seq_file", and the results of + * the first read are buffered for the second read. The other sort uses "raw" + * reads which always go direct to the device. In the latter case, the content of + * the virtual file must be retrieved with a single read otherwise a second read + * might get the new value instead of finding EOF immediately. That's the reason + * why the usage of fread(3) is prohibited in this case as it always performs a + * second call to read(2) looking for EOF. See issue 13585. */ + + fd = open(filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + /* Start size for files in /proc which usually report a file size of 0. */ + size = LINE_MAX / 2; + + /* Limit the number of attempts to read the number of bytes returned by fstat(). */ + n_retries = 3; + + for (;;) { + if (n_retries <= 0) + return -EIO; + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EBADF; + + /* Be prepared for files from /proc which generally report a file size of 0. */ + if (st.st_size > 0) { + size = st.st_size; + n_retries--; + } else + size = size * 2; + + if (size > READ_FULL_BYTES_MAX) + return -E2BIG; + + p = realloc(buf, size + 1); + if (!p) + return -ENOMEM; + buf = TAKE_PTR(p); + + for (;;) { + ssize_t k; + + /* Read one more byte so we can detect whether the content of the + * file has already changed or the guessed size for files from /proc + * wasn't large enough . */ + k = read(fd, buf, size + 1); + if (k >= 0) { + n = k; + break; + } + + if (errno != -EINTR) + return -errno; + } + + /* Consider a short read as EOF */ + if (n <= size) + break; + + /* Hmm... either we read too few bytes from /proc or less likely the content + * of the file might have been changed (and is now bigger) while we were + * processing, let's try again either with a bigger guessed size or the new + * file size. */ + + if (lseek(fd, 0, SEEK_SET) < 0) + return -errno; + } + + if (n < size) { + p = realloc(buf, n + 1); + if (!p) + return -ENOMEM; + buf = TAKE_PTR(p); + } + + if (!ret_size) { + /* Safety check: if the caller doesn't want to know the size of what we + * just read it will rely on the trailing NUL byte. But if there's an + * embedded NUL byte, then we should refuse operation as otherwise + * there'd be ambiguity about what we just read. */ + + if (memchr(buf, 0, n)) + return -EBADMSG; + } else + *ret_size = n; + + buf[n] = 0; + *ret_contents = TAKE_PTR(buf); + + return 0; +} + +int read_full_stream_full( FILE *f, + const char *filename, + ReadFullFileFlags flags, char **ret_contents, size_t *ret_size) { _cleanup_free_ char *buf = NULL; struct stat st; - size_t n, l; - int fd; + size_t n, n_next, l; + int fd, r; assert(f); assert(ret_contents); + assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)); + assert(!(flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) || ret_size); - n = LINE_MAX; /* Start size */ + n_next = LINE_MAX; /* Start size */ fd = fileno(f); if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's * optimize our buffering) */ - if (fstat(fileno(f), &st) < 0) + if (fstat(fd, &st) < 0) return -errno; if (S_ISREG(st.st_mode)) { @@ -294,31 +449,49 @@ int read_full_stream( if (st.st_size > READ_FULL_BYTES_MAX) return -E2BIG; - /* Start with the right file size, but be prepared for files from /proc which generally report a file - * size of 0. Note that we increase the size to read here by one, so that the first read attempt - * already makes us notice the EOF. */ + /* Start with the right file size. Note that we increase the size + * to read here by one, so that the first read attempt already + * makes us notice the EOF. */ if (st.st_size > 0) - n = st.st_size + 1; + n_next = st.st_size + 1; + + if (flags & READ_FULL_FILE_SECURE) + (void) warn_file_is_world_accessible(filename, &st, NULL, 0); } } - l = 0; + n = l = 0; for (;;) { char *t; size_t k; - t = realloc(buf, n + 1); - if (!t) - return -ENOMEM; + if (flags & READ_FULL_FILE_SECURE) { + t = malloc(n_next + 1); + if (!t) { + r = -ENOMEM; + goto finalize; + } + memcpy_safe(t, buf, n); + explicit_bzero_safe(buf, n); + buf = mfree(buf); + } else { + t = realloc(buf, n_next + 1); + if (!t) + return -ENOMEM; + } buf = t; + n = n_next; + errno = 0; k = fread(buf + l, 1, n - l, f); if (k > 0) l += k; - if (ferror(f)) - return errno > 0 ? -errno : -EIO; + if (ferror(f)) { + r = errno_or_else(EIO); + goto finalize; + } if (feof(f)) break; @@ -329,10 +502,21 @@ int read_full_stream( assert(l == n); /* Safety check */ - if (n >= READ_FULL_BYTES_MAX) - return -E2BIG; + if (n >= READ_FULL_BYTES_MAX) { + r = -E2BIG; + goto finalize; + } - n = MIN(n * 2, READ_FULL_BYTES_MAX); + n_next = MIN(n * 2, READ_FULL_BYTES_MAX); + } + + if (flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) { + buf[l++] = 0; + if (flags & READ_FULL_FILE_UNBASE64) + r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size); + else + r = unhexmem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size); + goto finalize; } if (!ret_size) { @@ -340,8 +524,10 @@ int read_full_stream( * trailing NUL byte. But if there's an embedded NUL byte, then we should refuse operation as otherwise * there'd be ambiguity about what we just read. */ - if (memchr(buf, 0, l)) - return -EBADMSG; + if (memchr(buf, 0, l)) { + r = -EBADMSG; + goto finalize; + } } buf[l] = 0; @@ -351,21 +537,26 @@ int read_full_stream( *ret_size = l; return 0; + +finalize: + if (flags & READ_FULL_FILE_SECURE) + explicit_bzero_safe(buf, n); + + return r; } -int read_full_file(const char *fn, char **contents, size_t *size) { +int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) { _cleanup_fclose_ FILE *f = NULL; + int r; - assert(fn); + assert(filename); assert(contents); - f = fopen(fn, "re"); - if (!f) - return -errno; - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(filename, "re", &f); + if (r < 0) + return r; - return read_full_stream(f, contents, size); + return read_full_stream_full(f, filename, flags, contents, size); } int executable_is_script(const char *path, char **interpreter) { @@ -418,7 +609,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin assert(pattern); assert(field); - r = read_full_file(filename, &status, NULL); + r = read_full_virtual_file(filename, &status, NULL); if (r < 0) return r; @@ -505,10 +696,7 @@ static int search_and_fopen_internal(const char *path, const char *mode, const c _cleanup_free_ char *p = NULL; FILE *f; - if (root) - p = strjoin(root, *i, "/", path); - else - p = strjoin(*i, "/", path); + p = path_join(root, *i, path); if (!p) return -ENOMEM; @@ -580,7 +768,7 @@ int fflush_and_check(FILE *f) { fflush(f); if (ferror(f)) - return errno > 0 ? -errno : -EIO; + return errno_or_else(EIO); return 0; } @@ -695,7 +883,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile); int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { size_t n = 0, allocated = 0, count = 0; _cleanup_free_ char *buffer = NULL; - int r; + int r, tty = -1; assert(f); @@ -769,6 +957,17 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { count++; + if (eol != EOL_NONE) { + /* If we are on a tty, we can't wait for more input. But we expect only + * \n as the single EOL marker, so there is no need to wait. We check + * this condition last to avoid isatty() check if not necessary. */ + + if (tty < 0) + tty = isatty(fileno(f)); + if (tty > 0) + break; + } + if (eol != EOL_NONE) { previous_eol |= eol; continue; @@ -807,7 +1006,7 @@ int safe_fgetc(FILE *f, char *ret) { k = fgetc(f); if (k == EOF) { if (ferror(f)) - return errno > 0 ? -errno : -EIO; + return errno_or_else(EIO); if (ret) *ret = 0; @@ -820,3 +1019,28 @@ int safe_fgetc(FILE *f, char *ret) { return 1; } + +int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line) { + struct stat _st; + + if (!filename) + return 0; + + if (!st) { + if (stat(filename, &_st) < 0) + return -errno; + st = &_st; + } + + if ((st->st_mode & S_IRWXO) == 0) + return 0; + + if (unit) + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s has %04o mode that is too permissive, please adjust the ownership and access mode.", + filename, st->st_mode & 07777); + else + log_warning("%s has %04o mode that is too permissive, please adjust the ownership and access mode.", + filename, st->st_mode & 07777); + return 0; +}