aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/sharefs
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-12-27 17:33:12 +0900
committerOphestra <cat@gensokyo.uk>2025-12-27 17:33:12 +0900
commit775a9f57c9ec3bc6877e3973a8c4972558707375 (patch)
tree7410791c10ca3d8adfd8a17914ef799d55be1b5a /cmd/sharefs
parent2f8ca8337633a199aae9b4cdd71c66ed1c9529a5 (diff)
cmd/sharefs: check option parsing behaviour
This change makes it possible to check parseOpts behaviour as part of Go tests. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd/sharefs')
-rw-r--r--cmd/sharefs/fuse.go44
-rw-r--r--cmd/sharefs/fuse_test.go113
-rw-r--r--cmd/sharefs/main.go17
3 files changed, 145 insertions, 29 deletions
diff --git a/cmd/sharefs/fuse.go b/cmd/sharefs/fuse.go
index ea3d928e..9517b561 100644
--- a/cmd/sharefs/fuse.go
+++ b/cmd/sharefs/fuse.go
@@ -24,6 +24,7 @@ import (
"os"
"os/exec"
"os/signal"
+ "path"
"runtime"
"runtime/cgo"
"strconv"
@@ -43,6 +44,9 @@ type (
// closure represents a C function pointer.
closure = C.closure
+ // fuseArgs represents the fuse_args structure.
+ fuseArgs = C.struct_fuse_args
+
// setupState holds state used for setup. Its cgo handle is included in
// sharefs_private and considered opaque to non-setup callbacks.
setupState struct {
@@ -127,7 +131,14 @@ func sharefs_destroy(private_data unsafe.Pointer) {
}
// showHelp prints the help message.
-func showHelp(args *C.struct_fuse_args) {
+func showHelp(args *fuseArgs) {
+ executableName := sharefsName
+ if args.argc > 0 {
+ executableName = path.Base(C.GoString(*args.argv))
+ } else if name, err := os.Executable(); err == nil {
+ executableName = path.Base(name)
+ }
+
fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName)
fmt.Println("Filesystem options:")
@@ -141,7 +152,7 @@ func showHelp(args *C.struct_fuse_args) {
}
// parseOpts parses fuse options via fuse_opt_parse.
-func parseOpts(args *C.struct_fuse_args, setup *setupState) (ok bool) {
+func parseOpts(args *fuseArgs, setup *setupState, log *log.Logger) (ok bool) {
var unsafeOpts struct {
// Pathname to writable source directory.
source *C.char
@@ -233,25 +244,28 @@ func parseOpts(args *C.struct_fuse_args, setup *setupState) (ok bool) {
return true
}
-// copyStrings returns a copy of s with null-termination.
-func copyStrings(s ...string) **C.char {
+// copyArgs returns a heap allocated copy of an argument slice in fuse_args representation.
+func copyArgs(s ...string) fuseArgs {
if len(s) == 0 {
- return nil
+ return fuseArgs{argc: 0, argv: nil, allocated: 0}
}
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]
+ return fuseArgs{argc: C.int(len(s)), argv: &args[0], allocated: 1}
}
+// freeArgs frees the contents of argument list.
+func freeArgs(args *fuseArgs) { C.fuse_opt_free_args(args) }
+
// 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) {
+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) (exitCode int) {
+func _main(s ...string) (exitCode int) {
msg := message.New(log.Default())
container.TryArgv0(msg)
runtime.LockOSThread()
@@ -262,7 +276,8 @@ func _main(argc int, argv **C.char) (exitCode int) {
var pinner runtime.Pinner
defer pinner.Unpin()
- args := C.struct_fuse_args{argc: C.int(argc), argv: argv, allocated: 1}
+ args := copyArgs(s...)
+ defer freeArgs(&args)
// this causes the kernel to enforce access control based on
// struct stat populated by sharefs_getattr
@@ -278,12 +293,9 @@ func _main(argc int, argv **C.char) (exitCode int) {
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.mountpoint != nil {
+ defer C.free(unsafe.Pointer(opts.mountpoint))
+ }
if opts.show_version != 0 {
fmt.Println("hakurei version", info.Version())
@@ -315,7 +327,7 @@ func _main(argc int, argv **C.char) (exitCode int) {
os.Args[pathnameArg] = container.Nonexistent
}
- if !parseOpts(&args, &setup) {
+ if !parseOpts(&args, &setup, msg.GetLogger()) {
return 1
}
diff --git a/cmd/sharefs/fuse_test.go b/cmd/sharefs/fuse_test.go
new file mode 100644
index 00000000..d25bc888
--- /dev/null
+++ b/cmd/sharefs/fuse_test.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+ "bytes"
+ "log"
+ "reflect"
+ "testing"
+
+ "hakurei.app/container/check"
+)
+
+func TestParseOpts(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ args []string
+ want setupState
+ wantLog string
+ wantOk bool
+ }{
+ {"zero length", []string{}, setupState{}, "", false},
+
+ {"not absolute", []string{"sharefs",
+ "-o", "source=nonexistent",
+ "-o", "setuid=1023",
+ "-o", "setgid=1023",
+ }, setupState{}, "sharefs: path \"nonexistent\" is not absolute\n", false},
+
+ {"not specified", []string{"sharefs",
+ "-o", "setuid=1023",
+ "-o", "setgid=1023",
+ }, setupState{}, "", false},
+
+ {"invalid setuid", []string{"sharefs",
+ "-o", "source=/proc/nonexistent",
+ "-o", "setuid=ff",
+ "-o", "setgid=1023",
+ }, setupState{
+ Source: check.MustAbs("/proc/nonexistent"),
+ }, "sharefs: invalid value for option setuid\n", false},
+
+ {"invalid setgid", []string{"sharefs",
+ "-o", "source=/proc/nonexistent",
+ "-o", "setuid=1023",
+ "-o", "setgid=ff",
+ }, setupState{
+ Source: check.MustAbs("/proc/nonexistent"),
+ Setuid: 1023,
+ }, "sharefs: invalid value for option setgid\n", false},
+
+ {"simple", []string{"sharefs",
+ "-o", "source=/proc/nonexistent",
+ }, setupState{
+ Source: check.MustAbs("/proc/nonexistent"),
+ Setuid: -1,
+ Setgid: -1,
+ }, "", true},
+
+ {"root", []string{"sharefs",
+ "-o", "source=/proc/nonexistent",
+ "-o", "setuid=1023",
+ "-o", "setgid=1023",
+ }, setupState{
+ Source: check.MustAbs("/proc/nonexistent"),
+ Setuid: 1023,
+ Setgid: 1023,
+ }, "", true},
+
+ {"setuid", []string{"sharefs",
+ "-o", "source=/proc/nonexistent",
+ "-o", "setuid=1023",
+ }, setupState{
+ Source: check.MustAbs("/proc/nonexistent"),
+ Setuid: 1023,
+ Setgid: -1,
+ }, "", true},
+
+ {"setgid", []string{"sharefs",
+ "-o", "source=/proc/nonexistent",
+ "-o", "setgid=1023",
+ }, setupState{
+ Source: check.MustAbs("/proc/nonexistent"),
+ Setuid: -1,
+ Setgid: 1023,
+ }, "", true},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ var (
+ got setupState
+ buf bytes.Buffer
+ )
+ args := copyArgs(tc.args...)
+ defer freeArgs(&args)
+ unsafeAddArgument(&args, "-odefault_permissions\x00")
+
+ if ok := parseOpts(&args, &got, log.New(&buf, "sharefs: ", 0)); ok != tc.wantOk {
+ t.Errorf("parseOpts: ok = %v, want %v", ok, tc.wantOk)
+ }
+
+ if !reflect.DeepEqual(&got, &tc.want) {
+ t.Errorf("parseOpts: setup = %#v, want %#v", got, tc.want)
+ }
+
+ if buf.String() != tc.wantLog {
+ t.Errorf("parseOpts: log =\n%s\nwant\n%s", buf.String(), tc.wantLog)
+ }
+ })
+ }
+}
diff --git a/cmd/sharefs/main.go b/cmd/sharefs/main.go
index c90c8624..941a1668 100644
--- a/cmd/sharefs/main.go
+++ b/cmd/sharefs/main.go
@@ -3,23 +3,14 @@ package main
import (
"log"
"os"
- "path"
)
-// executableName is the [path.Base] name of the executable that started the current process.
-var executableName = func() string {
- if len(os.Args) > 0 {
- return path.Base(os.Args[0])
- } else if name, err := os.Executable(); err != nil {
- return "sharefs"
- } else {
- return path.Base(name)
- }
-}()
+// sharefsName is the prefix used by log.std in the sharefs process.
+const sharefsName = "sharefs"
func main() {
log.SetFlags(0)
- log.SetPrefix(executableName + ": ")
+ log.SetPrefix(sharefsName + ": ")
- os.Exit(_main(len(os.Args), copyStrings(os.Args...)))
+ os.Exit(_main(os.Args...))
}