aboutsummaryrefslogtreecommitdiff
path: root/lib/io_dirfd.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/io_dirfd.c')
-rw-r--r--lib/io_dirfd.c276
1 files changed, 276 insertions, 0 deletions
diff --git a/lib/io_dirfd.c b/lib/io_dirfd.c
new file mode 100644
index 0000000..252658c
--- /dev/null
+++ b/lib/io_dirfd.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#define _GNU_SOURCE
+#include <hkvs.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/mman.h>
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+typedef struct hkvs_dirfd hkvs_dirfd;
+struct hkvs_dirfd {
+ hkvs_io base;
+ int fd;
+ uint64_t create_fi;
+};
+
+static const char *const base32 = "ABCDEFGHIJKLMNOPQRSTUVWXTZ234567";
+
+HKVS_INLINE bool hkvs_dirfd_check_size(size_t size) {
+ return (size_t)(off64_t)size == size && (off64_t)size >= 0;
+}
+
+static void hkvs_dirfd_name(char *name, uint64_t fi) {
+ name[0] = 'd';
+ name[1] = 'a';
+ name[2] = 't';
+ name[3] = 'a';
+ name[4] = '.';
+ name[18] = 0;
+ {
+ uint64_t val = fi;
+ for(size_t i = 0; i < 13; i++) {
+ name[17 - i] = base32[val % 32];
+ val /= 32;
+ }
+ }
+}
+
+static void hkvs_dirfd_destroy(hkvs_io *iov) {
+ hkvs_dirfd *io = (hkvs_dirfd*)iov;
+ (void) close(io->fd);
+ free(io);
+}
+
+static int hkvs_dirfd_random(hkvs_io *iov, char *buf, size_t n) {
+ (void)iov;
+ while(n) {
+ ssize_t r = getrandom(buf, n, 0);
+ if(r < 0) return -errno;
+ size_t k = (size_t)r;
+ buf += k;
+ n -= k;
+ }
+ return 0;
+}
+
+static int hkvs_dirfd_open(hkvs_io *iov, uint64_t fi, char **pmem, size_t *psize) {
+ hkvs_dirfd *io = (hkvs_dirfd*)iov;
+
+ int r;
+ if(fi == 0) {
+ r = openat(io->fd, "main", O_RDWR | O_CREAT | O_CLOEXEC, 0666);
+ } else {
+ char name[32];
+ hkvs_dirfd_name(name, fi);
+ r = openat(io->fd, name, O_RDWR | O_CLOEXEC, 0666);
+ }
+ if(r < 0) return -errno;
+ int fd = r;
+
+ off64_t fsize = lseek64(fd, 0, SEEK_END);
+ size_t size = (size_t)fsize;
+ if((off64_t)size != fsize) {
+ r = -EFBIG;
+ goto end;
+ }
+
+ void *addr;
+ if(size == 0) {
+ addr = NULL;
+ } else {
+ addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if(addr == MAP_FAILED) {
+ r = -errno;
+ goto end;
+ }
+ }
+
+ *psize = size;
+ *pmem = addr;
+ r = 0;
+
+end:
+ close(fd);
+ return r;
+}
+
+static int hkvs_dirfd_create(hkvs_io *iov, uint64_t *pfi, char **pmem, size_t size) {
+ hkvs_dirfd *io = (hkvs_dirfd*)iov;
+ uint64_t fi = io->create_fi;
+ if(fi == 0) abort();
+
+ if(!hkvs_dirfd_check_size(size)) return -EFBIG;
+
+ int fd;
+ char name[32];
+ hkvs_dirfd_name(name, fi);
+ while(true) {
+ int r = openat(io->fd, name, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0666);
+ if(r >= 0) {
+ fd = r;
+ break;
+ }
+ r = errno;
+ if(r != EEXIST) return -r;
+ fi++;
+ uint64_t val = fi;
+ char *p = name + 17;
+ while(true) {
+ *p = base32[val % 32];
+ if(val % 32 != 0) break;
+ val /= 32;
+ p--;
+ }
+ }
+
+ io->create_fi = fi + 1;
+
+ int r = ftruncate64(fd, (off64_t)size);
+ if(r < 0) {
+ r = -errno;
+ goto fail;
+ return r;
+ }
+
+ void *addr;
+ if(size == 0) {
+ addr = NULL;
+ } else {
+ addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if(addr == MAP_FAILED) {
+ r = -errno;
+ goto fail;
+ }
+ }
+
+ *pfi = fi;
+ *pmem = addr;
+ r = 0;
+
+end:
+ close(fd);
+ return r;
+
+fail:
+ (void) unlinkat(io->fd, name, 0);
+ goto end;
+}
+
+static void hkvs_dirfd_close(hkvs_io *iov, uint64_t fi, char *pmem, size_t size) {
+ (void)iov;
+ (void)fi;
+
+ if(size == 0) return;
+
+ int r = munmap(pmem, size);
+ if(r < 0) {
+ r = errno;
+ // TODO
+ (void) r;
+ abort();
+ }
+}
+
+static int hkvs_dirfd_resize(hkvs_io *iov, uint64_t fi, char **pmem, size_t old_size, size_t new_size) {
+ hkvs_dirfd *io = (hkvs_dirfd*)iov;
+
+ if(!hkvs_dirfd_check_size(new_size)) return -EFBIG;
+
+ int r;
+ if(fi == 0) {
+ r = openat(io->fd, "main", O_RDWR | O_CREAT | O_CLOEXEC, 0666);
+ } else {
+ char name[32];
+ hkvs_dirfd_name(name, fi);
+ r = openat(io->fd, name, O_RDWR | O_CLOEXEC, 0666);
+ }
+ if(r < 0) return -errno;
+ int fd = r;
+
+ void *addr = *pmem;
+ if(new_size > old_size) {
+ r = ftruncate64(fd, (off64_t)new_size);
+ if(r < 0) goto fail;
+
+ if(addr) {
+ addr = mremap(addr, old_size, new_size, MREMAP_MAYMOVE);
+ } else {
+ addr = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ }
+ if(addr == MAP_FAILED) {
+ r = -errno;
+ (void) ftruncate64(fd, (off64_t)old_size);
+ goto end;
+ }
+ *pmem = addr;
+ } else if(new_size > old_size) {
+ if(new_size == 0) {
+ munmap(addr, old_size);
+ addr = NULL;
+ } else {
+ addr = mremap(addr, old_size, new_size, MREMAP_MAYMOVE);
+ if(addr == MAP_FAILED) goto fail;
+ }
+
+ r = ftruncate64(fd, (off64_t)old_size);
+ if(r < 0) {
+ // TODO
+ (void) r;
+ }
+
+ *pmem = addr;
+ }
+
+ r = 0;
+
+end:
+ close(fd);
+ return r;
+
+fail:
+ r = -errno;
+ goto end;
+}
+
+static void hkvs_dirfd_unlink(hkvs_io *iov, uint64_t fi) {
+ hkvs_dirfd *io = (hkvs_dirfd*)iov;
+
+ if(fi == 0) abort();
+
+ char name[32];
+ hkvs_dirfd_name(name, fi);
+ int r = unlinkat(io->fd, name, 0);
+ if(r < 0) {
+ r = errno;
+ // TODO
+ (void) r;
+ } else {
+ if(fi < io->create_fi) {
+ io->create_fi = fi;
+ }
+ }
+}
+
+hkvs_io *hkvs_io_new_unsafe_dirfd(int fd) {
+ hkvs_dirfd *io = malloc(sizeof(*io));
+ if(!io) return NULL;
+ io->base = (hkvs_io){
+ .destroy = &hkvs_dirfd_destroy,
+ .random = &hkvs_dirfd_random,
+ .open = &hkvs_dirfd_open,
+ .create = &hkvs_dirfd_create,
+ .resize = &hkvs_dirfd_resize,
+ .close = &hkvs_dirfd_close,
+ .unlink = &hkvs_dirfd_unlink,
+ };
+ io->fd = fd;
+ io->create_fi = 1;
+ return &io->base;
+}