diff --git a/WORKSPACE b/WORKSPACE index ee963ebc6..414ecdf5c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1888,3 +1888,10 @@ go_repository( sum = "h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=", version = "v1.2.0", ) + +go_repository( + name = "com_github_hanwen_go_fuse_v2", + importpath = "github.com/hanwen/go-fuse/v2", + sum = "h1:fOTuYWxywhaliwMobGTP/6NUCgGdal6pCpuch4MHCnU=", + version = "v2.1.1-0.20220627082937-d01fda7edf17", +) diff --git a/go.mod b/go.mod index a59cc0773..26603ec95 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/gofuzz v1.0.0 // indirect github.com/googleapis/gnostic v0.4.0 // indirect + github.com/hanwen/go-fuse/v2 v2.1.1-0.20220627082937-d01fda7edf17 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/json-iterator/go v1.1.7 // indirect diff --git a/go.sum b/go.sum index e6c7248b7..8c6b69656 100644 --- a/go.sum +++ b/go.sum @@ -233,6 +233,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hanwen/go-fuse/v2 v2.1.1-0.20220627082937-d01fda7edf17 h1:fOTuYWxywhaliwMobGTP/6NUCgGdal6pCpuch4MHCnU= +github.com/hanwen/go-fuse/v2 v2.1.1-0.20220627082937-d01fda7edf17/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= @@ -266,6 +268,7 @@ github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1 h1:zc0R6cOw98cMengLA0fvU5 github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a h1:+J2gw7Bw77w/fbK7wnNJJDKmw1IbWft2Ul5BzrG1Qm8= diff --git a/test/runner/BUILD b/test/runner/BUILD index 084cdbf5a..48a8ef3a1 100644 --- a/test/runner/BUILD +++ b/test/runner/BUILD @@ -8,6 +8,7 @@ go_binary( srcs = ["main.go"], data = [ "//runsc", + "//test/runner/fuse", "//test/runner/setup_container", ], visibility = ["//:sandbox"], diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 1277a441b..e3d1e6210 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -72,6 +72,7 @@ def _syscall_test( iouring = False, container = None, one_sandbox = True, + use_fusefs = False, **kwargs): # Prepend "runsc" to non-native platform names. full_platform = platform if platform == "native" else "runsc_" + platform @@ -84,6 +85,8 @@ def _syscall_test( name += "_overlay" if network != "none": name += "_" + network + "net" + if use_fusefs: + name += "_fuse" # Apply all tags. if tags == None: @@ -131,6 +134,7 @@ def _syscall_test( "--platform-support=" + platform_support, "--network=" + network, "--use-tmpfs=" + str(use_tmpfs), + "--use-fusefs=" + str(use_fusefs), "--file-access=" + file_access, "--overlay=" + str(overlay), "--add-host-communication=" + str(add_host_communication), @@ -163,6 +167,7 @@ def all_platforms(): def syscall_test( test, use_tmpfs = False, + use_fusefs = False, add_overlay = False, add_host_communication = False, add_hostinet = False, @@ -263,3 +268,16 @@ def syscall_test( file_access = "shared", **kwargs ) + if use_fusefs: + _syscall_test( + test = test, + platform = default_platform, + use_tmpfs = True, + use_fusefs = True, + add_host_communication = add_host_communication, + tags = platforms.get(default_platform, []) + tags, + debug = debug, + container = container, + one_sandbox = one_sandbox, + **kwargs + ) diff --git a/test/runner/fuse/BUILD b/test/runner/fuse/BUILD new file mode 100644 index 000000000..5211d33d4 --- /dev/null +++ b/test/runner/fuse/BUILD @@ -0,0 +1,14 @@ +load("//tools:defs.bzl", "go_binary") + +package(licenses = ["notice"]) + +go_binary( + name = "fuse", + srcs = ["fuse.go"], + visibility = ["//test/runner:__subpackages__"], + deps = [ + "//pkg/log", + "@com_github_hanwen_go_fuse_v2//fs:go_default_library", + "@com_github_hanwen_go_fuse_v2//fuse:go_default_library", + ], +) diff --git a/test/runner/fuse/fuse.go b/test/runner/fuse/fuse.go new file mode 100644 index 000000000..fd1bf99ec --- /dev/null +++ b/test/runner/fuse/fuse.go @@ -0,0 +1,59 @@ +// Copyright 2022 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Binary main starts a fuse server that forwards filesystem operations from +// /tmp to /fuse. +package main + +import ( + "os" + "os/exec" + + "github.com/hanwen/go-fuse/v2/fs" + "github.com/hanwen/go-fuse/v2/fuse" + "gvisor.dev/gvisor/pkg/log" +) + +func main() { + loopbackRoot, err := fs.NewLoopbackRoot("/fuse") + if err != nil { + log.Warningf("could not create loopback root: %v", err) + os.Exit(1) + } + opts := &fuse.MountOptions{DirectMount: true} + rawFS := fs.NewNodeFS(loopbackRoot, &fs.Options{}) + server, err := fuse.NewServer(rawFS, "/tmp", opts) + if err != nil { + log.Warningf("could not create fuse server: %v", err) + os.Exit(1) + } + + go server.Serve() + defer func() { + server.Unmount() + server.Wait() + }() + if err := server.WaitMount(); err != nil { + // We don't shutdown the serve loop. If the mount does + // not succeed, the loop won't work and exit. + log.Warningf(`Could not mount fuse submount "/tmp": %v`, err) + os.Exit(1) + } + cmd := exec.Command(os.Args[1], os.Args[2:]...) + if err := cmd.Run(); err != nil { + log.Warningf(err.Error()) + os.Exit(1) + } + os.Exit(0) +} diff --git a/test/runner/main.go b/test/runner/main.go index caedde091..626d84137 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -49,6 +49,7 @@ var ( platformSupport = flag.String("platform-support", "", "String passed to the test as GVISOR_PLATFORM_SUPPORT environment variable. Used to determine which syscall tests are expected to work with the current platform.") network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") + useFUSEfs = flag.Bool("use-fusefs", false, "mounts a fusefs for /tmp") fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") container = flag.Bool("container", false, "run tests in their own namespaces (user ns, network ns, etc), pretending to be root. Implicitly enabled if network=host, or if using network namespaces") @@ -376,8 +377,16 @@ func runTestCaseRunsc(testBin string, tc *gtest.TestCase, args []string, t *test if args == nil { args = tc.Args() } - spec := testutil.NewSpecWithArgs(append([]string{testBin}, args...)...) - + var spec *specs.Spec + if *useFUSEfs { + fuseServer, err := testutil.FindFile("test/runner/fuse/fuse") + if err != nil { + fatalf("cannot find fuse: %v", err) + } + spec = testutil.NewSpecWithArgs(append([]string{fuseServer, testBin}, args...)...) + } else { + spec = testutil.NewSpecWithArgs(append([]string{testBin}, args...)...) + } // Mark the root as writeable, as some tests attempt to // write to the rootfs, and expect EACCES, not EROFS. spec.Root.Readonly = false @@ -424,6 +433,14 @@ func runTestCaseRunsc(testBin string, tc *gtest.TestCase, args []string, t *test testTmpDir = "/tmp" } } + if *useFUSEfs { + // In fuse tests, the fuse server forwards all filesystem ops from /tmp + // to /fuse. + spec.Mounts = append(spec.Mounts, specs.Mount{ + Destination: "/fuse", + Type: "tmpfs", + }) + } // Set environment variables that indicate we are running in gVisor with // the given platform, network, and filesystem stack. diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 1cfd7c363..379fcfabf 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -327,6 +327,7 @@ syscall_test( syscall_test( add_overlay = True, test = "//test/syscalls/linux:mknod_test", + use_fusefs = True, ) syscall_test( @@ -913,12 +914,14 @@ syscall_test( syscall_test( add_overlay = True, test = "//test/syscalls/linux:statfs_test", + use_fusefs = True, use_tmpfs = True, # Test specifically relies on TEST_TMPDIR to be tmpfs. ) syscall_test( add_overlay = True, test = "//test/syscalls/linux:stat_test", + use_fusefs = True, ) syscall_test(