cmd/sharefs: implement shared filesystem
All checks were successful
Test / Create distribution (push) Successful in 46s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m41s
Test / Hpkg (push) Successful in 4m42s
Test / Sandbox (race detector) (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / ShareFS (push) Successful in 38m10s
Test / Flake checks (push) Successful in 1m46s
All checks were successful
Test / Create distribution (push) Successful in 46s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m41s
Test / Hpkg (push) Successful in 4m42s
Test / Sandbox (race detector) (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 5m53s
Test / ShareFS (push) Successful in 38m10s
Test / Flake checks (push) Successful in 1m46s
This is for passing files between applications, similar to android /sdcard. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
265
cmd/sharefs/fuse.go
Normal file
265
cmd/sharefs/fuse.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user