]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/fileio.c
Merge pull request #18863 from keszybz/cmdline-escaping
[thirdparty/systemd.git] / src / basic / fileio.c
index 71a2654507f82f95e8b7b86f3a73ad63fdb6f88a..93d8547b32e8c11237dc69d09e1e7bb50a738906 100644 (file)
@@ -27,7 +27,8 @@
 #include "string-util.h"
 #include "tmpfile-util.h"
 
-#define READ_FULL_BYTES_MAX (4U*1024U*1024U)
+/* The maximum size of the file we'll read in one go. */
+#define READ_FULL_BYTES_MAX (4U*1024U*1024U - 1)
 
 int fopen_unlocked(const char *path, const char *options, FILE **ret) {
         assert(ret);
@@ -56,7 +57,7 @@ int fdopen_unlocked(int fd, const char *options, FILE **ret) {
 }
 
 int take_fdopen_unlocked(int *fd, const char *options, FILE **ret) {
-        int     r;
+        int r;
 
         assert(fd);
 
@@ -117,10 +118,10 @@ int write_string_stream_ts(
                 FILE *f,
                 const char *line,
                 WriteStringFileFlags flags,
-                struct timespec *ts) {
+                const struct timespec *ts) {
 
         bool needs_nl;
-        int r, fd;
+        int r, fd = -1;
 
         assert(f);
         assert(line);
@@ -139,8 +140,8 @@ int write_string_stream_ts(
         needs_nl = !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n");
 
         if (needs_nl && (flags & WRITE_STRING_FILE_DISABLE_BUFFER)) {
-                /* If STDIO buffering was disabled, then let's append the newline character to the string itself, so
-                 * that the write goes out in one go, instead of two */
+                /* If STDIO buffering was disabled, then let's append the newline character to the string
+                 * itself, so that the write goes out in one go, instead of two */
 
                 line = strjoina(line, "\n");
                 needs_nl = false;
@@ -161,8 +162,9 @@ int write_string_stream_ts(
                 return r;
 
         if (ts) {
-                struct timespec twice[2] = {*ts, *ts};
+                const struct timespec twice[2] = {*ts, *ts};
 
+                assert(fd >= 0);
                 if (futimens(fd, twice) < 0)
                         return -errno;
         }
@@ -174,7 +176,7 @@ static int write_string_file_atomic(
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
-                struct timespec *ts) {
+                const struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
@@ -221,7 +223,7 @@ int write_string_file_ts(
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
-                struct timespec *ts) {
+                const struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
         int q, r, fd;
@@ -284,7 +286,7 @@ fail:
         /* OK, the operation failed, but let's see if the right
          * contents in place already. If so, eat up the error. */
 
-        q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
+        q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE) || (flags & WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE));
         if (q <= 0)
                 return r;
 
@@ -362,38 +364,39 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         return 1;
 }
 
-int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) {
+int read_virtual_file(const char *filename, size_t max_size, 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;
+        bool truncated = false;
 
         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. */
+        /* 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.
+         *
+         * max_size specifies a limit on the bytes read. If max_size is SIZE_MAX, the full file is read. If
+         * the the full file is too large to read, an error is returned. For other values of max_size,
+         * *partial contents* may be returned. (Though the read is still done using one syscall.)
+         * Returns 0 on partial success, 1 if untruncated contents were read. */
 
         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;
+        assert(max_size <= READ_FULL_BYTES_MAX || max_size == SIZE_MAX);
 
         /* Limit the number of attempts to read the number of bytes returned by fstat(). */
         n_retries = 3;
 
         for (;;) {
-                if (n_retries <= 0)
-                        return -EIO;
+                struct stat st;
 
                 if (fstat(fd, &st) < 0)
                         return -errno;
@@ -402,19 +405,26 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re
                         return -EBADF;
 
                 /* Be prepared for files from /proc which generally report a file size of 0. */
+                assert_cc(READ_FULL_BYTES_MAX < SSIZE_MAX);
                 if (st.st_size > 0) {
-                        size = st.st_size;
-                        n_retries--;
-                } else
-                        size = size * 2;
+                        if (st.st_size > SSIZE_MAX) /* Avoid overflow with 32-bit size_t and 64-bit off_t. */
+                                return -EFBIG;
 
-                if (size > READ_FULL_BYTES_MAX)
-                        return -E2BIG;
+                        size = MIN((size_t) st.st_size, max_size);
+                        if (size > READ_FULL_BYTES_MAX)
+                                return -EFBIG;
 
-                p = realloc(buf, size + 1);
-                if (!p)
+                        n_retries--;
+                } else {
+                        size = MIN(READ_FULL_BYTES_MAX, max_size);
+                        n_retries = 0;
+                }
+
+                buf = malloc(size + 1);
+                if (!buf)
                         return -ENOMEM;
-                buf = TAKE_PTR(p);
+                /* Use a bigger allocation if we got it anyway, but not more than the limit. */
+                size = MIN3(malloc_usable_size(buf) - 1, max_size, READ_FULL_BYTES_MAX);
 
                 for (;;) {
                         ssize_t k;
@@ -441,43 +451,56 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re
                  * processing, let's try again either with a bigger guessed size or the new
                  * file size. */
 
+                if (n_retries <= 0) {
+                        if (max_size == SIZE_MAX)
+                                return st.st_size > 0 ? -EIO : -EFBIG;
+
+                        /* Accept a short read, but truncate it appropropriately. */
+                        n = MIN(n, max_size);
+                        truncated = true;
+                        break;
+                }
+
                 if (lseek(fd, 0, SEEK_SET) < 0)
                         return -errno;
+
+                buf = mfree(buf);
         }
 
         if (n < size) {
+                char *p;
+
+                /* Return rest of the buffer to libc */
                 p = realloc(buf, n + 1);
                 if (!p)
                         return -ENOMEM;
-                buf = TAKE_PTR(p);
+                buf = 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
+        if (ret_size)
                 *ret_size = n;
+        else if (memchr(buf, 0, n))
+                /* 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. */
+                return -EBADMSG;
 
         buf[n] = 0;
         *ret_contents = TAKE_PTR(buf);
 
-        return 0;
+        return !truncated;
 }
 
 int read_full_stream_full(
                 FILE *f,
                 const char *filename,
+                uint64_t offset,
+                size_t size,
                 ReadFullFileFlags flags,
                 char **ret_contents,
                 size_t *ret_size) {
 
         _cleanup_free_ char *buf = NULL;
-        struct stat st;
         size_t n, n_next, l;
         int fd, r;
 
@@ -485,32 +508,45 @@ int read_full_stream_full(
         assert(ret_contents);
         assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
 
-        n_next = LINE_MAX; /* Start size */
+        if (offset != UINT64_MAX && offset > LONG_MAX)
+                return -ERANGE;
+
+        n_next = size != SIZE_MAX ? size : 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 (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see
+                        * fmemopen()), let's optimize our buffering */
+                struct stat st;
 
                 if (fstat(fd, &st) < 0)
                         return -errno;
 
                 if (S_ISREG(st.st_mode)) {
-
-                        /* Safety check */
-                        if (st.st_size > READ_FULL_BYTES_MAX)
-                                return -E2BIG;
-
-                        /* 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_next = st.st_size + 1;
+                        if (size == SIZE_MAX) {
+                                uint64_t rsize =
+                                        LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset);
+
+                                /* Safety check */
+                                if (rsize > READ_FULL_BYTES_MAX)
+                                        return -E2BIG;
+
+                                /* 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 the reported size of the file is zero, we avoid this logic
+                                 * however, since quite likely it might be a virtual file in procfs that all
+                                 * report a zero file size. */
+                                if (st.st_size > 0)
+                                        n_next = rsize + 1;
+                        }
 
                         if (flags & READ_FULL_FILE_WARN_WORLD_READABLE)
                                 (void) warn_file_is_world_accessible(filename, &st, NULL, 0);
                 }
         }
 
+        if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0)
+                return -errno;
+
         n = l = 0;
         for (;;) {
                 char *t;
@@ -532,7 +568,9 @@ int read_full_stream_full(
                 }
 
                 buf = t;
-                n = n_next;
+                /* Unless a size has been explicitly specified, try to read as much as fits into the memory
+                 * we allocated (minus 1, to leave one byte for the safety NUL byte) */
+                n = size == SIZE_MAX ? malloc_usable_size(buf) - 1 : n_next;
 
                 errno = 0;
                 k = fread(buf + l, 1, n - l, f);
@@ -547,6 +585,11 @@ int read_full_stream_full(
                 if (feof(f))
                         break;
 
+                if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */
+                        assert(l == size);
+                        break;
+                }
+
                 assert(k > 0); /* we can't have read zero bytes because that would have been EOF */
 
                 /* Safety check */
@@ -605,15 +648,18 @@ finalize:
 int read_full_file_full(
                 int dir_fd,
                 const char *filename,
+                uint64_t offset,
+                size_t size,
                 ReadFullFileFlags flags,
                 const char *bind_name,
-                char **contents, size_t *size) {
+                char **ret_contents,
+                size_t *ret_size) {
 
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
         assert(filename);
-        assert(contents);
+        assert(ret_contents);
 
         r = xfopenat(dir_fd, filename, "re", 0, &f);
         if (r < 0) {
@@ -628,6 +674,10 @@ int read_full_file_full(
                 if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
                         return -ENXIO;
 
+                /* Seeking is not supported on AF_UNIX sockets */
+                if (offset != UINT64_MAX)
+                        return -ESPIPE;
+
                 if (dir_fd == AT_FDCWD)
                         r = sockaddr_un_set_path(&sa.un, filename);
                 else {
@@ -681,7 +731,7 @@ int read_full_file_full(
 
         (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
 
-        return read_full_stream_full(f, filename, flags, contents, size);
+        return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
 }
 
 int executable_is_script(const char *path, char **interpreter) {
@@ -882,12 +932,19 @@ int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **r
         return 0;
 }
 
-static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) {
+static int search_and_fopen_internal(
+                const char *path,
+                const char *mode,
+                const char *root,
+                char **search,
+                FILE **ret,
+                char **ret_path) {
+
         char **i;
 
         assert(path);
         assert(mode);
-        assert(_f);
+        assert(ret);
 
         if (!path_strv_resolve_uniq(search, root))
                 return -ENOMEM;
@@ -902,7 +959,10 @@ static int search_and_fopen_internal(const char *path, const char *mode, const c
 
                 f = fopen(p, mode);
                 if (f) {
-                        *_f = f;
+                        if (ret_path)
+                                *ret_path = path_simplify(TAKE_PTR(p), true);
+
+                        *ret = f;
                         return 0;
                 }
 
@@ -913,52 +973,84 @@ static int search_and_fopen_internal(const char *path, const char *mode, const c
         return -ENOENT;
 }
 
-int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) {
+int search_and_fopen(
+                const char *filename,
+                const char *mode,
+                const char *root,
+                const char **search,
+                FILE **ret,
+                char **ret_path) {
+
         _cleanup_strv_free_ char **copy = NULL;
 
-        assert(path);
+        assert(filename);
         assert(mode);
-        assert(_f);
+        assert(ret);
 
-        if (path_is_absolute(path)) {
-                FILE *f;
+        if (path_is_absolute(filename)) {
+                _cleanup_fclose_ FILE *f = NULL;
 
-                f = fopen(path, mode);
-                if (f) {
-                        *_f = f;
-                        return 0;
+                f = fopen(filename, mode);
+                if (!f)
+                        return -errno;
+
+                if (ret_path) {
+                        char *p;
+
+                        p = strdup(filename);
+                        if (!p)
+                                return -ENOMEM;
+
+                        *ret_path = path_simplify(p, true);
                 }
 
-                return -errno;
+                *ret = TAKE_PTR(f);
+                return 0;
         }
 
         copy = strv_copy((char**) search);
         if (!copy)
                 return -ENOMEM;
 
-        return search_and_fopen_internal(path, mode, root, copy, _f);
+        return search_and_fopen_internal(filename, mode, root, copy, ret, ret_path);
 }
 
-int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) {
+int search_and_fopen_nulstr(
+                const char *filename,
+                const char *mode,
+                const char *root,
+                const char *search,
+                FILE **ret,
+                char **ret_path) {
+
         _cleanup_strv_free_ char **s = NULL;
 
-        if (path_is_absolute(path)) {
-                FILE *f;
+        if (path_is_absolute(filename)) {
+                _cleanup_fclose_ FILE *f = NULL;
 
-                f = fopen(path, mode);
-                if (f) {
-                        *_f = f;
-                        return 0;
+                f = fopen(filename, mode);
+                if (!f)
+                        return -errno;
+
+                if (ret_path) {
+                        char *p;
+
+                        p = strdup(filename);
+                        if (!p)
+                                return -ENOMEM;
+
+                        *ret_path = path_simplify(p, true);
                 }
 
-                return -errno;
+                *ret = TAKE_PTR(f);
+                return 0;
         }
 
         s = strv_split_nulstr(search);
         if (!s)
                 return -ENOMEM;
 
-        return search_and_fopen_internal(path, mode, root, s, _f);
+        return search_and_fopen_internal(filename, mode, root, s, ret, ret_path);
 }
 
 int chase_symlinks_and_fopen_unlocked(
@@ -972,7 +1064,6 @@ int chase_symlinks_and_fopen_unlocked(
         _cleanup_close_ int fd = -1;
         _cleanup_free_ char *final_path = NULL;
         int mode_flags, r;
-        FILE *f;
 
         assert(path);
         assert(open_flags);
@@ -986,12 +1077,10 @@ int chase_symlinks_and_fopen_unlocked(
         if (fd < 0)
                 return fd;
 
-        r = fdopen_unlocked(fd, open_flags, &f);
+        r = take_fdopen_unlocked(&fd, open_flags, ret_file);
         if (r < 0)
                 return r;
-        TAKE_FD(fd);
 
-        *ret_file = f;
         if (ret_path)
                 *ret_path = TAKE_PTR(final_path);
         return 0;
@@ -1120,7 +1209,7 @@ static EndOfLineMarker categorize_eol(char c, ReadLineFlags flags) {
         return EOL_NONE;
 }
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, funlockfile, NULL);
 
 int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) {
         size_t n = 0, allocated = 0, count = 0;
@@ -1297,15 +1386,6 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c
         return 0;
 }
 
-int sync_rights(int from, int to) {
-        struct stat st;
-
-        if (fstat(from, &st) < 0)
-                return -errno;
-
-        return fchmod_and_chown(to, st.st_mode & 07777, st.st_uid, st.st_gid);
-}
-
 int rename_and_apply_smack_floor_label(const char *from, const char *to) {
         int r = 0;
         if (rename(from, to) < 0)