bookmarkfs/src/fsck_handler_simple.c
CismonX 4be158c988
fsck_util: rename: escape -> translit
In the `escape_control_chars()` function, we're not actually
"escaping" the characters, just replacing them.
2025-03-17 12:45:40 +08:00

389 lines
10 KiB
C

/**
* bookmarkfs/src/fsck_handler_simple.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 <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "backend_util.h"
#include "fsck_handler.h"
#include "fsck_util.h"
#include "xstd.h"
#define FSCK_HANDLER_EXPECT_INPUT ( 1u << 16 )
#define FSCK_HANDLER_HAS_ENTRY ( 1u << 17 )
#define FSCK_HANDLER_INHIBIT_NEXT ( 1u << 18 )
struct handler_ctx {
uint64_t parent_id;
uint32_t counter;
uint32_t flags;
struct parsed_opts {
char const *prompt;
int translit;
} opts;
struct bookmarkfs_fsck_data data_buf;
};
// Forward declaration start
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
static int expect_input (struct handler_ctx *, char **);
static int expect_next (struct handler_ctx *, int, char **);
static int handle_input (struct handler_ctx *,
union bookmarkfs_fsck_handler_data *);
static void print_entry (struct bookmarkfs_fsck_data const *);
static void print_usage (void);
#endif /* defined(BOOKMARKFS_INTERACTIVE_FSCK) */
static void fix_name_dup (struct handler_ctx *, struct bookmarkfs_fsck_data *);
static void fix_entry (struct handler_ctx *, enum bookmarkfs_fsck_result,
struct bookmarkfs_fsck_data *);
static int handle_entry (struct handler_ctx *, enum bookmarkfs_fsck_result,
union bookmarkfs_fsck_handler_data *);
static int parse_opts (struct bookmarkfs_conf_opt const *,
struct parsed_opts *);
// Forward declaration end
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
static int
expect_input (
struct handler_ctx *ctx,
char **data_ptr
) {
*data_ptr = (char *)(ctx->opts.prompt);
ctx->flags |= FSCK_HANDLER_EXPECT_INPUT;
return BOOKMARKFS_FSCK_USER_INPUT;
}
static int
expect_next (
struct handler_ctx *ctx,
int control,
char **data_ptr
) {
switch ((*data_ptr)[1]) {
case '-':
ctx->flags |= FSCK_HANDLER_INHIBIT_NEXT;
break;
case '\0':
break;
default:
print_usage();
return expect_input(ctx, data_ptr);
}
ctx->flags &= ~(FSCK_HANDLER_HAS_ENTRY | FSCK_HANDLER_EXPECT_INPUT);
return control;
}
static int
handle_input (
struct handler_ctx *ctx,
union bookmarkfs_fsck_handler_data *data
) {
char *input = data->str;
debug_assert(input != NULL);
char *next = strtok(input, " \t");
int control;
switch (input[0]) {
case 'p':
if (!(ctx->flags & FSCK_HANDLER_HAS_ENTRY)) {
no_entry:
puts("no entry");
} else {
print_entry(&ctx->data_buf);
}
control = expect_input(ctx, &data->str);
break;
case 'e':
if (next == NULL) {
goto bad_cmd;
}
strncpy(ctx->data_buf.name, next, sizeof(ctx->data_buf.name));
// fallthrough
case 'a':
if (ctx->flags & BOOKMARKFS_FSCK_HANDLER_READONLY) {
log_puts("cannot apply, fsck is running in readonly mode");
control = expect_input(ctx, &data->str);
break;
}
if (!(ctx->flags & FSCK_HANDLER_HAS_ENTRY)) {
goto no_entry;
}
data->entry.data = ctx->data_buf;
control = expect_next(ctx, BOOKMARKFS_FSCK_APPLY, &data->str);
break;
case 'c':
control = expect_next(ctx, BOOKMARKFS_FSCK_NEXT, &data->str);
break;
case 's':
control = expect_next(ctx, BOOKMARKFS_FSCK_SKIP, &data->str);
break;
case 'S':
control = expect_next(ctx, BOOKMARKFS_FSCK_SKIP_CHILDREN, &data->str);
break;
case 'r':
control = expect_next(ctx, BOOKMARKFS_FSCK_REWIND, &data->str);
break;
case 'R':
control = expect_next(ctx, BOOKMARKFS_FSCK_RESET, &data->str);
break;
case 'w':
control = expect_next(ctx, BOOKMARKFS_FSCK_SAVE, &data->str);
break;
case 'q':
control = BOOKMARKFS_FSCK_STOP;
break;
default:
bad_cmd:
print_usage();
// fallthrough
case '\0':
control = expect_input(ctx, &data->str);
break;
}
free(input);
return control;
}
static void
print_entry (
struct bookmarkfs_fsck_data const *data
) {
char name_buf[sizeof(data->name)];
translit_control_chars(name_buf, sizeof(name_buf), data->name, '?');
printf("id: %" PRIu64 "\nname: %s\n", data->id, name_buf);
}
static void
print_usage (void)
{
puts("Commands:\n"
" p Print current entry\n"
" a[-] Apply proposed fix\n"
" e[-] <new_name> Edit proposed fix and then apply\n"
" c Continue to next entry\n"
" s[-] Skip current directory\n"
" S[-] Skip current directory and its children\n"
" r[-] Rewind current directory\n"
" R[-] Rewind all\n"
" w[-] Save applied changes\n"
" q Quit\n"
"\n"
"The '-' suffix inhibits the default behavior of continuing to\n"
"the next entry after the command completes successfully.\n"
"\n"
"See the user manual for more information.");
}
#endif /* defined(BOOKMARKFS_INTERACTIVE_FSCK) */
static void
fix_name_dup (
struct handler_ctx *ctx,
struct bookmarkfs_fsck_data *data
) {
size_t name_len = strnlen(data->name, sizeof(data->name));
for (int nbytes; ; name_len = sizeof(data->name) - nbytes - 1) {
nbytes = snprintf(data->name + name_len, sizeof(data->name) - name_len,
"_%" PRIu32, ctx->counter);
xassert(nbytes > 0);
if (name_len + nbytes < sizeof(data->name)) {
++ctx->counter;
break;
}
}
}
static void
fix_entry (
struct handler_ctx *ctx,
enum bookmarkfs_fsck_result why,
struct bookmarkfs_fsck_data *data
) {
switch (why) {
case BOOKMARKFS_FSCK_RESULT_NAME_BADCHAR:
if (unlikely(data->extra >= sizeof(data->name))) {
data->extra = sizeof(data->name) - 1;
}
data->name[data->extra] = ctx->opts.translit;
break;
case BOOKMARKFS_FSCK_RESULT_NAME_DUPLICATE:
fix_name_dup(ctx, data);
break;
case BOOKMARKFS_FSCK_RESULT_NAME_BADLEN:
if (data->extra > 0) {
data->name[sizeof(data->name) - 1] = '\0';
break;
}
// fallthrough
case BOOKMARKFS_FSCK_RESULT_NAME_DOTDOT:
case BOOKMARKFS_FSCK_RESULT_NAME_INVALID:
sprintf(data->name, "fsck-%" PRIu64, data->id);
break;
default:
unreachable();
}
}
static int
handle_entry (
struct handler_ctx *ctx,
enum bookmarkfs_fsck_result why,
union bookmarkfs_fsck_handler_data *data
) {
struct bookmarkfs_fsck_handler_entry *entry = &data->entry;
if (entry->parent_id != ctx->parent_id) {
ctx->parent_id = entry->parent_id;
ctx->counter = 0;
}
struct bookmarkfs_fsck_data *entry_data = &entry->data;
if (0 != explain_fsck_result(why, entry_data)) {
return -1;
}
int control = BOOKMARKFS_FSCK_NEXT;
if (!(ctx->flags & BOOKMARKFS_FSCK_HANDLER_READONLY)) {
fix_entry(ctx, why, entry_data);
control = BOOKMARKFS_FSCK_APPLY;
}
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
if (ctx->flags & BOOKMARKFS_FSCK_HANDLER_INTERACTIVE) {
ctx->data_buf = *entry_data;
control = expect_input(ctx, &data->str);
}
ctx->flags |= FSCK_HANDLER_HAS_ENTRY;
#endif
return control;
}
static int
parse_opts (
struct bookmarkfs_conf_opt const *opts,
struct parsed_opts *out
) {
char const *prompt = "% ";
int translit = '_';
BACKEND_OPT_START(opts)
BACKEND_OPT_KEY("prompt") {
BACKEND_OPT_VAL_START
prompt = BACKEND_OPT_VAL_STR;
}
BACKEND_OPT_KEY("translit") {
BACKEND_OPT_VAL_START
char const *str = BACKEND_OPT_VAL_STR;
if (str[0] != '\0' && str[0] != '/' && str[1] == '\0') {
translit = str[0];
}
BACKEND_OPT_VAL_END
}
BACKEND_OPT_END
out->prompt = prompt;
out->translit = translit;
return 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;
}
struct handler_ctx *ctx = xmalloc(sizeof(*ctx));
*ctx = (struct handler_ctx) {
.parent_id = UINT64_MAX,
.opts = parsed_opts,
.flags = flags,
};
*handler_ctx_ptr = ctx;
return 0;
}
static void
fsck_handler_destroy (
void *handler_ctx
) {
struct handler_ctx *ctx = handler_ctx;
free(ctx);
}
static int
fsck_handler_run (
void *handler_ctx,
int why,
union bookmarkfs_fsck_handler_data *data
) {
struct handler_ctx *ctx = handler_ctx;
if (why > 0) {
return handle_entry(ctx, why, data);
}
#ifdef BOOKMARKFS_INTERACTIVE_FSCK
if (ctx->flags & FSCK_HANDLER_EXPECT_INPUT) {
return handle_input(ctx, data);
}
if (ctx->flags & FSCK_HANDLER_INHIBIT_NEXT) {
ctx->flags &= ~FSCK_HANDLER_INHIBIT_NEXT;
return expect_input(ctx, &data->str);
}
#endif
return BOOKMARKFS_FSCK_NEXT;
}
struct bookmarkfs_fsck_handler const fsck_handler_simple = {
.create = fsck_handler_create,
.destroy = fsck_handler_destroy,
.run = fsck_handler_run,
};