/** * bookmarkfs/src/mount.c * ---- * * Copyright (C) 2024 CismonX * * 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "frontend_util.h" #include "fs_ops.h" #include "lib.h" #include "macros.h" #include "version.h" #include "xstd.h" // The upper and default values are chosen according to Chromium's // `url::kMaxURLChars` and `content::kMaxURLDisplayChars` constants. // // See Chromium source code: // - /url/url_constants.h // - /content/public/common/content_constants.cc #define FILE_MAX_LOWER 1024 #define FILE_MAX_UPPER ( 2 * 1024 * 1024 ) #define FILE_MAX_DEFAULT ( 32 * 1024 ) struct mount_ctx { struct bookmarkfs_backend const *backend_impl; void *backend_ctx; void *backend_handle; struct fuse_session *session; }; struct mount_info { struct bookmarkfs_backend_conf backend_conf; char const *backend_name; char const *fs_name; char const *mount_target; struct fs_flags fs_flags; size_t file_max; struct fuse_args args; struct { unsigned is_foreground : 1; unsigned no_mount : 1; unsigned no_sandbox : 1; unsigned print_help : 1; unsigned print_version : 1; } flags; }; // Forward declaration start static void destroy_ctx (struct mount_ctx const *); static int enter_sandbox (struct mount_ctx const *, struct mount_info const *); static int init_all (struct mount_ctx *, int, char *[]); static int init_backend (struct mount_ctx *, struct mount_info *); static int init_fuse (struct mount_ctx *, struct mount_info *); static int parse_opts (struct mount_info *, int, char *[]); static void print_help (void); static void print_version (void); static int run_fuse (struct fuse_session *); // Forward declaration end static void destroy_ctx ( struct mount_ctx const *ctx ) { if (ctx->session != NULL) { fuse_remove_signal_handlers(ctx->session); fuse_session_unmount(ctx->session); fuse_session_destroy(ctx->session); } if (ctx->backend_impl != NULL) { ctx->backend_impl->backend_destroy(ctx->backend_ctx); } bookmarkfs_unload(ctx->backend_handle); } static int enter_sandbox ( struct mount_ctx const *ctx, struct mount_info const *info ) { if (info->flags.no_sandbox) { return 0; } struct bookmarkfs_backend_create_resp resp = { .bookmarks_root_id = UINT64_MAX, .tags_root_id = UINT64_MAX, }; if (0 != ctx->backend_impl->backend_sandbox(ctx->backend_ctx, &resp)) { return -1; } debug_puts("sandbox entered"); fs_init_metadata(resp.bookmarks_root_id, resp.tags_root_id, NULL); return 0; } static int init_all ( struct mount_ctx *ctx, int argc, char *argv[] ) { struct mount_info info = { .backend_conf = { .version = BOOKMARKFS_VERNUM, .flags = BOOKMARKFS_BACKEND_READONLY, }, .fs_flags = { .accmode = 0700, .readonly = 1, }, .file_max = FILE_MAX_DEFAULT, }; int status = -1; if (0 != parse_opts(&info, argc, argv)) { goto end; } if (0 != init_backend(ctx, &info)) { if (info.flags.no_mount) { status = 0; } goto end; } if (0 != init_fuse(ctx, &info)) { goto end; } if (0 != enter_sandbox(ctx, &info)) { goto end; } status = 0; end: bookmarkfs_opts_free(info.backend_conf.opts); fuse_opt_free_args(&info.args); return status; } static int init_backend ( struct mount_ctx *ctx, struct mount_info *info ) { if (info->backend_name == NULL) { if (info->flags.print_help) { print_help(); } else { print_version(); } return -1; } struct bookmarkfs_backend const *impl = bookmarkfs_load_backend(info->backend_name, &ctx->backend_handle); if (impl == NULL || ctx->backend_handle == NULL) { return -1; } uint32_t flags = BOOKMARKFS_FRONTEND_MOUNT; if (info->flags.no_mount) { if (impl->backend_info != NULL) { if (info->flags.print_help) { flags |= BOOKMARKFS_BACKEND_INFO_HELP; } else { flags |= BOOKMARKFS_BACKEND_INFO_VERSION; } impl->backend_info(flags); } return -1; } if (0 != bookmarkfs_lib_init()) { return -1; } if (impl->backend_init != NULL) { flags |= BOOKMARKFS_BACKEND_LIB_READY; if (0 != impl->backend_init(flags)) { return -1; } } struct bookmarkfs_backend_create_resp resp = { .bookmarks_root_id = UINT64_MAX, .tags_root_id = UINT64_MAX, .xattr_names = "", }; if (0 != impl->backend_create(&info->backend_conf, &resp)) { debug_puts("backend_create() failed"); return -1; } ctx->backend_impl = impl; ctx->backend_ctx = resp.backend_ctx; info->backend_name = resp.name; info->fs_flags.exclusive = !!(resp.flags & BOOKMARKFS_BACKEND_EXCLUSIVE); info->fs_flags.has_keyword = !!(resp.flags & BOOKMARKFS_BACKEND_HAS_KEYWORD); fs_init_backend(impl, resp.backend_ctx); fs_init_metadata(resp.bookmarks_root_id, resp.tags_root_id, resp.xattr_names); fs_init_opts(info->fs_flags, info->file_max); return 0; } static int init_fuse ( struct mount_ctx *ctx, struct mount_info *info ) { struct fuse_args *args = &info->args; char const *fsname = info->fs_name; if (fsname == NULL) { fsname = info->backend_name; } char const *rw = info->fs_flags.readonly ? "ro": "rw"; char *buf; xasprintf(&buf, "-odefault_permissions,fsname=%s,%s", fsname, rw); xassert(0 == fuse_opt_add_arg(args, buf)); free(buf); #define BOOKMARKFS_OP(name) .name = fs_op_##name static struct fuse_lowlevel_ops const ops = { BOOKMARKFS_OP(init), BOOKMARKFS_OP(destroy), BOOKMARKFS_OP(lookup), BOOKMARKFS_OP(getattr), BOOKMARKFS_OP(setattr), BOOKMARKFS_OP(rename), BOOKMARKFS_OP(ioctl), BOOKMARKFS_OP(setxattr), BOOKMARKFS_OP(getxattr), BOOKMARKFS_OP(listxattr), BOOKMARKFS_OP(removexattr), BOOKMARKFS_OP(create), BOOKMARKFS_OP(open), BOOKMARKFS_OP(read), BOOKMARKFS_OP(write), BOOKMARKFS_OP(fsync), BOOKMARKFS_OP(release), BOOKMARKFS_OP(link), BOOKMARKFS_OP(unlink), BOOKMARKFS_OP(rmdir), BOOKMARKFS_OP(mkdir), BOOKMARKFS_OP(opendir), BOOKMARKFS_OP(readdir), BOOKMARKFS_OP(readdirplus), BOOKMARKFS_OP(fsyncdir), BOOKMARKFS_OP(releasedir), }; struct fuse_session *session = fuse_session_new(args, &ops, sizeof(ops), NULL); if (session == NULL) { log_puts("fuse_session_new() failed"); return -1; } if (0 != fuse_set_signal_handlers(session)) { log_puts("fuse_set_signal_handlers() failed"); goto destroy; } if (0 != fuse_session_mount(session, info->mount_target)) { log_puts("fuse_session_mount() failed"); goto destroy; } if (0 != fuse_daemonize(info->flags.is_foreground)) { log_puts("fuse_daemonize() failed"); goto unmount; } ctx->session = session; fs_init_fuse(session); return 0; unmount: fuse_remove_signal_handlers(session); fuse_session_unmount(session); destroy: fuse_session_destroy(session); return -1; } static int parse_opts ( struct mount_info *info, int argc, char *argv[] ) { enum { // bookmarkfs options BOOKMARKFS_OPT_ACCMODE, BOOKMARKFS_OPT_BACKEND, BOOKMARKFS_OPT_CTIME, BOOKMARKFS_OPT_EOL, BOOKMARKFS_OPT_FILE_MAX, BOOKMARKFS_OPT_NO_LANDLOCK, BOOKMARKFS_OPT_NO_SANDBOX, // kernel options BOOKMARKFS_OPT_FSNAME, BOOKMARKFS_OPT_RO, BOOKMARKFS_OPT_RW, // ignored options BOOKMARKFS_OPT_ATIME, BOOKMARKFS_OPT_BLKDEV, BOOKMARKFS_OPT_BLKSIZE, BOOKMARKFS_OPT_DEV, BOOKMARKFS_OPT_DIRATIME, BOOKMARKFS_OPT_EXEC, BOOKMARKFS_OPT_FD, BOOKMARKFS_OPT_RELATIME, BOOKMARKFS_OPT_STRICTATIME, BOOKMARKFS_OPT_SUBTYPE, BOOKMARKFS_OPT_SUID, BOOKMARKFS_OPT_NOATIME, BOOKMARKFS_OPT_NODEV, BOOKMARKFS_OPT_NODIRATIME, BOOKMARKFS_OPT_NOEXEC, BOOKMARKFS_OPT_NORELATIME, BOOKMARKFS_OPT_NOSTRICTATIME, BOOKMARKFS_OPT_NOSUID, BOOKMARKFS_OPT_END_, }; #define BOOKMARKFS_OPT(name, token) [BOOKMARKFS_OPT_##name] = (token) char const *const opts[] = { BOOKMARKFS_OPT(ACCMODE, "accmode"), BOOKMARKFS_OPT(BACKEND, "backend"), BOOKMARKFS_OPT(CTIME, "ctime"), BOOKMARKFS_OPT(EOL, "eol"), BOOKMARKFS_OPT(FILE_MAX, "file_max"), BOOKMARKFS_OPT(NO_LANDLOCK, "no_landlock"), BOOKMARKFS_OPT(NO_SANDBOX, "no_sandbox"), BOOKMARKFS_OPT(FSNAME, "fsname"), BOOKMARKFS_OPT(RO, "ro"), BOOKMARKFS_OPT(RW, "rw"), BOOKMARKFS_OPT(ATIME, "atime"), BOOKMARKFS_OPT(BLKDEV, "blkdev"), BOOKMARKFS_OPT(BLKSIZE, "blksize"), BOOKMARKFS_OPT(DEV, "dev"), BOOKMARKFS_OPT(DIRATIME, "diratime"), BOOKMARKFS_OPT(EXEC, "exec"), BOOKMARKFS_OPT(FD, "fd"), BOOKMARKFS_OPT(RELATIME, "relatime"), BOOKMARKFS_OPT(STRICTATIME, "strictatime"), BOOKMARKFS_OPT(SUBTYPE, "subtype"), BOOKMARKFS_OPT(SUID, "suid"), BOOKMARKFS_OPT(NOATIME, "noatime"), BOOKMARKFS_OPT(NODEV, "nodev"), BOOKMARKFS_OPT(NODIRATIME, "nodiratime"), BOOKMARKFS_OPT(NOEXEC, "noexec"), BOOKMARKFS_OPT(NORELATIME, "norelatime"), BOOKMARKFS_OPT(NOSTRICTATIME, "nostrictatime"), BOOKMARKFS_OPT(NOSUID, "nosuid"), BOOKMARKFS_OPT(END_, NULL), }; char const *fargs_common = "-onoatime,noexec,nosuid," #ifdef __linux__ "nodev," #endif "subtype=bookmarkfs"; struct fuse_args *fargs = &info->args; xassert(0 == fuse_opt_add_arg(fargs, "")); xassert(0 == fuse_opt_add_arg(fargs, fargs_common)); #define SUBOPT_PARSE_NUM(min, max, result) \ do { \ long num_ = strtol(SUBOPT_VAL, NULL, 0); \ if (num_ < (min) || num_ > (max)) { \ return SUBOPT_ERR_BAD_VAL(); \ } \ (result) = num_; \ } while (0) OPT_START(argc, argv, "o:FhV") OPT_OPT('o') { SUBOPT_START(opts) SUBOPT_OPT(BOOKMARKFS_OPT_ACCMODE) SUBOPT_HAS_VAL { SUBOPT_PARSE_NUM(0, 0777, info->fs_flags.accmode); } SUBOPT_OPT(BOOKMARKFS_OPT_BACKEND) SUBOPT_HAS_VAL { char const *name = SUBOPT_VAL; if (name[0] == '\0') { log_puts("backend name must not be empty"); return -1; } info->backend_name = name; } SUBOPT_OPT(BOOKMARKFS_OPT_CTIME) SUBOPT_NO_VAL { info->fs_flags.ctime = 1; info->backend_conf.flags |= BOOKMARKFS_BACKEND_CTIME; } SUBOPT_OPT(BOOKMARKFS_OPT_EOL) SUBOPT_NO_VAL { info->fs_flags.eol = 1; } SUBOPT_OPT(BOOKMARKFS_OPT_FILE_MAX) SUBOPT_HAS_VAL { SUBOPT_PARSE_NUM(FILE_MAX_LOWER, FILE_MAX_UPPER, info->file_max); } SUBOPT_OPT(BOOKMARKFS_OPT_FSNAME) SUBOPT_HAS_VAL { info->fs_name = SUBOPT_VAL; } SUBOPT_OPT(BOOKMARKFS_OPT_NO_LANDLOCK) SUBOPT_NO_VAL { info->backend_conf.flags |= BOOKMARKFS_BACKEND_NO_LANDLOCK; } SUBOPT_OPT(BOOKMARKFS_OPT_NO_SANDBOX) SUBOPT_NO_VAL { info->flags.no_sandbox = 1; info->backend_conf.flags |= BOOKMARKFS_BACKEND_NO_SANDBOX; } SUBOPT_OPT(BOOKMARKFS_OPT_RO) SUBOPT_NO_VAL { info->fs_flags.readonly = 1; info->backend_conf.flags |= BOOKMARKFS_BACKEND_READONLY; } SUBOPT_OPT(BOOKMARKFS_OPT_RW) SUBOPT_NO_VAL { info->fs_flags.readonly = 0; info->backend_conf.flags &= ~BOOKMARKFS_BACKEND_READONLY; } SUBOPT_OPT_FALLBACK() { char *opt = SUBOPT_STR; if (opt[0] == '@') { bookmarkfs_opts_add(&info->backend_conf.opts, opt + 1); } else if (opt[0] != '\0') { xassert(0 == fuse_opt_add_opt(&fargs->argv[1], opt)); } } SUBOPT_OPT_DEFAULT() { debug_printf("option '-o %s' ignored", SUBOPT_STR); } SUBOPT_END } OPT_OPT('F') { info->flags.is_foreground = 1; break; } OPT_OPT('h') { info->flags.print_help = 1; info->flags.no_mount = 1; return 0; } OPT_OPT('V') { info->flags.print_version = 1; info->flags.no_mount = 1; return 0; } OPT_NOVAL OPT_END if (info->backend_name == NULL) { log_puts("backend not specified"); return -1; } if (argc != 2) { if (argc < 2) { log_puts("mount source and target must be specified"); } else { log_puts("too many arguments"); } return -1; } info->backend_conf.store_path = argv[0]; info->mount_target = argv[1]; return 0; } static void print_help (void) { puts("Usage: mount.bookmarkfs [options] \n" "\n" "Common options:\n" " -o backend= Backend used by the filesystem\n" " -o @[=] Backend-specific option\n" " -o accmode= File access mode\n" " -o ctime Maintain file change time\n" " -o eol Add a newline to the end of file\n" " -o file_max= Max file size limit\n" "\n" "Other options:\n" " -o no_sandbox Disable sandbox\n" #ifdef __linux__ " -o no_landlock Disable Landlock features for sandbox\n" #endif "\n" " -F Run in foreground, do not daemonize\n" " -h Print help message and exit\n" " -V Print version information and exit\n" "\n" "See the mount.bookmarkfs(1) manpage for more information,\n" "or run 'info bookmarkfs' for the full user manual.\n" "\n" "Project homepage: <" BOOKMARKFS_HOMEPAGE_URL ">."); } static void print_version (void) { printf("mount.bookmarkfs (BookmarkFS) %d.%d.%d\n", BOOKMARKFS_VER_MAJOR, BOOKMARKFS_VER_MINOR, BOOKMARKFS_VER_PATCH); puts(BOOKMARKFS_FEATURE_STRING(DEBUG, "debug")); bookmarkfs_print_lib_version("\n"); } static int run_fuse ( struct fuse_session *session ) { if (session == NULL) { return 0; } int status = fuse_session_loop(session); if (status != 0) { char const *desc; if (status < 0) { desc = xstrerror(-status); } else { // MT-unsafe desc = strsignal(status); } log_printf("fuse_session_loop(): %s", desc); } return status; } int main ( int argc, char *argv[] ) { int status = EXIT_FAILURE; struct mount_ctx ctx = { 0 }; if (0 != init_all(&ctx, argc, argv)) { goto end; } if (0 != run_fuse(ctx.session)) { goto end; } status = EXIT_SUCCESS; end: destroy_ctx(&ctx); return status; }