]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/basic/fileio.c
fileio: introduce read_full_virtual_file() for reading virtual files in sysfs, procfs
[thirdparty/systemd.git] / src / basic / fileio.c
index b22fcd0302708704e0b9589b37d041e15918e816..e1600a1e6ae89103ab386aa0a96ce8d04cb83c4a 100644 (file)
 #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"
 
 #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_next = MIN(n * 2, READ_FULL_BYTES_MAX);
+        }
 
-                n = 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;
 }
@@ -667,46 +855,6 @@ int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space)
         return fputs(s, f);
 }
 
-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 = TAKE_PTR(x);
-
-        return 0;
-}
-
 /* A bitmask of the EOL markers we know */
 typedef enum EndOfLineMarker {
         EOL_NONE     = 0,
@@ -715,11 +863,15 @@ typedef enum EndOfLineMarker {
         EOL_THIRTEEN = 1 << 2,  /* \r (aka CR)  */
 } EndOfLineMarker;
 
-static EndOfLineMarker categorize_eol(char c) {
-        if (c == '\n')
-                return EOL_TEN;
-        if (c == '\r')
-                return EOL_THIRTEEN;
+static EndOfLineMarker categorize_eol(char c, ReadLineFlags flags) {
+
+        if (!IN_SET(flags, READ_LINE_ONLY_NUL)) {
+                if (c == '\n')
+                        return EOL_TEN;
+                if (c == '\r')
+                        return EOL_THIRTEEN;
+        }
+
         if (c == '\0')
                 return EOL_ZERO;
 
@@ -728,9 +880,10 @@ static EndOfLineMarker categorize_eol(char c) {
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
 
-int read_line(FILE *f, size_t limit, char **ret) {
+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, tty = -1;
 
         assert(f);
 
@@ -770,7 +923,7 @@ int read_line(FILE *f, size_t limit, char **ret) {
 
                 for (;;) {
                         EndOfLineMarker eol;
-                        int c;
+                        char c;
 
                         if (n >= limit)
                                 return -ENOBUFS;
@@ -778,32 +931,43 @@ int read_line(FILE *f, size_t limit, char **ret) {
                         if (count >= INT_MAX) /* We couldn't return the counter anymore as "int", hence refuse this */
                                 return -ENOBUFS;
 
-                        errno = 0;
-                        c = fgetc_unlocked(f);
-                        if (c == EOF) {
-                                /* if we read an error, and have no data to return, then propagate the error */
-                                if (ferror_unlocked(f) && n == 0)
-                                        return errno > 0 ? -errno : -EIO;
-
-                                /* EOF is line ending too. */
+                        r = safe_fgetc(f, &c);
+                        if (r < 0)
+                                return r;
+                        if (r == 0) /* EOF is definitely EOL */
                                 break;
-                        }
-
-                        count++;
 
-                        eol = categorize_eol(c);
+                        eol = categorize_eol(c, flags);
 
                         if (FLAGS_SET(previous_eol, EOL_ZERO) ||
                             (eol == EOL_NONE && previous_eol != EOL_NONE) ||
                             (eol != EOL_NONE && (previous_eol & eol) != 0)) {
                                 /* Previous char was a NUL? This is not an EOL, but the previous char was? This type of
-                                 * EOL marker has been seen right before? In either of these three cases we are
-                                 * done. But first, let's put this character back in the queue. */
-                                assert_se(ungetc(c, f) != EOF);
-                                count--;
+                                 * EOL marker has been seen right before?  In either of these three cases we are
+                                 * done. But first, let's put this character back in the queue. (Note that we have to
+                                 * cast this to (unsigned char) here as ungetc() expects a positive 'int', and if we
+                                 * are on an architecture where 'char' equals 'signed char' we need to ensure we don't
+                                 * pass a negative value here. That said, to complicate things further ungetc() is
+                                 * actually happy with most negative characters and implicitly casts them back to
+                                 * positive ones as needed, except for \xff (aka -1, aka EOF), which it refuses. What a
+                                 * godawful API!) */
+                                assert_se(ungetc((unsigned char) c, f) != EOF);
                                 break;
                         }
 
+                        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;
@@ -813,7 +977,7 @@ int read_line(FILE *f, size_t limit, char **ret) {
                                 if (!GREEDY_REALLOC(buffer, allocated, n + 2))
                                         return -ENOMEM;
 
-                                buffer[n] = (char) c;
+                                buffer[n] = c;
                         }
 
                         n++;
@@ -828,3 +992,55 @@ int read_line(FILE *f, size_t limit, char **ret) {
 
         return (int) count;
 }
+
+int safe_fgetc(FILE *f, char *ret) {
+        int k;
+
+        assert(f);
+
+        /* A safer version of plain fgetc(): let's propagate the error that happened while reading as such, and
+         * separate the EOF condition from the byte read, to avoid those confusion signed/unsigned issues fgetc()
+         * has. */
+
+        errno = 0;
+        k = fgetc(f);
+        if (k == EOF) {
+                if (ferror(f))
+                        return errno_or_else(EIO);
+
+                if (ret)
+                        *ret = 0;
+
+                return 0;
+        }
+
+        if (ret)
+                *ret = k;
+
+        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;
+}