rcx

miscellaneous C library
git clone git://git.rr3.xyz/rcx
Log | Files | Refs | README | LICENSE

commit 0061a11e9a21e9e15aa44712406a47fe9fd1584d
parent 7a550f904f2c652523c10fb2d493b1d66b0b7797
Author: Robert Russell <robert@rr3.xyz>
Date:   Mon, 11 Nov 2024 18:24:09 -0800

Refine vmem module

Diffstat:
Minc/vmem.h | 54+++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/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)); }