Skip to content

Commit

Permalink
util/proc: add proc_read() helper
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dvdhrm committed Jul 6, 2023
1 parent 6baba31 commit bbcf781
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
74 changes: 74 additions & 0 deletions src/util/proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/util/proc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
34 changes: 34 additions & 0 deletions src/util/test-proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdlib.h>
#include "util/proc.h"
#include "util/string.h"
#include "util/syscall.h"

static void test_field(void) {
char *value;
Expand Down Expand Up @@ -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;
}

0 comments on commit bbcf781

Please sign in to comment.