aboutsummaryrefslogblamecommitdiff
path: root/lib/io_dirfd.c
blob: 252658c320fe35b29fa66af2414b78a8aeab9cbe (plain) (tree)



















































































































































































































































































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