cmd/sharefs: check option parsing behaviour
All checks were successful
Test / Create distribution (push) Successful in 44s
Test / ShareFS (push) Successful in 39s
Test / Sandbox (push) Successful in 47s
Test / Sandbox (race detector) (push) Successful in 46s
Test / Hakurei (race detector) (push) Successful in 54s
Test / Hpkg (push) Successful in 50s
Test / Hakurei (push) Successful in 55s
Test / Flake checks (push) Successful in 1m35s
All checks were successful
Test / Create distribution (push) Successful in 44s
Test / ShareFS (push) Successful in 39s
Test / Sandbox (push) Successful in 47s
Test / Sandbox (race detector) (push) Successful in 46s
Test / Hakurei (race detector) (push) Successful in 54s
Test / Hpkg (push) Successful in 50s
Test / Hakurei (push) Successful in 55s
Test / Flake checks (push) Successful in 1m35s
This change makes it possible to check parseOpts behaviour as part of Go tests. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/cgo"
|
"runtime/cgo"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -43,6 +44,9 @@ type (
|
|||||||
// closure represents a C function pointer.
|
// closure represents a C function pointer.
|
||||||
closure = C.closure
|
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
|
// setupState holds state used for setup. Its cgo handle is included in
|
||||||
// sharefs_private and considered opaque to non-setup callbacks.
|
// sharefs_private and considered opaque to non-setup callbacks.
|
||||||
setupState struct {
|
setupState struct {
|
||||||
@@ -127,7 +131,14 @@ func sharefs_destroy(private_data unsafe.Pointer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// showHelp prints the help message.
|
// 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.Printf("usage: %s [options] <mountpoint>\n\n", executableName)
|
||||||
|
|
||||||
fmt.Println("Filesystem options:")
|
fmt.Println("Filesystem options:")
|
||||||
@@ -141,7 +152,7 @@ func showHelp(args *C.struct_fuse_args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseOpts parses fuse options via fuse_opt_parse.
|
// 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 {
|
var unsafeOpts struct {
|
||||||
// Pathname to writable source directory.
|
// Pathname to writable source directory.
|
||||||
source *C.char
|
source *C.char
|
||||||
@@ -233,25 +244,28 @@ func parseOpts(args *C.struct_fuse_args, setup *setupState) (ok bool) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyStrings returns a copy of s with null-termination.
|
// copyArgs returns a heap allocated copy of an argument slice in fuse_args representation.
|
||||||
func copyStrings(s ...string) **C.char {
|
func copyArgs(s ...string) fuseArgs {
|
||||||
if len(s) == 0 {
|
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))
|
args := unsafe.Slice((**C.char)(C.malloc(C.size_t(uintptr(len(s))*unsafe.Sizeof(s[0])))), len(s))
|
||||||
for i, arg := range s {
|
for i, arg := range s {
|
||||||
args[i] = C.CString(arg)
|
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.
|
// unsafeAddArgument adds an argument to fuseArgs via fuse_opt_add_arg.
|
||||||
// The last byte of arg must be 0.
|
// 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))))
|
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())
|
msg := message.New(log.Default())
|
||||||
container.TryArgv0(msg)
|
container.TryArgv0(msg)
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
@@ -262,7 +276,8 @@ func _main(argc int, argv **C.char) (exitCode int) {
|
|||||||
var pinner runtime.Pinner
|
var pinner runtime.Pinner
|
||||||
defer pinner.Unpin()
|
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
|
// this causes the kernel to enforce access control based on
|
||||||
// struct stat populated by sharefs_getattr
|
// 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 {
|
if C.fuse_parse_cmdline(&args, &opts) != 0 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
defer func() {
|
if opts.mountpoint != nil {
|
||||||
if opts.mountpoint != nil {
|
defer C.free(unsafe.Pointer(opts.mountpoint))
|
||||||
C.free(unsafe.Pointer(opts.mountpoint))
|
}
|
||||||
}
|
|
||||||
C.fuse_opt_free_args(&args)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if opts.show_version != 0 {
|
if opts.show_version != 0 {
|
||||||
fmt.Println("hakurei version", info.Version())
|
fmt.Println("hakurei version", info.Version())
|
||||||
@@ -315,7 +327,7 @@ func _main(argc int, argv **C.char) (exitCode int) {
|
|||||||
os.Args[pathnameArg] = container.Nonexistent
|
os.Args[pathnameArg] = container.Nonexistent
|
||||||
}
|
}
|
||||||
|
|
||||||
if !parseOpts(&args, &setup) {
|
if !parseOpts(&args, &setup, msg.GetLogger()) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
113
cmd/sharefs/fuse_test.go
Normal file
113
cmd/sharefs/fuse_test.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,23 +3,14 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// executableName is the [path.Base] name of the executable that started the current process.
|
// sharefsName is the prefix used by log.std in the sharefs process.
|
||||||
var executableName = func() string {
|
const sharefsName = "sharefs"
|
||||||
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)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix(executableName + ": ")
|
log.SetPrefix(sharefsName + ": ")
|
||||||
|
|
||||||
os.Exit(_main(len(os.Args), copyStrings(os.Args...)))
|
os.Exit(_main(os.Args...))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user