nullfs: support fuse systemd service mode

This is the only example fuse server that exports a regular file instead
of a directory tree.  Port it to be usable as a systemd fuse service so
that we can test that capability.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
This commit is contained in:
Darrick J. Wong
2026-03-13 09:06:31 -07:00
committed by Bernd Schubert
parent e25d7b9e25
commit e881b37462
4 changed files with 171 additions and 3 deletions
+6
View File
@@ -10,6 +10,12 @@ if not platform.endswith('bsd') and platform != 'dragonfly'
# support mounting files, This is enforced in vfs_domount_first()
# with the v_type != VDIR check.
examples += [ 'null' ]
if platform.endswith('linux')
configure_file(input: 'null.socket.in',
output: 'null.socket',
configuration: private_cfg)
endif
endif
single_file_examples = [ ]
+48 -3
View File
@@ -17,15 +17,24 @@
*
* gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
*
* Change the ExecStart line in nullfile@.service:
*
* ExecStart=/path/to/null
*
* to point to the actual path of the null binary.
*
* Finally, install the null@.service and null.socket files to the
* systemd service directory, usually /run/systemd/system.
*
* ## Source code ##
* \include passthrough_fh.c
*/
#define FUSE_USE_VERSION 31
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 19)
#include <fuse.h>
#include <fuse_lowlevel.h>
#include <fuse_service.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -33,6 +42,8 @@
#include <time.h>
#include <errno.h>
static mode_t mode = 0644;
static int null_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
@@ -41,7 +52,7 @@ static int null_getattr(const char *path, struct stat *stbuf,
if(strcmp(path, "/") != 0)
return -ENOENT;
stbuf->st_mode = S_IFREG | 0644;
stbuf->st_mode = S_IFREG | mode;
stbuf->st_nlink = 1;
stbuf->st_uid = getuid();
stbuf->st_gid = getgid();
@@ -112,11 +123,45 @@ static const struct fuse_operations null_oper = {
.write = null_write,
};
static int null_service(struct fuse_service *service, struct fuse_args *args)
{
int ret = 1;
if (fuse_service_append_args(service, args))
goto err_service;
if (fuse_service_finish_file_requests(service))
goto err_service;
fuse_service_expect_mount_format(service, S_IFREG);
/*
* In non-service mode, we set up the file to be owned and writable
* by the same user that starts the fuse server. When running in a
* container as a dynamic user, we just grant world write access.
*/
mode = 0666;
ret = fuse_service_main(service, args, &null_oper, NULL);
err_service:
fuse_service_send_goodbye(service, ret);
fuse_service_destroy(&service);
fuse_opt_free_args(args);
return fuse_service_exit(ret);
}
int main(int argc, char *argv[])
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse_cmdline_opts opts;
struct stat stbuf;
struct fuse_service *service = NULL;
if (fuse_service_accept(&service) != 0)
return 1;
if (fuse_service_accepted(service))
return null_service(service, &args);
if (fuse_parse_cmdline(&args, &opts) != 0)
return 1;
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2026 Oracle. All Rights Reserved.
# Author: Darrick J. Wong <djwong@kernel.org>
[Unit]
Description=Socket for null Service
[Socket]
ListenSequentialPacket=@FUSE_SERVICE_SOCKET_DIR_RAW@/null
Accept=yes
SocketMode=@FUSE_SERVICE_SOCKET_PERMS@
RemoveOnStop=yes
[Install]
WantedBy=sockets.target
+102
View File
@@ -0,0 +1,102 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2026 Oracle. All Rights Reserved.
# Author: Darrick J. Wong <djwong@kernel.org>
[Unit]
Description=null Sample Fuse Service
# Don't leave failed units behind, systemd does not clean them up!
CollectMode=inactive-or-failed
[Service]
Type=exec
ExecStart=/path/to/null
# Try to capture core dumps
LimitCORE=infinity
SyslogIdentifier=%N
# No realtime CPU scheduling
RestrictRealtime=true
# Don't let us see anything in the regular system, and don't run as root
DynamicUser=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
PrivateUsers=true
# No network access
PrivateNetwork=true
ProtectHostname=true
RestrictAddressFamilies=none
IPAddressDeny=any
# Don't let the program mess with the kernel configuration at all
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
ProtectProc=invisible
RestrictNamespaces=true
RestrictFileSystems=
# Hide everything in /proc, even /proc/mounts
ProcSubset=pid
# Only allow the default personality Linux
LockPersonality=true
# No writable memory pages
MemoryDenyWriteExecute=true
# Don't let our mounts leak out to the host
PrivateMounts=true
# Restrict system calls to the native arch and only enough to get things going
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
SystemCallFilter=~@resources
SystemCallFilter=~@clock
SystemCallFilter=~@cpu-emulation
SystemCallFilter=~@debug
SystemCallFilter=~@module
SystemCallFilter=~@reboot
SystemCallFilter=~@swap
SystemCallFilter=~@mount
# libfuse io_uring wants to pin cores and memory
SystemCallFilter=mbind
SystemCallFilter=sched_setaffinity
# Leave a breadcrumb if we get whacked by the system call filter
SystemCallErrorNumber=EL3RST
# Log to the kernel dmesg, just like an in-kernel filesystem driver
StandardOutput=append:/dev/ttyprintk
StandardError=append:/dev/ttyprintk
# Run with no capabilities at all
CapabilityBoundingSet=
AmbientCapabilities=
NoNewPrivileges=true
# We don't create files
UMask=7777
# No access to hardware /dev files at all
ProtectClock=true
DevicePolicy=closed
# Don't mess with set[ug]id anything.
RestrictSUIDSGID=true
# Don't let OOM kills of processes in this containment group kill the whole
# service, because we don't want filesystem drivers to go down.
OOMPolicy=continue
OOMScoreAdjust=-1000