mirror of
https://git.sr.ht/~cismonx/bookmarkfs
synced 2025-06-07 19:58:50 +00:00
403 lines
12 KiB
C
403 lines
12 KiB
C
/**
|
|
* bookmarkfs/src/fsck_handler_tcl.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 <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <tcl.h>
|
|
|
|
#include "backend_util.h"
|
|
#include "fsck_handler.h"
|
|
#include "macros.h"
|
|
#include "version.h"
|
|
#include "xstd.h"
|
|
|
|
#define FSCK_HANDLER_UNSAFE ( 1u << 16 )
|
|
#define FSCK_HANDLER_EXPECT_INPUT ( 1u << 17 )
|
|
#define FSCK_HANDLER_INITIALIZED ( 1u << 18 )
|
|
|
|
struct handler_ctx {
|
|
Tcl_Interp *interp;
|
|
uint32_t flags;
|
|
|
|
union {
|
|
Tcl_Obj *obj;
|
|
int fd;
|
|
} script;
|
|
};
|
|
|
|
struct parsed_opts {
|
|
char const *script;
|
|
uint32_t flags;
|
|
};
|
|
|
|
// Forward declaration start
|
|
static void finalize_tcl (void) FUNCATTR_DTOR;
|
|
static int init_handler (struct handler_ctx *);
|
|
static Tcl_Interp * init_interp (uint32_t);
|
|
static int parse_opts (struct bookmarkfs_conf_opt const *,
|
|
struct parsed_opts *);
|
|
static void print_help (void);
|
|
static void print_version (void);
|
|
static int set_tcl_var (Tcl_Interp *, char const *, size_t, int);
|
|
// Forward declaration end
|
|
|
|
static void
|
|
finalize_tcl (void)
|
|
{
|
|
Tcl_Finalize();
|
|
}
|
|
|
|
static int
|
|
init_handler (
|
|
struct handler_ctx *ctx
|
|
) {
|
|
if (ctx->flags & FSCK_HANDLER_INITIALIZED) {
|
|
return 0;
|
|
}
|
|
|
|
struct stat stat_buf;
|
|
if (unlikely(0 != fstat(ctx->script.fd, &stat_buf))) {
|
|
log_printf("fstat(): %s", xstrerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
void *buf = mmap(NULL, stat_buf.st_size, PROT_READ | PROT_MAX(PROT_READ),
|
|
MAP_PRIVATE, ctx->script.fd, 0);
|
|
if (unlikely(buf == MAP_FAILED)) {
|
|
log_printf("mmap(): %s", xstrerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
Tcl_Interp *interp = ctx->interp;
|
|
int result = Tcl_EvalEx(interp, buf, stat_buf.st_size, TCL_EVAL_GLOBAL);
|
|
munmap(buf, stat_buf.st_size);
|
|
if (result != TCL_OK) {
|
|
log_puts(Tcl_GetStringResult(interp));
|
|
return -1;
|
|
}
|
|
close(ctx->script.fd);
|
|
ctx->script.obj = Tcl_GetObjResult(interp);
|
|
Tcl_IncrRefCount(ctx->script.obj);
|
|
|
|
ctx->flags |= FSCK_HANDLER_INITIALIZED;
|
|
return 0;
|
|
}
|
|
|
|
static Tcl_Interp *
|
|
init_interp (
|
|
uint32_t flags
|
|
) {
|
|
Tcl_Interp *interp = Tcl_CreateInterp();
|
|
if (flags & FSCK_HANDLER_UNSAFE) {
|
|
if (TCL_OK != Tcl_Init(interp)) {
|
|
log_puts(Tcl_GetStringResult(interp));
|
|
goto fail;
|
|
}
|
|
Tcl_InitMemory(interp);
|
|
} else {
|
|
xassert(TCL_OK == Tcl_MakeSafe(interp));
|
|
}
|
|
|
|
#define WITH_NAMESPACE(name) "bookmarkfs::fsck::" name
|
|
#define DO_CREATE_NAMESPACE(interp, name) \
|
|
if (NULL == Tcl_CreateNamespace(interp, WITH_NAMESPACE(name), \
|
|
NULL, NULL) \
|
|
) { \
|
|
log_puts(Tcl_GetStringResult(interp)); \
|
|
goto fail; \
|
|
}
|
|
DO_CREATE_NAMESPACE(interp, "handler");
|
|
DO_CREATE_NAMESPACE(interp, "result");
|
|
|
|
#define DO_SET_VAR(interp, name, val) \
|
|
if (0 != set_tcl_var(interp, STR_WITHLEN(WITH_NAMESPACE(name)), val)) { \
|
|
log_puts(Tcl_GetStringResult(interp)); \
|
|
goto fail; \
|
|
}
|
|
DO_SET_VAR(interp, "isInteractive",
|
|
!!(flags & BOOKMARKFS_FSCK_HANDLER_INTERACTIVE));
|
|
DO_SET_VAR(interp, "isReadonly",
|
|
!!(flags & BOOKMARKFS_FSCK_HANDLER_READONLY));
|
|
DO_SET_VAR(interp, "handler::next", BOOKMARKFS_FSCK_NEXT);
|
|
DO_SET_VAR(interp, "handler::apply", BOOKMARKFS_FSCK_APPLY);
|
|
DO_SET_VAR(interp, "handler::userInput", BOOKMARKFS_FSCK_USER_INPUT);
|
|
DO_SET_VAR(interp, "handler::save", BOOKMARKFS_FSCK_SAVE);
|
|
DO_SET_VAR(interp, "handler::stop", BOOKMARKFS_FSCK_STOP);
|
|
DO_SET_VAR(interp, "handler::rewind", BOOKMARKFS_FSCK_REWIND);
|
|
DO_SET_VAR(interp, "handler::skip", BOOKMARKFS_FSCK_SKIP);
|
|
DO_SET_VAR(interp, "handler::skipChildren", BOOKMARKFS_FSCK_SKIP_CHILDREN);
|
|
DO_SET_VAR(interp, "handler::reset", BOOKMARKFS_FSCK_RESET);
|
|
#define DO_SET_RESULT_VAR(interp, name, val) \
|
|
DO_SET_VAR(interp, "result::" name, BOOKMARKFS_FSCK_RESULT_##val)
|
|
DO_SET_RESULT_VAR(interp, "nameDuplicate", NAME_DUPLICATE);
|
|
DO_SET_RESULT_VAR(interp, "nameBadChar", NAME_BADCHAR);
|
|
DO_SET_RESULT_VAR(interp, "nameBadLen", NAME_BADLEN);
|
|
DO_SET_RESULT_VAR(interp, "nameDotDot", NAME_DOTDOT);
|
|
DO_SET_RESULT_VAR(interp, "nameInvalid", NAME_INVALID);
|
|
|
|
return interp;
|
|
|
|
fail:
|
|
Tcl_DeleteInterp(interp);
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
parse_opts (
|
|
struct bookmarkfs_conf_opt const *opts,
|
|
struct parsed_opts *parsed_opts
|
|
) {
|
|
char const *script = NULL;
|
|
|
|
BACKEND_OPT_START(opts)
|
|
BACKEND_OPT_KEY("script") {
|
|
BACKEND_OPT_VAL_START
|
|
script = BACKEND_OPT_VAL_STR;
|
|
}
|
|
BACKEND_OPT_KEY("unsafe") {
|
|
BACKEND_OPT_NO_VAL
|
|
parsed_opts->flags |= FSCK_HANDLER_UNSAFE;
|
|
}
|
|
BACKEND_OPT_END
|
|
|
|
parsed_opts->script = script;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
print_help (void)
|
|
{
|
|
printf("Tcl-based fsck handler for BookmarkFS\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" script=<path> Path to Tcl script file\n"
|
|
" unsafe Enable unsafe Tcl interpreter features\n"
|
|
"\n"
|
|
"Run 'info bookmarkfs' for more information.\n\n"
|
|
"Project homepage: <" BOOKMARKFS_HOMEPAGE_URL ">.\n");
|
|
}
|
|
|
|
static void
|
|
print_version (void)
|
|
{
|
|
printf("bookmarkfs-fsck-handler-tcl %d.%d.%d\n",
|
|
BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, BOOKMARKFS_VER_PATCH);
|
|
puts(BOOKMARKFS_FEATURE_STRING(DEBUG, "debug"));
|
|
}
|
|
|
|
static int
|
|
set_tcl_var (
|
|
Tcl_Interp *interp,
|
|
char const *name,
|
|
size_t name_len,
|
|
int val
|
|
) {
|
|
Tcl_Obj *name_obj = Tcl_NewStringObj(name, name_len);
|
|
Tcl_Obj *val_obj = Tcl_NewIntObj(val);
|
|
|
|
Tcl_IncrRefCount(name_obj);
|
|
val_obj = Tcl_ObjSetVar2(interp, name_obj, NULL, val_obj,
|
|
TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG);
|
|
Tcl_DecrRefCount(name_obj);
|
|
return val_obj == NULL ? -1 : 0;
|
|
}
|
|
|
|
static int
|
|
fsck_handler_create (
|
|
struct bookmarkfs_conf_opt const *opts,
|
|
uint32_t flags,
|
|
void **handler_ctx_ptr
|
|
) {
|
|
struct parsed_opts parsed_opts = { 0 };
|
|
if (0 != parse_opts(opts, &parsed_opts)) {
|
|
return -1;
|
|
}
|
|
flags |= parsed_opts.flags;
|
|
|
|
if (parsed_opts.script == NULL) {
|
|
log_puts("script not specified");
|
|
return -1;
|
|
}
|
|
int fd = open(parsed_opts.script, O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
log_printf("open(): %s", xstrerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
Tcl_Interp *interp = init_interp(flags);
|
|
if (interp == NULL) {
|
|
goto fail;
|
|
}
|
|
|
|
struct handler_ctx *ctx = xmalloc(sizeof(*ctx));
|
|
*ctx = (struct handler_ctx) {
|
|
.interp = interp,
|
|
.flags = flags,
|
|
.script.fd = fd,
|
|
};
|
|
*handler_ctx_ptr = ctx;
|
|
return 0;
|
|
|
|
fail:
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
fsck_handler_destroy (
|
|
void *handler_ctx
|
|
) {
|
|
struct handler_ctx *ctx = handler_ctx;
|
|
|
|
if (ctx->flags & FSCK_HANDLER_INITIALIZED) {
|
|
Tcl_DecrRefCount(ctx->script.obj);
|
|
} else {
|
|
close(ctx->script.fd);
|
|
}
|
|
Tcl_DeleteInterp(ctx->interp);
|
|
free(ctx);
|
|
}
|
|
|
|
static void
|
|
fsck_handler_info (
|
|
uint32_t flags
|
|
) {
|
|
if (flags & BOOKMARKFS_FSCK_HANDLER_INFO_HELP) {
|
|
print_help();
|
|
} else if (flags & BOOKMARKFS_FSCK_HANDLER_INFO_VERSION) {
|
|
print_version();
|
|
}
|
|
}
|
|
|
|
static int
|
|
fsck_handler_run (
|
|
void *handler_ctx,
|
|
int why,
|
|
union bookmarkfs_fsck_handler_data *data
|
|
) {
|
|
struct handler_ctx *ctx = handler_ctx;
|
|
|
|
if (0 != init_handler(ctx)) {
|
|
return -1;
|
|
}
|
|
Tcl_Interp *interp = ctx->interp;
|
|
|
|
Tcl_Obj *data_obj = Tcl_NewObj();
|
|
if (why < 0) {
|
|
if (ctx->flags & FSCK_HANDLER_EXPECT_INPUT) {
|
|
Tcl_SetStringObj(data_obj, data->str, -1);
|
|
}
|
|
} else {
|
|
struct bookmarkfs_fsck_handler_entry *entry = &data->entry;
|
|
Tcl_Obj *elems[] = {
|
|
Tcl_NewWideIntObj(entry->data.id),
|
|
Tcl_NewWideIntObj(entry->data.extra),
|
|
Tcl_NewStringObj(entry->data.name,
|
|
strnlen(entry->data.name, sizeof(entry->data.name))),
|
|
Tcl_NewWideIntObj(entry->parent_id),
|
|
};
|
|
Tcl_SetListObj(data_obj, 4, elems);
|
|
}
|
|
Tcl_Obj *elems[] = { Tcl_NewIntObj(why), data_obj };
|
|
Tcl_Obj *args_obj = Tcl_NewListObj(2, elems);
|
|
Tcl_IncrRefCount(args_obj);
|
|
|
|
Tcl_Obj *args[] = { ctx->script.obj, args_obj };
|
|
int result = Tcl_EvalObjv(interp, 2, args, TCL_EVAL_GLOBAL);
|
|
Tcl_DecrRefCount(args_obj);
|
|
if (result != TCL_OK) {
|
|
log_puts(Tcl_GetStringResult(interp));
|
|
return -1;
|
|
}
|
|
|
|
Tcl_Obj *result_obj = Tcl_GetObjResult(interp);
|
|
if (TCL_OK != Tcl_ListObjIndex(interp, result_obj, 0, &data_obj)) {
|
|
log_puts("bad return value, cannot convert to list");
|
|
return -1;
|
|
}
|
|
if (TCL_OK != Tcl_GetIntFromObj(interp, data_obj, &result)) {
|
|
log_printf("bad result code '%s'", Tcl_GetString(data_obj));
|
|
return -1;
|
|
}
|
|
switch (result) {
|
|
case BOOKMARKFS_FSCK_USER_INPUT:
|
|
if (!(ctx->flags & BOOKMARKFS_FSCK_HANDLER_INTERACTIVE)) {
|
|
log_printf("bad result code %d, not in interactive mode", result);
|
|
return -1;
|
|
}
|
|
if (TCL_OK != Tcl_ListObjIndex(interp, result_obj, 1, &data_obj)) {
|
|
log_puts("bad return value, no prompt string given");
|
|
return -1;
|
|
}
|
|
data->str = Tcl_GetString(data_obj);
|
|
ctx->flags |= FSCK_HANDLER_EXPECT_INPUT;
|
|
break;
|
|
|
|
case BOOKMARKFS_FSCK_APPLY:
|
|
if (ctx->flags & BOOKMARKFS_FSCK_HANDLER_READONLY) {
|
|
log_puts("cannot apply, fsck is running in readonly mode");
|
|
return -1;
|
|
}
|
|
if (TCL_OK != Tcl_ListObjIndex(interp, result_obj, 1, &data_obj)) {
|
|
log_puts("bad return value, no new name given");
|
|
return -1;
|
|
}
|
|
struct bookmarkfs_fsck_data *data_buf = &data->entry.data;
|
|
char const *new_name = Tcl_GetString(data_obj);
|
|
strncpy(data_buf->name, new_name, sizeof(data_buf->name));
|
|
// fallthrough
|
|
case BOOKMARKFS_FSCK_NEXT:
|
|
case BOOKMARKFS_FSCK_SAVE:
|
|
case BOOKMARKFS_FSCK_STOP:
|
|
case BOOKMARKFS_FSCK_REWIND:
|
|
case BOOKMARKFS_FSCK_SKIP:
|
|
case BOOKMARKFS_FSCK_SKIP_CHILDREN:
|
|
case BOOKMARKFS_FSCK_RESET:
|
|
ctx->flags &= ~FSCK_HANDLER_EXPECT_INPUT;
|
|
break;
|
|
|
|
default:
|
|
log_printf("bad result code %d", result);
|
|
return -1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BOOKMARKFS_API
|
|
struct bookmarkfs_fsck_handler const bookmarkfs_fsck_handler_tcl = {
|
|
.info = fsck_handler_info,
|
|
.create = fsck_handler_create,
|
|
.destroy = fsck_handler_destroy,
|
|
.run = fsck_handler_run,
|
|
};
|