From bbcf78193f95af363a78c0a74b331a76ebdf140f Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Thu, 6 Jul 2023 13:49:46 +0200 Subject: [PATCH] util/proc: add proc_read() helper Add another proc-fs helper proc_read() which reads an entire proc-fs file into memory. It is designed for virtual files of proc-fs and follows the exact requirements of them. Signed-off-by: David Rheinsberg --- src/util/proc.c | 74 ++++++++++++++++++++++++++++++++++++++++++++ src/util/proc.h | 1 + src/util/test-proc.c | 34 ++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/src/util/proc.c b/src/util/proc.c index 77bdbf96..adbd1725 100644 --- a/src/util/proc.c +++ b/src/util/proc.c @@ -64,6 +64,80 @@ int proc_field(const char *data, const char *key, char **valuep) { return 0; } +/** + * proc_read() - Read a proc-file into memory + * @fd: file-descriptor to a file in procfs + * @datap: output variable for the read data + * @n_datap: output variable for the length of the data blob + * + * Read the entire proc-fs file given as @fd into memory and return it to the + * caller. This will always read from file position 0 regardless of the current + * file position. + * + * The resulting data block is always terminated by a binary zero. This allows + * string operations on the data blob without any length checks. @n_datap will + * not include this sentinal zero, unless it was actually part of the file. + * + * Note that standard procfs files cannot exceed 4M-1 in size, and their API + * implementation actually limits it to 4M-2. + * + * If the proc-fs file in question does not follow the standard proc-fs rules, + * the caller should be aware of the limitations of this function. + * + * It is the responsibility of the caller to free the data via `free()`. + * + * Return: 0 on success, negative error code on failure. + */ +int proc_read(int fd, char **datap, size_t *n_datap) { + _c_cleanup_(c_freep) char *data = NULL; + ssize_t l; + + data = malloc(PROC_SIZE_MIN); + if (!data) + return error_origin(-ENOMEM); + + l = pread(fd, data, PROC_SIZE_MIN, 0); + if (l < 0) + return error_origin(-errno); + + /* + * Proc never returns short reads unless end-of-file was reached. Thus, + * a short read implies end-of-file. Furthermore, in case the proc file + * is backed by a direct driver read, it might always return fresh data + * on each read, as if we used `pread(..., 0)`. Hence, we rely on short + * reads to know how long the file was. + * + * Lastly, note that we cannot ever attempt a read longer than + * PROC_SIZE_MAX, since it would be immediately refused by the kernel. + * So the longest successful read we can return to the caller is + * actually `PROC_SIZE_MAX - 1`, otherwise we wouldn't know whether it + * was complete. + */ + if (l >= (ssize_t)PROC_SIZE_MIN) { + data = c_free(data); + data = malloc(PROC_SIZE_MAX); + if (!data) + return error_origin(-ENOMEM); + + l = pread(fd, data, PROC_SIZE_MAX, 0); + if (l < 0) + return error_origin(-errno); + if (l >= (ssize_t)PROC_SIZE_MAX) + return error_origin(-E2BIG); + } + + /* Ensure a terminating 0 to allow direct searches of text-files. */ + data[l] = 0; + + if (datap) { + *datap = data; + data = NULL; + } + if (n_datap) + *n_datap = (size_t)l; + return 0; +} + int proc_get_seclabel(pid_t pid, char **labelp, size_t *n_labelp) { _c_cleanup_(c_fclosep) FILE *f = NULL; char path[64], buffer[LINE_MAX] = {}, *c, *label; diff --git a/src/util/proc.h b/src/util/proc.h index 6983948c..24e8544a 100644 --- a/src/util/proc.h +++ b/src/util/proc.h @@ -17,5 +17,6 @@ enum { }; int proc_field(const char *data, const char *key, char **valuep); +int proc_read(int fd, char **datap, size_t *n_datap); int proc_get_seclabel(pid_t pid, char **labelp, size_t *n_labelp); diff --git a/src/util/test-proc.c b/src/util/test-proc.c index fd8bbffb..7063a117 100644 --- a/src/util/test-proc.c +++ b/src/util/test-proc.c @@ -7,6 +7,7 @@ #include #include "util/proc.h" #include "util/string.h" +#include "util/syscall.h" static void test_field(void) { char *value; @@ -51,7 +52,40 @@ static void test_field(void) { c_assert(r == PROC_E_NOT_FOUND); } +static void test_read(void) { + _c_cleanup_(c_closep) int fd = -1; + const char *str = "01234567"; + size_t i, n_data; + char *data; + ssize_t l; + int r; + + fd = syscall_memfd_create("test-proc", 0x1); + c_assert(fd >= 0); + + for (i = 0; i < 1024; i += strlen(str)) { + l = pwrite(fd, str, strlen(str), i); + c_assert(l == 8); + } + + r = proc_read(fd, &data, &n_data); + c_assert(!r); + c_assert(n_data == 1024); + c_free(data); + + for ( ; i < 8192; i += strlen(str)) { + l = pwrite(fd, str, strlen(str), i); + c_assert(l == 8); + } + + r = proc_read(fd, &data, &n_data); + c_assert(!r); + c_assert(n_data == 8192); + c_free(data); +} + int main(int argc, char **argv) { test_field(); + test_read(); return 0; }