| /* |
| * FUSE: Filesystem in Userspace |
| * Copyright (C) 2026 Oracle. |
| * |
| * This program can be distributed under the terms of the GNU GPLv2. |
| * See the file GPL2.txt. |
| */ |
| |
| /** @file |
| * |
| * Minimal example filesystem using low-level API and systemd service API. |
| * |
| * - Shows how to build a low level FUSE filesystem server that can be managed |
| * by systemd |
| * - Enables on-demand filesystem mounting via socket activation |
| * - Demonstrates requesting resources from the mount-caller's environment |
| * - Allows running FUSE servers with minimal privileges; isolated mount, |
| * network, and pid namespaces; and a separate uid/gid (unlike traditional |
| * FUSE which needs mount permissions and runs in the caller's environment) |
| * |
| * Compile with: |
| * |
| * gcc -Wall single_file.c service_ll.c `pkg-config fuse3 --cflags --libs` -o service_ll |
| * |
| * Note: If the pkg-config command fails due to the absence of the fuse3.pc |
| * file, you should configure the path to the fuse3.pc file in the |
| * PKG_CONFIG_PATH variable. |
| * |
| * Change the ExecStart line in service_ll@.service: |
| * |
| * ExecStart=/path/to/service_ll |
| * |
| * to point to the actual path of the service_ll binary. |
| * |
| * Finally, install the service_ll@.service and service_ll.socket files to the |
| * systemd service directory, usually /run/systemd/system. Run these commands |
| * to activate: |
| * |
| * systemctl daemon-reload |
| * systemctl start service_ll.socket |
| * |
| * Then mount with: |
| * |
| * mount -t fuse.service_ll /dev/sda /mnt |
| * |
| * ## Source code ## |
| * \include service_ll.c |
| * \include service_ll.socket |
| * \include service_ll@.service |
| * \include single_file.c |
| * \include single_file.h |
| */ |
| |
| #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19) |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| #include <fuse_lowlevel.h> |
| #include <fuse_service.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <assert.h> |
| #include <pthread.h> |
| #define USE_SINGLE_FILE_LL_API |
| #include "single_file.h" |
| |
| struct service_ll { |
| struct fuse_session *se; |
| char *device; |
| struct fuse_service *service; |
| |
| /* really booleans */ |
| int debug; |
| }; |
| |
| static struct service_ll ll = { }; |
| |
| static void service_ll_init(void *userdata, struct fuse_conn_info *conn) |
| { |
| (void)userdata; |
| |
| /* Always replies inline on the io-uring worker thread */ |
| fuse_set_conn_flag(conn, FUSE_CONN_FLAG_SINGLE_ISSUER); |
| |
| conn->time_gran = 1; |
| } |
| |
| static void service_ll_read(fuse_req_t req, fuse_ino_t ino, size_t count, |
| off_t pos, struct fuse_file_info *fi) |
| { |
| void *buf = NULL; |
| ssize_t got; |
| int ret; |
| |
| if (!is_single_file_ino(ino)) { |
| ret = EIO; |
| goto out_reply; |
| } |
| |
| if (ll.debug) |
| fprintf(stderr, "%s: pos 0x%llx count 0x%llx\n", |
| __func__, |
| (unsigned long long)pos, |
| (unsigned long long)count); |
| |
| if (!single_file.allow_dio && fi->direct_io) { |
| ret = ENOSYS; |
| goto out_reply; |
| } |
| |
| single_file_check_read(pos, &count); |
| |
| if (!count) { |
| fuse_reply_buf(req, buf, 0); |
| return; |
| } |
| |
| buf = malloc(count); |
| if (!buf) { |
| ret = ENOMEM; |
| goto out_reply; |
| } |
| |
| got = single_file_pread(buf, count, pos); |
| if (got < 0) { |
| ret = -got; |
| goto out_reply; |
| } |
| |
| fuse_reply_buf(req, buf, got); |
| goto out_buf; |
| |
| out_reply: |
| fuse_reply_err(req, ret); |
| out_buf: |
| free(buf); |
| } |
| |
| static void service_ll_write(fuse_req_t req, fuse_ino_t ino, const char *buf, |
| size_t count, off_t pos, |
| struct fuse_file_info *fi) |
| { |
| ssize_t got; |
| int ret; |
| |
| if (!is_single_file_ino(ino)) { |
| ret = EIO; |
| goto out_reply; |
| } |
| |
| if (ll.debug) |
| fprintf(stderr, "%s: pos 0x%llx count 0x%llx\n", |
| __func__, |
| (unsigned long long)pos, |
| (unsigned long long)count); |
| |
| if (!single_file.allow_dio && fi->direct_io) { |
| ret = ENOSYS; |
| goto out_reply; |
| } |
| |
| ret = -single_file_check_write(pos, &count); |
| if (ret) |
| goto out_reply; |
| |
| if (!count) { |
| fuse_reply_write(req, 0); |
| return; |
| } |
| |
| got = single_file_pwrite(buf, count, pos); |
| if (got < 0) { |
| ret = -got; |
| goto out_reply; |
| } |
| |
| fuse_reply_write(req, got); |
| return; |
| |
| out_reply: |
| fuse_reply_err(req, ret); |
| } |
| |
| static const struct fuse_lowlevel_ops service_ll_oper = { |
| .lookup = single_file_ll_lookup, |
| .getattr = single_file_ll_getattr, |
| .setattr = single_file_ll_setattr, |
| .readdir = single_file_ll_readdir, |
| .open = single_file_ll_open, |
| .statfs = single_file_ll_statfs, |
| .statx = single_file_ll_statx, |
| .fsync = single_file_ll_fsync, |
| |
| .init = service_ll_init, |
| .read = service_ll_read, |
| .write = service_ll_write, |
| }; |
| |
| #define SERVICE_LL_OPT(t, p, v) { t, offsetof(struct service_ll, p), v } |
| |
| static struct fuse_opt service_ll_opts[] = { |
| SERVICE_LL_OPT("debug", debug, 1), |
| SINGLE_FILE_OPT_KEYS, |
| FUSE_OPT_END |
| }; |
| |
| static int service_ll_opt_proc(void *data, const char *arg, int key, |
| struct fuse_args *outargs) |
| { |
| int ret = single_file_opt_proc(data, arg, key, outargs); |
| |
| if (ret < 1) |
| return ret; |
| |
| switch (key) { |
| case FUSE_OPT_KEY_NONOPT: |
| if (!ll.device) { |
| ll.device = strdup(arg); |
| return 0; |
| } |
| return 1; |
| } |
| |
| return 1; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| struct fuse_args args = FUSE_ARGS_INIT(argc, argv); |
| struct fuse_cmdline_opts opts = { }; |
| struct fuse_loop_config *config = NULL; |
| int ret = 1; |
| |
| if (fuse_service_accept(&ll.service)) |
| goto err_args; |
| |
| if (!fuse_service_accepted(ll.service)) |
| goto err_args; |
| |
| if (fuse_service_append_args(ll.service, &args)) |
| goto err_service; |
| |
| if (fuse_opt_parse(&args, &ll, service_ll_opts, service_ll_opt_proc)) |
| goto err_service; |
| |
| if (fuse_service_parse_cmdline_opts(&args, &opts)) |
| goto err_service; |
| |
| if (opts.show_help) { |
| printf("usage: %s [options] <device> <mountpoint>\n\n", argv[0]); |
| fuse_cmdline_help(); |
| fuse_lowlevel_help(); |
| ret = 0; |
| goto err_service; |
| } else if (opts.show_version) { |
| printf("FUSE library version %s\n", fuse_pkgversion()); |
| fuse_lowlevel_version(); |
| ret = 0; |
| goto err_service; |
| } |
| |
| if (!opts.mountpoint || !ll.device) { |
| printf("usage: %s [options] <device> <mountpoint>\n", argv[0]); |
| printf(" %s --help\n", argv[0]); |
| goto err_service; |
| } |
| |
| if (single_file_service_open(ll.service, ll.device)) |
| goto err_service; |
| |
| if (fuse_service_finish_file_requests(ll.service)) |
| goto err_singlefile; |
| |
| if (single_file_configure(ll.device, NULL)) |
| goto err_singlefile; |
| |
| ll.se = fuse_session_new(&args, &service_ll_oper, |
| sizeof(service_ll_oper), NULL); |
| if (ll.se == NULL) |
| goto err_singlefile; |
| |
| if (!opts.singlethread) { |
| config = fuse_loop_cfg_create(); |
| if (!config) { |
| ret = 1; |
| goto err_session; |
| } |
| } |
| |
| if (fuse_set_signal_handlers(ll.se)) |
| goto err_loopcfg; |
| |
| if (fuse_service_session_mount(ll.service, ll.se, S_IFDIR, &opts)) |
| goto err_signals; |
| |
| /* Block until ctrl+c or fusermount -u */ |
| if (opts.singlethread) { |
| fuse_service_send_goodbye(ll.service, 0); |
| fuse_service_release(ll.service); |
| ret = fuse_session_loop(ll.se); |
| } else { |
| fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); |
| fuse_loop_cfg_set_max_threads(config, opts.max_threads); |
| |
| fuse_service_send_goodbye(ll.service, 0); |
| fuse_service_release(ll.service); |
| ret = fuse_session_loop_mt(ll.se, config); |
| } |
| |
| err_signals: |
| fuse_remove_signal_handlers(ll.se); |
| err_loopcfg: |
| fuse_loop_cfg_destroy(config); |
| err_session: |
| fuse_session_destroy(ll.se); |
| err_singlefile: |
| single_file_close(); |
| err_service: |
| free(opts.mountpoint); |
| free(ll.device); |
| fuse_service_send_goodbye(ll.service, ret); |
| fuse_service_destroy(&ll.service); |
| err_args: |
| fuse_opt_free_args(&args); |
| return fuse_service_exit(ret); |
| } |