diff options
Diffstat (limited to 'lib/io_dirfd.c')
-rw-r--r-- | lib/io_dirfd.c | 276 |
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; +} |