// SPDX-License-Identifier: LGPL-3.0-or-later #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include 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; }