mirror of
https://github.com/libfuse/libfuse.git
synced 2026-07-07 14:47:41 +08:00
2ad90f04dc
These example filesystems always send their reply from the per-queue io-uring worker thread. The ones that run a helper thread use it only for cache-invalidation notifications over /dev/fuse, not ring submissions, so they too satisfy the io_uring_single_issuer contract. Call fuse_set_conn_flag(conn, FUSE_CONN_FLAG_SINGLE_ISSUER) in their init() handlers so they take the lock-free, registered-ring-fd fast path. It is harmless when io-uring is not in use. Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
333 lines
7.3 KiB
C
333 lines
7.3 KiB
C
/*
|
|
* 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);
|
|
}
|