commit 0061a11e9a21e9e15aa44712406a47fe9fd1584d
parent 7a550f904f2c652523c10fb2d493b1d66b0b7797
Author: Robert Russell <robert@rr3.xyz>
Date: Mon, 11 Nov 2024 18:24:09 -0800
Refine vmem module
Diffstat:
| M | inc/vmem.h | | | 54 | +++++++++++++++++++++++++++++++++++++++++++++--------- |
| M | src/vmem.c | | | 115 | +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- |
2 files changed, 122 insertions(+), 47 deletions(-)
diff --git a/inc/vmem.h b/inc/vmem.h
@@ -4,12 +4,48 @@
#include "def.h"
-/* TODO: Before next usage of this module, document each function and
- * add r_ prefixes. */
-
-usize vmem_page_size(void);
-void *vmem_alloc(void *p, usize size);
-void *vmem_reserve(void *p, usize size, bool swap);
-// TODO: using len as an in and out parameter is awful
-void *vmem_open(usize *len, void *p, char *path, char *opt);
-void vmem_free(void *p, usize size);
+/* Get the system page size. Crash if the page size could not be determined or
+ * is not a power of two. */
+usize r_vmem_page_size(void);
+
+/* Allocate a page-aligned block of size bytes with read and write permission
+ * and return a pointer to them. If p is not NULL, place the mapping there (so
+ * the return value is p on success) (e.g., p could come from r_vmem_reserve).
+ * On failure, set errno and return NULL. */
+void *r_vmem_alloc(void *p, usize size);
+
+/* Reserve a page-aligned block of size bytes and return a pointer to them. If
+ * p is not NULL, place the mapping there (so the return value is p on
+ * success). When swap is false, only reserve virtual memory; when swap is
+ * true, also reserve swap space. In either case, never allocate physical
+ * memory; see r_vmem_alloc for this functionality. On failure, set errno and
+ * return NULL. */
+void *r_vmem_reserve(void *p, usize size, bool swap);
+
+/* Open and memory-map the file referred to by path. If p is not NULL, place
+ * the mapping there (so the return value is p on success). If target_size > 0,
+ * make the mapping have that size by ignoring trailing bytes if target_size
+ * is less than the file size or by appending null bytes if target_size is
+ * greater then the file size. If size is not NULL, set *size to the mapping
+ * size (which is always target_size if target_size > 0). The presence of the
+ * following characters in opt (in any order) enable certain boolean options:
+ * - 'c': Create the file if it does not exist. If 'c' is set, then an
+ * additional mode argument is mandatory (like open(2)).
+ * - 'r': Create the mapping with read permission.
+ * - 's': Create a shared mapping instead of a private mapping.
+ * - 't': Truncate the file to length 0. If 't' is set, then target_size must
+ * be positive.
+ * - 'w': Create the mapping with write permission.
+ * - 'x': Ensure that this call creates the file. This option implies the 'c'
+ * option (so, in particular, if 'x' is set, then the mode argument is
+ * mandatory).
+ * (Note that the 'c', 't', and 'x' options basically correspond respectively
+ * to the O_CREAT, O_TRUNC, and O_EXCL flags for open(2). Similarly, 's', 'r',
+ * and 'w' correspond respectively to MAP_SHARED (as opposed to MAP_PRIVATE),
+ * PROT_READ, and PROT_WRITE for mmap(2).) On failure, set errno and return
+ * NULL. */
+void *r_vmem_open(usize *size, void *p, char *path, usize target_size, char *opt, ... /* mode_t mode */);
+
+/* Unmap the mapped region described by p and size, which should have been
+ * obtained by a previous call to r_vmem_{alloc,reserve,open}. */
+void r_vmem_free(void *p, usize size);
diff --git a/src/vmem.c b/src/vmem.c
@@ -10,45 +10,61 @@
#include "rcx.h"
#include "vmem.h"
+#define ASSERT_ALIGNED(p, func) \
+ ASSERT(((uptr)p & (uptr)(r_vmem_page_size() - 1)) == 0, \
+ func ": misaligned pointer");
+
usize
-vmem_page_size(void) {
+r_vmem_page_size(void) {
long ps = sysconf(_SC_PAGE_SIZE);
+
REQUIRE(ps > 0,
- "vmem_page_size: unable to determine page size");
+ "r_vmem_page_size: unable to determine page size");
+
REQUIRE((ps & (ps - 1)) == 0,
- "vmem_page_size: page size not a power of 2");
+ "r_vmem_page_size: page size not a power of 2");
+
return ps;
}
void *
-vmem_alloc(void *p, usize size) {
- ASSERT(((uptr)p & (uptr)(vmem_page_size() - 1)) == 0,
- "vmem_alloc: misaligned pointer");
+r_vmem_alloc(void *p, usize size) {
+ ASSERT_ALIGNED(p, "r_vmem_alloc");
+
int flags = MAP_PRIVATE | MAP_ANONYMOUS
| (p ? MAP_FIXED : 0);
+
void *q = mmap(p, size, PROT_READ | PROT_WRITE, flags, -1, 0);
if (q == MAP_FAILED) return 0;
+
return q;
}
void *
-vmem_reserve(void *p, usize size, bool swap) {
- ASSERT(((uptr)p & (uptr)(vmem_page_size() - 1)) == 0,
- "vmem_reserve: misaligned pointer");
+r_vmem_reserve(void *p, usize size, bool swap) {
+ ASSERT_ALIGNED(p, "r_vmem_reserve");
+
int flags = MAP_PRIVATE | MAP_ANONYMOUS
| (p ? MAP_FIXED : 0)
| (!swap ? MAP_NORESERVE : 0);
+
void *q = mmap(p, size, PROT_NONE, flags, -1, 0);
if (q == MAP_FAILED) return 0;
+
return q;
}
void *
-vmem_open(usize *len, void *p, char *path, char *opt) {
- ASSERT(((uptr)p & (uptr)(vmem_page_size() - 1)) == 0,
- "vmem_open: misaligned pointer");
- ASSERT(strspn(opt, "crstwx") == strlen(opt),
- "vmem_open: invalid option");
+r_vmem_open(
+ usize *size, void *p, char *path,
+ usize target_size, char *opt, ... /* mode_t mode */
+) {
+ ASSERT_ALIGNED(p, "r_vmem_open");
+
+ if (strspn(opt, "crstwx") != strlen(opt)) {
+ errno = EINVAL;
+ return 0;
+ }
bool c = !!strchr(opt, 'c');
bool r = !!strchr(opt, 'r');
@@ -56,51 +72,74 @@ vmem_open(usize *len, void *p, char *path, char *opt) {
bool t = !!strchr(opt, 't');
bool w = !!strchr(opt, 'w');
bool x = !!strchr(opt, 'x');
+ if (x) c = true;
- struct stat sb;
- if (stat(path, &sb) < 0) return 0;
- bool grow = *len > sb.st_size;
+ if (t && target_size == 0) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ mode_t mode = 0;
+ if (c) {
+ va_list args;
+ va_start(args, opt);
+ mode = va_arg(args, mode_t);
+ va_end(args);
+ }
int oflags = O_CLOEXEC
- | (c || x ? O_CREAT : 0)
+ | (c ? O_CREAT : 0)
| (t ? O_TRUNC : 0)
| (x ? O_EXCL : 0)
- | ((w && s) || t || grow ? O_RDWR : O_RDONLY);
+ | ((w && s) || t || target_size > 0 ? O_RDWR : O_RDONLY);
int prot = r || w
? (r ? PROT_READ : 0) | (w ? PROT_WRITE : 0)
: PROT_NONE;
int mflags = (s ? MAP_SHARED : MAP_PRIVATE)
| (p ? MAP_FIXED : 0);
- int fd = open(path, oflags, 0666);
+ int fd = open(path, oflags, mode);
if (fd < 0) return 0;
- if (grow) {
- if (ftruncate(fd, *len) < 0) {
- int e = errno;
- if (close(fd) < 0)
- r_errorf("vmem_open: close: %s", strerror(errno));
- errno = e;
- return 0;
- }
+ struct stat sb;
+ if (fstat(fd, &sb) < 0) goto fail_after_open;
+
+ /* Sanity check */
+ ASSERT(!t || sb.st_size == 0,
+ "r_vmem_open: expected st_size == 0 after open with O_TRUNC");
+
+ if (target_size > sb.st_size) {
+ if (ftruncate(fd, target_size) < 0) goto fail_after_open;
}
- void *q = mmap(p, *len == 0 ? sb.st_size : *len, prot, mflags, fd, 0);
- int e = errno;
- if (close(fd) < 0)
- r_errorf("vmem_open: close: %s", strerror(errno));
- errno = e;
+ usize mapping_size = target_size > 0 ? target_size : sb.st_size;
+ void *q = mmap(p, mapping_size, prot, mflags, fd, 0);
+ {
+ int e = errno;
+ if (close(fd) < 0)
+ r_errorf("r_vmem_open: close: %s", strerror(errno));
+ errno = e;
+ }
if (q == MAP_FAILED) return 0;
- if (*len == 0) *len = sb.st_size;
+ if (size) *size = mapping_size;
return q;
+
+fail_after_open:
+ {
+ int e = errno;
+ if (close(fd) < 0)
+ r_errorf("r_vmem_open: close: %s", strerror(errno));
+ errno = e;
+ }
+ return 0;
}
void
-vmem_free(void *p, usize size) {
- ASSERT(((uptr)p & (uptr)(vmem_page_size() - 1)) == 0,
- "vmem_free: misaligned pointer");
+r_vmem_free(void *p, usize size) {
+ ASSERT_ALIGNED(p, "r_vmem_free");
+
int ret = munmap(p, size);
/* munmap should never fail. */
- if (ret < 0) r_errorf("vmem_free: munmap: %s", strerror(errno));
+ if (ret < 0) r_errorf("r_vmem_free: munmap: %s", strerror(errno));
}