diff --git a/cmd/sharefs/fuse-operations.h b/cmd/sharefs/fuse-operations.h index 9abc47c..edcfb2a 100644 --- a/cmd/sharefs/fuse-operations.h +++ b/cmd/sharefs/fuse-operations.h @@ -1,9 +1,9 @@ -#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 4) +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) #include #include /* for fuse_cmdline_help */ -#if (FUSE_VERSION < FUSE_MAKE_VERSION(3, 4)) -#error This package requires libfuse >= v3.4 +#if (FUSE_VERSION < FUSE_MAKE_VERSION(3, 12)) +#error This package requires libfuse >= v3.12 #endif #define SHAREFS_MEDIA_RW_ID (1 << 10) - 1 /* owning gid presented to userspace */ diff --git a/cmd/sharefs/fuse.go b/cmd/sharefs/fuse.go index 4b9764d..8b638b7 100644 --- a/cmd/sharefs/fuse.go +++ b/cmd/sharefs/fuse.go @@ -25,6 +25,8 @@ import ( "strconv" "syscall" "unsafe" + + "hakurei.app/internal/info" ) type ( @@ -207,6 +209,8 @@ func unsafeAddArgument(args *fuseArgs, arg string) { } func _main(argc int, argv **C.char) int { + runtime.LockOSThread() + // don't mask creation mode, kernel already did that syscall.Umask(0) @@ -222,6 +226,34 @@ func _main(argc int, argv **C.char) int { var priv C.struct_sharefs_private pinner.Pin(&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 + } + { source, setuid, setgid, ret := parseOpts(&args) if ret != 0 { @@ -247,8 +279,7 @@ func _main(argc int, argv **C.char) int { priv.setuid, priv.setgid = C.uintptr_t(setuid), C.uintptr_t(setgid) } - // TODO(ophestra): spawn container here, set PR_SET_NO_NEW_PRIVS and enforce landlock - fuse_main_return := C._fuse_main(args.argc, args.argv, &C.struct_fuse_operations{ + op := C.struct_fuse_operations{ init: closure(C.sharefs_init), destroy: closure(C.sharefs_destroy), @@ -268,11 +299,52 @@ func _main(argc int, argv **C.char) int { statfs: closure(C.sharefs_statfs), release: closure(C.sharefs_release), fsync: closure(C.sharefs_fsync), - }, unsafe.Pointer(&priv)) + } + + 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 priv.init_failed { return 1 - } else { - return int(fuse_main_return) } + return 0 } diff --git a/cmd/sharefs/main.go b/cmd/sharefs/main.go index 60924e0..c90c862 100644 --- a/cmd/sharefs/main.go +++ b/cmd/sharefs/main.go @@ -4,7 +4,6 @@ import ( "log" "os" "path" - "runtime" ) // executableName is the [path.Base] name of the executable that started the current process. @@ -19,7 +18,6 @@ var executableName = func() string { }() func main() { - runtime.LockOSThread() log.SetFlags(0) log.SetPrefix(executableName + ": ") diff --git a/cmd/sharefs/test/test.py b/cmd/sharefs/test/test.py index 9b30379..cd1d897 100644 --- a/cmd/sharefs/test/test.py +++ b/cmd/sharefs/test/test.py @@ -1,6 +1,13 @@ start_all() machine.wait_for_unit("multi-user.target") +# To check sharefs version: +print(machine.succeed("/etc/sharefs -V")) + +# Make sure sharefs did not terminate: +machine.wait_for_unit("sharefs.service") + +machine.succeed("mkdir /mnt") def check_bad_opts_output(opts, want, privileged=False): output = machine.fail(("" if privileged else "sudo -u alice -i ") + f"/etc/sharefs -f -o source=/proc/nonexistent,{opts} /mnt 2>&1") if output != want: @@ -26,12 +33,17 @@ check_bad_opts_output("allow_other", "sharefs: setuid and setgid must not be 0\n check_bad_opts_output("setuid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True) check_bad_opts_output("setgid=1023", "sharefs: setuid and setgid must not be 0\n", privileged=True) +# Make sure nothing actually got mounted: +machine.fail("umount /mnt") +machine.succeed("rmdir /mnt") + # Benchmark sharefs: machine.succeed("fs_mark -v -d /sdcard/fs_mark -l /tmp/fs_log.txt") machine.copy_from_vm("/tmp/fs_log.txt", "") # Check permissions: -machine.succeed("ls /var/lib/hakurei/sdcard/fs_mark") +machine.succeed("sudo -u sharefs touch /var/lib/hakurei/sdcard/fs_mark/.check") +machine.succeed("sudo -u sharefs rm /var/lib/hakurei/sdcard/fs_mark/.check") machine.succeed("sudo -u alice rm -rf /sdcard/fs_mark") machine.fail("ls /var/lib/hakurei/sdcard/fs_mark")