mirror of
https://git.sr.ht/~cismonx/bookmarkfs
synced 2025-07-16 14:18:54 +00:00
284 lines
6.9 KiB
C
284 lines
6.9 KiB
C
/**
|
|
* bookmarkfs/src/json.c
|
|
* ----
|
|
*
|
|
* Copyright (C) 2024 CismonX <admin@cismon.net>
|
|
*
|
|
* This file is part of BookmarkFS.
|
|
*
|
|
* BookmarkFS is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* BookmarkFS is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with BookmarkFS. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "json.h"
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/uio.h>
|
|
#include <unistd.h>
|
|
|
|
#include "prng.h"
|
|
#include "xstd.h"
|
|
|
|
#define DUMP_BUFSIZE ( 32 * 1024 )
|
|
struct dump_ctx {
|
|
int fd;
|
|
size_t blksize;
|
|
size_t data_len;
|
|
char buf[DUMP_BUFSIZE];
|
|
};
|
|
|
|
// Forward declaration start
|
|
static int dump_cb (char const *, size_t, void *);
|
|
static int write_iov (int, struct iovec *, int);
|
|
// Forward declaration end
|
|
|
|
static int
|
|
dump_cb (
|
|
char const *buf,
|
|
size_t buf_len,
|
|
void *user_data
|
|
) {
|
|
struct dump_ctx *ctx = user_data;
|
|
|
|
size_t new_len = ctx->data_len + buf_len;
|
|
if (new_len <= DUMP_BUFSIZE) {
|
|
memcpy(ctx->buf + ctx->data_len, buf, buf_len);
|
|
ctx->data_len = new_len;
|
|
return 0;
|
|
}
|
|
new_len %= ctx->blksize;
|
|
buf_len -= new_len;
|
|
|
|
struct iovec bufv[] = {
|
|
{ .iov_base = ctx->buf, .iov_len = ctx->data_len },
|
|
{ .iov_base = (char *)buf, .iov_len = buf_len },
|
|
};
|
|
if (0 != write_iov(ctx->fd, bufv, 2)) {
|
|
return -1;
|
|
}
|
|
memcpy(ctx->buf, buf + buf_len, new_len);
|
|
ctx->data_len = new_len;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
write_iov (
|
|
int fd,
|
|
struct iovec *bufv,
|
|
int bufcnt
|
|
) {
|
|
while (1) {
|
|
ssize_t nbytes = writev(fd, bufv, bufcnt);
|
|
if (unlikely(nbytes < 0)) {
|
|
int err;
|
|
log_printf("writev(): %s", xstrerror_save(&err));
|
|
|
|
switch (err) {
|
|
case EIO:
|
|
#ifdef __FreeBSD__
|
|
case EINTEGRITY:
|
|
#endif
|
|
abort();
|
|
|
|
case EINTR:
|
|
continue;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
while ((size_t)nbytes >= bufv->iov_len) {
|
|
nbytes -= (bufv++)->iov_len;
|
|
if (--bufcnt == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
bufv->iov_base = (char *)(bufv->iov_base) + nbytes;
|
|
bufv->iov_len -= nbytes;
|
|
}
|
|
}
|
|
|
|
size_t
|
|
json_array_search (
|
|
json_t const *haystack,
|
|
json_t const *needle
|
|
) {
|
|
size_t idx;
|
|
#ifdef BOOKMARKFS_DEBUG
|
|
size_t cnt = json_array_size(haystack);
|
|
#endif
|
|
for (idx = 0; needle != json_array_get(haystack, idx); ++idx) {
|
|
#ifdef BOOKMARKFS_DEBUG
|
|
xassert(idx < cnt);
|
|
#endif
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
int
|
|
json_dump_file_at (
|
|
json_t const *json,
|
|
int dirfd,
|
|
char const *name,
|
|
size_t flags
|
|
) {
|
|
#define TMPNAME_MAX ( NAME_MAX + 21 ) // strlen(".0123456789abcdef.tmp")
|
|
char buf[TMPNAME_MAX + 1];
|
|
|
|
again: ;
|
|
int name_len = snprintf(buf, TMPNAME_MAX + 1, "%s.%016" PRIx64 ".tmp",
|
|
name, prng_rand());
|
|
if (unlikely(name_len > TMPNAME_MAX || name_len < 0)) {
|
|
errno = ENAMETOOLONG;
|
|
goto open_fail;
|
|
}
|
|
char const *tmp_name = buf;
|
|
if (name_len > NAME_MAX) {
|
|
tmp_name += NAME_MAX - name_len;
|
|
}
|
|
|
|
int fd = openat(dirfd, tmp_name,
|
|
O_CREAT | O_WRONLY | O_EXCL | O_RESOLVE_BENEATH, 0600);
|
|
if (fd < 0) {
|
|
if (unlikely(errno == EEXIST)) {
|
|
goto again;
|
|
}
|
|
|
|
open_fail:
|
|
log_printf("openat(): %s", xstrerror(errno));
|
|
return -1;
|
|
}
|
|
int status = -1;
|
|
|
|
if (0 != json_dumpfd_ex(json, fd, flags)) {
|
|
goto fail;
|
|
}
|
|
if (0 != renameat(dirfd, tmp_name, dirfd, name)) {
|
|
log_printf("renameat(): %s", xstrerror(errno));
|
|
goto fail;
|
|
}
|
|
if (unlikely(0 != xfsync(dirfd))) {
|
|
goto fail;
|
|
}
|
|
status = 0;
|
|
goto end;
|
|
|
|
fail:
|
|
unlinkat(dirfd, tmp_name, 0);
|
|
|
|
end:
|
|
close(fd);
|
|
return status;
|
|
}
|
|
|
|
int
|
|
json_dumpfd_ex (
|
|
json_t const *json,
|
|
int fd,
|
|
size_t flags
|
|
) {
|
|
struct stat stat_buf;
|
|
if (unlikely(0 != fstat(fd, &stat_buf))) {
|
|
log_printf("fstat(): %s", xstrerror(errno));
|
|
return -1;
|
|
}
|
|
size_t blksize = stat_buf.st_blksize;
|
|
if (unlikely(blksize == 0 || DUMP_BUFSIZE % blksize != 0)) {
|
|
debug_printf("unexpected st_blksize: %zu", blksize);
|
|
blksize = DUMP_BUFSIZE;
|
|
}
|
|
|
|
struct dump_ctx ctx;
|
|
ctx.fd = fd,
|
|
ctx.blksize = blksize,
|
|
ctx.data_len = 0;
|
|
if (0 != json_dump_callback(json, dump_cb, &ctx, flags)) {
|
|
log_puts("json_dump_callback() failed");
|
|
return -1;
|
|
}
|
|
if (ctx.data_len > 0) {
|
|
struct iovec buf = {
|
|
.iov_base = ctx.buf,
|
|
.iov_len = ctx.data_len,
|
|
};
|
|
if (0 != write_iov(fd, &buf, 1)) {
|
|
return -1;
|
|
}
|
|
}
|
|
if (unlikely(0 != xfsync(fd))) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
json_t *
|
|
json_load_file_at (
|
|
int dirfd,
|
|
char const *name,
|
|
size_t flags
|
|
) {
|
|
int fd = openat(dirfd, name, O_RDONLY | O_RESOLVE_BENEATH);
|
|
if (fd < 0) {
|
|
log_printf("openat(): %s", xstrerror(errno));
|
|
return NULL;
|
|
}
|
|
json_t *result = NULL;
|
|
|
|
struct stat stat_buf;
|
|
if (unlikely(0 != fstat(fd, &stat_buf))) {
|
|
log_printf("fstat(): %s", xstrerror(errno));
|
|
goto end;
|
|
}
|
|
void *buf = mmap(NULL, stat_buf.st_size, PROT_READ | PROT_MAX(PROT_READ),
|
|
MAP_PRIVATE, fd, 0);
|
|
if (unlikely(buf == MAP_FAILED)) {
|
|
log_printf("mmap(): %s", xstrerror(errno));
|
|
goto end;
|
|
}
|
|
xassert(0 == posix_madvise(buf, stat_buf.st_size, POSIX_MADV_SEQUENTIAL));
|
|
|
|
json_error_t err;
|
|
// XXX: If the file is truncated before json_loadb() returns,
|
|
// SIGBUS may be delivered, terminating the process.
|
|
//
|
|
// A possible workaround is to siglongjmp() back from a signal handler,
|
|
// and release any memory allocated from within json_loadb().
|
|
//
|
|
// We choose not to implement such hacks, since the common practice
|
|
// (that Chromium and we ourselves are doing for bookmark files)
|
|
// is to write to a temporary file and then rename to the target path,
|
|
// which does not cause the aforementioned problem.
|
|
result = json_loadb(buf, stat_buf.st_size, flags, &err);
|
|
if (result == NULL) {
|
|
log_printf("json_loadb(): %s", err.text);
|
|
}
|
|
munmap(buf, stat_buf.st_size);
|
|
|
|
end:
|
|
close(fd);
|
|
return result;
|
|
}
|