diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-12-25 04:05:54 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-12-25 05:13:02 +0900 |
| commit | 7bfbd5981025282007f09225e22e895523d7a7bf (patch) | |
| tree | 14e4ea7475ab63c8fa22845d287a307fb029c110 /cmd/sharefs/fuse.go | |
| parent | ea815a59e85a5acad8e22e98a16cdc6de1323824 (diff) | |
cmd/sharefs: implement shared filesystem
This is for passing files between applications, similar to android /sdcard.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd/sharefs/fuse.go')
| -rw-r--r-- | cmd/sharefs/fuse.go | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/cmd/sharefs/fuse.go b/cmd/sharefs/fuse.go new file mode 100644 index 00000000..ac276dd7 --- /dev/null +++ b/cmd/sharefs/fuse.go @@ -0,0 +1,265 @@ +package main + +/* +#cgo pkg-config: --static fuse3 + +#include "fuse-helper.h" +#include <stdlib.h> +#include <string.h> + +extern void *sharefs_init(struct fuse_conn_info *conn, struct fuse_config *cfg); +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 ( + "fmt" + "log" + "os" + "path/filepath" + "strconv" + "syscall" + "unsafe" +) + +type ( + // closure represents a C function pointer. + closure = C.closure + + // fuseArgs represents the fuse_args structure. + fuseArgs = C.struct_fuse_args +) + +var ( + // initFailed is set to true by sharefs_init if initialisation was unsuccessful. + initFailed bool + + // initSource is pathname to the writable source directory used by sharefs_init to populate sharefs_private. + initSource string + // initSetUidGid is the uid and gid to set by sharefs_init when running as root. + initSetUidGid [2]int +) + +//export sharefs_init +func sharefs_init(_ *C.struct_fuse_conn_info, cfg *C.struct_fuse_config) unsafe.Pointer { + private_data := C.malloc(C.size_t(unsafe.Sizeof(C.struct_sharefs_private{}))) + priv := (*C.struct_sharefs_private)(private_data) + + if os.Geteuid() == 0 { + if initSetUidGid[0] <= 0 || initSetUidGid[1] <= 0 { + log.Println("setuid and setgid must not be 0") + goto fail + } + if err := syscall.Setresgid(initSetUidGid[1], initSetUidGid[1], initSetUidGid[1]); 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(initSetUidGid[0], initSetUidGid[0], initSetUidGid[0]); 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 fd, err := syscall.Open(initSource, syscall.O_DIRECTORY|syscall.O_RDONLY, 0); err != nil { + log.Printf("cannot open %q: %v", initSource, err) + goto fail + } else if err = syscall.Fchdir(fd); err != nil { + log.Printf("cannot enter %q: %s", initSource, err) + goto fail + } else { + priv.dirfd = C.int(fd) + } + + return private_data + +fail: + C.free(private_data) + C.fuse_exit(C.fuse_get_context().fuse) + initFailed = true + return nil +} + +//export sharefs_destroy +func sharefs_destroy(private_data unsafe.Pointer) { + if private_data != nil { + defer C.free(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() { + fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName) + C.fuse_cmdline_help() + C.fuse_lowlevel_help() + 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") +} + +// parseOpts parses fuse options via fuse_opt_parse. +func parseOpts(args *C.struct_fuse_args) ( + source string, + setuid, setgid int, + ret int, +) { + 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 + } + + 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}, + + C._FUSE_OPT_END(), + }[0], nil) == -1 { + ret = 1 + return + } + + 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.source == nil { + showHelp() + ret = 1 + return + } else { + source = C.GoString(unsafeOpts.source) + } + + if unsafeOpts.setuid == nil { + setuid = -1 + } else if v, err := strconv.Atoi(C.GoString(unsafeOpts.setuid)); err != nil || v <= 0 { + log.Println("invalid value for option setuid") + ret = 1 + return + } else { + setuid = v + } + if unsafeOpts.setgid == nil { + setgid = -1 + } else if v, err := strconv.Atoi(C.GoString(unsafeOpts.setgid)); err != nil || v <= 0 { + log.Println("invalid value for option setgid") + ret = 1 + return + } else { + setgid = v + } + + return +} + +// 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 *fuseArgs, arg string) { + C.fuse_opt_add_arg(args, (*C.char)(unsafe.Pointer(unsafe.StringData(arg)))) +} + +func _main(argc int, argv **C.char) int { + 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") + + { + source, setuid, setgid, ret := parseOpts(&args) + if ret != 0 { + return ret + } + + if a, err := filepath.Abs(source); err != nil { + log.Println(err) + return 1 + } else { + initSource = a + } + + if os.Geteuid() == 0 { + if setuid <= 0 || setgid <= 0 { + log.Println("setuid and setgid must not be 0") + return 1 + } + } else if setuid > 0 || setgid > 0 { + log.Println("setuid and setgid has no effect when not starting as root") + return 1 + } + initSetUidGid[0], initSetUidGid[1] = setuid, 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{ + init: closure(C.sharefs_init), + destroy: closure(C.sharefs_destroy), + + // implemented in fuse-helper.c + getattr: closure(C.sharefs_getattr), + readlink: closure(C.sharefs_readlink), + 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), + }, nil) + + if initFailed { + return 1 + } else { + return int(fuse_main_return) + } +} |
