Files
hakurei/cmd/sharefs/fuse.go
Ophestra 1db6f59bb3
All checks were successful
Test / Create distribution (push) Successful in 43s
Test / Sandbox (push) Successful in 2m25s
Test / Hakurei (push) Successful in 3m29s
Test / ShareFS (push) Successful in 3m29s
Test / Hpkg (push) Successful in 4m25s
Test / Sandbox (race detector) (push) Successful in 4m35s
Test / Hakurei (race detector) (push) Successful in 5m37s
Test / Flake checks (push) Successful in 1m46s
cmd/sharefs: opaque setup state
This allows unrestricted use of the type system and prepares setup code for cross-process initialisation.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-12-27 03:45:24 +09:00

379 lines
10 KiB
Go

package main
/*
#cgo pkg-config: --static fuse3
#include "fuse-operations.h"
#include <stdlib.h>
#include <string.h>
extern void *sharefs_init(struct fuse_conn_info *conn, struct fuse_config *cfg);
extern void sharefs_destroy(void *private_data);
typedef void (*closure)();
static inline struct fuse_opt _FUSE_OPT_END() { return (struct fuse_opt)FUSE_OPT_END; };
static inline int _fuse_main(int argc, char *argv[], const struct fuse_operations *op, void *user_data) { return fuse_main(argc, argv, op, user_data); }
*/
import "C"
import (
"encoding/gob"
"fmt"
"log"
"os"
"runtime"
"runtime/cgo"
"strconv"
"syscall"
"unsafe"
"hakurei.app/container/check"
"hakurei.app/internal/info"
)
type (
// closure represents a C function pointer.
closure = C.closure
// setupState holds state used for setup. Its cgo handle is included in
// sharefs_private and considered opaque to non-setup callbacks.
setupState struct {
// Whether sharefs_init failed.
initFailed bool
// Absolute pathname to open for dirfd.
Source *check.Absolute
// Opened by parent, ignored unless Source is nil.
Dirfd int
// New uid and gid to set by sharefs_init when starting as root.
Setuid, Setgid int
}
)
func init() { gob.Register(new(setupState)) }
// destroySetup invalidates the setup [cgo.Handle] in a sharefs_private structure.
func destroySetup(private_data unsafe.Pointer) (ok bool) {
if private_data == nil {
return false
}
priv := (*C.struct_sharefs_private)(private_data)
if h := cgo.Handle(priv.setup); h != 0 {
priv.setup = 0
h.Delete()
ok = true
}
return
}
//export sharefs_init
func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.Pointer {
ctx := C.fuse_get_context()
priv := (*C.struct_sharefs_private)(ctx.private_data)
setup := cgo.Handle(priv.setup).Value().(*setupState)
if os.Geteuid() == 0 {
if setup.Setuid <= 0 || setup.Setgid <= 0 {
log.Println("setuid and setgid must not be 0")
goto fail
}
if err := syscall.Setresgid(setup.Setgid, setup.Setgid, setup.Setgid); err != nil {
log.Printf("cannot set gid: %v", err)
goto fail
}
if err := syscall.Setgroups(nil); err != nil {
log.Printf("cannot set supplementary groups: %v", err)
goto fail
}
if err := syscall.Setresuid(setup.Setuid, setup.Setuid, setup.Setuid); err != nil {
log.Printf("cannot set uid: %v", err)
goto fail
}
}
cfg.use_ino = C.true
cfg.direct_io = C.false
// getattr is context-dependent
cfg.attr_timeout = 0
cfg.entry_timeout = 0
cfg.negative_timeout = 0
// all future filesystem operations happen through this dirfd
if setup.Source == nil {
priv.dirfd = C.int(setup.Dirfd)
} else if fd, err := syscall.Open(setup.Source.String(), syscall.O_DIRECTORY|syscall.O_RDONLY, 0); err != nil {
log.Printf("cannot open %q: %v", setup.Source.String(), err)
goto fail
} else if err = syscall.Fchdir(fd); err != nil {
log.Printf("cannot enter %q: %s", setup.Source.String(), err)
goto fail
} else {
priv.dirfd = C.int(fd)
}
return ctx.private_data
fail:
setup.initFailed = true
C.fuse_exit(ctx.fuse)
return nil
}
//export sharefs_destroy
func sharefs_destroy(private_data unsafe.Pointer) {
if private_data != nil {
destroySetup(private_data)
priv := (*C.struct_sharefs_private)(private_data)
if err := syscall.Close(int(priv.dirfd)); err != nil {
log.Printf("cannot close source directory: %v", err)
}
}
}
// showHelp prints the help message.
func showHelp(args *C.struct_fuse_args) {
fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName)
fmt.Println("Filesystem options:")
fmt.Println(" -o source=/data/media source directory to be mounted")
fmt.Println(" -o setuid=1023 uid to run as when starting as root")
fmt.Println(" -o setgid=1023 gid to run as when starting as root")
fmt.Println("\nFUSE options:")
C.fuse_cmdline_help()
C.fuse_lib_help(args)
}
// parseOpts parses fuse options via fuse_opt_parse.
func parseOpts(args *C.struct_fuse_args, setup *setupState) bool {
var unsafeOpts struct {
// Pathname to writable source directory.
source *C.char
// Decimal string representation of uid to set when running as root.
setuid *C.char
// Decimal string representation of gid to set when running as root.
setgid *C.char
// Decimal string representation of open file descriptor to read setupState from.
// This is an internal detail for containerisation and must not be specified directly.
setup *C.char
}
if C.fuse_opt_parse(args, unsafe.Pointer(&unsafeOpts), &[]C.struct_fuse_opt{
{templ: C.CString("source=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.source)), value: 0},
{templ: C.CString("setuid=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.setuid)), value: 0},
{templ: C.CString("setgid=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.setgid)), value: 0},
{templ: C.CString("setup=%s"), offset: C.ulong(unsafe.Offsetof(unsafeOpts.setup)), value: 0},
C._FUSE_OPT_END(),
}[0], nil) == -1 {
return false
}
if unsafeOpts.source != nil {
defer C.free(unsafe.Pointer(unsafeOpts.source))
}
if unsafeOpts.setuid != nil {
defer C.free(unsafe.Pointer(unsafeOpts.setuid))
}
if unsafeOpts.setgid != nil {
defer C.free(unsafe.Pointer(unsafeOpts.setgid))
}
if unsafeOpts.setup != nil {
defer C.free(unsafe.Pointer(unsafeOpts.setup))
if v, err := strconv.Atoi(C.GoString(unsafeOpts.setup)); err != nil || v < 3 {
log.Println("invalid value for option setup")
return false
} else if err = gob.NewDecoder(os.NewFile(uintptr(v), "setup")).Decode(setup); err != nil {
log.Println(err)
return false
}
return true
}
if unsafeOpts.source == nil {
showHelp(args)
return false
} else if a, err := check.NewAbs(C.GoString(unsafeOpts.source)); err != nil {
log.Println(err)
return false
} else {
setup.Source = a
}
if unsafeOpts.setuid == nil {
setup.Setuid = -1
} else if v, err := strconv.Atoi(C.GoString(unsafeOpts.setuid)); err != nil || v <= 0 {
log.Println("invalid value for option setuid")
return false
} else {
setup.Setuid = v
}
if unsafeOpts.setgid == nil {
setup.Setgid = -1
} else if v, err := strconv.Atoi(C.GoString(unsafeOpts.setgid)); err != nil || v <= 0 {
log.Println("invalid value for option setgid")
return false
} else {
setup.Setgid = v
}
return true
}
// copyStrings returns a copy of s with null-termination.
func copyStrings(s ...string) **C.char {
if len(s) == 0 {
return nil
}
args := unsafe.Slice((**C.char)(C.malloc(C.size_t(uintptr(len(s))*unsafe.Sizeof(s[0])))), len(s))
for i, arg := range s {
args[i] = C.CString(arg)
}
return &args[0]
}
// unsafeAddArgument adds an argument to fuseArgs via fuse_opt_add_arg.
// The last byte of arg must be 0.
func unsafeAddArgument(args *C.struct_fuse_args, arg string) {
C.fuse_opt_add_arg(args, (*C.char)(unsafe.Pointer(unsafe.StringData(arg))))
}
func _main(argc int, argv **C.char) int {
runtime.LockOSThread()
// don't mask creation mode, kernel already did that
syscall.Umask(0)
var pinner runtime.Pinner
defer pinner.Unpin()
args := C.struct_fuse_args{argc: C.int(argc), argv: argv, allocated: 1}
// this causes the kernel to enforce access control based on
// struct stat populated by sharefs_getattr
unsafeAddArgument(&args, "-odefault_permissions\x00")
var priv C.struct_sharefs_private
pinner.Pin(&priv)
var setup setupState
priv.setup = C.uintptr_t(cgo.NewHandle(&setup))
defer destroySetup(unsafe.Pointer(&priv))
var opts C.struct_fuse_cmdline_opts
if C.fuse_parse_cmdline(&args, &opts) != 0 {
return 1
}
defer func() {
if opts.mountpoint != nil {
C.free(unsafe.Pointer(opts.mountpoint))
}
C.fuse_opt_free_args(&args)
}()
if opts.show_version != 0 {
fmt.Println("hakurei version", info.Version())
fmt.Println("FUSE library version", C.GoString(C.fuse_pkgversion()))
C.fuse_lowlevel_version()
return 0
}
if opts.show_help != 0 {
showHelp(&args)
return 0
}
if opts.show_help == 0 && opts.mountpoint == nil {
log.Println("no mountpoint specified")
return 2
}
if !parseOpts(&args, &setup) {
return 1
}
if os.Geteuid() == 0 {
if setup.Setuid <= 0 || setup.Setgid <= 0 {
log.Println("setuid and setgid must not be 0")
return 1
}
} else if setup.Setuid > 0 || setup.Setgid > 0 {
log.Println("setuid and setgid has no effect when not starting as root")
return 1
}
op := C.struct_fuse_operations{
init: closure(C.sharefs_init),
destroy: closure(C.sharefs_destroy),
// implemented in fuse-helper.c
getattr: closure(C.sharefs_getattr),
readdir: closure(C.sharefs_readdir),
mkdir: closure(C.sharefs_mkdir),
unlink: closure(C.sharefs_unlink),
rmdir: closure(C.sharefs_rmdir),
rename: closure(C.sharefs_rename),
truncate: closure(C.sharefs_truncate),
utimens: closure(C.sharefs_utimens),
create: closure(C.sharefs_create),
open: closure(C.sharefs_open),
read: closure(C.sharefs_read),
write: closure(C.sharefs_write),
statfs: closure(C.sharefs_statfs),
release: closure(C.sharefs_release),
fsync: closure(C.sharefs_fsync),
}
fuse := C.fuse_new_fn(&args, &op, C.size_t(unsafe.Sizeof(op)), unsafe.Pointer(&priv))
if fuse == nil {
return 3
}
defer C.fuse_destroy(fuse)
if C.fuse_mount(fuse, opts.mountpoint) != 0 {
return 4
}
defer C.fuse_unmount(fuse)
// TODO(ophestra): spawn container here, set PR_SET_NO_NEW_PRIVS and enforce landlock
if C.fuse_daemonize(opts.foreground) != 0 {
return 5
}
se := C.fuse_get_session(fuse)
if C.fuse_set_signal_handlers(se) != 0 {
return 6
}
defer C.fuse_remove_signal_handlers(se)
if opts.singlethread != 0 {
if C.fuse_loop(fuse) != 0 {
return 8
}
} else {
loopConfig := C.fuse_loop_cfg_create()
if loopConfig == nil {
return 7
}
defer C.fuse_loop_cfg_destroy(loopConfig)
C.fuse_loop_cfg_set_clone_fd(loopConfig, C.uint(opts.clone_fd))
C.fuse_loop_cfg_set_idle_threads(loopConfig, opts.max_idle_threads)
C.fuse_loop_cfg_set_max_threads(loopConfig, opts.max_threads)
if C.fuse_loop_mt(fuse, loopConfig) != 0 {
return 8
}
}
if setup.initFailed {
return 1
}
return 0
}