// 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;
}