diff --git a/pkg/sentry/control/BUILD b/pkg/sentry/control/BUILD index 4761aad46..9faffd728 100644 --- a/pkg/sentry/control/BUILD +++ b/pkg/sentry/control/BUILD @@ -34,8 +34,8 @@ go_library( "//pkg/fspath", "//pkg/log", "//pkg/sentry/fdimport", - "//pkg/sentry/fs/user", "//pkg/sentry/fsimpl/host", + "//pkg/sentry/fsimpl/user", "//pkg/sentry/fsmetric", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/control/lifecycle.go b/pkg/sentry/control/lifecycle.go index fabae99c5..0d26100b7 100644 --- a/pkg/sentry/control/lifecycle.go +++ b/pkg/sentry/control/lifecycle.go @@ -24,7 +24,7 @@ import ( "gvisor.dev/gvisor/pkg/log" pb "gvisor.dev/gvisor/pkg/sentry/control/control_go_proto" "gvisor.dev/gvisor/pkg/sentry/fdimport" - "gvisor.dev/gvisor/pkg/sentry/fs/user" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/user" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/limits" diff --git a/pkg/sentry/control/proc.go b/pkg/sentry/control/proc.go index 1d46236b4..d78a8abc4 100644 --- a/pkg/sentry/control/proc.go +++ b/pkg/sentry/control/proc.go @@ -26,8 +26,8 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/fd" "gvisor.dev/gvisor/pkg/sentry/fdimport" - "gvisor.dev/gvisor/pkg/sentry/fs/user" "gvisor.dev/gvisor/pkg/sentry/fsimpl/host" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/user" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" diff --git a/pkg/sentry/fs/BUILD b/pkg/sentry/fs/BUILD index ada184c5b..5a6795556 100644 --- a/pkg/sentry/fs/BUILD +++ b/pkg/sentry/fs/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "go_library", "go_test") +load("//tools:defs.bzl", "go_library") load("//tools/go_generics:defs.bzl", "go_template_instance") load("//pkg/sync/locking:locking.bzl", "declare_mutex") @@ -60,7 +60,7 @@ go_library( "//pkg/secio", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs/lock", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsmetric", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/time", @@ -123,42 +123,3 @@ declare_mutex( package = "fs", prefix = "inotify", ) - -go_test( - name = "fs_x_test", - size = "small", - srcs = [ - "copy_up_test.go", - "file_overlay_test.go", - "inode_overlay_test.go", - "mounts_test.go", - ], - deps = [ - ":fs", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/ramfs", - "//pkg/sentry/fs/tmpfs", - "//pkg/sentry/kernel/contexttest", - "//pkg/sync", - "//pkg/usermem", - ], -) - -go_test( - name = "fs_test", - size = "small", - srcs = [ - "dirent_cache_test.go", - "dirent_refs_test.go", - "mount_test.go", - "path_test.go", - ], - library = ":fs", - deps = [ - "//pkg/context", - "//pkg/sentry/contexttest", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/sentry/fs/anon/BUILD b/pkg/sentry/fs/anon/BUILD deleted file mode 100644 index 1ce56d79f..000000000 --- a/pkg/sentry/fs/anon/BUILD +++ /dev/null @@ -1,20 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "anon", - srcs = [ - "anon.go", - "device.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/hostarch", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - ], -) diff --git a/pkg/sentry/fs/anon/anon.go b/pkg/sentry/fs/anon/anon.go deleted file mode 100644 index 8bda22a8e..000000000 --- a/pkg/sentry/fs/anon/anon.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 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. - -// Package anon implements an anonymous inode, useful for implementing -// inodes for pseudo filesystems. -package anon - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" -) - -// NewInode constructs an anonymous Inode that is not associated -// with any real filesystem. Some types depend on completely pseudo -// "anon" inodes (eventfds, epollfds, etc). -func NewInode(ctx context.Context) *fs.Inode { - iops := &fsutil.SimpleFileInode{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermissions{ - User: fs.PermMask{Read: true, Write: true}, - }, linux.ANON_INODE_FS_MAGIC), - } - return fs.NewInode(ctx, iops, fs.NewPseudoMountSource(ctx), fs.StableAttr{ - Type: fs.Anonymous, - DeviceID: PseudoDevice.DeviceID(), - InodeID: PseudoDevice.NextIno(), - BlockSize: hostarch.PageSize, - }) -} diff --git a/pkg/sentry/fs/anon/device.go b/pkg/sentry/fs/anon/device.go deleted file mode 100644 index d9ac14956..000000000 --- a/pkg/sentry/fs/anon/device.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 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. - -package anon - -import ( - "gvisor.dev/gvisor/pkg/sentry/device" -) - -// PseudoDevice is the device on which all anonymous inodes reside. -var PseudoDevice = device.NewAnonDevice() diff --git a/pkg/sentry/fs/copy_up_test.go b/pkg/sentry/fs/copy_up_test.go deleted file mode 100644 index 82b7610e2..000000000 --- a/pkg/sentry/fs/copy_up_test.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2018 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. - -package fs_test - -import ( - "bytes" - "crypto/rand" - "fmt" - "io" - "testing" - - "gvisor.dev/gvisor/pkg/sentry/fs" - _ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -const ( - // origFileSize is the original file size. This many bytes should be - // copied up before the test file is modified. - origFileSize = 4096 - - // truncatedFileSize is the size to truncate all test files. - truncateFileSize = 10 -) - -// TestConcurrentCopyUp is a copy up stress test for an overlay. -// -// It creates a 64-level deep directory tree in the lower filesystem and -// populates the last subdirectory with 64 files containing random content: -// -// /lower -// /sudir0/.../subdir63/ -// /file0 -// ... -// /file63 -// -// The files are truncated concurrently by 4 goroutines per file. -// These goroutines contend with copying up all parent 64 subdirectories -// as well as the final file content. -// -// At the end of the test, we assert that the files respect the new truncated -// size and contain the content we expect. -func TestConcurrentCopyUp(t *testing.T) { - ctx := contexttest.Context(t) - files := makeOverlayTestFiles(t) - - var wg sync.WaitGroup - for _, file := range files { - for i := 0; i < 4; i++ { - wg.Add(1) - go func(o *overlayTestFile) { - if err := o.File.Dirent.Inode.Truncate(ctx, o.File.Dirent, truncateFileSize); err != nil { - t.Errorf("failed to copy up: %v", err) - } - wg.Done() - }(file) - } - } - wg.Wait() - - for _, file := range files { - got := make([]byte, origFileSize) - n, err := file.File.Readv(ctx, usermem.BytesIOSequence(got)) - if int(n) != truncateFileSize { - t.Fatalf("read %d bytes from file, want %d", n, truncateFileSize) - } - if err != nil && err != io.EOF { - t.Fatalf("read got error %v, want nil", err) - } - if !bytes.Equal(got[:n], file.content[:truncateFileSize]) { - t.Fatalf("file content is %v, want %v", got[:n], file.content[:truncateFileSize]) - } - } -} - -type overlayTestFile struct { - File *fs.File - name string - content []byte -} - -func makeOverlayTestFiles(t *testing.T) []*overlayTestFile { - ctx := contexttest.Context(t) - - // Create a lower tmpfs mount. - fsys, _ := fs.FindFilesystem("tmpfs") - lower, err := fsys.Mount(contexttest.Context(t), "", fs.MountSourceFlags{}, "", nil) - if err != nil { - t.Fatalf("failed to mount tmpfs: %v", err) - } - lowerRoot := fs.NewDirent(ctx, lower, "") - - // Make a deep set of subdirectories that everyone shares. - next := lowerRoot - for i := 0; i < 64; i++ { - name := fmt.Sprintf("subdir%d", i) - err := next.CreateDirectory(ctx, lowerRoot, name, fs.FilePermsFromMode(0777)) - if err != nil { - t.Fatalf("failed to create dir %q: %v", name, err) - } - next, err = next.Walk(ctx, lowerRoot, name) - if err != nil { - t.Fatalf("failed to walk to %q: %v", name, err) - } - } - - // Make a bunch of files in the last directory. - var files []*overlayTestFile - for i := 0; i < 64; i++ { - name := fmt.Sprintf("file%d", i) - f, err := next.Create(ctx, next, name, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0666)) - if err != nil { - t.Fatalf("failed to create file %q: %v", name, err) - } - defer f.DecRef(ctx) - - relname, _ := f.Dirent.FullName(lowerRoot) - - o := &overlayTestFile{ - name: relname, - content: make([]byte, origFileSize), - } - - if _, err := rand.Read(o.content); err != nil { - t.Fatalf("failed to read from /dev/urandom: %v", err) - } - - if _, err := f.Writev(ctx, usermem.BytesIOSequence(o.content)); err != nil { - t.Fatalf("failed to write content to file %q: %v", name, err) - } - - files = append(files, o) - } - - // Create an empty upper tmpfs mount which we will copy up into. - upper, err := fsys.Mount(ctx, "", fs.MountSourceFlags{}, "", nil) - if err != nil { - t.Fatalf("failed to mount tmpfs: %v", err) - } - - // Construct an overlay root. - overlay, err := fs.NewOverlayRoot(ctx, upper, lower, fs.MountSourceFlags{}) - if err != nil { - t.Fatalf("failed to construct overlay root: %v", err) - } - - // Create a MountNamespace to traverse the file system. - mns, err := fs.NewMountNamespace(ctx, overlay) - if err != nil { - t.Fatalf("failed to construct mount manager: %v", err) - } - - // Walk to all of the files in the overlay, open them readable. - for _, f := range files { - maxTraversals := uint(0) - d, err := mns.FindInode(ctx, mns.Root(), mns.Root(), f.name, &maxTraversals) - if err != nil { - t.Fatalf("failed to find %q: %v", f.name, err) - } - defer d.DecRef(ctx) - - f.File, err = d.Inode.GetFile(ctx, d, fs.FileFlags{Read: true}) - if err != nil { - t.Fatalf("failed to open file %q readable: %v", f.name, err) - } - } - - return files -} diff --git a/pkg/sentry/fs/dev/BUILD b/pkg/sentry/fs/dev/BUILD deleted file mode 100644 index 92a8535d4..000000000 --- a/pkg/sentry/fs/dev/BUILD +++ /dev/null @@ -1,43 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "dev", - srcs = [ - "dev.go", - "device.go", - "fs.go", - "full.go", - "net_tun.go", - "null.go", - "random.go", - "tty.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/bufferv2", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/rand", - "//pkg/safemem", - "//pkg/sentry/arch", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/ramfs", - "//pkg/sentry/fs/tmpfs", - "//pkg/sentry/inet", - "//pkg/sentry/kernel", - "//pkg/sentry/memmap", - "//pkg/sentry/mm", - "//pkg/sentry/pgalloc", - "//pkg/sentry/socket/netstack", - "//pkg/tcpip/link/tun", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/sentry/fs/dev/dev.go b/pkg/sentry/fs/dev/dev.go deleted file mode 100644 index c62effd52..000000000 --- a/pkg/sentry/fs/dev/dev.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2018 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. - -// Package dev provides a filesystem with simple devices. -package dev - -import ( - "fmt" - "math" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs" - "gvisor.dev/gvisor/pkg/sentry/inet" - "gvisor.dev/gvisor/pkg/usermem" -) - -// Memory device numbers are from Linux's drivers/char/mem.c -const ( - // Mem device major. - memDevMajor uint16 = 1 - - // Mem device minors. - nullDevMinor uint32 = 3 - zeroDevMinor uint32 = 5 - fullDevMinor uint32 = 7 - randomDevMinor uint32 = 8 - urandomDevMinor uint32 = 9 -) - -// TTY major device number comes from include/uapi/linux/major.h. -const ( - ttyDevMinor = 0 - ttyDevMajor = 5 -) - -func newCharacterDevice(ctx context.Context, iops fs.InodeOperations, msrc *fs.MountSource, major uint16, minor uint32) *fs.Inode { - return fs.NewInode(ctx, iops, msrc, fs.StableAttr{ - DeviceID: devDevice.DeviceID(), - InodeID: devDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.CharacterDevice, - DeviceFileMajor: major, - DeviceFileMinor: minor, - }) -} - -func newMemDevice(ctx context.Context, iops fs.InodeOperations, msrc *fs.MountSource, minor uint32) *fs.Inode { - return fs.NewInode(ctx, iops, msrc, fs.StableAttr{ - DeviceID: devDevice.DeviceID(), - InodeID: devDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.CharacterDevice, - DeviceFileMajor: memDevMajor, - DeviceFileMinor: minor, - }) -} - -func newDirectory(ctx context.Context, contents map[string]*fs.Inode, msrc *fs.MountSource) *fs.Inode { - iops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)) - return fs.NewInode(ctx, iops, msrc, fs.StableAttr{ - DeviceID: devDevice.DeviceID(), - InodeID: devDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Directory, - }) -} - -func newSymlink(ctx context.Context, target string, msrc *fs.MountSource) *fs.Inode { - iops := ramfs.NewSymlink(ctx, fs.RootOwner, target) - return fs.NewInode(ctx, iops, msrc, fs.StableAttr{ - DeviceID: devDevice.DeviceID(), - InodeID: devDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Symlink, - }) -} - -// New returns the root node of a device filesystem. -func New(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - shm, err := tmpfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermsFromMode(0777), msrc, nil /* parent */) - if err != nil { - panic(fmt.Sprintf("tmpfs.NewDir failed: %v", err)) - } - - contents := map[string]*fs.Inode{ - "fd": newSymlink(ctx, "/proc/self/fd", msrc), - "stdin": newSymlink(ctx, "/proc/self/fd/0", msrc), - "stdout": newSymlink(ctx, "/proc/self/fd/1", msrc), - "stderr": newSymlink(ctx, "/proc/self/fd/2", msrc), - - "null": newMemDevice(ctx, newNullDevice(ctx, fs.RootOwner, 0666), msrc, nullDevMinor), - "zero": newMemDevice(ctx, newZeroDevice(ctx, fs.RootOwner, 0666), msrc, zeroDevMinor), - "full": newMemDevice(ctx, newFullDevice(ctx, fs.RootOwner, 0666), msrc, fullDevMinor), - - // This is not as good as /dev/random in linux because go - // runtime uses sys_random and /dev/urandom internally. - // According to 'man 4 random', this will be sufficient unless - // application uses this to generate long-lived GPG/SSL/SSH - // keys. - "random": newMemDevice(ctx, newRandomDevice(ctx, fs.RootOwner, 0444), msrc, randomDevMinor), - "urandom": newMemDevice(ctx, newRandomDevice(ctx, fs.RootOwner, 0444), msrc, urandomDevMinor), - - "shm": shm, - - // A devpts is typically mounted at /dev/pts to provide - // pseudoterminal support. Place an empty directory there for - // the devpts to be mounted over. - "pts": newDirectory(ctx, nil, msrc), - // Similarly, applications expect a ptmx device at /dev/ptmx - // connected to the terminals provided by /dev/pts/. Rather - // than creating a device directly (which requires a hairy - // lookup on open to determine if a devpts exists), just create - // a symlink to the ptmx provided by devpts. (The Linux devpts - // documentation recommends this). - // - // If no devpts is mounted, this will simply be a dangling - // symlink, which is fine. - "ptmx": newSymlink(ctx, "pts/ptmx", msrc), - - "tty": newCharacterDevice(ctx, newTTYDevice(ctx, fs.RootOwner, 0666), msrc, ttyDevMajor, ttyDevMinor), - } - - if isNetTunSupported(inet.StackFromContext(ctx)) { - contents["net"] = newDirectory(ctx, map[string]*fs.Inode{ - "tun": newCharacterDevice(ctx, newNetTunDevice(ctx, fs.RootOwner, 0666), msrc, netTunDevMajor, netTunDevMinor), - }, msrc) - } - - iops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)) - return fs.NewInode(ctx, iops, msrc, fs.StableAttr{ - DeviceID: devDevice.DeviceID(), - InodeID: devDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Directory, - }) -} - -// readZeros implements fs.FileOperations.Read with infinite null bytes. -type readZeros struct{} - -// Read implements fs.FileOperations.Read. -func (*readZeros) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - return dst.ZeroOut(ctx, math.MaxInt64) -} diff --git a/pkg/sentry/fs/dev/device.go b/pkg/sentry/fs/dev/device.go deleted file mode 100644 index a0493474e..000000000 --- a/pkg/sentry/fs/dev/device.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018 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. - -package dev - -import "gvisor.dev/gvisor/pkg/sentry/device" - -// devDevice is the pseudo-filesystem device. -var devDevice = device.NewAnonDevice() diff --git a/pkg/sentry/fs/dev/fs.go b/pkg/sentry/fs/dev/fs.go deleted file mode 100644 index feae825ce..000000000 --- a/pkg/sentry/fs/dev/fs.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 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. - -package dev - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// filesystem is a devtmpfs. -// -// +stateify savable -type filesystem struct{} - -var _ fs.Filesystem = (*filesystem)(nil) - -func init() { - fs.RegisterFilesystem(&filesystem{}) -} - -// FilesystemName is the name under which the filesystem is registered. -// Name matches drivers/base/devtmpfs.c:dev_fs_type.name. -const FilesystemName = "devtmpfs" - -// Name is the name of the file system. -func (*filesystem) Name() string { - return FilesystemName -} - -// AllowUserMount allows users to mount(2) this file system. -func (*filesystem) AllowUserMount() bool { - return true -} - -// AllowUserList allows this filesystem to be listed in /proc/filesystems. -func (*filesystem) AllowUserList() bool { - return true -} - -// Flags returns that there is nothing special about this file system. -// -// In Linux, devtmpfs does the same thing. -func (*filesystem) Flags() fs.FilesystemFlags { - return 0 -} - -// Mount returns a devtmpfs root that can be positioned in the vfs. -func (f *filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, _ any) (*fs.Inode, error) { - // devtmpfs backed by ramfs ignores bad options. See fs/ramfs/inode.c:ramfs_parse_options. - // -> we should consider parsing the mode and backing devtmpfs by this. - return New(ctx, fs.NewNonCachingMountSource(ctx, f, flags)), nil -} diff --git a/pkg/sentry/fs/dev/full.go b/pkg/sentry/fs/dev/full.go deleted file mode 100644 index 6f0c1fc68..000000000 --- a/pkg/sentry/fs/dev/full.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2018 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. - -package dev - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// fullDevice is used to implement /dev/full. -// -// +stateify savable -type fullDevice struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopAllocate `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes -} - -var _ fs.InodeOperations = (*fullDevice)(nil) - -func newFullDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *fullDevice { - f := &fullDevice{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC), - } - return f -} - -// GetFile implements fs.InodeOperations.GetFile. -func (f *fullDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - return fs.NewFile(ctx, dirent, flags, &fullFileOperations{}), nil -} - -// +stateify savable -type fullFileOperations struct { - waiter.AlwaysReady `state:"nosave"` - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - readZeros `state:"nosave"` -} - -var _ fs.FileOperations = (*fullFileOperations)(nil) - -// Write implements FileOperations.Write. -func (*fullFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.ENOSPC -} diff --git a/pkg/sentry/fs/dev/net_tun.go b/pkg/sentry/fs/dev/net_tun.go deleted file mode 100644 index 6787e1d43..000000000 --- a/pkg/sentry/fs/dev/net_tun.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2020 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. - -package dev - -import ( - "io" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/bufferv2" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/inet" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/socket/netstack" - "gvisor.dev/gvisor/pkg/tcpip/link/tun" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -const ( - netTunDevMajor = 10 - netTunDevMinor = 200 -) - -// +stateify savable -type netTunInodeOperations struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopAllocate `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes -} - -var _ fs.InodeOperations = (*netTunInodeOperations)(nil) - -func newNetTunDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *netTunInodeOperations { - return &netTunInodeOperations{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC), - } -} - -// GetFile implements fs.InodeOperations.GetFile. -func (*netTunInodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, d, flags, &netTunFileOperations{}), nil -} - -// +stateify savable -type netTunFileOperations struct { - fsutil.FileNoSeek `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - device tun.Device -} - -var _ fs.FileOperations = (*netTunFileOperations)(nil) - -// Release implements fs.FileOperations.Release. -func (n *netTunFileOperations) Release(ctx context.Context) { - n.device.Release(ctx) -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (n *netTunFileOperations) Ioctl(ctx context.Context, file *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - request := args[1].Uint() - data := args[2].Pointer() - - t := kernel.TaskFromContext(ctx) - if t == nil { - panic("Ioctl should be called from a task context") - } - - switch request { - case linux.TUNSETIFF: - if !t.HasCapability(linux.CAP_NET_ADMIN) { - return 0, linuxerr.EPERM - } - stack, ok := t.NetworkContext().(*netstack.Stack) - if !ok { - return 0, linuxerr.EINVAL - } - - var req linux.IFReq - if _, err := req.CopyIn(t, data); err != nil { - return 0, err - } - - // Validate flags. - flags, err := netstack.LinuxToTUNFlags(hostarch.ByteOrder.Uint16(req.Data[:])) - if err != nil { - return 0, err - } - return 0, n.device.SetIff(stack.Stack, req.Name(), flags) - - case linux.TUNGETIFF: - var req linux.IFReq - copy(req.IFName[:], n.device.Name()) - hostarch.ByteOrder.PutUint16(req.Data[:], netstack.TUNFlagsToLinux(n.device.Flags())) - _, err := req.CopyOut(t, data) - return 0, err - - default: - return 0, linuxerr.ENOTTY - } -} - -// Write implements fs.FileOperations.Write. -func (n *netTunFileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, unix.EINVAL - } - data := bufferv2.NewView(int(src.NumBytes())) - defer data.Release() - if _, err := io.CopyN(data, src.Reader(ctx), src.NumBytes()); err != nil { - return 0, err - } - return n.device.Write(data) -} - -// Read implements fs.FileOperations.Read. -func (n *netTunFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - data, err := n.device.Read() - if err != nil { - return 0, err - } - defer data.Release() - dataSize := data.Size() - bytesCopied, err := io.CopyN(dst.Writer(ctx), data, dst.NumBytes()) - if bytesCopied > 0 && bytesCopied < int64(dataSize) { - // Not an error for partial copying. Packet truncated. - err = nil - } - return int64(bytesCopied), err -} - -// Readiness implements watier.Waitable.Readiness. -func (n *netTunFileOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - return n.device.Readiness(mask) -} - -// EventRegister implements watier.Waitable.EventRegister. -func (n *netTunFileOperations) EventRegister(e *waiter.Entry) error { - n.device.EventRegister(e) - return nil -} - -// EventUnregister implements watier.Waitable.EventUnregister. -func (n *netTunFileOperations) EventUnregister(e *waiter.Entry) { - n.device.EventUnregister(e) -} - -// isNetTunSupported returns whether /dev/net/tun device is supported for s. -func isNetTunSupported(s inet.Stack) bool { - _, ok := s.(*netstack.Stack) - return ok -} diff --git a/pkg/sentry/fs/dev/null.go b/pkg/sentry/fs/dev/null.go deleted file mode 100644 index aec33d0d9..000000000 --- a/pkg/sentry/fs/dev/null.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2018 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. - -package dev - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/mm" - "gvisor.dev/gvisor/pkg/sentry/pgalloc" - "gvisor.dev/gvisor/pkg/waiter" -) - -// +stateify savable -type nullDevice struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopAllocate `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes -} - -var _ fs.InodeOperations = (*nullDevice)(nil) - -func newNullDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *nullDevice { - n := &nullDevice{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC), - } - return n -} - -// GetFile implements fs.FileOperations.GetFile. -func (n *nullDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - flags.Pwrite = true - - return fs.NewFile(ctx, dirent, flags, &nullFileOperations{}), nil -} - -// +stateify savable -type nullFileOperations struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRead `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopWrite `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` -} - -var _ fs.FileOperations = (*nullFileOperations)(nil) - -// +stateify savable -type zeroDevice struct { - nullDevice -} - -var _ fs.InodeOperations = (*zeroDevice)(nil) - -func newZeroDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *zeroDevice { - zd := &zeroDevice{ - nullDevice: nullDevice{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC), - }, - } - return zd -} - -// GetFile implements fs.FileOperations.GetFile. -func (zd *zeroDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - flags.Pwrite = true - flags.NonSeekable = true - - return fs.NewFile(ctx, dirent, flags, &zeroFileOperations{}), nil -} - -// +stateify savable -type zeroFileOperations struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopWrite `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - readZeros `state:"nosave"` -} - -var _ fs.FileOperations = (*zeroFileOperations)(nil) - -// ConfigureMMap implements fs.FileOperations.ConfigureMMap. -func (*zeroFileOperations) ConfigureMMap(ctx context.Context, file *fs.File, opts *memmap.MMapOpts) error { - m, err := mm.NewSharedAnonMappable(opts.Length, pgalloc.MemoryFileProviderFromContext(ctx)) - if err != nil { - return err - } - opts.MappingIdentity = m - opts.Mappable = m - return nil -} diff --git a/pkg/sentry/fs/dev/random.go b/pkg/sentry/fs/dev/random.go deleted file mode 100644 index 2a9bbeb18..000000000 --- a/pkg/sentry/fs/dev/random.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 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. - -package dev - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/rand" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// +stateify savable -type randomDevice struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopAllocate `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes -} - -var _ fs.InodeOperations = (*randomDevice)(nil) - -func newRandomDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *randomDevice { - r := &randomDevice{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC), - } - return r -} - -// GetFile implements fs.InodeOperations.GetFile. -func (*randomDevice) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &randomFileOperations{}), nil -} - -// +stateify savable -type randomFileOperations struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopWrite `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` -} - -var _ fs.FileOperations = (*randomFileOperations)(nil) - -// Read implements fs.FileOperations.Read. -func (*randomFileOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - return dst.CopyOutFrom(ctx, safemem.FromIOReader{rand.Reader}) -} diff --git a/pkg/sentry/fs/dev/tty.go b/pkg/sentry/fs/dev/tty.go deleted file mode 100644 index 760ca563d..000000000 --- a/pkg/sentry/fs/dev/tty.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 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. - -package dev - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/waiter" -) - -// +stateify savable -type ttyInodeOperations struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopAllocate `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotOpenable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes -} - -var _ fs.InodeOperations = (*ttyInodeOperations)(nil) - -func newTTYDevice(ctx context.Context, owner fs.FileOwner, mode linux.FileMode) *ttyInodeOperations { - return &ttyInodeOperations{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(mode), linux.TMPFS_MAGIC), - } -} - -// +stateify savable -type ttyFileOperations struct { - fsutil.FileNoSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopWrite `state:"nosave"` - fsutil.FileNoopRead `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` -} - -var _ fs.FileOperations = (*ttyFileOperations)(nil) diff --git a/pkg/sentry/fs/dirent_cache_test.go b/pkg/sentry/fs/dirent_cache_test.go deleted file mode 100644 index 395c879f5..000000000 --- a/pkg/sentry/fs/dirent_cache_test.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2018 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. - -package fs - -import ( - "testing" -) - -func TestDirentCache(t *testing.T) { - const maxSize = 5 - - c := NewDirentCache(maxSize) - - // Size starts at 0. - if got, want := c.Size(), uint64(0); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // Create a Dirent d. - d := NewNegativeDirent("") - - // c does not contain d. - if got, want := c.contains(d), false; got != want { - t.Errorf("c.contains(d) got %v want %v", got, want) - } - - // Add d to the cache. - c.Add(d) - - // Size is now 1. - if got, want := c.Size(), uint64(1); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // c contains d. - if got, want := c.contains(d), true; got != want { - t.Errorf("c.contains(d) got %v want %v", got, want) - } - - // Add maxSize-1 more elements. d should be oldest element. - for i := 0; i < maxSize-1; i++ { - c.Add(NewNegativeDirent("")) - } - - // Size is maxSize. - if got, want := c.Size(), uint64(maxSize); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // c contains d. - if got, want := c.contains(d), true; got != want { - t.Errorf("c.contains(d) got %v want %v", got, want) - } - - // "Bump" d to the front by re-adding it. - c.Add(d) - - // Size is maxSize. - if got, want := c.Size(), uint64(maxSize); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // c contains d. - if got, want := c.contains(d), true; got != want { - t.Errorf("c.contains(d) got %v want %v", got, want) - } - - // Add maxSize-1 more elements. d should again be oldest element. - for i := 0; i < maxSize-1; i++ { - c.Add(NewNegativeDirent("")) - } - - // Size is maxSize. - if got, want := c.Size(), uint64(maxSize); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // c contains d. - if got, want := c.contains(d), true; got != want { - t.Errorf("c.contains(d) got %v want %v", got, want) - } - - // Add one more element, which will bump d from the cache. - c.Add(NewNegativeDirent("")) - - // Size is maxSize. - if got, want := c.Size(), uint64(maxSize); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // c does not contain d. - if got, want := c.contains(d), false; got != want { - t.Errorf("c.contains(d) got %v want %v", got, want) - } - - // Invalidating causes size to be 0 and list to be empty. - c.Invalidate() - if got, want := c.Size(), uint64(0); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - if got, want := c.list.Empty(), true; got != want { - t.Errorf("c.list.Empty() got %v, want %v", got, want) - } - - // Fill cache with maxSize dirents. - for i := 0; i < maxSize; i++ { - c.Add(NewNegativeDirent("")) - } -} - -func TestDirentCacheLimiter(t *testing.T) { - const ( - globalMaxSize = 5 - maxSize = 3 - ) - - limit := NewDirentCacheLimiter(globalMaxSize) - c1 := NewDirentCache(maxSize) - c1.limit = limit - c2 := NewDirentCache(maxSize) - c2.limit = limit - - // Create a Dirent d. - d := NewNegativeDirent("") - - // Add d to the cache. - c1.Add(d) - if got, want := c1.Size(), uint64(1); got != want { - t.Errorf("c1.Size() got %v, want %v", got, want) - } - - // Add maxSize-1 more elements. d should be oldest element. - for i := 0; i < maxSize-1; i++ { - c1.Add(NewNegativeDirent("")) - } - if got, want := c1.Size(), uint64(maxSize); got != want { - t.Errorf("c1.Size() got %v, want %v", got, want) - } - - // Check that d is still there. - if got, want := c1.contains(d), true; got != want { - t.Errorf("c1.contains(d) got %v want %v", got, want) - } - - // Fill up the other cache, it will start dropping old entries from the cache - // when the global limit is reached. - for i := 0; i < maxSize; i++ { - c2.Add(NewNegativeDirent("")) - } - - // Check is what's remaining from global max. - if got, want := c2.Size(), globalMaxSize-maxSize; int(got) != want { - t.Errorf("c2.Size() got %v, want %v", got, want) - } - - // Check that d was not dropped. - if got, want := c1.contains(d), true; got != want { - t.Errorf("c1.contains(d) got %v want %v", got, want) - } - - // Add an entry that will eventually be dropped. Check is done later... - drop := NewNegativeDirent("") - c1.Add(drop) - - // Check that d is bumped to front even when global limit is reached. - c1.Add(d) - if got, want := c1.contains(d), true; got != want { - t.Errorf("c1.contains(d) got %v want %v", got, want) - } - - // Add 2 more element and check that: - // - d is still in the list: to verify that d was bumped - // - d2/d3 are in the list: older entries are dropped when global limit is - // reached. - // - drop is not in the list: indeed older elements are dropped. - d2 := NewNegativeDirent("") - c1.Add(d2) - d3 := NewNegativeDirent("") - c1.Add(d3) - if got, want := c1.contains(d), true; got != want { - t.Errorf("c1.contains(d) got %v want %v", got, want) - } - if got, want := c1.contains(d2), true; got != want { - t.Errorf("c1.contains(d2) got %v want %v", got, want) - } - if got, want := c1.contains(d3), true; got != want { - t.Errorf("c1.contains(d3) got %v want %v", got, want) - } - if got, want := c1.contains(drop), false; got != want { - t.Errorf("c1.contains(drop) got %v want %v", got, want) - } - - // Drop all entries from one cache. The other will be allowed to grow. - c1.Invalidate() - c2.Add(NewNegativeDirent("")) - if got, want := c2.Size(), uint64(maxSize); got != want { - t.Errorf("c2.Size() got %v, want %v", got, want) - } -} - -// TestNilDirentCache tests that a nil cache supports all cache operations, but -// treats them as noop. -func TestNilDirentCache(t *testing.T) { - // Create a nil cache. - var c *DirentCache - - // Size is zero. - if got, want := c.Size(), uint64(0); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // Call Add. - c.Add(NewNegativeDirent("")) - - // Size is zero. - if got, want := c.Size(), uint64(0); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // Call Remove. - c.Remove(NewNegativeDirent("")) - - // Size is zero. - if got, want := c.Size(), uint64(0); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } - - // Call Invalidate. - c.Invalidate() - - // Size is zero. - if got, want := c.Size(), uint64(0); got != want { - t.Errorf("c.Size() got %v, want %v", got, want) - } -} diff --git a/pkg/sentry/fs/dirent_refs_test.go b/pkg/sentry/fs/dirent_refs_test.go deleted file mode 100644 index e2b66f357..000000000 --- a/pkg/sentry/fs/dirent_refs_test.go +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright 2018 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. - -package fs - -import ( - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/contexttest" -) - -func newMockDirInode(ctx context.Context, cache *DirentCache) *Inode { - return NewMockInode(ctx, NewMockMountSource(cache), StableAttr{Type: Directory}) -} - -func TestWalkPositive(t *testing.T) { - // refs == 0 -> one reference. - // refs == -1 -> has been destroyed. - - ctx := contexttest.Context(t) - root := NewDirent(ctx, newMockDirInode(ctx, nil), "root") - - if got := root.ReadRefs(); got != 1 { - t.Fatalf("root has a ref count of %d, want %d", got, 1) - } - - name := "d" - d, err := root.walk(ctx, root, name, false) - if err != nil { - t.Fatalf("root.walk(root, %q) got %v, want nil", name, err) - } - - if got := root.ReadRefs(); got != 2 { - t.Fatalf("root has a ref count of %d, want %d", got, 2) - } - - if got := d.ReadRefs(); got != 1 { - t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, 1) - } - - d.DecRef(ctx) - - if got := root.ReadRefs(); got != 1 { - t.Fatalf("root has a ref count of %d, want %d", got, 1) - } - - if got := d.ReadRefs(); got != 0 { - t.Fatalf("child name = %q has a ref count of %d, want %d", d.name, got, 0) - } - - root.flush(ctx) - - if got := len(root.children); got != 0 { - t.Fatalf("root has %d children, want %d", got, 0) - } -} - -func TestWalkNegative(t *testing.T) { - // refs == 0 -> one reference. - // refs == -1 -> has been destroyed. - - ctx := contexttest.Context(t) - root := NewDirent(ctx, NewEmptyDir(ctx, nil), "root") - mn := root.Inode.InodeOperations.(*mockInodeOperationsLookupNegative) - - if got := root.ReadRefs(); got != 1 { - t.Fatalf("root has a ref count of %d, want %d", got, 1) - } - - name := "d" - for i := 0; i < 100; i++ { - _, err := root.walk(ctx, root, name, false) - if err != unix.ENOENT { - t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, unix.ENOENT) - } - } - - if got := root.ReadRefs(); got != 1 { - t.Fatalf("root has a ref count of %d, want %d", got, 1) - } - - if got := len(root.children); got != 1 { - t.Fatalf("root has %d children, want %d", got, 1) - } - - w, ok := root.children[name] - if !ok { - t.Fatalf("root wants child at %q", name) - } - - child := w.Get() - if child == nil { - t.Fatalf("root wants to resolve weak reference") - } - - if !child.(*Dirent).IsNegative() { - t.Fatalf("root found positive child at %q, want negative", name) - } - - if got := child.(*Dirent).ReadRefs(); got != 2 { - t.Fatalf("child has a ref count of %d, want %d", got, 2) - } - - child.DecRef(ctx) - - if got := child.(*Dirent).ReadRefs(); got != 1 { - t.Fatalf("child has a ref count of %d, want %d", got, 1) - } - - if got := len(root.children); got != 1 { - t.Fatalf("root has %d children, want %d", got, 1) - } - - root.DecRef(ctx) - - if got := root.ReadRefs(); got != 0 { - t.Fatalf("root has a ref count of %d, want %d", got, 0) - } - - AsyncBarrier() - - if got := mn.releaseCalled; got != true { - t.Fatalf("root.Close was called %v, want true", got) - } -} - -type mockInodeOperationsLookupNegative struct { - *MockInodeOperations - releaseCalled bool -} - -func NewEmptyDir(ctx context.Context, cache *DirentCache) *Inode { - m := NewMockMountSource(cache) - return NewInode(ctx, &mockInodeOperationsLookupNegative{ - MockInodeOperations: NewMockInodeOperations(ctx), - }, m, StableAttr{Type: Directory}) -} - -func (m *mockInodeOperationsLookupNegative) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) { - return NewNegativeDirent(p), nil -} - -func (m *mockInodeOperationsLookupNegative) Release(context.Context) { - m.releaseCalled = true -} - -func TestHashNegativeToPositive(t *testing.T) { - // refs == 0 -> one reference. - // refs == -1 -> has been destroyed. - - ctx := contexttest.Context(t) - root := NewDirent(ctx, NewEmptyDir(ctx, nil), "root") - - name := "d" - _, err := root.walk(ctx, root, name, false) - if err != unix.ENOENT { - t.Fatalf("root.walk(root, %q) got %v, want %v", name, err, unix.ENOENT) - } - - if got := root.exists(ctx, root, name); got != false { - t.Fatalf("got %q exists, want does not exist", name) - } - - f, err := root.Create(ctx, root, name, FileFlags{}, FilePermissions{}) - if err != nil { - t.Fatalf("root.Create(%q, _), got error %v, want nil", name, err) - } - d := f.Dirent - - if d.IsNegative() { - t.Fatalf("got negative Dirent, want positive") - } - - if got := d.ReadRefs(); got != 1 { - t.Fatalf("child %q has a ref count of %d, want %d", name, got, 1) - } - - if got := root.ReadRefs(); got != 2 { - t.Fatalf("root has a ref count of %d, want %d", got, 2) - } - - if got := len(root.children); got != 1 { - t.Fatalf("got %d children, want %d", got, 1) - } - - w, ok := root.children[name] - if !ok { - t.Fatalf("failed to find weak reference to %q", name) - } - - child := w.Get() - if child == nil { - t.Fatalf("want to resolve weak reference") - } - - if child.(*Dirent) != d { - t.Fatalf("got foreign child") - } -} - -func TestRevalidate(t *testing.T) { - // refs == 0 -> one reference. - // refs == -1 -> has been destroyed. - - for _, test := range []struct { - // desc is the test's description. - desc string - - // Whether to make negative Dirents. - makeNegative bool - }{ - { - desc: "Revalidate negative Dirent", - makeNegative: true, - }, - { - desc: "Revalidate positive Dirent", - makeNegative: false, - }, - } { - t.Run(test.desc, func(t *testing.T) { - ctx := contexttest.Context(t) - root := NewDirent(ctx, NewMockInodeRevalidate(ctx, test.makeNegative), "root") - - name := "d" - d1, err := root.walk(ctx, root, name, false) - if !test.makeNegative && err != nil { - t.Fatalf("root.walk(root, %q) got %v, want nil", name, err) - } - d2, err := root.walk(ctx, root, name, false) - if !test.makeNegative && err != nil { - t.Fatalf("root.walk(root, %q) got %v, want nil", name, err) - } - if !test.makeNegative && d1 == d2 { - t.Fatalf("revalidating walk got same *Dirent, want different") - } - if got := len(root.children); got != 1 { - t.Errorf("revalidating walk got %d children, want %d", got, 1) - } - }) - } -} - -type MockInodeOperationsRevalidate struct { - *MockInodeOperations - makeNegative bool -} - -func NewMockInodeRevalidate(ctx context.Context, makeNegative bool) *Inode { - mn := NewMockInodeOperations(ctx) - m := NewMockMountSource(nil) - m.MountSourceOperations.(*MockMountSourceOps).revalidate = true - return NewInode(ctx, &MockInodeOperationsRevalidate{MockInodeOperations: mn, makeNegative: makeNegative}, m, StableAttr{Type: Directory}) -} - -func (m *MockInodeOperationsRevalidate) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) { - if !m.makeNegative { - return m.MockInodeOperations.Lookup(ctx, dir, p) - } - return NewNegativeDirent(p), nil -} - -func TestCreateExtraRefs(t *testing.T) { - // refs == 0 -> one reference. - // refs == -1 -> has been destroyed. - - ctx := contexttest.Context(t) - for _, test := range []struct { - // desc is the test's description. - desc string - - // root is the Dirent to create from. - root *Dirent - - // expected references on walked Dirent. - refs int64 - }{ - { - desc: "Create caching", - root: NewDirent(ctx, NewEmptyDir(ctx, NewDirentCache(1)), "root"), - refs: 2, - }, - { - desc: "Create not caching", - root: NewDirent(ctx, NewEmptyDir(ctx, nil), "root"), - refs: 1, - }, - } { - t.Run(test.desc, func(t *testing.T) { - name := "d" - f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{}) - if err != nil { - t.Fatalf("root.Create(root, %q) failed: %v", name, err) - } - d := f.Dirent - - if got := d.ReadRefs(); got != test.refs { - t.Errorf("dirent has a ref count of %d, want %d", got, test.refs) - } - }) - } -} - -func TestRemoveExtraRefs(t *testing.T) { - // refs == 0 -> one reference. - // refs == -1 -> has been destroyed. - - ctx := contexttest.Context(t) - for _, test := range []struct { - // desc is the test's description. - desc string - - // root is the Dirent to make and remove from. - root *Dirent - }{ - { - desc: "Remove caching", - root: NewDirent(ctx, NewEmptyDir(ctx, NewDirentCache(1)), "root"), - }, - { - desc: "Remove not caching", - root: NewDirent(ctx, NewEmptyDir(ctx, nil), "root"), - }, - } { - t.Run(test.desc, func(t *testing.T) { - name := "d" - f, err := test.root.Create(ctx, test.root, name, FileFlags{}, FilePermissions{}) - if err != nil { - t.Fatalf("root.Create(%q, _) failed: %v", name, err) - } - d := f.Dirent - - if err := test.root.Remove(contexttest.Context(t), test.root, name, false /* dirPath */); err != nil { - t.Fatalf("root.Remove(root, %q) failed: %v", name, err) - } - - if got := d.ReadRefs(); got != 1 { - t.Fatalf("dirent has a ref count of %d, want %d", got, 1) - } - - d.DecRef(ctx) - - test.root.flush(ctx) - - if got := len(test.root.children); got != 0 { - t.Errorf("root has %d children, want %d", got, 0) - } - }) - } -} - -func TestRenameExtraRefs(t *testing.T) { - // refs == 0 -> one reference. - // refs == -1 -> has been destroyed. - - for _, test := range []struct { - // desc is the test's description. - desc string - - // cache of extra Dirent references, may be nil. - cache *DirentCache - }{ - { - desc: "Rename no caching", - cache: nil, - }, - { - desc: "Rename caching", - cache: NewDirentCache(5), - }, - } { - t.Run(test.desc, func(t *testing.T) { - ctx := contexttest.Context(t) - - dirAttr := StableAttr{Type: Directory} - - oldParent := NewDirent(ctx, NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "old_parent") - newParent := NewDirent(ctx, NewMockInode(ctx, NewMockMountSource(test.cache), dirAttr), "new_parent") - - renamed, err := oldParent.Walk(ctx, oldParent, "old_child") - if err != nil { - t.Fatalf("Walk(oldParent, %q) got error %v, want nil", "old_child", err) - } - replaced, err := newParent.Walk(ctx, oldParent, "new_child") - if err != nil { - t.Fatalf("Walk(newParent, %q) got error %v, want nil", "new_child", err) - } - - if err := Rename(contexttest.RootContext(t), oldParent /*root */, oldParent, "old_child", newParent, "new_child"); err != nil { - t.Fatalf("Rename got error %v, want nil", err) - } - - oldParent.flush(ctx) - newParent.flush(ctx) - - // Expect to have only active references. - if got := renamed.ReadRefs(); got != 1 { - t.Errorf("renamed has ref count %d, want only active references %d", got, 1) - } - if got := replaced.ReadRefs(); got != 1 { - t.Errorf("replaced has ref count %d, want only active references %d", got, 1) - } - }) - } -} diff --git a/pkg/sentry/fs/fdpipe/BUILD b/pkg/sentry/fs/fdpipe/BUILD deleted file mode 100644 index 9f1fe5160..000000000 --- a/pkg/sentry/fs/fdpipe/BUILD +++ /dev/null @@ -1,52 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "fdpipe", - srcs = [ - "pipe.go", - "pipe_opener.go", - "pipe_state.go", - ], - imports = ["gvisor.dev/gvisor/pkg/sentry/fs"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fd", - "//pkg/fdnotifier", - "//pkg/log", - "//pkg/safemem", - "//pkg/secio", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "fdpipe_test", - size = "small", - srcs = [ - "pipe_opener_test.go", - "pipe_test.go", - ], - library = ":fdpipe", - deps = [ - "//pkg/context", - "//pkg/errors", - "//pkg/errors/linuxerr", - "//pkg/fd", - "//pkg/fdnotifier", - "//pkg/hostarch", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - "//pkg/usermem", - "@com_github_google_uuid//:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/sentry/fs/fdpipe/pipe.go b/pkg/sentry/fs/fdpipe/pipe.go deleted file mode 100644 index 97e40ce87..000000000 --- a/pkg/sentry/fs/fdpipe/pipe.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2018 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. - -// Package fdpipe implements common namedpipe opening and accessing logic. -package fdpipe - -import ( - "fmt" - "os" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/secio" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// pipeOperations are the fs.FileOperations of a host pipe. -// -// +stateify savable -type pipeOperations struct { - fsutil.FilePipeSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - waiter.Queue - - // flags are the flags used to open the pipe. - flags fs.FileFlags `state:".(fs.FileFlags)"` - - // opener is how the pipe was opened. - opener NonBlockingOpener `state:"wait"` - - // file represents the host pipe. - file *fd.FD `state:"nosave"` - - // mu protects readAheadBuffer access below. - mu sync.Mutex `state:"nosave"` - - // readAheadBuffer contains read bytes that have not yet been read - // by the application but need to be buffered for save-restore for correct - // opening semantics. The readAheadBuffer will only be non-empty when the - // is first opened and will be drained by subsequent reads on the pipe. - readAheadBuffer []byte -} - -// newPipeOperations returns an implementation of fs.FileOperations for a pipe. -func newPipeOperations(ctx context.Context, opener NonBlockingOpener, flags fs.FileFlags, file *fd.FD, readAheadBuffer []byte) (*pipeOperations, error) { - pipeOps := &pipeOperations{ - flags: flags, - opener: opener, - file: file, - readAheadBuffer: readAheadBuffer, - } - if err := pipeOps.init(); err != nil { - return nil, err - } - return pipeOps, nil -} - -// init initializes p.file. -func (p *pipeOperations) init() error { - var s unix.Stat_t - if err := unix.Fstat(p.file.FD(), &s); err != nil { - log.Warningf("pipe: cannot stat fd %d: %v", p.file.FD(), err) - return unix.EINVAL - } - if (s.Mode & unix.S_IFMT) != unix.S_IFIFO { - log.Warningf("pipe: cannot load fd %d as pipe, file type: %o", p.file.FD(), s.Mode) - return unix.EINVAL - } - if err := unix.SetNonblock(p.file.FD(), true); err != nil { - return err - } - return fdnotifier.AddFD(int32(p.file.FD()), &p.Queue) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (p *pipeOperations) EventRegister(e *waiter.Entry) error { - p.Queue.EventRegister(e) - if err := fdnotifier.UpdateFD(int32(p.file.FD())); err != nil { - p.Queue.EventUnregister(e) - return err - } - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (p *pipeOperations) EventUnregister(e *waiter.Entry) { - p.Queue.EventUnregister(e) - if err := fdnotifier.UpdateFD(int32(p.file.FD())); err != nil { - panic(fmt.Sprint("UpdateFD:", err)) - } -} - -// Readiness returns a mask of ready events for stream. -func (p *pipeOperations) Readiness(mask waiter.EventMask) (eventMask waiter.EventMask) { - return fdnotifier.NonBlockingPoll(int32(p.file.FD()), mask) -} - -// Release implements fs.FileOperations.Release. -func (p *pipeOperations) Release(context.Context) { - fdnotifier.RemoveFD(int32(p.file.FD())) - p.file.Close() - p.file = nil -} - -// Read implements fs.FileOperations.Read. -func (p *pipeOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - // Drain the read ahead buffer, if it contains anything first. - var bufN int - var bufErr error - p.mu.Lock() - if len(p.readAheadBuffer) > 0 { - bufN, bufErr = dst.CopyOut(ctx, p.readAheadBuffer) - p.readAheadBuffer = p.readAheadBuffer[bufN:] - dst = dst.DropFirst(bufN) - } - p.mu.Unlock() - if dst.NumBytes() == 0 || bufErr != nil { - return int64(bufN), bufErr - } - - // Pipes expect full reads. - n, err := dst.CopyOutFrom(ctx, safemem.FromIOReader{secio.FullReader{p.file}}) - total := int64(bufN) + n - if err != nil && isBlockError(err) { - return total, linuxerr.ErrWouldBlock - } - return total, err -} - -// Write implements fs.FileOperations.Write. -func (p *pipeOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - n, err := src.CopyInTo(ctx, safemem.FromIOWriter{p.file}) - if err != nil && isBlockError(err) { - return n, linuxerr.ErrWouldBlock - } - return n, err -} - -// isBlockError unwraps os errors and checks if they are caused by EAGAIN or -// EWOULDBLOCK. This is so they can be transformed into linuxerr.ErrWouldBlock. -func isBlockError(err error) bool { - if linuxerr.Equals(linuxerr.EAGAIN, err) || linuxerr.Equals(linuxerr.EWOULDBLOCK, err) { - return true - } - if pe, ok := err.(*os.PathError); ok { - return isBlockError(pe.Err) - } - return false -} diff --git a/pkg/sentry/fs/fdpipe/pipe_opener.go b/pkg/sentry/fs/fdpipe/pipe_opener.go deleted file mode 100644 index 5d4efe0a9..000000000 --- a/pkg/sentry/fs/fdpipe/pipe_opener.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2018 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. - -package fdpipe - -import ( - "io" - "os" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/waiter" -) - -// NonBlockingOpener is a generic host file opener used to retry opening host -// pipes if necessary. -type NonBlockingOpener interface { - // NonBlockingOpen tries to open a host pipe in a non-blocking way, - // and otherwise returns an error. Implementations should be idempotent. - NonBlockingOpen(context.Context, fs.PermMask) (*fd.FD, error) -} - -// Open blocks until a host pipe can be opened or the action was cancelled. -// On success, returns fs.FileOperations wrapping the opened host pipe. -func Open(ctx context.Context, opener NonBlockingOpener, flags fs.FileFlags) (fs.FileOperations, error) { - var ( - p pipeOpenState - q waiter.NeverReady - canceled bool - ) - for { - if file, err := p.TryOpen(ctx, opener, flags); err != linuxerr.ErrWouldBlock { - return file, err - } - - // Honor the cancellation request if open still blocks. - if canceled { - // If we were canceled but we have a handle to a host - // file, we need to close it. - if p.hostFile != nil { - p.hostFile.Close() - } - return nil, linuxerr.ErrInterrupted - } - - // Block for up to the requested amount of time. - if left, ok := ctx.BlockWithTimeoutOn(&q, waiter.ReadableEvents, 100*time.Millisecond); !ok && left != 0 { - canceled = true - } - } -} - -// pipeOpenState holds state needed to open a blocking named pipe read only, for instance the -// file that has been opened but doesn't yet have a corresponding writer. -type pipeOpenState struct { - // hostFile is the read only named pipe which lacks a corresponding writer. - hostFile *fd.FD -} - -// unwrapError is needed to match against ENXIO primarily. -func unwrapError(err error) error { - if pe, ok := err.(*os.PathError); ok { - return pe.Err - } - return err -} - -// TryOpen uses a NonBlockingOpener to try to open a host pipe, respecting the fs.FileFlags. -func (p *pipeOpenState) TryOpen(ctx context.Context, opener NonBlockingOpener, flags fs.FileFlags) (*pipeOperations, error) { - switch { - // Reject invalid configurations so they don't accidentally succeed below. - case !flags.Read && !flags.Write: - return nil, unix.EINVAL - - // Handle opening RDWR or with O_NONBLOCK: will never block, so try only once. - case (flags.Read && flags.Write) || flags.NonBlocking: - f, err := opener.NonBlockingOpen(ctx, fs.PermMask{Read: flags.Read, Write: flags.Write}) - if err != nil { - return nil, err - } - return newPipeOperations(ctx, opener, flags, f, nil) - - // Handle opening O_WRONLY blocking: convert ENXIO to linuxerr.ErrWouldBlock. - // See TryOpenWriteOnly for more details. - case flags.Write: - return p.TryOpenWriteOnly(ctx, opener) - - default: - // Handle opening O_RDONLY blocking: convert EOF from read to linuxerr.ErrWouldBlock. - // See TryOpenReadOnly for more details. - return p.TryOpenReadOnly(ctx, opener) - } -} - -// TryOpenReadOnly tries to open a host pipe read only but only returns a fs.File when -// there is a coordinating writer. Call TryOpenReadOnly repeatedly on the same pipeOpenState -// until linuxerr.ErrWouldBlock is no longer returned. -// -// How it works: -// -// Opening a pipe read only will return no error, but each non zero Read will return EOF -// until a writer becomes available, then EWOULDBLOCK. This is the only state change -// available to us. We keep a read ahead buffer in case we read bytes instead of getting -// EWOULDBLOCK, to be read from on the first read request to this fs.File. -func (p *pipeOpenState) TryOpenReadOnly(ctx context.Context, opener NonBlockingOpener) (*pipeOperations, error) { - // Waiting for a blocking read only open involves reading from the host pipe until - // bytes or other writers are available, so instead of retrying opening the pipe, - // it's necessary to retry reading from the pipe. To do this we need to keep around - // the read only pipe we opened, until success or an irrecoverable read error (at - // which point it must be closed). - if p.hostFile == nil { - var err error - p.hostFile, err = opener.NonBlockingOpen(ctx, fs.PermMask{Read: true}) - if err != nil { - return nil, err - } - } - - // Try to read from the pipe to see if writers are around. - tryReadBuffer := make([]byte, 1) - n, rerr := p.hostFile.Read(tryReadBuffer) - - // No bytes were read. - if n == 0 { - // EOF means that we're not ready yet. - if rerr == nil || rerr == io.EOF { - return nil, linuxerr.ErrWouldBlock - } - // Any error that is not EWOULDBLOCK also means we're not - // ready yet, and probably never will be ready. In this - // case we need to close the host pipe we opened. - if unwrapError(rerr) != unix.EWOULDBLOCK { - p.hostFile.Close() - return nil, rerr - } - } - - // If any bytes were read, no matter the corresponding error, we need - // to keep them around so they can be read by the application. - var readAheadBuffer []byte - if n > 0 { - readAheadBuffer = tryReadBuffer - } - - // Successfully opened read only blocking pipe with either bytes available - // to read and/or a writer available. - return newPipeOperations(ctx, opener, fs.FileFlags{Read: true}, p.hostFile, readAheadBuffer) -} - -// TryOpenWriteOnly tries to open a host pipe write only but only returns a fs.File when -// there is a coordinating reader. Call TryOpenWriteOnly repeatedly on the same pipeOpenState -// until linuxerr.ErrWouldBlock is no longer returned. -// -// How it works: -// -// Opening a pipe write only will return ENXIO until readers are available. Converts the ENXIO -// to an linuxerr.ErrWouldBlock, to tell callers to retry. -func (*pipeOpenState) TryOpenWriteOnly(ctx context.Context, opener NonBlockingOpener) (*pipeOperations, error) { - hostFile, err := opener.NonBlockingOpen(ctx, fs.PermMask{Write: true}) - if unwrapError(err) == unix.ENXIO { - return nil, linuxerr.ErrWouldBlock - } - if err != nil { - return nil, err - } - return newPipeOperations(ctx, opener, fs.FileFlags{Write: true}, hostFile, nil) -} diff --git a/pkg/sentry/fs/fdpipe/pipe_opener_test.go b/pkg/sentry/fs/fdpipe/pipe_opener_test.go deleted file mode 100644 index e1587288e..000000000 --- a/pkg/sentry/fs/fdpipe/pipe_opener_test.go +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright 2018 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. - -package fdpipe - -import ( - "bytes" - "fmt" - "io" - "os" - "path" - "testing" - "time" - - "github.com/google/uuid" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/usermem" -) - -type hostOpener struct { - name string -} - -func (h *hostOpener) NonBlockingOpen(_ context.Context, p fs.PermMask) (*fd.FD, error) { - var flags int - switch { - case p.Read && p.Write: - flags = unix.O_RDWR - case p.Write: - flags = unix.O_WRONLY - case p.Read: - flags = unix.O_RDONLY - default: - return nil, unix.EINVAL - } - f, err := unix.Open(h.name, flags|unix.O_NONBLOCK, 0666) - if err != nil { - return nil, err - } - return fd.New(f), nil -} - -func pipename() string { - return fmt.Sprintf(path.Join(os.TempDir(), "test-named-pipe-%s"), uuid.New()) -} - -func mkpipe(name string) error { - return unix.Mknod(name, unix.S_IFIFO|0666, 0) -} - -func TestTryOpen(t *testing.T) { - for _, test := range []struct { - // desc is the test's description. - desc string - - // makePipe is true if the test case should create the pipe. - makePipe bool - - // flags are the fs.FileFlags used to open the pipe. - flags fs.FileFlags - - // expectFile is true if a fs.File is expected. - expectFile bool - - // err is the expected error - err error - }{ - { - desc: "FileFlags lacking Read and Write are invalid", - makePipe: false, - flags: fs.FileFlags{}, /* bogus */ - expectFile: false, - err: unix.EINVAL, - }, - { - desc: "NonBlocking Read only error returns immediately", - makePipe: false, /* causes the error */ - flags: fs.FileFlags{Read: true, NonBlocking: true}, - expectFile: false, - err: unix.ENOENT, - }, - { - desc: "NonBlocking Read only success returns immediately", - makePipe: true, - flags: fs.FileFlags{Read: true, NonBlocking: true}, - expectFile: true, - err: nil, - }, - { - desc: "NonBlocking Write only error returns immediately", - makePipe: false, /* causes the error */ - flags: fs.FileFlags{Write: true, NonBlocking: true}, - expectFile: false, - err: unix.ENOENT, - }, - { - desc: "NonBlocking Write only no reader error returns immediately", - makePipe: true, - flags: fs.FileFlags{Write: true, NonBlocking: true}, - expectFile: false, - err: unix.ENXIO, - }, - { - desc: "ReadWrite error returns immediately", - makePipe: false, /* causes the error */ - flags: fs.FileFlags{Read: true, Write: true}, - expectFile: false, - err: unix.ENOENT, - }, - { - desc: "ReadWrite returns immediately", - makePipe: true, - flags: fs.FileFlags{Read: true, Write: true}, - expectFile: true, - err: nil, - }, - { - desc: "Blocking Write only returns open error", - makePipe: false, /* causes the error */ - flags: fs.FileFlags{Write: true}, - expectFile: false, - err: unix.ENOENT, /* from bogus perms */ - }, - { - desc: "Blocking Read only returns open error", - makePipe: false, /* causes the error */ - flags: fs.FileFlags{Read: true}, - expectFile: false, - err: unix.ENOENT, - }, - { - desc: "Blocking Write only returns with linuxerr.ErrWouldBlock", - makePipe: true, - flags: fs.FileFlags{Write: true}, - expectFile: false, - err: linuxerr.ErrWouldBlock, - }, - { - desc: "Blocking Read only returns with linuxerr.ErrWouldBlock", - makePipe: true, - flags: fs.FileFlags{Read: true}, - expectFile: false, - err: linuxerr.ErrWouldBlock, - }, - } { - name := pipename() - if test.makePipe { - // Create the pipe. We do this per-test case to keep tests independent. - if err := mkpipe(name); err != nil { - t.Errorf("%s: failed to make host pipe: %v", test.desc, err) - continue - } - defer unix.Unlink(name) - } - - // Use a host opener to keep things simple. - opener := &hostOpener{name: name} - - pipeOpenState := &pipeOpenState{} - ctx := contexttest.Context(t) - pipeOps, err := pipeOpenState.TryOpen(ctx, opener, test.flags) - if unwrapError(err) != test.err { - t.Errorf("%s: got error %v, want %v", test.desc, err, test.err) - if pipeOps != nil { - // Cleanup the state of the pipe, and remove the fd from the - // fdnotifier. Sadly this needed to maintain the correctness - // of other tests because the fdnotifier is global. - pipeOps.Release(ctx) - } - continue - } - if (pipeOps != nil) != test.expectFile { - t.Errorf("%s: got non-nil file %v, want %v", test.desc, pipeOps != nil, test.expectFile) - } - if pipeOps != nil { - // Same as above. - pipeOps.Release(ctx) - } - } -} - -func TestPipeOpenUnblocksEventually(t *testing.T) { - for _, test := range []struct { - // desc is the test's description. - desc string - - // partnerIsReader is true if the goroutine opening the same pipe as the test case - // should open the pipe read only. Otherwise write only. This also means that the - // test case will open the pipe in the opposite way. - partnerIsReader bool - - // partnerIsBlocking is true if the goroutine opening the same pipe as the test case - // should do so without the O_NONBLOCK flag, otherwise opens the pipe with O_NONBLOCK - // until ENXIO is not returned. - partnerIsBlocking bool - }{ - { - desc: "Blocking Read with blocking writer partner opens eventually", - partnerIsReader: false, - partnerIsBlocking: true, - }, - { - desc: "Blocking Write with blocking reader partner opens eventually", - partnerIsReader: true, - partnerIsBlocking: true, - }, - { - desc: "Blocking Read with non-blocking writer partner opens eventually", - partnerIsReader: false, - partnerIsBlocking: false, - }, - { - desc: "Blocking Write with non-blocking reader partner opens eventually", - partnerIsReader: true, - partnerIsBlocking: false, - }, - } { - // Create the pipe. We do this per-test case to keep tests independent. - name := pipename() - if err := mkpipe(name); err != nil { - t.Errorf("%s: failed to make host pipe: %v", test.desc, err) - continue - } - defer unix.Unlink(name) - - // Spawn the partner. - type fderr struct { - fd int - err error - } - errch := make(chan fderr, 1) - go func() { - var flags int - if test.partnerIsReader { - flags = unix.O_RDONLY - } else { - flags = unix.O_WRONLY - } - if test.partnerIsBlocking { - fd, err := unix.Open(name, flags, 0666) - errch <- fderr{fd: fd, err: err} - } else { - var fd int - err := error(unix.ENXIO) - for err == unix.ENXIO { - fd, err = unix.Open(name, flags|unix.O_NONBLOCK, 0666) - time.Sleep(1 * time.Second) - } - errch <- fderr{fd: fd, err: err} - } - }() - - // Setup file flags for either a read only or write only open. - flags := fs.FileFlags{ - Read: !test.partnerIsReader, - Write: test.partnerIsReader, - } - - // Open the pipe in a blocking way, which should succeed eventually. - opener := &hostOpener{name: name} - ctx := contexttest.Context(t) - pipeOps, err := Open(ctx, opener, flags) - if pipeOps != nil { - // Same as TestTryOpen. - pipeOps.Release(ctx) - } - - // Check that the partner opened the file successfully. - e := <-errch - if e.err != nil { - t.Errorf("%s: partner got error %v, wanted nil", test.desc, e.err) - continue - } - // If so, then close the partner fd to avoid leaking an fd. - unix.Close(e.fd) - - // Check that our blocking open was successful. - if err != nil { - t.Errorf("%s: blocking open got error %v, wanted nil", test.desc, err) - continue - } - if pipeOps == nil { - t.Errorf("%s: blocking open got nil file, wanted non-nil", test.desc) - continue - } - } -} - -func TestCopiedReadAheadBuffer(t *testing.T) { - // Create the pipe. - name := pipename() - if err := mkpipe(name); err != nil { - t.Fatalf("failed to make host pipe: %v", err) - } - defer unix.Unlink(name) - - // We're taking advantage of the fact that pipes opened read only always return - // success, but internally they are not deemed "opened" until we're sure that - // another writer comes along. This means we can open the same pipe write only - // with no problems + write to it, given that opener.Open already tried to open - // the pipe RDONLY and succeeded, which we know happened if TryOpen returns - // linuxerr.ErrwouldBlock. - // - // This simulates the open(RDONLY) <-> open(WRONLY)+write race we care about, but - // does not cause our test to be racy (which would be terrible). - opener := &hostOpener{name: name} - pipeOpenState := &pipeOpenState{} - ctx := contexttest.Context(t) - pipeOps, err := pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true}) - if pipeOps != nil { - pipeOps.Release(ctx) - t.Fatalf("open(%s, %o) got file, want nil", name, unix.O_RDONLY) - } - if err != linuxerr.ErrWouldBlock { - t.Fatalf("open(%s, %o) got error %v, want %v", name, unix.O_RDONLY, err, linuxerr.ErrWouldBlock) - } - - // Then open the same pipe write only and write some bytes to it. The next - // time we try to open the pipe read only again via the pipeOpenState, we should - // succeed and buffer some of the bytes written. - fd, err := unix.Open(name, unix.O_WRONLY, 0666) - if err != nil { - t.Fatalf("open(%s, %o) got error %v, want nil", name, unix.O_WRONLY, err) - } - defer unix.Close(fd) - - data := []byte("hello") - if n, err := unix.Write(fd, data); n != len(data) || err != nil { - t.Fatalf("write(%v) got (%d, %v), want (%d, nil)", data, n, err, len(data)) - } - - // Try the read again, knowing that it should succeed this time. - pipeOps, err = pipeOpenState.TryOpen(ctx, opener, fs.FileFlags{Read: true}) - if pipeOps == nil { - t.Fatalf("open(%s, %o) got nil file, want not nil", name, unix.O_RDONLY) - } - defer pipeOps.Release(ctx) - - if err != nil { - t.Fatalf("open(%s, %o) got error %v, want nil", name, unix.O_RDONLY, err) - } - - inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{ - Type: fs.Pipe, - }) - file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, pipeOps) - - // Check that the file we opened points to a pipe with a non-empty read ahead buffer. - bufsize := len(pipeOps.readAheadBuffer) - if bufsize != 1 { - t.Fatalf("read ahead buffer got %d bytes, want %d", bufsize, 1) - } - - // Now for the final test, try to read everything in, expecting to get back all of - // the bytes that were written at once. Note that in the wild there is no atomic - // read size so expecting to get all bytes from a single writer when there are - // multiple readers is a bad expectation. - buf := make([]byte, len(data)) - ioseq := usermem.BytesIOSequence(buf) - n, err := pipeOps.Read(ctx, file, ioseq, 0) - if err != nil { - t.Fatalf("read request got error %v, want nil", err) - } - if n != int64(len(data)) { - t.Fatalf("read request got %d bytes, want %d", n, len(data)) - } - if !bytes.Equal(buf, data) { - t.Errorf("read request got bytes [%v], want [%v]", buf, data) - } -} - -func TestPipeHangup(t *testing.T) { - for _, test := range []struct { - // desc is the test's description. - desc string - - // flags control how we open our end of the pipe and must be read - // only or write only. They also dicate how a coordinating partner - // fd is opened, which is their inverse (read only -> write only, etc). - flags fs.FileFlags - - // hangupSelf if true causes the test case to close our end of the pipe - // and causes hangup errors to be asserted on our coordinating partner's - // fd. If hangupSelf is false, then our partner's fd is closed and the - // hangup errors are expected on our end of the pipe. - hangupSelf bool - }{ - { - desc: "Read only gets hangup error", - flags: fs.FileFlags{Read: true}, - }, - { - desc: "Write only gets hangup error", - flags: fs.FileFlags{Write: true}, - }, - { - desc: "Read only generates hangup error", - flags: fs.FileFlags{Read: true}, - hangupSelf: true, - }, - { - desc: "Write only generates hangup error", - flags: fs.FileFlags{Write: true}, - hangupSelf: true, - }, - } { - if test.flags.Read == test.flags.Write { - t.Errorf("%s: test requires a single reader or writer", test.desc) - continue - } - - // Create the pipe. We do this per-test case to keep tests independent. - name := pipename() - if err := mkpipe(name); err != nil { - t.Errorf("%s: failed to make host pipe: %v", test.desc, err) - continue - } - defer unix.Unlink(name) - - // Fire off a partner routine which tries to open the same pipe blocking, - // which will synchronize with us. The channel allows us to get back the - // fd once we expect this partner routine to succeed, so we can manifest - // hangup events more directly. - fdchan := make(chan int, 1) - go func() { - // Be explicit about the flags to protect the test from - // misconfiguration. - var flags int - if test.flags.Read { - flags = unix.O_WRONLY - } else { - flags = unix.O_RDONLY - } - fd, err := unix.Open(name, flags, 0666) - if err != nil { - t.Logf("Open(%q, %o, 0666) partner failed: %v", name, flags, err) - } - fdchan <- fd - }() - - // Open our end in a blocking way to ensure that we coordinate. - opener := &hostOpener{name: name} - ctx := contexttest.Context(t) - pipeOps, err := Open(ctx, opener, test.flags) - if err != nil { - t.Errorf("%s: Open got error %v, want nil", test.desc, err) - continue - } - // Don't defer file.DecRef here because that causes the hangup we're - // trying to test for. - - // Expect the partner routine to have coordinated with us and get back - // its open fd. - f := <-fdchan - if f < 0 { - t.Errorf("%s: partner routine got fd %d, want > 0", test.desc, f) - pipeOps.Release(ctx) - continue - } - - if test.hangupSelf { - // Hangup self and assert that our partner got the expected hangup - // error. - pipeOps.Release(ctx) - - if test.flags.Read { - // Partner is writer. - assertWriterHungup(t, test.desc, fd.NewReadWriter(f)) - } else { - // Partner is reader. - assertReaderHungup(t, test.desc, fd.NewReadWriter(f)) - } - } else { - // Hangup our partner and expect us to get the hangup error. - unix.Close(f) - defer pipeOps.Release(ctx) - - if test.flags.Read { - assertReaderHungup(t, test.desc, pipeOps.(*pipeOperations).file) - } else { - assertWriterHungup(t, test.desc, pipeOps.(*pipeOperations).file) - } - } - } -} - -func assertReaderHungup(t *testing.T, desc string, reader io.Reader) bool { - // Drain the pipe completely, it might have crap in it, but expect EOF eventually. - var err error - for err == nil { - _, err = reader.Read(make([]byte, 10)) - } - if err != io.EOF { - t.Errorf("%s: read from self after hangup got error %v, want %v", desc, err, io.EOF) - return false - } - return true -} - -func assertWriterHungup(t *testing.T, desc string, writer io.Writer) bool { - if _, err := writer.Write([]byte("hello")); !linuxerr.Equals(linuxerr.EPIPE, unwrapError(err)) { - t.Errorf("%s: write to self after hangup got error %v, want %v", desc, err, linuxerr.EPIPE) - return false - } - return true -} diff --git a/pkg/sentry/fs/fdpipe/pipe_state.go b/pkg/sentry/fs/fdpipe/pipe_state.go deleted file mode 100644 index d92838202..000000000 --- a/pkg/sentry/fs/fdpipe/pipe_state.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018 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. - -package fdpipe - -import ( - "fmt" - "io/ioutil" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sync" -) - -// beforeSave is invoked by stateify. -func (p *pipeOperations) beforeSave() { - if p.flags.Read { - data, err := ioutil.ReadAll(p.file) - if err != nil && !isBlockError(err) { - panic(fmt.Sprintf("failed to read from pipe: %v", err)) - } - p.readAheadBuffer = append(p.readAheadBuffer, data...) - } else if p.flags.Write { - // It's not really possible to evaluate what can be reopened on restore. - file, err := p.opener.NonBlockingOpen(context.Background(), fs.PermMask{Write: true}) - if err != nil { - panic(&fs.ErrSaveRejection{ - Err: fmt.Errorf("write-only pipe end cannot be re-opened as %#v: %w", p, err), - }) - } - file.Close() - } -} - -// saveFlags is invoked by stateify. -func (p *pipeOperations) saveFlags() fs.FileFlags { - return p.flags -} - -// readPipeOperationsLoading is used to ensure that write-only pipe fds are -// opened after read/write and read-only pipe fds, to avoid ENXIO when -// multiple pipe fds refer to different ends of the same pipe. -var readPipeOperationsLoading sync.WaitGroup - -// loadFlags is invoked by stateify. -func (p *pipeOperations) loadFlags(flags fs.FileFlags) { - // This is a hack to ensure that readPipeOperationsLoading includes all - // readable pipe fds before any asynchronous calls to - // readPipeOperationsLoading.Wait(). - if flags.Read { - readPipeOperationsLoading.Add(1) - } - p.flags = flags -} - -// afterLoad is invoked by stateify. -func (p *pipeOperations) afterLoad() { - load := func() error { - if !p.flags.Read { - readPipeOperationsLoading.Wait() - } else { - defer readPipeOperationsLoading.Done() - } - var err error - p.file, err = p.opener.NonBlockingOpen(context.Background(), fs.PermMask{ - Read: p.flags.Read, - Write: p.flags.Write, - }) - if err != nil { - return fmt.Errorf("unable to open pipe %v: %v", p, err) - } - if err := p.init(); err != nil { - return fmt.Errorf("unable to initialize pipe %v: %v", p, err) - } - return nil - } - - // Do background opening of pipe ends. Note for write-only pipe ends we - // have to do it asynchronously to avoid blocking the restore. - fs.Async(fs.CatchError(load)) -} diff --git a/pkg/sentry/fs/fdpipe/pipe_test.go b/pkg/sentry/fs/fdpipe/pipe_test.go deleted file mode 100644 index 9ccf12dc4..000000000 --- a/pkg/sentry/fs/fdpipe/pipe_test.go +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2018 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. - -package fdpipe - -import ( - "bytes" - "io" - "os" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/errors" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/usermem" -) - -func singlePipeFD() (int, error) { - fds := make([]int, 2) - if err := unix.Pipe(fds); err != nil { - return -1, err - } - unix.Close(fds[1]) - return fds[0], nil -} - -func singleDirFD() (int, error) { - return unix.Open(os.TempDir(), unix.O_RDONLY, 0666) -} - -func mockPipeDirent(t *testing.T) *fs.Dirent { - ctx := contexttest.Context(t) - node := fs.NewMockInodeOperations(ctx) - node.UAttr = fs.UnstableAttr{ - Perms: fs.FilePermissions{ - User: fs.PermMask{Read: true, Write: true}, - }, - } - inode := fs.NewInode(ctx, node, fs.NewMockMountSource(nil), fs.StableAttr{ - Type: fs.Pipe, - BlockSize: hostarch.PageSize, - }) - return fs.NewDirent(ctx, inode, "") -} - -func TestNewPipe(t *testing.T) { - for _, test := range []struct { - // desc is the test's description. - desc string - - // getfd generates the fd to pass to newPipeOperations. - getfd func() (int, error) - - // flags are the fs.FileFlags passed to newPipeOperations. - flags fs.FileFlags - - // readAheadBuffer is the buffer passed to newPipeOperations. - readAheadBuffer []byte - - // err is the expected error. - err error - }{ - { - desc: "Cannot make new pipe from bad fd", - getfd: func() (int, error) { return -1, nil }, - err: unix.EINVAL, - }, - { - desc: "Cannot make new pipe from non-pipe fd", - getfd: singleDirFD, - err: unix.EINVAL, - }, - { - desc: "Can make new pipe from pipe fd", - getfd: singlePipeFD, - flags: fs.FileFlags{Read: true}, - readAheadBuffer: []byte("hello"), - }, - } { - gfd, err := test.getfd() - if err != nil { - t.Errorf("%s: getfd got (%d, %v), want (fd, nil)", test.desc, gfd, err) - continue - } - f := fd.New(gfd) - - ctx := contexttest.Context(t) - p, err := newPipeOperations(ctx, nil, test.flags, f, test.readAheadBuffer) - if p != nil { - // This is necessary to remove the fd from the global fd notifier. - defer p.Release(ctx) - } else { - // If there is no p to DecRef on, because newPipeOperations failed, then the - // file still needs to be closed. - defer f.Close() - } - - if err != test.err { - t.Errorf("%s: got error %v, want %v", test.desc, err, test.err) - continue - } - // Check the state of the pipe given that it was successfully opened. - if err == nil { - if p == nil { - t.Errorf("%s: got nil pipe and nil error, want (pipe, nil)", test.desc) - continue - } - if flags := p.flags; test.flags != flags { - t.Errorf("%s: got file flags %v, want %v", test.desc, flags, test.flags) - continue - } - if len(test.readAheadBuffer) != len(p.readAheadBuffer) { - t.Errorf("%s: got read ahead buffer length %d, want %d", test.desc, len(p.readAheadBuffer), len(test.readAheadBuffer)) - continue - } - fileFlags, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(p.file.FD()), unix.F_GETFL, 0) - if errno != 0 { - t.Errorf("%s: failed to get file flags for fd %d, got %v, want 0", test.desc, p.file.FD(), errno) - continue - } - if fileFlags&unix.O_NONBLOCK == 0 { - t.Errorf("%s: pipe is blocking, expected non-blocking", test.desc) - continue - } - if !fdnotifier.HasFD(int32(f.FD())) { - t.Errorf("%s: pipe fd %d is not registered for events", test.desc, f.FD()) - } - } - } -} - -func TestPipeDestruction(t *testing.T) { - fds := make([]int, 2) - if err := unix.Pipe(fds); err != nil { - t.Fatalf("failed to create pipes: got %v, want nil", err) - } - f := fd.New(fds[0]) - - // We don't care about the other end, just use the read end. - unix.Close(fds[1]) - - // Test the read end, but it doesn't really matter which. - ctx := contexttest.Context(t) - p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, f, nil) - if err != nil { - f.Close() - t.Fatalf("newPipeOperations got error %v, want nil", err) - } - // Drop our only reference, which should trigger the destructor. - p.Release(ctx) - - if fdnotifier.HasFD(int32(fds[0])) { - t.Fatalf("after DecRef fdnotifier has fd %d, want no longer registered", fds[0]) - } - if p.file != nil { - t.Errorf("after DecRef got file, want nil") - } -} - -type Seek struct{} - -type ReadDir struct{} - -type Writev struct { - Src usermem.IOSequence -} - -type Readv struct { - Dst usermem.IOSequence -} - -type Fsync struct{} - -func TestPipeRequest(t *testing.T) { - for _, test := range []struct { - // desc is the test's description. - desc string - - // request to execute. - context any - - // flags determines whether to use the read or write end - // of the pipe, for this test it can only be Read or Write. - flags fs.FileFlags - - // keepOpenPartner if false closes the other end of the pipe, - // otherwise this is delayed until the end of the test. - keepOpenPartner bool - - // expected error - err error - }{ - { - desc: "ReadDir on pipe returns ENOTDIR", - context: &ReadDir{}, - err: linuxerr.ENOTDIR, - }, - { - desc: "Fsync on pipe returns EINVAL", - context: &Fsync{}, - err: linuxerr.EINVAL, - }, - { - desc: "Seek on pipe returns ESPIPE", - context: &Seek{}, - err: linuxerr.ESPIPE, - }, - { - desc: "Readv on pipe from empty buffer returns nil", - context: &Readv{Dst: usermem.BytesIOSequence(nil)}, - flags: fs.FileFlags{Read: true}, - }, - { - desc: "Readv on pipe from non-empty buffer and closed partner returns EOF", - context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))}, - flags: fs.FileFlags{Read: true}, - err: io.EOF, - }, - { - desc: "Readv on pipe from non-empty buffer and open partner returns EWOULDBLOCK", - context: &Readv{Dst: usermem.BytesIOSequence(make([]byte, 10))}, - flags: fs.FileFlags{Read: true}, - keepOpenPartner: true, - err: linuxerr.ErrWouldBlock, - }, - { - desc: "Writev on pipe from empty buffer returns nil", - context: &Writev{Src: usermem.BytesIOSequence(nil)}, - flags: fs.FileFlags{Write: true}, - }, - { - desc: "Writev on pipe from non-empty buffer and closed partner returns EPIPE", - context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))}, - flags: fs.FileFlags{Write: true}, - err: linuxerr.EPIPE, - }, - { - desc: "Writev on pipe from non-empty buffer and open partner succeeds", - context: &Writev{Src: usermem.BytesIOSequence([]byte("hello"))}, - flags: fs.FileFlags{Write: true}, - keepOpenPartner: true, - }, - } { - if test.flags.Read && test.flags.Write { - panic("both read and write not supported for this test") - } - - fds := make([]int, 2) - if err := unix.Pipe(fds); err != nil { - t.Errorf("%s: failed to create pipes: got %v, want nil", test.desc, err) - continue - } - - // Configure the fd and partner fd based on the file flags. - testFd, partnerFd := fds[0], fds[1] - if test.flags.Write { - testFd, partnerFd = fds[1], fds[0] - } - - // Configure closing the fds. - if test.keepOpenPartner { - defer unix.Close(partnerFd) - } else { - unix.Close(partnerFd) - } - - // Create the pipe. - ctx := contexttest.Context(t) - p, err := newPipeOperations(ctx, nil, test.flags, fd.New(testFd), nil) - if err != nil { - t.Fatalf("%s: newPipeOperations got error %v, want nil", test.desc, err) - } - defer p.Release(ctx) - - inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe}) - file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) - - // Issue request via the appropriate function. - switch c := test.context.(type) { - case *Seek: - _, err = p.Seek(ctx, file, 0, 0) - case *ReadDir: - _, err = p.Readdir(ctx, file, nil) - case *Readv: - _, err = p.Read(ctx, file, c.Dst, 0) - case *Writev: - _, err = p.Write(ctx, file, c.Src, 0) - case *Fsync: - err = p.Fsync(ctx, file, 0, fs.FileMaxOffset, fs.SyncAll) - default: - t.Errorf("%s: unknown request type %T", test.desc, test.context) - } - - if linuxErr, ok := test.err.(*errors.Error); ok { - if !linuxerr.Equals(linuxErr, unwrapError(err)) { - t.Errorf("%s: got error %v, want %v", test.desc, err, test.err) - } - } else if test.err != unwrapError(err) { - t.Errorf("%s: got error %v, want %v", test.desc, err, test.err) - } - } -} - -func TestPipeReadAheadBuffer(t *testing.T) { - fds := make([]int, 2) - if err := unix.Pipe(fds); err != nil { - t.Fatalf("failed to create pipes: got %v, want nil", err) - } - rfile := fd.New(fds[0]) - - // Eventually close the write end, which is not wrapped in a pipe object. - defer unix.Close(fds[1]) - - // Write some bytes to this end. - data := []byte("world") - if n, err := unix.Write(fds[1], data); n != len(data) || err != nil { - rfile.Close() - t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data)) - } - // Close the write end immediately, we don't care about it. - - buffered := []byte("hello ") - ctx := contexttest.Context(t) - p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, buffered) - if err != nil { - rfile.Close() - t.Fatalf("newPipeOperations got error %v, want nil", err) - } - defer p.Release(ctx) - - inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{ - Type: fs.Pipe, - }) - file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) - - // In total we expect to read data + buffered. - total := append(buffered, data...) - - buf := make([]byte, len(total)) - iov := usermem.BytesIOSequence(buf) - n, err := p.Read(contexttest.Context(t), file, iov, 0) - if err != nil { - t.Fatalf("read request got error %v, want nil", err) - } - if n != int64(len(total)) { - t.Fatalf("read request got %d bytes, want %d", n, len(total)) - } - if !bytes.Equal(buf, total) { - t.Errorf("read request got bytes [%v], want [%v]", buf, total) - } -} - -// This is very important for pipes in general because they can return -// EWOULDBLOCK and for those that block they must continue until they have read -// all of the data (and report it as such). -func TestPipeReadsAccumulate(t *testing.T) { - fds := make([]int, 2) - if err := unix.Pipe(fds); err != nil { - t.Fatalf("failed to create pipes: got %v, want nil", err) - } - rfile := fd.New(fds[0]) - - // Eventually close the write end, it doesn't depend on a pipe object. - defer unix.Close(fds[1]) - - // Get a new read only pipe reference. - ctx := contexttest.Context(t) - p, err := newPipeOperations(ctx, nil, fs.FileFlags{Read: true}, rfile, nil) - if err != nil { - rfile.Close() - t.Fatalf("newPipeOperations got error %v, want nil", err) - } - // Don't forget to remove the fd from the fd notifier. Otherwise other tests will - // likely be borked, because it's global :( - defer p.Release(ctx) - - inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{ - Type: fs.Pipe, - }) - file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) - - // Write some some bytes to the pipe. - data := []byte("some message") - if n, err := unix.Write(fds[1], data); n != len(data) || err != nil { - t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(data)) - } - - // Construct a segment vec that is a bit more than we have written so we - // trigger an EWOULDBLOCK. - wantBytes := len(data) + 1 - readBuffer := make([]byte, wantBytes) - iov := usermem.BytesIOSequence(readBuffer) - n, err := p.Read(ctx, file, iov, 0) - total := n - iov = iov.DropFirst64(n) - if err != linuxerr.ErrWouldBlock { - t.Fatalf("Readv got error %v, want %v", err, linuxerr.ErrWouldBlock) - } - - // Write a few more bytes to allow us to read more/accumulate. - extra := []byte("extra") - if n, err := unix.Write(fds[1], extra); n != len(extra) || err != nil { - t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(extra)) - } - - // This time, using the same request, we should not block. - n, err = p.Read(ctx, file, iov, 0) - total += n - if err != nil { - t.Fatalf("Readv got error %v, want nil", err) - } - - // Assert that the result we got back is cumulative. - if total != int64(wantBytes) { - t.Fatalf("Readv sequence got %d bytes, want %d", total, wantBytes) - } - - if want := append(data, extra[0]); !bytes.Equal(readBuffer, want) { - t.Errorf("Readv sequence got %v, want %v", readBuffer, want) - } -} - -// Same as TestReadsAccumulate. -func TestPipeWritesAccumulate(t *testing.T) { - fds := make([]int, 2) - if err := unix.Pipe(fds); err != nil { - t.Fatalf("failed to create pipes: got %v, want nil", err) - } - wfile := fd.New(fds[1]) - - // Eventually close the read end, it doesn't depend on a pipe object. - defer unix.Close(fds[0]) - - // Get a new write only pipe reference. - ctx := contexttest.Context(t) - p, err := newPipeOperations(ctx, nil, fs.FileFlags{Write: true}, wfile, nil) - if err != nil { - wfile.Close() - t.Fatalf("newPipeOperations got error %v, want nil", err) - } - // Don't forget to remove the fd from the fd notifier. Otherwise other tests - // will likely be borked, because it's global :( - defer p.Release(ctx) - - inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{ - Type: fs.Pipe, - }) - file := fs.NewFile(ctx, fs.NewDirent(ctx, inode, "pipe"), fs.FileFlags{Read: true}, p) - - pipeSize, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(wfile.FD()), unix.F_GETPIPE_SZ, 0) - if errno != 0 { - t.Fatalf("fcntl(F_GETPIPE_SZ) failed: %v", errno) - } - t.Logf("Pipe buffer size: %d", pipeSize) - - // Construct a segment vec that is larger than the pipe size to trigger an - // EWOULDBLOCK. - wantBytes := int(pipeSize) * 2 - writeBuffer := make([]byte, wantBytes) - for i := 0; i < wantBytes; i++ { - writeBuffer[i] = 'a' - } - iov := usermem.BytesIOSequence(writeBuffer) - n, err := p.Write(ctx, file, iov, 0) - if err != linuxerr.ErrWouldBlock { - t.Fatalf("Writev got error %v, want %v", err, linuxerr.ErrWouldBlock) - } - if n != int64(pipeSize) { - t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize) - } - total := n - iov = iov.DropFirst64(n) - - // Read the entire pipe buf size to make space for the second half. - readBuffer := make([]byte, n) - if n, err := unix.Read(fds[0], readBuffer); n != len(readBuffer) || err != nil { - t.Fatalf("write to pipe got (%d, %v), want (%d, nil)", n, err, len(readBuffer)) - } - if !bytes.Equal(readBuffer, writeBuffer[:len(readBuffer)]) { - t.Fatalf("wrong data read from pipe, got: %v, want: %v", readBuffer, writeBuffer) - } - - // This time we should not block. - n, err = p.Write(ctx, file, iov, 0) - if err != nil { - t.Fatalf("Writev got error %v, want nil", err) - } - if n != int64(pipeSize) { - t.Fatalf("Writev partial write, got: %v, want %v", n, pipeSize) - } - total += n - - // Assert that the result we got back is cumulative. - if total != int64(wantBytes) { - t.Fatalf("Writev sequence got %d bytes, want %d", total, wantBytes) - } -} diff --git a/pkg/sentry/fs/file.go b/pkg/sentry/fs/file.go index 610ca57e2..9537503f0 100644 --- a/pkg/sentry/fs/file.go +++ b/pkg/sentry/fs/file.go @@ -21,7 +21,7 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/fsmetric" "gvisor.dev/gvisor/pkg/sentry/limits" "gvisor.dev/gvisor/pkg/sentry/memmap" diff --git a/pkg/sentry/fs/file_overlay_test.go b/pkg/sentry/fs/file_overlay_test.go deleted file mode 100644 index 49a2b7ef7..000000000 --- a/pkg/sentry/fs/file_overlay_test.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2018 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. - -package fs_test - -import ( - "reflect" - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest" -) - -func TestReaddir(t *testing.T) { - ctx := contexttest.Context(t) - ctx = &rootContext{ - Context: ctx, - root: fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root"), - } - for _, test := range []struct { - // Test description. - desc string - - // Lookup parameters. - dir *fs.Inode - - // Want from lookup. - err error - names []string - }{ - { - desc: "no upper, lower has entries", - dir: fs.NewTestOverlayDir(ctx, - nil, /* upper */ - newTestRamfsDir(ctx, []dirContent{ - {name: "a"}, - {name: "b"}, - }, nil), /* lower */ - false /* revalidate */), - names: []string{".", "..", "a", "b"}, - }, - { - desc: "upper has entries, no lower", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - {name: "a"}, - {name: "b"}, - }, nil), /* upper */ - nil, /* lower */ - false /* revalidate */), - names: []string{".", "..", "a", "b"}, - }, - { - desc: "upper and lower, entries combine", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - {name: "a"}, - }, nil), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - {name: "b"}, - }, nil), /* lower */ - false /* revalidate */), - names: []string{".", "..", "a", "b"}, - }, - { - desc: "upper and lower, entries combine, none are masked", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - {name: "a"}, - }, []string{"b"}), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - {name: "c"}, - }, nil), /* lower */ - false /* revalidate */), - names: []string{".", "..", "a", "c"}, - }, - { - desc: "upper and lower, entries combine, upper masks some of lower", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - {name: "a"}, - }, []string{"b"}), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - {name: "b"}, /* will be masked */ - {name: "c"}, - }, nil), /* lower */ - false /* revalidate */), - names: []string{".", "..", "a", "c"}, - }, - } { - t.Run(test.desc, func(t *testing.T) { - openDir, err := test.dir.GetFile(ctx, fs.NewDirent(ctx, test.dir, "stub"), fs.FileFlags{Read: true}) - if err != nil { - t.Fatalf("GetFile got error %v, want nil", err) - } - stubSerializer := &fs.CollectEntriesSerializer{} - err = openDir.Readdir(ctx, stubSerializer) - if err != test.err { - t.Fatalf("Readdir got error %v, want nil", err) - } - if err != nil { - return - } - if !reflect.DeepEqual(stubSerializer.Order, test.names) { - t.Errorf("Readdir got names %v, want %v", stubSerializer.Order, test.names) - } - }) - } -} - -func TestReaddirRevalidation(t *testing.T) { - ctx := contexttest.Context(t) - ctx = &rootContext{ - Context: ctx, - root: fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root"), - } - - // Create an overlay with two directories, each with one file. - upper := newTestRamfsDir(ctx, []dirContent{{name: "a"}}, nil) - lower := newTestRamfsDir(ctx, []dirContent{{name: "b"}}, nil) - overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */) - - // Get a handle to the dirent in the upper filesystem so that we can - // modify it without going through the dirent. - upperDir := upper.InodeOperations.(*dir).InodeOperations.(*ramfs.Dir) - - // Check that overlay returns the files from both upper and lower. - openDir, err := overlay.GetFile(ctx, fs.NewDirent(ctx, overlay, "stub"), fs.FileFlags{Read: true}) - if err != nil { - t.Fatalf("GetFile got error %v, want nil", err) - } - ser := &fs.CollectEntriesSerializer{} - if err := openDir.Readdir(ctx, ser); err != nil { - t.Fatalf("Readdir got error %v, want nil", err) - } - got, want := ser.Order, []string{".", "..", "a", "b"} - if !reflect.DeepEqual(got, want) { - t.Errorf("Readdir got names %v, want %v", got, want) - } - - // Remove "a" from the upper and add "c". - if err := upperDir.Remove(ctx, upper, "a"); err != nil { - t.Fatalf("error removing child: %v", err) - } - upperDir.AddChild(ctx, "c", fs.NewInode(ctx, fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermissions{}, 0), - upper.MountSource, fs.StableAttr{Type: fs.RegularFile})) - - // Seek to beginning of the directory and do the readdir again. - if _, err := openDir.Seek(ctx, fs.SeekSet, 0); err != nil { - t.Fatalf("error seeking to beginning of dir: %v", err) - } - ser = &fs.CollectEntriesSerializer{} - if err := openDir.Readdir(ctx, ser); err != nil { - t.Fatalf("Readdir got error %v, want nil", err) - } - - // Readdir should return the updated children. - got, want = ser.Order, []string{".", "..", "b", "c"} - if !reflect.DeepEqual(got, want) { - t.Errorf("Readdir got names %v, want %v", got, want) - } -} - -type rootContext struct { - context.Context - root *fs.Dirent -} - -// Value implements context.Context. -func (r *rootContext) Value(key any) any { - switch key { - case fs.CtxRoot: - r.root.IncRef() - return r.root - default: - return r.Context.Value(key) - } -} diff --git a/pkg/sentry/fs/filetest/BUILD b/pkg/sentry/fs/filetest/BUILD deleted file mode 100644 index a8000e010..000000000 --- a/pkg/sentry/fs/filetest/BUILD +++ /dev/null @@ -1,19 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "filetest", - testonly = 1, - srcs = ["filetest.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/context", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - "//pkg/sentry/fs/anon", - "//pkg/sentry/fs/fsutil", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/fs/filetest/filetest.go b/pkg/sentry/fs/filetest/filetest.go deleted file mode 100644 index ec3d3f96c..000000000 --- a/pkg/sentry/fs/filetest/filetest.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 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. - -// Package filetest provides a test implementation of an fs.File. -package filetest - -import ( - "fmt" - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/anon" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// TestFileOperations is an implementation of the File interface. It provides all -// required methods. -type TestFileOperations struct { - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FilePipeSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` -} - -// NewTestFile creates and initializes a new test file. -func NewTestFile(tb testing.TB) *fs.File { - ctx := contexttest.Context(tb) - dirent := fs.NewDirent(ctx, anon.NewInode(ctx), "test") - return fs.NewFile(ctx, dirent, fs.FileFlags{}, &TestFileOperations{}) -} - -// Read just fails the request. -func (*TestFileOperations) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, fmt.Errorf("TestFileOperations.Read not implemented") -} - -// Write just fails the request. -func (*TestFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, fmt.Errorf("TestFileOperations.Write not implemented") -} diff --git a/pkg/sentry/fs/g3doc/.gitignore b/pkg/sentry/fs/g3doc/.gitignore deleted file mode 100644 index 2d19fc766..000000000 --- a/pkg/sentry/fs/g3doc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.html diff --git a/pkg/sentry/fs/g3doc/inotify.md b/pkg/sentry/fs/g3doc/inotify.md deleted file mode 100644 index 85063d4e6..000000000 --- a/pkg/sentry/fs/g3doc/inotify.md +++ /dev/null @@ -1,122 +0,0 @@ -# Inotify - -Inotify implements the like-named filesystem event notification system for the -sentry, see `inotify(7)`. - -## Architecture - -For the most part, the sentry implementation of inotify mirrors the Linux -architecture. Inotify instances (i.e. the fd returned by inotify_init(2)) are -backed by a pseudo-filesystem. Events are generated from various places in the -sentry, including the [syscall layer][syscall_dir], the [vfs layer][dirent] and -the [process fd table][fd_table]. Watches are stored in inodes and generated -events are queued to the inotify instance owning the watches for delivery to the -user. - -## Objects - -Here is a brief description of the existing and new objects involved in the -sentry inotify mechanism, and how they interact: - -### [`fs.Inotify`][inotify] - -- An inotify instances, created by inotify_init(2)/inotify_init1(2). -- The inotify fd has a `fs.Dirent`, supports filesystem syscalls to read - events. -- Has multiple `fs.Watch`es, with at most one watch per target inode, per - inotify instance. -- Has an instance `id` which is globally unique. This is *not* the fd number - for this instance, since the fd can be duped. This `id` is not externally - visible. - -### [`fs.Watch`][watch] - -- An inotify watch, created/deleted by - inotify_add_watch(2)/inotify_rm_watch(2). -- Owned by an `fs.Inotify` instance, each watch keeps a pointer to the - `owner`. -- Associated with a single `fs.Inode`, which is the watch `target`. While the - watch is active, it indirectly pins `target` to memory. See the "Reference - Model" section for a detailed explanation. -- Filesystem operations on `target` generate `fs.Event`s. - -### [`fs.Event`][event] - -- A simple struct encapsulating all the fields for an inotify event. -- Generated by `fs.Watch`es and forwarded to the watches' `owner`s. -- Serialized to the user during read(2) syscalls on the associated - `fs.Inotify`'s fd. - -### [`fs.Dirent`][dirent] - -- Many inotify events are generated inside dirent methods. Events are - generated in the dirent methods rather than `fs.Inode` methods because some - events carry the name of the subject node, and node names are generally - unavailable in an `fs.Inode`. -- Dirents do not directly contain state for any watches. Instead, they forward - notifications to the underlying `fs.Inode`. - -### [`fs.Inode`][inode] - -- Interacts with inotify through `fs.Watch`es. -- Inodes contain a map of all active `fs.Watch`es on them. -- An `fs.Inotify` instance can have at most one `fs.Watch` per inode. - `fs.Watch`es on an inode are indexed by their `owner`'s `id`. -- All inotify logic is encapsulated in the [`Watches`][inode_watches] struct - in an inode. Logically, `Watches` is the set of inotify watches on the - inode. - -## Reference Model - -The sentry inotify implementation has a complex reference model. An inotify -watch observes a single inode. For efficient lookup, the state for a watch is -stored directly on the target inode. This state needs to be persistent for the -lifetime of watch. Unlike usual filesystem metadata, the watch state has no -"on-disk" representation, so they cannot be reconstructed by the filesystem if -the inode is flushed from memory. This effectively means we need to keep any -inodes with actives watches pinned to memory. - -We can't just hold an extra ref on the inode to pin it to memory because some -filesystems (such as gofer-based filesystems) don't have persistent inodes. In -such a filesystem, if we just pin the inode, nothing prevents the enclosing -dirent from being GCed. Once the dirent is GCed, the pinned inode is -unreachable -- these filesystems generate a new inode by re-reading the node -state on the next walk. Incidentally, hardlinks also don't work on these -filesystems for this reason. - -To prevent the above scenario, when a new watch is added on an inode, we *pin* -the dirent we used to reach the inode. Note that due to hardlinks, this dirent -may not be the only dirent pointing to the inode. Attempting to set an inotify -watch via multiple hardlinks to the same file results in the same watch being -returned for both links. However, for each new dirent we use to reach the same -inode, we add a new pin. We need a new pin for each new dirent used to reach the -inode because we have no guarantees about the deletion order of the different -links to the inode. - -## Lock Ordering - -There are 4 locks related to the inotify implementation: - -- `Inotify.mu`: the inotify instance lock. -- `Inotify.evMu`: the inotify event queue lock. -- `Watch.mu`: the watch lock, used to protect pins. -- `fs.Watches.mu`: the inode watch set mu, used to protect the collection of - watches on the inode. - -The correct lock ordering for inotify code is: - -`Inotify.mu` -> `fs.Watches.mu` -> `Watch.mu` -> `Inotify.evMu`. - -We need a distinct lock for the event queue because by the time a goroutine -attempts to queue a new event, it is already holding `fs.Watches.mu`. If we used -`Inotify.mu` to also protect the event queue, this would violate the above lock -ordering. - -[dirent]: https://github.com/google/gvisor/blob/master/pkg/sentry/fs/dirent.go -[event]: https://github.com/google/gvisor/blob/master/pkg/sentry/fs/inotify_event.go -[fd_table]: https://github.com/google/gvisor/blob/master/pkg/sentry/kernel/fd_table.go -[inode]: https://github.com/google/gvisor/blob/master/pkg/sentry/fs/inode.go -[inode_watches]: https://github.com/google/gvisor/blob/master/pkg/sentry/fs/inode_inotify.go -[inotify]: https://github.com/google/gvisor/blob/master/pkg/sentry/fs/inotify.go -[syscall_dir]: https://github.com/google/gvisor/blob/master/pkg/sentry/syscalls/linux/ -[watch]: https://github.com/google/gvisor/blob/master/pkg/sentry/fs/inotify_watch.go diff --git a/pkg/sentry/fs/gofer/BUILD b/pkg/sentry/fs/gofer/BUILD deleted file mode 100644 index ee2f287d9..000000000 --- a/pkg/sentry/fs/gofer/BUILD +++ /dev/null @@ -1,72 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "gofer", - srcs = [ - "attr.go", - "cache_policy.go", - "context_file.go", - "device.go", - "fifo.go", - "file.go", - "file_state.go", - "fs.go", - "handles.go", - "inode.go", - "inode_state.go", - "path.go", - "session.go", - "session_state.go", - "socket.go", - "util.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors", - "//pkg/errors/linuxerr", - "//pkg/fd", - "//pkg/hostarch", - "//pkg/log", - "//pkg/metric", - "//pkg/p9", - "//pkg/refs", - "//pkg/safemem", - "//pkg/secio", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fdpipe", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/host", - "//pkg/sentry/fsmetric", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/pipe", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/unix/transport", - "//pkg/sync", - "//pkg/syserr", - "//pkg/unet", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "gofer_test", - size = "small", - srcs = ["gofer_test.go"], - library = ":gofer", - deps = [ - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/p9", - "//pkg/p9/p9test", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - ], -) diff --git a/pkg/sentry/fs/gofer/attr.go b/pkg/sentry/fs/gofer/attr.go deleted file mode 100644 index d6bff3f40..000000000 --- a/pkg/sentry/fs/gofer/attr.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" -) - -// getattr returns the 9p attributes of the p9.File. On success, Mode, Size, and RDev -// are guaranteed to be masked as valid. -func getattr(ctx context.Context, file contextFile) (p9.QID, p9.AttrMask, p9.Attr, error) { - // Retrieve attributes over the wire. - qid, valid, attr, err := file.getAttr(ctx, p9.AttrMaskAll()) - if err != nil { - return qid, valid, attr, err - } - - // Require mode, size, and raw device id. - if !valid.Mode || !valid.Size || !valid.RDev { - return qid, valid, attr, unix.EIO - } - - return qid, valid, attr, nil -} - -func unstable(ctx context.Context, valid p9.AttrMask, pattr p9.Attr, mounter fs.FileOwner, client *p9.Client) fs.UnstableAttr { - return fs.UnstableAttr{ - Size: int64(pattr.Size), - Usage: int64(pattr.Size), - Perms: perms(valid, pattr, client), - Owner: owner(mounter, valid, pattr), - AccessTime: atime(ctx, valid, pattr), - ModificationTime: mtime(ctx, valid, pattr), - StatusChangeTime: ctime(ctx, valid, pattr), - Links: links(valid, pattr), - } -} - -func perms(valid p9.AttrMask, pattr p9.Attr, client *p9.Client) fs.FilePermissions { - if pattr.Mode.IsDir() && !p9.VersionSupportsMultiUser(client.Version()) { - // If user and group permissions bits are not supplied, use - // "other" bits to supplement them. - // - // Older Gofer's fake directories only have "other" permission, - // but will often be accessed via user or group permissions. - if pattr.Mode&0770 == 0 { - other := pattr.Mode & 07 - pattr.Mode = pattr.Mode | other<<3 | other<<6 - } - } - return fs.FilePermsFromP9(pattr.Mode) -} - -func owner(mounter fs.FileOwner, valid p9.AttrMask, pattr p9.Attr) fs.FileOwner { - // Unless the file returned its UID and GID, it belongs to the mounting - // task's EUID/EGID. - owner := mounter - if valid.UID { - if pattr.UID.Ok() { - owner.UID = auth.KUID(pattr.UID) - } else { - owner.UID = auth.KUID(auth.OverflowUID) - } - } - if valid.GID { - if pattr.GID.Ok() { - owner.GID = auth.KGID(pattr.GID) - } else { - owner.GID = auth.KGID(auth.OverflowGID) - } - } - return owner -} - -// bsize returns a block size from 9p attributes. -func bsize(pattr p9.Attr) int64 { - if pattr.BlockSize > 0 { - return int64(pattr.BlockSize) - } - // Some files, particularly those that are not on a local file system, - // may have no clue of their block size. Better not to report something - // misleading or buggy and have a safe default. - return hostarch.PageSize -} - -// ntype returns an fs.InodeType from 9p attributes. -func ntype(pattr p9.Attr) fs.InodeType { - switch { - case pattr.Mode.IsNamedPipe(): - return fs.Pipe - case pattr.Mode.IsDir(): - return fs.Directory - case pattr.Mode.IsSymlink(): - return fs.Symlink - case pattr.Mode.IsCharacterDevice(): - return fs.CharacterDevice - case pattr.Mode.IsBlockDevice(): - return fs.BlockDevice - case pattr.Mode.IsSocket(): - return fs.Socket - default: - return fs.RegularFile - } -} - -// ctime returns a change time from 9p attributes. -func ctime(ctx context.Context, valid p9.AttrMask, pattr p9.Attr) ktime.Time { - if valid.CTime { - return ktime.FromUnix(int64(pattr.CTimeSeconds), int64(pattr.CTimeNanoSeconds)) - } - // Approximate ctime with mtime if ctime isn't available. - return mtime(ctx, valid, pattr) -} - -// atime returns an access time from 9p attributes. -func atime(ctx context.Context, valid p9.AttrMask, pattr p9.Attr) ktime.Time { - if valid.ATime { - return ktime.FromUnix(int64(pattr.ATimeSeconds), int64(pattr.ATimeNanoSeconds)) - } - return ktime.NowFromContext(ctx) -} - -// mtime returns a modification time from 9p attributes. -func mtime(ctx context.Context, valid p9.AttrMask, pattr p9.Attr) ktime.Time { - if valid.MTime { - return ktime.FromUnix(int64(pattr.MTimeSeconds), int64(pattr.MTimeNanoSeconds)) - } - return ktime.NowFromContext(ctx) -} - -// links returns a hard link count from 9p attributes. -func links(valid p9.AttrMask, pattr p9.Attr) uint64 { - // For gofer file systems that support link count (such as a local file gofer), - // we return the link count reported by the underlying file system. - if valid.NLink { - return pattr.NLink - } - - // This node is likely backed by a file system that doesn't support links. - // - // We could readdir() and count children directories to provide an accurate - // link count. However this may be expensive since the gofer may be backed by remote - // storage. Instead, simply return 2 links for directories and 1 for everything else - // since no one relies on an accurate link count for gofer-based file systems. - switch ntype(pattr) { - case fs.Directory: - return 2 - default: - return 1 - } -} diff --git a/pkg/sentry/fs/gofer/cache_policy.go b/pkg/sentry/fs/gofer/cache_policy.go deleted file mode 100644 index f8b7a60fc..000000000 --- a/pkg/sentry/fs/gofer/cache_policy.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// cachePolicy is a 9p cache policy. It has methods that determine what to -// cache (if anything) for a given inode. -type cachePolicy int - -const ( - // Cache nothing. - cacheNone cachePolicy = iota - - // Use virtual file system cache for everything. - cacheAll - - // Use virtual file system cache for everything, but send writes to the - // fs agent immediately. - cacheAllWritethrough - - // Use the (host) page cache for reads/writes, but don't cache anything - // else. This allows the sandbox filesystem to stay in sync with any - // changes to the remote filesystem. - // - // This policy should *only* be used with remote filesystems that - // donate their host FDs to the sandbox and thus use the host page - // cache, otherwise the dirent state will be inconsistent. - cacheRemoteRevalidating -) - -// String returns the string name of the cache policy. -func (cp cachePolicy) String() string { - switch cp { - case cacheNone: - return "cacheNone" - case cacheAll: - return "cacheAll" - case cacheAllWritethrough: - return "cacheAllWritethrough" - case cacheRemoteRevalidating: - return "cacheRemoteRevalidating" - default: - return "unknown" - } -} - -func parseCachePolicy(policy string) (cachePolicy, error) { - switch policy { - case "fscache": - return cacheAll, nil - case "none": - return cacheNone, nil - case "fscache_writethrough": - return cacheAllWritethrough, nil - case "remote_revalidating": - return cacheRemoteRevalidating, nil - } - return cacheNone, fmt.Errorf("unsupported cache mode: %s", policy) -} - -// cacheUAtters determines whether unstable attributes should be cached for the -// given inode. -func (cp cachePolicy) cacheUAttrs(inode *fs.Inode) bool { - if !fs.IsFile(inode.StableAttr) && !fs.IsDir(inode.StableAttr) { - return false - } - return cp == cacheAll || cp == cacheAllWritethrough -} - -// cacheReaddir determines whether readdir results should be cached. -func (cp cachePolicy) cacheReaddir() bool { - return cp == cacheAll || cp == cacheAllWritethrough -} - -// useCachingInodeOps determines whether the page cache should be used for the -// given inode. If the remote filesystem donates host FDs to the sentry, then -// the host kernel's page cache will be used, otherwise we will use a -// sentry-internal page cache. -func (cp cachePolicy) useCachingInodeOps(inode *fs.Inode) bool { - // Do cached IO for regular files only. Some "character devices" expect - // no caching. - if !fs.IsFile(inode.StableAttr) { - return false - } - return cp == cacheAll || cp == cacheAllWritethrough -} - -// writeThough indicates whether writes to the file should be synced to the -// gofer immediately. -func (cp cachePolicy) writeThrough(inode *fs.Inode) bool { - return cp == cacheNone || cp == cacheAllWritethrough -} - -// revalidate revalidates the child Inode if the cache policy allows it. -// -// Depending on the cache policy, revalidate will walk from the parent to the -// child inode, and if any unstable attributes have changed, will update the -// cached attributes on the child inode. If the walk fails, or the returned -// inode id is different from the one being revalidated, then the entire Dirent -// must be reloaded. -func (cp cachePolicy) revalidate(ctx context.Context, name string, parent, child *fs.Inode) bool { - if cp == cacheAll || cp == cacheAllWritethrough { - return false - } - - if cp == cacheNone { - return true - } - - childIops, ok := child.InodeOperations.(*inodeOperations) - if !ok { - if _, ok := child.InodeOperations.(*fifo); ok { - return false - } - panic(fmt.Sprintf("revalidating inode operations of unknown type %T", child.InodeOperations)) - } - parentIops, ok := parent.InodeOperations.(*inodeOperations) - if !ok { - panic(fmt.Sprintf("revalidating inode operations with parent of unknown type %T", parent.InodeOperations)) - } - - // Walk from parent to child again. - // - // NOTE(b/112031682): If we have a directory FD in the parent - // inodeOperations, then we can use fstatat(2) to get the inode - // attributes instead of making this RPC. - qids, f, mask, attr, err := parentIops.fileState.file.walkGetAttr(ctx, []string{name}) - if err != nil { - // Can't look up the name. Trigger reload. - return true - } - f.close(ctx) - - // If the Path has changed, then we are not looking at the file file. - // We must reload. - if qids[0].Path != childIops.fileState.key.Inode { - return true - } - - // If we are not caching unstable attrs, then there is nothing to - // update on this inode. - if !cp.cacheUAttrs(child) { - return false - } - - // Update the inode's cached unstable attrs. - s := childIops.session() - childIops.cachingInodeOps.UpdateUnstable(unstable(ctx, mask, attr, s.mounter, s.client)) - - return false -} - -// keep indicates that dirents should be kept pinned in the dirent tree even if -// there are no application references on the file. -func (cp cachePolicy) keep(d *fs.Dirent) bool { - if cp == cacheNone { - return false - } - sattr := d.Inode.StableAttr - // NOTE(b/31979197): Only cache files, directories, and symlinks. - return fs.IsFile(sattr) || fs.IsDir(sattr) || fs.IsSymlink(sattr) -} - -// cacheNegativeDirents indicates that negative dirents should be held in the -// dirent tree. -func (cp cachePolicy) cacheNegativeDirents() bool { - return cp == cacheAll || cp == cacheAllWritethrough -} diff --git a/pkg/sentry/fs/gofer/context_file.go b/pkg/sentry/fs/gofer/context_file.go deleted file mode 100644 index 096351cf6..000000000 --- a/pkg/sentry/fs/gofer/context_file.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/p9" -) - -// contextFile is a wrapper around p9.File that notifies the context that -// it's about to sleep before calling the Gofer over P9. -type contextFile struct { - file p9.File -} - -func (c *contextFile) walk(ctx context.Context, names []string) ([]p9.QID, contextFile, error) { - ctx.UninterruptibleSleepStart(false) - - q, f, err := c.file.Walk(names) - if err != nil { - ctx.UninterruptibleSleepFinish(false) - return nil, contextFile{}, err - } - ctx.UninterruptibleSleepFinish(false) - return q, contextFile{file: f}, nil -} - -func (c *contextFile) statFS(ctx context.Context) (p9.FSStat, error) { - ctx.UninterruptibleSleepStart(false) - s, err := c.file.StatFS() - ctx.UninterruptibleSleepFinish(false) - return s, err -} - -func (c *contextFile) getAttr(ctx context.Context, req p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) { - ctx.UninterruptibleSleepStart(false) - q, m, a, err := c.file.GetAttr(req) - ctx.UninterruptibleSleepFinish(false) - return q, m, a, err -} - -func (c *contextFile) setAttr(ctx context.Context, valid p9.SetAttrMask, attr p9.SetAttr) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.SetAttr(valid, attr) - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) getXattr(ctx context.Context, name string, size uint64) (string, error) { - ctx.UninterruptibleSleepStart(false) - val, err := c.file.GetXattr(name, size) - ctx.UninterruptibleSleepFinish(false) - return val, err -} - -func (c *contextFile) setXattr(ctx context.Context, name, value string, flags uint32) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.SetXattr(name, value, flags) - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) listXattr(ctx context.Context, size uint64) (map[string]struct{}, error) { - ctx.UninterruptibleSleepStart(false) - xattrs, err := c.file.ListXattr(size) - ctx.UninterruptibleSleepFinish(false) - return xattrs, err -} - -func (c *contextFile) removeXattr(ctx context.Context, name string) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.RemoveXattr(name) - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) allocate(ctx context.Context, mode p9.AllocateMode, offset, length uint64) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.Allocate(mode, offset, length) - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) rename(ctx context.Context, directory contextFile, name string) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.Rename(directory.file, name) - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) close(ctx context.Context) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.Close() - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) open(ctx context.Context, mode p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) { - ctx.UninterruptibleSleepStart(false) - f, q, u, err := c.file.Open(mode) - ctx.UninterruptibleSleepFinish(false) - return f, q, u, err -} - -func (c *contextFile) readAt(ctx context.Context, p []byte, offset uint64) (int, error) { - ctx.UninterruptibleSleepStart(false) - n, err := c.file.ReadAt(p, offset) - ctx.UninterruptibleSleepFinish(false) - return n, err -} - -func (c *contextFile) writeAt(ctx context.Context, p []byte, offset uint64) (int, error) { - ctx.UninterruptibleSleepStart(false) - n, err := c.file.WriteAt(p, offset) - ctx.UninterruptibleSleepFinish(false) - return n, err -} - -func (c *contextFile) fsync(ctx context.Context) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.FSync() - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) create(ctx context.Context, name string, flags p9.OpenFlags, permissions p9.FileMode, uid p9.UID, gid p9.GID) (*fd.FD, error) { - ctx.UninterruptibleSleepStart(false) - fd, _, _, _, err := c.file.Create(name, flags, permissions, uid, gid) - ctx.UninterruptibleSleepFinish(false) - return fd, err -} - -func (c *contextFile) mkdir(ctx context.Context, name string, permissions p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, error) { - ctx.UninterruptibleSleepStart(false) - q, err := c.file.Mkdir(name, permissions, uid, gid) - ctx.UninterruptibleSleepFinish(false) - return q, err -} - -func (c *contextFile) symlink(ctx context.Context, oldName string, newName string, uid p9.UID, gid p9.GID) (p9.QID, error) { - ctx.UninterruptibleSleepStart(false) - q, err := c.file.Symlink(oldName, newName, uid, gid) - ctx.UninterruptibleSleepFinish(false) - return q, err -} - -func (c *contextFile) link(ctx context.Context, target *contextFile, newName string) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.Link(target.file, newName) - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) mknod(ctx context.Context, name string, permissions p9.FileMode, major uint32, minor uint32, uid p9.UID, gid p9.GID) (p9.QID, error) { - ctx.UninterruptibleSleepStart(false) - q, err := c.file.Mknod(name, permissions, major, minor, uid, gid) - ctx.UninterruptibleSleepFinish(false) - return q, err -} - -func (c *contextFile) unlinkAt(ctx context.Context, name string, flags uint32) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.UnlinkAt(name, flags) - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) readdir(ctx context.Context, offset uint64, count uint32) ([]p9.Dirent, error) { - ctx.UninterruptibleSleepStart(false) - d, err := c.file.Readdir(offset, count) - ctx.UninterruptibleSleepFinish(false) - return d, err -} - -func (c *contextFile) readlink(ctx context.Context) (string, error) { - ctx.UninterruptibleSleepStart(false) - s, err := c.file.Readlink() - ctx.UninterruptibleSleepFinish(false) - return s, err -} - -func (c *contextFile) flush(ctx context.Context) error { - ctx.UninterruptibleSleepStart(false) - err := c.file.Flush() - ctx.UninterruptibleSleepFinish(false) - return err -} - -func (c *contextFile) walkGetAttr(ctx context.Context, names []string) ([]p9.QID, contextFile, p9.AttrMask, p9.Attr, error) { - ctx.UninterruptibleSleepStart(false) - q, f, m, a, err := c.file.WalkGetAttr(names) - if err != nil { - ctx.UninterruptibleSleepFinish(false) - return nil, contextFile{}, p9.AttrMask{}, p9.Attr{}, err - } - ctx.UninterruptibleSleepFinish(false) - return q, contextFile{file: f}, m, a, nil -} - -func (c *contextFile) connect(ctx context.Context, socketType p9.SocketType) (*fd.FD, error) { - ctx.UninterruptibleSleepStart(false) - f, err := c.file.Connect(socketType) - ctx.UninterruptibleSleepFinish(false) - return f, err -} diff --git a/pkg/sentry/fs/gofer/device.go b/pkg/sentry/fs/gofer/device.go deleted file mode 100644 index cbd3c5da2..000000000 --- a/pkg/sentry/fs/gofer/device.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import "gvisor.dev/gvisor/pkg/sentry/device" - -// goferDevice is the gofer virtual device. -var goferDevice = device.NewAnonMultiDevice() diff --git a/pkg/sentry/fs/gofer/fifo.go b/pkg/sentry/fs/gofer/fifo.go deleted file mode 100644 index 456557058..000000000 --- a/pkg/sentry/fs/gofer/fifo.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 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. - -package gofer - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// +stateify savable -type fifo struct { - fs.InodeOperations - fileIops *inodeOperations -} - -var _ fs.InodeOperations = (*fifo)(nil) - -// Rename implements fs.InodeOperations. It forwards the call to the underlying -// file inode to handle the file rename. Note that file key remains the same -// after the rename to keep the endpoint mapping. -func (i *fifo) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return i.fileIops.Rename(ctx, inode, oldParent, oldName, newParent, newName, replacement) -} - -// StatFS implements fs.InodeOperations. -func (i *fifo) StatFS(ctx context.Context) (fs.Info, error) { - return i.fileIops.StatFS(ctx) -} diff --git a/pkg/sentry/fs/gofer/file.go b/pkg/sentry/fs/gofer/file.go deleted file mode 100644 index 62a517cd7..000000000 --- a/pkg/sentry/fs/gofer/file.go +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "fmt" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/metric" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fsmetric" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// fileOperations implements fs.FileOperations for a remote file system. -// -// +stateify savable -type fileOperations struct { - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoSplice `state:"nosplice"` - waiter.AlwaysReady `state:"nosave"` - - // inodeOperations is the inodeOperations backing the file. It is protected - // by a reference held by File.Dirent.Inode which is stable until - // FileOperations.Release is called. - inodeOperations *inodeOperations `state:"wait"` - - // dirCursor is the directory cursor. - dirCursor string - - // handles are the opened remote file system handles, which may - // be shared with other files. - handles *handles `state:"nosave"` - - // flags are the flags used to open handles. - flags fs.FileFlags `state:"wait"` -} - -// fileOperations implements fs.FileOperations. -var _ fs.FileOperations = (*fileOperations)(nil) - -// NewFile returns a file. NewFile is not appropriate with host pipes and sockets. -// -// The `name` argument is only used to log a warning if we are returning a -// writeable+executable file. (A metric counter is incremented in this case as -// well.) Note that we cannot call d.BaseName() directly in this function, -// because that would lead to a lock order violation, since this is called in -// d.Create which holds d.mu, while d.BaseName() takes d.parent.mu, and the two -// locks must be taken in the opposite order. -func NewFile(ctx context.Context, dirent *fs.Dirent, name string, flags fs.FileFlags, i *inodeOperations, handles *handles) *fs.File { - // Remote file systems enforce readability/writability at an offset, - // see fs/9p/vfs_inode.c:v9fs_vfs_atomic_open -> fs/open.c:finish_open. - flags.Pread = true - flags.Pwrite = true - - if fs.IsFile(dirent.Inode.StableAttr) { - // If cache policy is "remote revalidating", then we must - // ensure that we have a host FD. Otherwise, the - // sentry-internal page cache will be used, and we can end up - // in an inconsistent state if the remote file changes. - cp := dirent.Inode.InodeOperations.(*inodeOperations).session().cachePolicy - if cp == cacheRemoteRevalidating && handles.Host == nil { - panic(fmt.Sprintf("remote-revalidating cache policy requires gofer to donate host FD, but file %q did not have host FD", name)) - } - } - - f := &fileOperations{ - inodeOperations: i, - handles: handles, - flags: flags, - } - if flags.Write { - if err := dirent.Inode.CheckPermission(ctx, fs.PermMask{Execute: true}); err == nil { - metric.SuspiciousOperationsMetric.Increment("opened_write_execute_file") - log.Warningf("Opened a writable executable: %q", name) - } - } - if handles.Host != nil { - fsmetric.GoferOpensHost.Increment() - } else { - fsmetric.GoferOpens9P.Increment() - } - return fs.NewFile(ctx, dirent, flags, f) -} - -// Release implements fs.FileOpeations.Release. -func (f *fileOperations) Release(context.Context) { - f.handles.DecRef() -} - -// Readdir implements fs.FileOperations.Readdir. -func (f *fileOperations) Readdir(ctx context.Context, file *fs.File, serializer fs.DentrySerializer) (int64, error) { - root := fs.RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - - dirCtx := &fs.DirCtx{ - Serializer: serializer, - DirCursor: &f.dirCursor, - } - n, err := fs.DirentReaddir(ctx, file.Dirent, f, root, dirCtx, file.Offset()) - if f.inodeOperations.session().cachePolicy.cacheUAttrs(file.Dirent.Inode) { - f.inodeOperations.cachingInodeOps.TouchAccessTime(ctx, file.Dirent.Inode) - } - return n, err -} - -// IterateDir implements fs.DirIterator.IterateDir. -func (f *fileOperations) IterateDir(ctx context.Context, d *fs.Dirent, dirCtx *fs.DirCtx, offset int) (int, error) { - f.inodeOperations.readdirMu.Lock() - defer f.inodeOperations.readdirMu.Unlock() - - // Fetch directory entries if needed. - if !f.inodeOperations.session().cachePolicy.cacheReaddir() || f.inodeOperations.readdirCache == nil { - entries, err := f.readdirAll(ctx) - if err != nil { - return offset, err - } - - // Cache the readdir result. - f.inodeOperations.readdirCache = fs.NewSortedDentryMap(entries) - } - - // Serialize the entries. - n, err := fs.GenericReaddir(dirCtx, f.inodeOperations.readdirCache) - return offset + n, err -} - -// readdirAll fetches fs.DentAttrs for f, using the attributes of g. -func (f *fileOperations) readdirAll(ctx context.Context) (map[string]fs.DentAttr, error) { - entries := make(map[string]fs.DentAttr) - var readOffset uint64 - for { - // We choose some arbitrary high number of directory entries (64k) and call - // Readdir until we've exhausted them all. - dirents, err := f.handles.File.readdir(ctx, readOffset, 64*1024) - if err != nil { - return nil, err - } - if len(dirents) == 0 { - // We're done, we reached EOF. - break - } - - // The last dirent contains the offset into the next set of dirents. The gofer - // returns the offset as an index into directories, not as a byte offset, because - // converting a byte offset to an index into directories entries is a huge pain. - // But everything is fine if we're consistent. - readOffset = dirents[len(dirents)-1].Offset - - for _, dirent := range dirents { - if dirent.Name == "." || dirent.Name == ".." { - // These must not be included in Readdir results. - continue - } - - // Find a best approximation of the type. - var nt fs.InodeType - switch dirent.Type { - case p9.TypeDir: - nt = fs.Directory - case p9.TypeSymlink: - nt = fs.Symlink - default: - nt = fs.RegularFile - } - - // Install the DentAttr. - entries[dirent.Name] = fs.DentAttr{ - Type: nt, - // Construct the key to find the virtual inode. - // Directory entries reside on the same Device - // and SecondaryDevice as their parent. - InodeID: goferDevice.Map(device.MultiDeviceKey{ - Device: f.inodeOperations.fileState.key.Device, - SecondaryDevice: f.inodeOperations.fileState.key.SecondaryDevice, - Inode: dirent.QID.Path, - }), - } - } - } - - return entries, nil -} - -// maybeSync will call FSync on the file if the file flags require it. -func (f *fileOperations) maybeSync(ctx context.Context, file *fs.File, offset, n int64) error { - flags := file.Flags() - var syncType fs.SyncType - switch { - case flags.Direct || flags.Sync: - syncType = fs.SyncAll - case flags.DSync: - syncType = fs.SyncData - default: - // No need to sync. - return nil - } - - return f.Fsync(ctx, file, offset, offset+n, syncType) -} - -// Write implements fs.FileOperations.Write. -func (f *fileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if fs.IsDir(file.Dirent.Inode.StableAttr) { - // Not all remote file systems enforce this so this client does. - return 0, linuxerr.EISDIR - } - - var ( - n int64 - err error - ) - // The write is handled in different ways depending on the cache policy - // and availability of a host-mappable FD. - if f.inodeOperations.session().cachePolicy.useCachingInodeOps(file.Dirent.Inode) { - n, err = f.inodeOperations.cachingInodeOps.Write(ctx, src, offset) - } else { - uattr, e := f.UnstableAttr(ctx, file) - if e != nil { - return 0, e - } - if f.inodeOperations.fileState.hostMappable != nil { - n, err = f.inodeOperations.fileState.hostMappable.Write(ctx, src, offset, uattr) - } else { - n, err = src.CopyInTo(ctx, f.handles.readWriterAt(ctx, offset)) - if n > 0 && uattr.Perms.HasSetUIDOrGID() { - uattr.Perms.DropSetUIDAndMaybeGID() - f.inodeOperations.SetPermissions(ctx, file.Dirent.Inode, uattr.Perms) - } - } - } - - if n == 0 { - // Nothing written. We are done. - return 0, err - } - - // Write the dirty pages and attributes if cache policy tells us to. - if f.inodeOperations.session().cachePolicy.writeThrough(file.Dirent.Inode) { - if werr := f.inodeOperations.cachingInodeOps.WriteDirtyPagesAndAttrs(ctx, file.Dirent.Inode); werr != nil { - // Report no bytes written since the write faild. - return 0, werr - } - } - - // We may need to sync the written bytes. - if syncErr := f.maybeSync(ctx, file, offset, n); syncErr != nil { - // Sync failed. Report 0 bytes written, since none of them are - // guaranteed to have been synced. - return 0, syncErr - } - - return n, err -} - -// incrementReadCounters increments the read counters for the read starting at the given time. We -// use this function rather than using a defer in Read() to avoid the performance hit of defer. -func (f *fileOperations) incrementReadCounters(start time.Time) { - if f.handles.Host != nil { - fsmetric.GoferReadsHost.Increment() - fsmetric.FinishReadWait(fsmetric.GoferReadWaitHost, start) - } else { - fsmetric.GoferReads9P.Increment() - fsmetric.FinishReadWait(fsmetric.GoferReadWait9P, start) - } -} - -// Read implements fs.FileOperations.Read. -func (f *fileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - start := fsmetric.StartReadWait() - if fs.IsDir(file.Dirent.Inode.StableAttr) { - // Not all remote file systems enforce this so this client does. - f.incrementReadCounters(start) - return 0, linuxerr.EISDIR - } - - if f.inodeOperations.session().cachePolicy.useCachingInodeOps(file.Dirent.Inode) { - n, err := f.inodeOperations.cachingInodeOps.Read(ctx, file, dst, offset) - f.incrementReadCounters(start) - return n, err - } - n, err := dst.CopyOutFrom(ctx, f.handles.readWriterAt(ctx, offset)) - f.incrementReadCounters(start) - return n, err -} - -// Fsync implements fs.FileOperations.Fsync. -func (f *fileOperations) Fsync(ctx context.Context, file *fs.File, start, end int64, syncType fs.SyncType) error { - switch syncType { - case fs.SyncAll, fs.SyncData: - if err := file.Dirent.Inode.WriteOut(ctx); err != nil { - return err - } - fallthrough - case fs.SyncBackingStorage: - // Sync remote caches. - if f.handles.Host != nil { - // Sync the host fd directly. - return unix.Fsync(f.handles.Host.FD()) - } - // Otherwise sync on the p9.File handle. - return f.handles.File.fsync(ctx) - } - panic("invalid sync type") -} - -// Flush implements fs.FileOperations.Flush. -func (f *fileOperations) Flush(ctx context.Context, file *fs.File) error { - // If this file is not opened writable then there is nothing to flush. - // We do this because some p9 server implementations of Flush are - // over-zealous. - // - // FIXME(edahlgren): weaken these implementations and remove this check. - if !file.Flags().Write { - return nil - } - // Execute the flush. - return f.handles.File.flush(ctx) -} - -// ConfigureMMap implements fs.FileOperations.ConfigureMMap. -func (f *fileOperations) ConfigureMMap(ctx context.Context, file *fs.File, opts *memmap.MMapOpts) error { - return f.inodeOperations.configureMMap(file, opts) -} - -// UnstableAttr implements fs.FileOperations.UnstableAttr. -func (f *fileOperations) UnstableAttr(ctx context.Context, file *fs.File) (fs.UnstableAttr, error) { - s := f.inodeOperations.session() - if s.cachePolicy.cacheUAttrs(file.Dirent.Inode) { - return f.inodeOperations.cachingInodeOps.UnstableAttr(ctx, file.Dirent.Inode) - } - // Use f.handles.File, which represents 9P fids that have been opened, - // instead of inodeFileState.file, which represents 9P fids that have not. - // This may be significantly more efficient in some implementations. - _, valid, pattr, err := getattr(ctx, f.handles.File) - if err != nil { - return fs.UnstableAttr{}, err - } - return unstable(ctx, valid, pattr, s.mounter, s.client), nil -} - -// Seek implements fs.FileOperations.Seek. -func (f *fileOperations) Seek(ctx context.Context, file *fs.File, whence fs.SeekWhence, offset int64) (int64, error) { - return fsutil.SeekWithDirCursor(ctx, file, whence, offset, &f.dirCursor) -} diff --git a/pkg/sentry/fs/gofer/file_state.go b/pkg/sentry/fs/gofer/file_state.go deleted file mode 100644 index edd6576aa..000000000 --- a/pkg/sentry/fs/gofer/file_state.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// afterLoad is invoked by stateify. -func (f *fileOperations) afterLoad() { - load := func() error { - f.inodeOperations.fileState.waitForLoad() - - // Manually load the open handles. - var err error - - // The file may have been opened with Truncate, but we don't - // want to re-open it with Truncate or we will lose data. - flags := f.flags - flags.Truncate = false - - f.handles, err = f.inodeOperations.fileState.getHandles(context.Background(), flags, f.inodeOperations.cachingInodeOps) - if err != nil { - return fmt.Errorf("failed to re-open handle: %v", err) - } - return nil - } - fs.Async(fs.CatchError(load)) -} diff --git a/pkg/sentry/fs/gofer/fs.go b/pkg/sentry/fs/gofer/fs.go deleted file mode 100644 index 69095e6fe..000000000 --- a/pkg/sentry/fs/gofer/fs.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2018 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. - -// Package gofer implements a remote 9p filesystem. -package gofer - -import ( - "errors" - "fmt" - "strconv" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// The following are options defined by the Linux 9p client that we support, -// see Documentation/filesystems/9p.txt. -const ( - // The transport method. - transportKey = "trans" - - // The file tree to access when the file server - // is exporting several file systems. Stands for "attach name". - anameKey = "aname" - - // The caching policy. - cacheKey = "cache" - - // The file descriptor for reading with trans=fd. - readFDKey = "rfdno" - - // The file descriptor for writing with trans=fd. - writeFDKey = "wfdno" - - // The number of bytes to use for a 9p packet payload. - msizeKey = "msize" - - // The 9p protocol version. - versionKey = "version" - - // If set to true allows the creation of unix domain sockets inside the - // sandbox using files backed by the gofer. If set to false, unix sockets - // cannot be bound to gofer files without an overlay on top. - privateUnixSocketKey = "privateunixsocket" - - // If present, sets CachingInodeOperationsOptions.LimitHostFDTranslation to - // true. - limitHostFDTranslationKey = "limit_host_fd_translation" - - // overlayfsStaleRead if present closes cached readonly file after the first - // write. This is done to workaround a limitation of Linux overlayfs. - overlayfsStaleRead = "overlayfs_stale_read" -) - -// defaultAname is the default attach name. -const defaultAname = "/" - -// defaultMSize is the message size used for chunking large read and write requests. -// This has been tested to give good enough performance up to 64M. -const defaultMSize = 1024 * 1024 // 1M - -// defaultVersion is the default 9p protocol version. Will negotiate downwards with -// file server if needed. -var defaultVersion = p9.HighestVersionString() - -// Number of names of non-children to cache, preventing unneeded walks. 64 is -// plenty for nodejs, which seems to stat about 4 children on every require(). -const nonChildrenCacheSize = 64 - -var ( - // ErrNoTransport is returned when there is no 'trans' option. - ErrNoTransport = errors.New("missing required option: 'trans='") - - // ErrFileNoReadFD is returned when there is no 'rfdno' option. - ErrFileNoReadFD = errors.New("missing required option: 'rfdno='") - - // ErrFileNoWriteFD is returned when there is no 'wfdno' option. - ErrFileNoWriteFD = errors.New("missing required option: 'wfdno='") -) - -// filesystem is a 9p client. -// -// +stateify savable -type filesystem struct{} - -var _ fs.Filesystem = (*filesystem)(nil) - -func init() { - fs.RegisterFilesystem(&filesystem{}) -} - -// FilesystemName is the name under which the filesystem is registered. -// The name matches fs/9p/vfs_super.c:v9fs_fs_type.name. -const FilesystemName = "9p" - -// Name is the name of the filesystem. -func (*filesystem) Name() string { - return FilesystemName -} - -// AllowUserMount prohibits users from using mount(2) with this file system. -func (*filesystem) AllowUserMount() bool { - return false -} - -// AllowUserList allows this filesystem to be listed in /proc/filesystems. -func (*filesystem) AllowUserList() bool { - return true -} - -// Flags returns that there is nothing special about this file system. -// -// The 9p Linux client returns FS_RENAME_DOES_D_MOVE, see fs/9p/vfs_super.c. -func (*filesystem) Flags() fs.FilesystemFlags { - return 0 -} - -// Mount returns an attached 9p client that can be positioned in the vfs. -func (f *filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, _ any) (*fs.Inode, error) { - // Parse and validate the mount options. - o, err := options(data) - if err != nil { - return nil, err - } - - // Construct the 9p root to mount. We intentionally diverge from Linux in that - // the first Tversion and Tattach requests are done lazily. - return Root(ctx, device, f, flags, o) -} - -// opts are parsed 9p mount options. -type opts struct { - fd int - aname string - policy cachePolicy - msize uint32 - version string - privateunixsocket bool - limitHostFDTranslation bool - overlayfsStaleRead bool -} - -// options parses mount(2) data into structured options. -func options(data string) (opts, error) { - var o opts - - // Parse generic comma-separated key=value options, this file system expects them. - options := fs.GenericMountSourceOptions(data) - - // Check for the required 'trans=fd' option. - trans, ok := options[transportKey] - if !ok { - return o, ErrNoTransport - } - if trans != "fd" { - return o, fmt.Errorf("unsupported transport: 'trans=%s'", trans) - } - delete(options, transportKey) - - // Check for the required 'rfdno=' option. - srfd, ok := options[readFDKey] - if !ok { - return o, ErrFileNoReadFD - } - delete(options, readFDKey) - - // Check for the required 'wfdno=' option. - swfd, ok := options[writeFDKey] - if !ok { - return o, ErrFileNoWriteFD - } - delete(options, writeFDKey) - - // Parse the read fd. - rfd, err := strconv.Atoi(srfd) - if err != nil { - return o, fmt.Errorf("invalid fd for 'rfdno=%s': %v", srfd, err) - } - - // Parse the write fd. - wfd, err := strconv.Atoi(swfd) - if err != nil { - return o, fmt.Errorf("invalid fd for 'wfdno=%s': %v", swfd, err) - } - - // Require that the read and write fd are the same. - if rfd != wfd { - return o, fmt.Errorf("fd in 'rfdno=%d' and 'wfdno=%d' must match", rfd, wfd) - } - o.fd = rfd - - // Parse the attach name. - o.aname = defaultAname - if an, ok := options[anameKey]; ok { - o.aname = an - delete(options, anameKey) - } - - // Parse the cache policy. Reject unsupported policies. - o.policy = cacheAll - if policy, ok := options[cacheKey]; ok { - cp, err := parseCachePolicy(policy) - if err != nil { - return o, err - } - o.policy = cp - delete(options, cacheKey) - } - - // Parse the message size. Reject malformed options. - o.msize = uint32(defaultMSize) - if m, ok := options[msizeKey]; ok { - i, err := strconv.ParseUint(m, 10, 32) - if err != nil { - return o, fmt.Errorf("invalid message size for 'msize=%s': %v", m, err) - } - o.msize = uint32(i) - delete(options, msizeKey) - } - - // Parse the protocol version. - o.version = defaultVersion - if v, ok := options[versionKey]; ok { - o.version = v - delete(options, versionKey) - } - - // Parse the unix socket policy. Reject non-booleans. - if v, ok := options[privateUnixSocketKey]; ok { - b, err := strconv.ParseBool(v) - if err != nil { - return o, fmt.Errorf("invalid boolean value for '%s=%s': %v", privateUnixSocketKey, v, err) - } - o.privateunixsocket = b - delete(options, privateUnixSocketKey) - } - - if _, ok := options[limitHostFDTranslationKey]; ok { - o.limitHostFDTranslation = true - delete(options, limitHostFDTranslationKey) - } - - if _, ok := options[overlayfsStaleRead]; ok { - o.overlayfsStaleRead = true - delete(options, overlayfsStaleRead) - } - - // Fail to attach if the caller wanted us to do something that we - // don't support. - if len(options) > 0 { - return o, fmt.Errorf("unsupported mount options: %v", options) - } - - return o, nil -} diff --git a/pkg/sentry/fs/gofer/gofer_test.go b/pkg/sentry/fs/gofer/gofer_test.go deleted file mode 100644 index 4924debeb..000000000 --- a/pkg/sentry/fs/gofer/gofer_test.go +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "fmt" - "testing" - "time" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/p9/p9test" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// rootTest runs a test with a p9 mock and an fs.InodeOperations created from -// the attached root directory. The root file will be closed and client -// disconnected, but additional files must be closed manually. -func rootTest(t *testing.T, name string, cp cachePolicy, fn func(context.Context, *p9test.Harness, *p9test.Mock, *fs.Inode)) { - t.Run(name, func(t *testing.T) { - h, c := p9test.NewHarness(t) - defer h.Finish() - - // Create a new root. Note that we pass an empty, but non-nil - // map here. This allows tests to extend the root children - // dynamically. - root := h.NewDirectory(map[string]p9test.Generator{})(nil) - - // Return this as the root. - h.Attacher.EXPECT().Attach().Return(root, nil).Times(1) - - // ... and open via the client. - rootFile, err := c.Attach("/") - if err != nil { - t.Fatalf("unable to attach: %v", err) - } - defer rootFile.Close() - - // Wrap an a session. - s := &session{ - mounter: fs.RootOwner, - cachePolicy: cp, - client: c, - } - - // ... and an INode, with only the mode being explicitly valid for now. - ctx := contexttest.Context(t) - sattr, rootInodeOperations := newInodeOperations(ctx, s, contextFile{ - file: rootFile, - }, root.QID, p9.AttrMaskAll(), root.Attr) - m := fs.NewMountSource(ctx, s, &filesystem{}, fs.MountSourceFlags{}) - rootInode := fs.NewInode(ctx, rootInodeOperations, m, sattr) - - // Ensure that the cache is fully invalidated, so that any - // close actions actually take place before the full harness is - // torn down. - defer func() { - m.FlushDirentRefs() - - // Wait for all resources to be released, otherwise the - // operations may fail after we close the rootFile. - fs.AsyncBarrier() - }() - - // Execute the test. - fn(ctx, h, root, rootInode) - }) -} - -func TestLookup(t *testing.T) { - type lookupTest struct { - // Name of the test. - name string - - // Expected return value. - want error - } - - tests := []lookupTest{ - { - name: "mock Walk passes (function succeeds)", - want: nil, - }, - { - name: "mock Walk fails (function fails)", - want: linuxerr.ENOENT, - }, - } - - const file = "file" // The walked target file. - - for _, test := range tests { - rootTest(t, test.name, cacheNone, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) { - // Setup the appropriate result. - rootFile.WalkCallback = func() error { - return test.want - } - if test.want == nil { - // Set the contents of the root. We expect a - // normal file generator for ppp above. This is - // overriden by setting WalkErr in the mock. - rootFile.AddChild(file, h.NewFile()) - } - - // Call function. - dirent, err := rootInode.Lookup(ctx, file) - - // Unwrap the InodeOperations. - var newInodeOperations fs.InodeOperations - if dirent != nil { - if dirent.IsNegative() { - err = linuxerr.ENOENT - } else { - newInodeOperations = dirent.Inode.InodeOperations - } - } - - // Check return values. - if err != test.want { - t.Logf("err: %v %T", err, err) - t.Errorf("Lookup got err %v, want %v", err, test.want) - } - if err == nil && newInodeOperations == nil { - t.Logf("err: %v %T", err, err) - t.Errorf("Lookup got non-nil err and non-nil node, wanted at least one non-nil") - } - }) - } -} - -func TestRevalidation(t *testing.T) { - type revalidationTest struct { - cachePolicy cachePolicy - - // Whether dirent should be reloaded before any modifications. - preModificationWantReload bool - - // Whether dirent should be reloaded after updating an unstable - // attribute on the remote fs. - postModificationWantReload bool - - // Whether dirent unstable attributes should be updated after - // updating an attribute on the remote fs. - postModificationWantUpdatedAttrs bool - - // Whether dirent should be reloaded after the remote has - // removed the file. - postRemovalWantReload bool - } - - tests := []revalidationTest{ - { - // Policy cacheNone causes Revalidate to always return - // true. - cachePolicy: cacheNone, - preModificationWantReload: true, - postModificationWantReload: true, - postModificationWantUpdatedAttrs: true, - postRemovalWantReload: true, - }, - { - // Policy cacheAll causes Revalidate to always return - // false. - cachePolicy: cacheAll, - preModificationWantReload: false, - postModificationWantReload: false, - postModificationWantUpdatedAttrs: false, - postRemovalWantReload: false, - }, - { - // Policy cacheAllWritethrough causes Revalidate to - // always return false. - cachePolicy: cacheAllWritethrough, - preModificationWantReload: false, - postModificationWantReload: false, - postModificationWantUpdatedAttrs: false, - postRemovalWantReload: false, - }, - { - // Policy cacheRemoteRevalidating causes Revalidate to - // return update cached unstable attrs, and returns - // true only when the remote inode itself has been - // removed or replaced. - cachePolicy: cacheRemoteRevalidating, - preModificationWantReload: false, - postModificationWantReload: false, - postModificationWantUpdatedAttrs: true, - postRemovalWantReload: true, - }, - } - - const file = "file" // The file walked below. - - for _, test := range tests { - name := fmt.Sprintf("cachepolicy=%s", test.cachePolicy) - rootTest(t, name, test.cachePolicy, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) { - // Wrap in a dirent object. - rootDir := fs.NewDirent(ctx, rootInode, "root") - - // Create a mock file a child of the root. We save when - // this is generated, so that when the time changed, we - // can update the original entry. - var origMocks []*p9test.Mock - rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock { - // Regular a regular file that has a consistent - // path number. This might be used by - // validation so we don't change it. - m := h.NewMock(parent, 0, p9.Attr{ - Mode: p9.ModeRegular, - }) - origMocks = append(origMocks, m) - return m - }) - - // Do the walk. - dirent, err := rootDir.Walk(ctx, rootDir, file) - if err != nil { - t.Fatalf("Lookup failed: %v", err) - } - - // We must release the dirent, of the test will fail - // with a reference leak. This is tracked by p9test. - defer dirent.DecRef(ctx) - - // Walk again. Depending on the cache policy, we may - // get a new dirent. - newDirent, err := rootDir.Walk(ctx, rootDir, file) - if err != nil { - t.Fatalf("Lookup failed: %v", err) - } - if test.preModificationWantReload && dirent == newDirent { - t.Errorf("Lookup with cachePolicy=%s got old dirent %+v, wanted a new dirent", test.cachePolicy, dirent) - } - if !test.preModificationWantReload && dirent != newDirent { - t.Errorf("Lookup with cachePolicy=%s got new dirent %+v, wanted old dirent %+v", test.cachePolicy, newDirent, dirent) - } - newDirent.DecRef(ctx) // See above. - - // Modify the underlying mocked file's modification - // time for the next walk that occurs. - nowSeconds := time.Now().Unix() - rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock { - // Ensure that the path is the same as above, - // but we change only the modification time of - // the file. - return h.NewMock(parent, 0, p9.Attr{ - Mode: p9.ModeRegular, - MTimeSeconds: uint64(nowSeconds), - }) - }) - - // We also modify the original time, so that GetAttr - // behaves as expected for the caching case. - for _, m := range origMocks { - m.Attr.MTimeSeconds = uint64(nowSeconds) - } - - // Walk again. Depending on the cache policy, we may - // get a new dirent. - newDirent, err = rootDir.Walk(ctx, rootDir, file) - if err != nil { - t.Fatalf("Lookup failed: %v", err) - } - if test.postModificationWantReload && dirent == newDirent { - t.Errorf("Lookup with cachePolicy=%s got old dirent, wanted a new dirent", test.cachePolicy) - } - if !test.postModificationWantReload && dirent != newDirent { - t.Errorf("Lookup with cachePolicy=%s got new dirent, wanted old dirent", test.cachePolicy) - } - uattrs, err := newDirent.Inode.UnstableAttr(ctx) - if err != nil { - t.Fatalf("Error getting unstable attrs: %v", err) - } - gotModTimeSeconds := uattrs.ModificationTime.Seconds() - if test.postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds { - t.Fatalf("Lookup with cachePolicy=%s got new modification time %v, wanted %v", test.cachePolicy, gotModTimeSeconds, nowSeconds) - } - newDirent.DecRef(ctx) // See above. - - // Remove the file from the remote fs, subsequent walks - // should now fail to find anything. - rootFile.RemoveChild(file) - - // Walk again. Depending on the cache policy, we may - // get ENOENT. - newDirent, err = rootDir.Walk(ctx, rootDir, file) - if test.postRemovalWantReload && err == nil { - t.Errorf("Lookup with cachePolicy=%s got nil error, wanted ENOENT", test.cachePolicy) - } - if !test.postRemovalWantReload && (err != nil || dirent != newDirent) { - t.Errorf("Lookup with cachePolicy=%s got new dirent and error %v, wanted old dirent and nil error", test.cachePolicy, err) - } - if err == nil { - newDirent.DecRef(ctx) // See above. - } - }) - } -} diff --git a/pkg/sentry/fs/gofer/handles.go b/pkg/sentry/fs/gofer/handles.go deleted file mode 100644 index f324dbf26..000000000 --- a/pkg/sentry/fs/gofer/handles.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "io" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/secio" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// handles are the open handles of a gofer file. They are reference counted to -// support open handle sharing between files for read only filesystems. -// -// If Host != nil then it will be used exclusively over File. -type handles struct { - refs.AtomicRefCount - - // File is a p9.File handle. Must not be nil. - File contextFile - - // Host is an *fd.FD handle. May be nil. - Host *fd.FD - - // isHostBorrowed tells whether 'Host' is owned or borrowed. If owned, it's - // closed on destruction, otherwise it's released. - isHostBorrowed bool -} - -// DecRef drops a reference on handles. -func (h *handles) DecRef() { - ctx := context.Background() - h.DecRefWithDestructor(ctx, func(context.Context) { - if h.Host != nil { - if h.isHostBorrowed { - h.Host.Release() - } else { - if err := h.Host.Close(); err != nil { - log.Warningf("error closing host file: %v", err) - } - } - } - if err := h.File.close(ctx); err != nil { - log.Warningf("error closing p9 file: %v", err) - } - }) -} - -func newHandles(ctx context.Context, client *p9.Client, file contextFile, flags fs.FileFlags) (*handles, error) { - _, newFile, err := file.walk(ctx, nil) - if err != nil { - return nil, err - } - - var p9flags p9.OpenFlags - switch { - case flags.Read && flags.Write: - p9flags = p9.ReadWrite - case flags.Read && !flags.Write: - p9flags = p9.ReadOnly - case !flags.Read && flags.Write: - p9flags = p9.WriteOnly - default: - panic("impossible fs.FileFlags") - } - if flags.Truncate && p9.VersionSupportsOpenTruncateFlag(client.Version()) { - p9flags |= p9.OpenTruncate - } - - hostFile, _, _, err := newFile.open(ctx, p9flags) - if err != nil { - newFile.close(ctx) - return nil, err - } - h := handles{ - File: newFile, - Host: hostFile, - } - h.EnableLeakCheck("gofer.handles") - return &h, nil -} - -type handleReadWriter struct { - ctx context.Context - h *handles - off int64 -} - -func (h *handles) readWriterAt(ctx context.Context, offset int64) *handleReadWriter { - return &handleReadWriter{ctx, h, offset} -} - -// ReadToBlocks implements safemem.Reader.ReadToBlocks. -func (rw *handleReadWriter) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) { - var r io.Reader - if rw.h.Host != nil { - r = secio.NewOffsetReader(rw.h.Host, rw.off) - } else { - r = &p9.ReadWriterFile{File: rw.h.File.file, Offset: uint64(rw.off)} - } - - rw.ctx.UninterruptibleSleepStart(false) - defer rw.ctx.UninterruptibleSleepFinish(false) - n, err := safemem.FromIOReader{r}.ReadToBlocks(dsts) - rw.off += int64(n) - return n, err -} - -// WriteFromBlocks implements safemem.Writer.WriteFromBlocks. -func (rw *handleReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) { - var w io.Writer - if rw.h.Host != nil { - w = secio.NewOffsetWriter(rw.h.Host, rw.off) - } else { - w = &p9.ReadWriterFile{File: rw.h.File.file, Offset: uint64(rw.off)} - } - - rw.ctx.UninterruptibleSleepStart(false) - defer rw.ctx.UninterruptibleSleepFinish(false) - n, err := safemem.FromIOWriter{w}.WriteFromBlocks(srcs) - rw.off += int64(n) - return n, err -} diff --git a/pkg/sentry/fs/gofer/inode.go b/pkg/sentry/fs/gofer/inode.go deleted file mode 100644 index e0aa3bef1..000000000 --- a/pkg/sentry/fs/gofer/inode.go +++ /dev/null @@ -1,738 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "errors" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - gErr "gvisor.dev/gvisor/pkg/errors" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fdpipe" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/host" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sync" -) - -// inodeOperations implements fs.InodeOperations. -// -// +stateify savable -type inodeOperations struct { - fsutil.InodeNotVirtual `state:"nosave"` - - // fileState implements fs.CachedFileObject. It exists - // to break a circular load dependency between inodeOperations - // and cachingInodeOps (below). - fileState *inodeFileState `state:"wait"` - - // cachingInodeOps implement memmap.Mappable for inodeOperations. - cachingInodeOps *fsutil.CachingInodeOperations - - // readdirMu protects readdirCache and concurrent Readdirs. - readdirMu sync.Mutex `state:"nosave"` - - // readdirCache is a cache of readdir results in the form of - // a fs.SortedDentryMap. - // - // Starts out as nil, and is initialized under readdirMu lazily; - // invalidating the cache means setting it to nil. - readdirCache *fs.SortedDentryMap `state:"nosave"` -} - -// inodeFileState implements fs.CachedFileObject and otherwise fully -// encapsulates state that needs to be manually loaded on restore for -// this file object. -// -// This unfortunate structure exists because fs.CachingInodeOperations -// defines afterLoad and therefore cannot be lazily loaded (to break a -// circular load dependency between it and inodeOperations). Even with -// lazy loading, this approach defines the dependencies between objects -// and the expected load behavior more concretely. -// -// +stateify savable -type inodeFileState struct { - // s is common file system state for Gofers. - s *session `state:"wait"` - - // MultiDeviceKey consists of: - // - // * Device: file system device from a specific gofer. - // * SecondaryDevice: unique identifier of the attach point. - // * Inode: the inode of this resource, unique per Device.= - // - // These fields combined enable consistent hashing of virtual inodes - // on goferDevice. - key device.MultiDeviceKey `state:"nosave"` - - // file is the p9 file that contains a single unopened fid. - file contextFile `state:"nosave"` - - // sattr caches the stable attributes. - sattr fs.StableAttr `state:"wait"` - - // handlesMu protects the below fields. - handlesMu sync.RWMutex `state:"nosave"` - - // If readHandles is non-nil, it holds handles that are either read-only or - // read/write. If writeHandles is non-nil, it holds write-only handles if - // writeHandlesRW is false, and read/write handles if writeHandlesRW is - // true. - // - // Once readHandles becomes non-nil, it can't be changed until - // inodeFileState.Release()*, because of a defect in the - // fsutil.CachedFileObject interface: there's no way for the caller of - // fsutil.CachedFileObject.FD() to keep the returned FD open, so if we - // racily replace readHandles after inodeFileState.FD() has returned - // readHandles.Host.FD(), fsutil.CachingInodeOperations may use a closed - // FD. writeHandles can be changed if writeHandlesRW is false, since - // inodeFileState.FD() can't return a write-only FD, but can't be changed - // if writeHandlesRW is true for the same reason. - // - // * There is one notable exception in recreateReadHandles(), where it dup's - // the FD and invalidates the page cache. - readHandles *handles `state:"nosave"` - writeHandles *handles `state:"nosave"` - writeHandlesRW bool `state:"nosave"` - - // loading is acquired when the inodeFileState begins an asynchronous - // load. It releases when the load is complete. Callers that require all - // state to be available should call waitForLoad() to ensure that. - loading sync.CrossGoroutineMutex `state:".(struct{})"` - - // savedUAttr is only allocated during S/R. It points to the save-time - // unstable attributes and is used to validate restore-time ones. - // - // Note that these unstable attributes are only used to detect cross-S/R - // external file system metadata changes. They may differ from the - // cached unstable attributes in cachingInodeOps, as that might differ - // from the external file system attributes if there had been WriteOut - // failures. S/R is transparent to Sentry and the latter will continue - // using its cached values after restore. - savedUAttr *fs.UnstableAttr - - // hostMappable is created when using 'cacheRemoteRevalidating' to map pages - // directly from host. - hostMappable *fsutil.HostMappable -} - -// Release releases file handles. -func (i *inodeFileState) Release(ctx context.Context) { - i.file.close(ctx) - if i.readHandles != nil { - i.readHandles.DecRef() - } - if i.writeHandles != nil { - i.writeHandles.DecRef() - } -} - -func (i *inodeFileState) canShareHandles() bool { - // Only share handles for regular files, since for other file types, - // distinct handles may have special semantics even if they represent the - // same file. Disable handle sharing for cache policy cacheNone, since this - // is legacy behavior. - return fs.IsFile(i.sattr) && i.s.cachePolicy != cacheNone -} - -// Preconditions: i.handlesMu must be locked for writing. -func (i *inodeFileState) setSharedHandlesLocked(flags fs.FileFlags, h *handles) { - if flags.Read && i.readHandles == nil { - h.IncRef() - i.readHandles = h - } - if flags.Write { - if i.writeHandles == nil { - h.IncRef() - i.writeHandles = h - i.writeHandlesRW = flags.Read - } else if !i.writeHandlesRW && flags.Read { - // Upgrade i.writeHandles. - i.writeHandles.DecRef() - h.IncRef() - i.writeHandles = h - i.writeHandlesRW = flags.Read - } - } -} - -// getHandles returns a set of handles for a new file using i opened with the -// given flags. -func (i *inodeFileState) getHandles(ctx context.Context, flags fs.FileFlags, cache *fsutil.CachingInodeOperations) (*handles, error) { - if !i.canShareHandles() { - return newHandles(ctx, i.s.client, i.file, flags) - } - - i.handlesMu.Lock() - h, invalidate, err := i.getHandlesLocked(ctx, flags) - i.handlesMu.Unlock() - - if invalidate { - cache.NotifyChangeFD() - if i.hostMappable != nil { - i.hostMappable.NotifyChangeFD() - } - } - - return h, err -} - -// getHandlesLocked returns a pointer to cached handles and a boolean indicating -// whether previously open read handle was recreated. Host mappings must be -// invalidated if so. -func (i *inodeFileState) getHandlesLocked(ctx context.Context, flags fs.FileFlags) (*handles, bool, error) { - // Check if we are able to use cached handles. - if flags.Truncate && p9.VersionSupportsOpenTruncateFlag(i.s.client.Version()) { - // If we are truncating (and the gofer supports it), then we - // always need a new handle. Don't return one from the cache. - } else if flags.Write { - if i.writeHandles != nil && (i.writeHandlesRW || !flags.Read) { - // File is opened for writing, and we have cached write - // handles that we can use. - i.writeHandles.IncRef() - return i.writeHandles, false, nil - } - } else if i.readHandles != nil { - // File is opened for reading and we have cached handles. - i.readHandles.IncRef() - return i.readHandles, false, nil - } - - // Get new handles and cache them for future sharing. - h, err := newHandles(ctx, i.s.client, i.file, flags) - if err != nil { - return nil, false, err - } - - // Read handles invalidation is needed if: - // - Mount option 'overlayfs_stale_read' is set - // - Read handle is open: nothing to invalidate otherwise - // - Write handle is not open: file was not open for write and is being open - // for write now (will trigger copy up in overlayfs). - invalidate := false - if i.s.overlayfsStaleRead && i.readHandles != nil && i.writeHandles == nil && flags.Write { - if err := i.recreateReadHandles(ctx, h, flags); err != nil { - return nil, false, err - } - invalidate = true - } - i.setSharedHandlesLocked(flags, h) - return h, invalidate, nil -} - -func (i *inodeFileState) recreateReadHandles(ctx context.Context, writer *handles, flags fs.FileFlags) error { - h := writer - if !flags.Read { - // Writer can't be used for read, must create a new handle. - var err error - h, err = newHandles(ctx, i.s.client, i.file, fs.FileFlags{Read: true}) - if err != nil { - return err - } - defer h.DecRef() - } - - if i.readHandles.Host == nil { - // If current readHandles doesn't have a host FD, it can simply be replaced. - i.readHandles.DecRef() - - h.IncRef() - i.readHandles = h - return nil - } - - if h.Host == nil { - // Current read handle has a host FD and can't be replaced with one that - // doesn't, because it breaks fsutil.CachedFileObject.FD() contract. - log.Warningf("Read handle can't be invalidated, reads may return stale data") - return nil - } - - // Due to a defect in the fsutil.CachedFileObject interface, - // readHandles.Host.FD() may be used outside locks, making it impossible to - // reliably close it. To workaround it, we dup the new FD into the old one, so - // operations on the old will see the new data. Then, make the new handle take - // ownereship of the old FD and mark the old readHandle to not close the FD - // when done. - if err := unix.Dup3(h.Host.FD(), i.readHandles.Host.FD(), unix.O_CLOEXEC); err != nil { - return err - } - - h.Host.Close() - h.Host = fd.New(i.readHandles.Host.FD()) - i.readHandles.isHostBorrowed = true - i.readHandles.DecRef() - - h.IncRef() - i.readHandles = h - return nil -} - -// ReadToBlocksAt implements fsutil.CachedFileObject.ReadToBlocksAt. -func (i *inodeFileState) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) { - i.handlesMu.RLock() - n, err := i.readHandles.readWriterAt(ctx, int64(offset)).ReadToBlocks(dsts) - i.handlesMu.RUnlock() - return n, err -} - -// WriteFromBlocksAt implements fsutil.CachedFileObject.WriteFromBlocksAt. -func (i *inodeFileState) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) { - i.handlesMu.RLock() - n, err := i.writeHandles.readWriterAt(ctx, int64(offset)).WriteFromBlocks(srcs) - i.handlesMu.RUnlock() - return n, err -} - -// SetMaskedAttributes implements fsutil.CachedFileObject.SetMaskedAttributes. -func (i *inodeFileState) SetMaskedAttributes(ctx context.Context, mask fs.AttrMask, attr fs.UnstableAttr, forceSetTimestamps bool) error { - if i.skipSetAttr(mask, forceSetTimestamps) { - return nil - } - as, ans := attr.AccessTime.Unix() - ms, mns := attr.ModificationTime.Unix() - // An update of status change time is implied by mask.AccessTime - // or mask.ModificationTime. Updating status change time to a - // time earlier than the system time is not possible. - return i.file.setAttr( - ctx, - p9.SetAttrMask{ - Permissions: mask.Perms, - Size: mask.Size, - UID: mask.UID, - GID: mask.GID, - ATime: mask.AccessTime, - ATimeNotSystemTime: true, - MTime: mask.ModificationTime, - MTimeNotSystemTime: true, - }, p9.SetAttr{ - Permissions: p9.FileMode(attr.Perms.LinuxMode()), - UID: p9.UID(attr.Owner.UID), - GID: p9.GID(attr.Owner.GID), - Size: uint64(attr.Size), - ATimeSeconds: uint64(as), - ATimeNanoSeconds: uint64(ans), - MTimeSeconds: uint64(ms), - MTimeNanoSeconds: uint64(mns), - }) -} - -// skipSetAttr checks if attribute change can be skipped. It can be skipped -// when: -// - Mask is empty -// - Mask contains only attributes that cannot be set in the gofer -// - forceSetTimestamps is false and mask contains only atime and/or mtime -// and host FD exists -// -// Updates to atime and mtime can be skipped because cached value will be -// "close enough" to host value, given that operation went directly to host FD. -// Skipping atime updates is particularly important to reduce the number of -// operations sent to the Gofer for readonly files. -func (i *inodeFileState) skipSetAttr(mask fs.AttrMask, forceSetTimestamps bool) bool { - // First remove attributes that cannot be updated. - cpy := mask - cpy.Type = false - cpy.DeviceID = false - cpy.InodeID = false - cpy.BlockSize = false - cpy.Usage = false - cpy.Links = false - if cpy.Empty() { - return true - } - - // Then check if more than just atime and mtime is being set. - cpy.AccessTime = false - cpy.ModificationTime = false - if !cpy.Empty() { - return false - } - - // If forceSetTimestamps was passed, then we cannot skip. - if forceSetTimestamps { - return false - } - - // Skip if we have a host FD. - i.handlesMu.RLock() - defer i.handlesMu.RUnlock() - return (i.readHandles != nil && i.readHandles.Host != nil) || - (i.writeHandles != nil && i.writeHandles.Host != nil) -} - -// Sync implements fsutil.CachedFileObject.Sync. -func (i *inodeFileState) Sync(ctx context.Context) error { - i.handlesMu.RLock() - defer i.handlesMu.RUnlock() - if i.writeHandles == nil { - return nil - } - return i.writeHandles.File.fsync(ctx) -} - -// FD implements fsutil.CachedFileObject.FD. -func (i *inodeFileState) FD() int { - i.handlesMu.RLock() - defer i.handlesMu.RUnlock() - if i.writeHandlesRW && i.writeHandles != nil && i.writeHandles.Host != nil { - return int(i.writeHandles.Host.FD()) - } - if i.readHandles != nil && i.readHandles.Host != nil { - return int(i.readHandles.Host.FD()) - } - return -1 -} - -// waitForLoad makes sure any restore-issued loading is done. -func (i *inodeFileState) waitForLoad() { - // This is not a no-op. The loading mutex is hold upon restore until - // all loading actions are done. - i.loading.Lock() - i.loading.Unlock() -} - -func (i *inodeFileState) unstableAttr(ctx context.Context) (fs.UnstableAttr, error) { - _, valid, pattr, err := getattr(ctx, i.file) - if err != nil { - return fs.UnstableAttr{}, err - } - return unstable(ctx, valid, pattr, i.s.mounter, i.s.client), nil -} - -func (i *inodeFileState) Allocate(ctx context.Context, offset, length int64) error { - i.handlesMu.RLock() - defer i.handlesMu.RUnlock() - - // No options are supported for now. - mode := p9.AllocateMode{} - return i.writeHandles.File.allocate(ctx, mode, uint64(offset), uint64(length)) -} - -// session extracts the gofer's session from the MountSource. -func (i *inodeOperations) session() *session { - return i.fileState.s -} - -// Release implements fs.InodeOperations.Release. -func (i *inodeOperations) Release(ctx context.Context) { - i.cachingInodeOps.Release() - - // Releasing the fileState may make RPCs to the gofer. There is - // no need to wait for those to return, so we can do this - // asynchronously. - // - // We use AsyncWithContext to avoid needing to allocate an extra - // anonymous function on the heap. We must use background context - // because the async work cannot happen on the task context. - fs.AsyncWithContext(context.Background(), i.fileState.Release) -} - -// Mappable implements fs.InodeOperations.Mappable. -func (i *inodeOperations) Mappable(inode *fs.Inode) memmap.Mappable { - if i.session().cachePolicy.useCachingInodeOps(inode) { - return i.cachingInodeOps - } - // This check is necessary because it's returning an interface type. - if i.fileState.hostMappable != nil { - return i.fileState.hostMappable - } - return nil -} - -// UnstableAttr implements fs.InodeOperations.UnstableAttr. -func (i *inodeOperations) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - if i.session().cachePolicy.cacheUAttrs(inode) { - return i.cachingInodeOps.UnstableAttr(ctx, inode) - } - return i.fileState.unstableAttr(ctx) -} - -// Check implements fs.InodeOperations.Check. -func (i *inodeOperations) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { - return fs.ContextCanAccessFile(ctx, inode, p) -} - -// GetFile implements fs.InodeOperations.GetFile. -func (i *inodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - switch d.Inode.StableAttr.Type { - case fs.Socket: - if i.session().overrides != nil { - return nil, linuxerr.ENXIO - } - return i.getFileSocket(ctx, d, flags) - case fs.Pipe: - return i.getFilePipe(ctx, d, flags) - default: - return i.getFileDefault(ctx, d, flags) - } -} - -func (i *inodeOperations) getFileSocket(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - f, err := i.fileState.file.connect(ctx, p9.AnonymousSocket) - if err != nil { - return nil, unix.EIO - } - fsf, err := host.NewSocketWithDirent(ctx, d, f, flags) - if err != nil { - f.Close() - return nil, err - } - return fsf, nil -} - -func (i *inodeOperations) getFilePipe(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - // Try to open as a host pipe; if that doesn't work, handle it normally. - pipeOps, err := fdpipe.Open(ctx, i, flags) - if err == errNotHostFile { - return i.getFileDefault(ctx, d, flags) - } - if err != nil { - return nil, err - } - return fs.NewFile(ctx, d, flags, pipeOps), nil -} - -// errNotHostFile indicates that the file is not a host file. -var errNotHostFile = errors.New("not a host file") - -// NonBlockingOpen implements fdpipe.NonBlockingOpener for opening host named pipes. -func (i *inodeOperations) NonBlockingOpen(ctx context.Context, p fs.PermMask) (*fd.FD, error) { - i.fileState.waitForLoad() - - // Get a cloned fid which we will open. - _, newFile, err := i.fileState.file.walk(ctx, nil) - if err != nil { - log.Warningf("Open Walk failed: %v", err) - return nil, err - } - defer newFile.close(ctx) - - flags, err := openFlagsFromPerms(p) - if err != nil { - log.Warningf("Open flags %s parsing failed: %v", p, err) - return nil, err - } - hostFile, _, _, err := newFile.open(ctx, flags) - // If the host file returned is nil and the error is nil, - // then this was never a host file to begin with, and should - // be treated like a remote file. - if hostFile == nil && err == nil { - return nil, errNotHostFile - } - return hostFile, err -} - -func (i *inodeOperations) getFileDefault(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - h, err := i.fileState.getHandles(ctx, flags, i.cachingInodeOps) - if err != nil { - return nil, err - } - return NewFile(ctx, d, d.BaseName(), flags, i, h), nil -} - -// SetPermissions implements fs.InodeOperations.SetPermissions. -func (i *inodeOperations) SetPermissions(ctx context.Context, inode *fs.Inode, p fs.FilePermissions) bool { - if i.session().cachePolicy.cacheUAttrs(inode) { - return i.cachingInodeOps.SetPermissions(ctx, inode, p) - } - - mask := p9.SetAttrMask{Permissions: true} - pattr := p9.SetAttr{Permissions: p9.FileMode(p.LinuxMode())} - // Execute the chmod. - return i.fileState.file.setAttr(ctx, mask, pattr) == nil -} - -// SetOwner implements fs.InodeOperations.SetOwner. -func (i *inodeOperations) SetOwner(ctx context.Context, inode *fs.Inode, owner fs.FileOwner) error { - // Save the roundtrip. - if !owner.UID.Ok() && !owner.GID.Ok() { - return nil - } - - if i.session().cachePolicy.cacheUAttrs(inode) { - return i.cachingInodeOps.SetOwner(ctx, inode, owner) - } - - var mask p9.SetAttrMask - var attr p9.SetAttr - if owner.UID.Ok() { - mask.UID = true - attr.UID = p9.UID(owner.UID) - } - if owner.GID.Ok() { - mask.GID = true - attr.GID = p9.GID(owner.GID) - } - return i.fileState.file.setAttr(ctx, mask, attr) -} - -// SetTimestamps implements fs.InodeOperations.SetTimestamps. -func (i *inodeOperations) SetTimestamps(ctx context.Context, inode *fs.Inode, ts fs.TimeSpec) error { - if i.session().cachePolicy.cacheUAttrs(inode) { - return i.cachingInodeOps.SetTimestamps(ctx, inode, ts) - } - - return utimes(ctx, i.fileState.file, ts) -} - -// Truncate implements fs.InodeOperations.Truncate. -func (i *inodeOperations) Truncate(ctx context.Context, inode *fs.Inode, length int64) error { - // This can only be called for files anyway. - if i.session().cachePolicy.useCachingInodeOps(inode) { - return i.cachingInodeOps.Truncate(ctx, inode, length) - } - - uattr, err := i.fileState.unstableAttr(ctx) - if err != nil { - return err - } - - if i.session().cachePolicy == cacheRemoteRevalidating { - return i.fileState.hostMappable.Truncate(ctx, length, uattr) - } - - mask := p9.SetAttrMask{Size: true} - attr := p9.SetAttr{Size: uint64(length)} - if uattr.Perms.HasSetUIDOrGID() { - mask.Permissions = true - uattr.Perms.DropSetUIDAndMaybeGID() - attr.Permissions = p9.FileMode(uattr.Perms.LinuxMode()) - } - - return i.fileState.file.setAttr(ctx, mask, attr) -} - -// GetXattr implements fs.InodeOperations.GetXattr. -func (i *inodeOperations) GetXattr(ctx context.Context, _ *fs.Inode, name string, size uint64) (string, error) { - return i.fileState.file.getXattr(ctx, name, size) -} - -// SetXattr implements fs.InodeOperations.SetXattr. -func (i *inodeOperations) SetXattr(ctx context.Context, _ *fs.Inode, name string, value string, flags uint32) error { - return i.fileState.file.setXattr(ctx, name, value, flags) -} - -// ListXattr implements fs.InodeOperations.ListXattr. -func (i *inodeOperations) ListXattr(ctx context.Context, _ *fs.Inode, size uint64) (map[string]struct{}, error) { - return i.fileState.file.listXattr(ctx, size) -} - -// RemoveXattr implements fs.InodeOperations.RemoveXattr. -func (i *inodeOperations) RemoveXattr(ctx context.Context, _ *fs.Inode, name string) error { - return i.fileState.file.removeXattr(ctx, name) -} - -// Allocate implements fs.InodeOperations.Allocate. -func (i *inodeOperations) Allocate(ctx context.Context, inode *fs.Inode, offset, length int64) error { - // This can only be called for files anyway. - if i.session().cachePolicy.useCachingInodeOps(inode) { - return i.cachingInodeOps.Allocate(ctx, offset, length) - } - if i.session().cachePolicy == cacheRemoteRevalidating { - return i.fileState.hostMappable.Allocate(ctx, offset, length) - } - - // No options are supported for now. - mode := p9.AllocateMode{} - return i.fileState.file.allocate(ctx, mode, uint64(offset), uint64(length)) -} - -// WriteOut implements fs.InodeOperations.WriteOut. -func (i *inodeOperations) WriteOut(ctx context.Context, inode *fs.Inode) error { - if inode.MountSource.Flags.ReadOnly || !i.session().cachePolicy.cacheUAttrs(inode) { - return nil - } - - return i.cachingInodeOps.WriteOut(ctx, inode) -} - -// Readlink implements fs.InodeOperations.Readlink. -func (i *inodeOperations) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - if !fs.IsSymlink(inode.StableAttr) { - return "", unix.ENOLINK - } - return i.fileState.file.readlink(ctx) -} - -// Getlink implementfs fs.InodeOperations.Getlink. -func (i *inodeOperations) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) { - if !fs.IsSymlink(i.fileState.sattr) { - return nil, linuxerr.ENOLINK - } - return nil, fs.ErrResolveViaReadlink -} - -// StatFS makes a StatFS request. -func (i *inodeOperations) StatFS(ctx context.Context) (fs.Info, error) { - fsstat, err := i.fileState.file.statFS(ctx) - if err != nil { - return fs.Info{}, err - } - - info := fs.Info{ - // This is primarily for distinguishing a gofer file system in - // tests. Testing is important, so instead of defining - // something completely random, use a standard value. - Type: linux.V9FS_MAGIC, - TotalBlocks: fsstat.Blocks, - FreeBlocks: fsstat.BlocksFree, - TotalFiles: fsstat.Files, - FreeFiles: fsstat.FilesFree, - } - - // If blocks available is non-zero, prefer that. - if fsstat.BlocksAvailable != 0 { - info.FreeBlocks = fsstat.BlocksAvailable - } - - return info, nil -} - -func (i *inodeOperations) configureMMap(file *fs.File, opts *memmap.MMapOpts) error { - if i.session().cachePolicy.useCachingInodeOps(file.Dirent.Inode) { - return fsutil.GenericConfigureMMap(file, i.cachingInodeOps, opts) - } - if i.fileState.hostMappable != nil { - return fsutil.GenericConfigureMMap(file, i.fileState.hostMappable, opts) - } - return linuxerr.ENODEV -} - -func init() { - linuxerr.AddErrorUnwrapper(func(err error) (*gErr.Error, bool) { - if _, ok := err.(p9.ErrSocket); ok { - // Treat as an I/O error. - return linuxerr.EIO, true - } - return nil, false - }) -} - -// AddLink implements InodeOperations.AddLink, but is currently a noop. -func (*inodeOperations) AddLink() {} - -// DropLink implements InodeOperations.DropLink, but is currently a noop. -func (*inodeOperations) DropLink() {} - -// NotifyStatusChange implements fs.InodeOperations.NotifyStatusChange. -func (i *inodeOperations) NotifyStatusChange(ctx context.Context) {} diff --git a/pkg/sentry/fs/gofer/inode_state.go b/pkg/sentry/fs/gofer/inode_state.go deleted file mode 100644 index bb7abc741..000000000 --- a/pkg/sentry/fs/gofer/inode_state.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "errors" - "fmt" - "path/filepath" - "strings" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel/time" -) - -// Some fs implementations may not support atime, ctime, or mtime in getattr. -// The unstable() logic would try to use clock time for them. However, we do not -// want to use such time during S/R as that would cause restore timestamp -// checking failure. Hence a dummy stable-time clock is needed. -// -// Note that application-visible UnstableAttrs either come from CachingInodeOps -// (in which case they are saved), or they are requested from the gofer on each -// stat (for non-caching), so the dummy time only affects the modification -// timestamp check. -type dummyClock struct { - time.Clock -} - -// Now returns a stable dummy time. -func (d *dummyClock) Now() time.Time { - return time.Time{} -} - -type dummyClockContext struct { - context.Context -} - -// Value implements context.Context -func (d *dummyClockContext) Value(key any) any { - switch key { - case time.CtxRealtimeClock: - return &dummyClock{} - default: - return d.Context.Value(key) - } -} - -// beforeSave is invoked by stateify. -func (i *inodeFileState) beforeSave() { - if _, ok := i.s.inodeMappings[i.sattr.InodeID]; !ok { - panic(fmt.Sprintf("failed to find path for inode number %d. Device %s contains %s", i.sattr.InodeID, i.s.connID, fs.InodeMappings(i.s.inodeMappings))) - } - if i.sattr.Type == fs.RegularFile { - uattr, err := i.unstableAttr(&dummyClockContext{context.Background()}) - if err != nil { - panic(&fs.ErrSaveRejection{ - Err: fmt.Errorf("failed to get unstable atttribute of %s: %w", i.s.inodeMappings[i.sattr.InodeID], err), - }) - } - i.savedUAttr = &uattr - } -} - -// saveLoading is invoked by stateify. -func (i *inodeFileState) saveLoading() struct{} { - return struct{}{} -} - -// splitAbsolutePath splits the path on slashes ignoring the leading slash. -func splitAbsolutePath(path string) []string { - if len(path) == 0 { - panic("There is no path!") - } - if path != filepath.Clean(path) { - panic(fmt.Sprintf("path %q is not clean", path)) - } - // This case is to return {} rather than {""} - if path == "/" { - return []string{} - } - if path[0] != '/' { - panic(fmt.Sprintf("path %q is not absolute", path)) - } - - s := strings.Split(path, "/") - - // Since p is absolute, the first component of s - // is an empty string. We must remove that. - return s[1:] -} - -// loadLoading is invoked by stateify. -func (i *inodeFileState) loadLoading(_ struct{}) { - i.loading.Lock() -} - -// afterLoad is invoked by stateify. -// +checklocks:i.loading -func (i *inodeFileState) afterLoad() { - load := func() (err error) { - // Manually restore the p9.File. - name, ok := i.s.inodeMappings[i.sattr.InodeID] - if !ok { - // This should be impossible, see assertion in - // beforeSave. - return fmt.Errorf("failed to find path for inode number %d. Device %s contains %s", i.sattr.InodeID, i.s.connID, fs.InodeMappings(i.s.inodeMappings)) - } - ctx := &dummyClockContext{context.Background()} - - _, i.file, err = i.s.attach.walk(ctx, splitAbsolutePath(name)) - if err != nil { - return fs.ErrCorruption{fmt.Errorf("failed to walk to %q: %v", name, err)} - } - - // Remap the saved inode number into the gofer device using the - // actual device and actual inode that exists in our new - // environment. - qid, mask, attrs, err := i.file.getAttr(ctx, p9.AttrMaskAll()) - if err != nil { - return fs.ErrCorruption{fmt.Errorf("failed to get file attributes of %s: %v", name, err)} - } - if !mask.RDev { - return fs.ErrCorruption{fmt.Errorf("file %s lacks device", name)} - } - i.key = device.MultiDeviceKey{ - Device: attrs.RDev, - SecondaryDevice: i.s.connID, - Inode: qid.Path, - } - if !goferDevice.Load(i.key, i.sattr.InodeID) { - return fs.ErrCorruption{fmt.Errorf("gofer device %s -> %d conflict in gofer device mappings: %s", i.key, i.sattr.InodeID, goferDevice)} - } - - if i.sattr.Type == fs.RegularFile { - env, ok := fs.CurrentRestoreEnvironment() - if !ok { - return errors.New("missing restore environment") - } - uattr := unstable(ctx, mask, attrs, i.s.mounter, i.s.client) - if env.ValidateFileSize && uattr.Size != i.savedUAttr.Size { - return fs.ErrCorruption{fmt.Errorf("file size has changed for %s: previously %d, now %d", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.Size, uattr.Size)} - } - if env.ValidateFileTimestamp && uattr.ModificationTime != i.savedUAttr.ModificationTime { - return fs.ErrCorruption{fmt.Errorf("file modification time has changed for %s: previously %v, now %v", i.s.inodeMappings[i.sattr.InodeID], i.savedUAttr.ModificationTime, uattr.ModificationTime)} - } - i.savedUAttr = nil - } - - // See comment on i.loading(). This only unlocks on the - // non-error path. - i.loading.Unlock() // +checklocksforce: per comment. - return nil - } - - fs.Async(fs.CatchError(load)) -} diff --git a/pkg/sentry/fs/gofer/path.go b/pkg/sentry/fs/gofer/path.go deleted file mode 100644 index 2f8769f1e..000000000 --- a/pkg/sentry/fs/gofer/path.go +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" -) - -// maxFilenameLen is the maximum length of a filename. This is dictated by 9P's -// encoding of strings, which uses 2 bytes for the length prefix. -const maxFilenameLen = (1 << 16) - 1 - -func changeType(mode p9.FileMode, newType p9.FileMode) p9.FileMode { - if newType&^p9.FileModeMask != 0 { - panic(fmt.Sprintf("newType contained more bits than just file mode: %x", newType)) - } - clear := mode &^ p9.FileModeMask - return clear | newType -} - -// Lookup loads an Inode at name into a Dirent based on the session's cache -// policy. -func (i *inodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string) (*fs.Dirent, error) { - if len(name) > maxFilenameLen { - return nil, linuxerr.ENAMETOOLONG - } - - s := i.session() - cp := s.cachePolicy - if cp.cacheReaddir() { - // Check to see if we have readdirCache that indicates the - // child does not exist. Avoid holding readdirMu longer than - // we need to. - i.readdirMu.Lock() - if i.readdirCache != nil && !i.readdirCache.Contains(name) { - // No such child. - i.readdirMu.Unlock() - if cp.cacheNegativeDirents() { - return fs.NewNegativeDirent(name), nil - } - return nil, linuxerr.ENOENT - } - i.readdirMu.Unlock() - } - - // Get a p9.File for name. - qids, newFile, mask, p9attr, err := i.fileState.file.walkGetAttr(ctx, []string{name}) - if err != nil { - if linuxerr.Equals(linuxerr.ENOENT, err) { - if cp.cacheNegativeDirents() { - // Return a negative Dirent. It will stay cached until something - // is created over it. - return fs.NewNegativeDirent(name), nil - } - return nil, linuxerr.ENOENT - } - return nil, err - } - - if s.overrides != nil { - // Check if file belongs to a internal named pipe. Note that it doesn't need - // to check for sockets because it's done in newInodeOperations below. - deviceKey := device.MultiDeviceKey{ - Device: p9attr.RDev, - SecondaryDevice: i.session().connID, - Inode: qids[0].Path, - } - s.overrides.lock() - if pipeInode := s.overrides.getPipe(deviceKey); pipeInode != nil { - s.overrides.unlock() - pipeInode.IncRef() - return fs.NewDirent(ctx, pipeInode, name), nil - } - s.overrides.unlock() - } - - // Construct the Inode operations. - sattr, node := newInodeOperations(ctx, i.fileState.s, newFile, qids[0], mask, p9attr) - - // Construct a positive Dirent. - return fs.NewDirent(ctx, fs.NewInode(ctx, node, dir.MountSource, sattr), name), nil -} - -// Creates a new Inode at name and returns its File based on the session's cache policy. -// -// Ownership is currently ignored. -func (i *inodeOperations) Create(ctx context.Context, dir *fs.Inode, name string, flags fs.FileFlags, perm fs.FilePermissions) (*fs.File, error) { - if len(name) > maxFilenameLen { - return nil, linuxerr.ENAMETOOLONG - } - - // Create replaces the directory fid with the newly created/opened - // file, so clone this directory so it doesn't change out from under - // this node. - _, newFile, err := i.fileState.file.walk(ctx, nil) - if err != nil { - return nil, err - } - - // Map the FileFlags to p9 OpenFlags. - var openFlags p9.OpenFlags - switch { - case flags.Read && flags.Write: - openFlags = p9.ReadWrite - case flags.Read: - openFlags = p9.ReadOnly - case flags.Write: - openFlags = p9.WriteOnly - default: - panic(fmt.Sprintf("Create called with unknown or unset open flags: %v", flags)) - } - - // If the parent directory has setgid enabled, change the new file's owner. - owner := fs.FileOwnerFromContext(ctx) - parentUattr, err := dir.UnstableAttr(ctx) - if err != nil { - return nil, err - } - if parentUattr.Perms.SetGID { - owner.GID = parentUattr.Owner.GID - } - - hostFile, err := newFile.create(ctx, name, openFlags, p9.FileMode(perm.LinuxMode()), p9.UID(owner.UID), p9.GID(owner.GID)) - if err != nil { - // Could not create the file. - newFile.close(ctx) - return nil, err - } - - i.touchModificationAndStatusChangeTime(ctx, dir) - - // Get an unopened p9.File for the file we created so that it can be cloned - // and re-opened multiple times after creation, while also getting its - // attributes. Both are required for inodeOperations. - qids, unopened, mask, p9attr, err := i.fileState.file.walkGetAttr(ctx, []string{name}) - if err != nil { - newFile.close(ctx) - if hostFile != nil { - hostFile.Close() - } - return nil, err - } - if len(qids) != 1 { - log.Warningf("WalkGetAttr(%s) succeeded, but returned %d QIDs (%v), wanted 1", name, len(qids), qids) - newFile.close(ctx) - if hostFile != nil { - hostFile.Close() - } - unopened.close(ctx) - return nil, linuxerr.EIO - } - qid := qids[0] - - // Construct the InodeOperations. - sattr, iops := newInodeOperations(ctx, i.fileState.s, unopened, qid, mask, p9attr) - - // Construct the positive Dirent. - d := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name) - defer d.DecRef(ctx) - - // Construct the new file, caching the handles if allowed. - h := handles{ - File: newFile, - Host: hostFile, - } - h.EnableLeakCheck("gofer.handles") - if iops.fileState.canShareHandles() { - iops.fileState.handlesMu.Lock() - iops.fileState.setSharedHandlesLocked(flags, &h) - iops.fileState.handlesMu.Unlock() - } - return NewFile(ctx, d, name, flags, iops, &h), nil -} - -// CreateLink uses Create to create a symlink between oldname and newname. -func (i *inodeOperations) CreateLink(ctx context.Context, dir *fs.Inode, oldname string, newname string) error { - if len(newname) > maxFilenameLen { - return linuxerr.ENAMETOOLONG - } - - owner := fs.FileOwnerFromContext(ctx) - if _, err := i.fileState.file.symlink(ctx, oldname, newname, p9.UID(owner.UID), p9.GID(owner.GID)); err != nil { - return err - } - i.touchModificationAndStatusChangeTime(ctx, dir) - return nil -} - -// CreateHardLink implements InodeOperations.CreateHardLink. -func (i *inodeOperations) CreateHardLink(ctx context.Context, inode *fs.Inode, target *fs.Inode, newName string) error { - if len(newName) > maxFilenameLen { - return linuxerr.ENAMETOOLONG - } - - targetOpts, ok := target.InodeOperations.(*inodeOperations) - if !ok { - return linuxerr.EXDEV - } - - if err := i.fileState.file.link(ctx, &targetOpts.fileState.file, newName); err != nil { - return err - } - - s := i.session() - if s.cachePolicy.cacheUAttrs(inode) { - // Increase link count. - targetOpts.cachingInodeOps.IncLinks(ctx) - } - - i.touchModificationAndStatusChangeTime(ctx, inode) - return nil -} - -// CreateDirectory uses Create to create a directory named s under inodeOperations. -func (i *inodeOperations) CreateDirectory(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions) error { - if len(name) > maxFilenameLen { - return linuxerr.ENAMETOOLONG - } - - // If the parent directory has setgid enabled, change the new directory's - // owner and enable setgid. - owner := fs.FileOwnerFromContext(ctx) - parentUattr, err := dir.UnstableAttr(ctx) - if err != nil { - return err - } - if parentUattr.Perms.SetGID { - owner.GID = parentUattr.Owner.GID - perm.SetGID = true - } - - if _, err := i.fileState.file.mkdir(ctx, name, p9.FileMode(perm.LinuxMode()), p9.UID(owner.UID), p9.GID(owner.GID)); err != nil { - return err - } - - s := i.session() - if s.cachePolicy.cacheUAttrs(dir) { - // Increase link count. - // - // N.B. This will update the modification time. - i.cachingInodeOps.IncLinks(ctx) - } - if s.cachePolicy.cacheReaddir() { - // Invalidate readdir cache. - i.markDirectoryDirty() - } - return nil -} - -// Bind implements InodeOperations.Bind. -func (i *inodeOperations) Bind(ctx context.Context, dir *fs.Inode, name string, ep transport.BoundEndpoint, perm fs.FilePermissions) (*fs.Dirent, error) { - if len(name) > maxFilenameLen { - return nil, linuxerr.ENAMETOOLONG - } - - s := i.session() - if s.overrides == nil { - return nil, linuxerr.EOPNOTSUPP - } - - // Stabilize the override map while creation is in progress. - s.overrides.lock() - defer s.overrides.unlock() - - sattr, iops, err := i.createEndpointFile(ctx, dir, name, perm, p9.ModeSocket) - if err != nil { - return nil, err - } - - // Construct the positive Dirent. - childDir := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name) - s.overrides.addBoundEndpoint(iops.fileState.key, childDir, ep) - return childDir, nil -} - -// CreateFifo implements fs.InodeOperations.CreateFifo. -func (i *inodeOperations) CreateFifo(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions) error { - if len(name) > maxFilenameLen { - return linuxerr.ENAMETOOLONG - } - - owner := fs.FileOwnerFromContext(ctx) - mode := p9.FileMode(perm.LinuxMode()) | p9.ModeNamedPipe - - // N.B. FIFOs use major/minor numbers 0. - s := i.session() - if _, err := i.fileState.file.mknod(ctx, name, mode, 0, 0, p9.UID(owner.UID), p9.GID(owner.GID)); err != nil { - if s.overrides == nil || !linuxerr.Equals(linuxerr.EPERM, err) { - return err - } - // If gofer doesn't support mknod, check if we can create an internal fifo. - return i.createInternalFifo(ctx, dir, name, owner, perm) - } - - i.touchModificationAndStatusChangeTime(ctx, dir) - return nil -} - -func (i *inodeOperations) createInternalFifo(ctx context.Context, dir *fs.Inode, name string, owner fs.FileOwner, perm fs.FilePermissions) error { - s := i.session() - if s.overrides == nil { - return linuxerr.EPERM - } - - // Stabilize the override map while creation is in progress. - s.overrides.lock() - defer s.overrides.unlock() - - sattr, fileOps, err := i.createEndpointFile(ctx, dir, name, perm, p9.ModeNamedPipe) - if err != nil { - return err - } - - // First create a pipe. - p := pipe.NewPipe(true /* isNamed */, pipe.DefaultPipeSize) - - // Wrap the fileOps with our Fifo. - iops := &fifo{ - InodeOperations: pipe.NewInodeOperations(ctx, perm, p), - fileIops: fileOps, - } - inode := fs.NewInode(ctx, iops, dir.MountSource, sattr) - - // Construct the positive Dirent. - childDir := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name) - s.overrides.addPipe(fileOps.fileState.key, childDir, inode) - return nil -} - -// Caller must hold Session.endpoint lock. -func (i *inodeOperations) createEndpointFile(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions, fileType p9.FileMode) (fs.StableAttr, *inodeOperations, error) { - _, dirClone, err := i.fileState.file.walk(ctx, nil) - if err != nil { - return fs.StableAttr{}, nil, err - } - // We're not going to use dirClone after return. - defer dirClone.close(ctx) - - // Create a regular file in the gofer and then mark it as a socket by - // adding this inode key in the 'overrides' map. - owner := fs.FileOwnerFromContext(ctx) - hostFile, err := dirClone.create(ctx, name, p9.ReadWrite, p9.FileMode(perm.LinuxMode()), p9.UID(owner.UID), p9.GID(owner.GID)) - if err != nil { - return fs.StableAttr{}, nil, err - } - // We're not going to use this file. - hostFile.Close() - - i.touchModificationAndStatusChangeTime(ctx, dir) - - // Get the attributes of the file to create inode key. - qid, mask, attr, err := getattr(ctx, dirClone) - if err != nil { - return fs.StableAttr{}, nil, err - } - - // Get an unopened p9.File for the file we created so that it can be - // cloned and re-opened multiple times after creation. - _, unopened, err := i.fileState.file.walk(ctx, []string{name}) - if err != nil { - return fs.StableAttr{}, nil, err - } - - // Construct new inode with file type overridden. - attr.Mode = changeType(attr.Mode, fileType) - sattr, iops := newInodeOperations(ctx, i.fileState.s, unopened, qid, mask, attr) - return sattr, iops, nil -} - -// Remove implements InodeOperations.Remove. -func (i *inodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string) error { - if len(name) > maxFilenameLen { - return linuxerr.ENAMETOOLONG - } - - s := i.session() - var key *device.MultiDeviceKey - if s.overrides != nil { - // Find out if file being deleted is a socket or pipe that needs to be - // removed from endpoint map. - if d, err := i.Lookup(ctx, dir, name); err == nil { - defer d.DecRef(ctx) - - if fs.IsSocket(d.Inode.StableAttr) || fs.IsPipe(d.Inode.StableAttr) { - switch iops := d.Inode.InodeOperations.(type) { - case *inodeOperations: - key = &iops.fileState.key - case *fifo: - key = &iops.fileIops.fileState.key - } - - // Stabilize the override map while deletion is in progress. - s.overrides.lock() - defer s.overrides.unlock() - } - } - } - - if err := i.fileState.file.unlinkAt(ctx, name, 0); err != nil { - return err - } - if key != nil { - s.overrides.remove(ctx, *key) - } - i.touchModificationAndStatusChangeTime(ctx, dir) - - return nil -} - -// Remove implements InodeOperations.RemoveDirectory. -func (i *inodeOperations) RemoveDirectory(ctx context.Context, dir *fs.Inode, name string) error { - if len(name) > maxFilenameLen { - return linuxerr.ENAMETOOLONG - } - - // 0x200 = AT_REMOVEDIR. - if err := i.fileState.file.unlinkAt(ctx, name, 0x200); err != nil { - return err - } - - s := i.session() - if s.cachePolicy.cacheUAttrs(dir) { - // Decrease link count and updates atime. - i.cachingInodeOps.DecLinks(ctx) - } - if s.cachePolicy.cacheReaddir() { - // Invalidate readdir cache. - i.markDirectoryDirty() - } - return nil -} - -// Rename renames this node. -func (i *inodeOperations) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - if len(newName) > maxFilenameLen { - return linuxerr.ENAMETOOLONG - } - - // Don't allow renames across different mounts. - if newParent.MountSource != oldParent.MountSource { - return linuxerr.EXDEV - } - - // Unwrap the new parent to a *inodeOperations. - newParentInodeOperations := newParent.InodeOperations.(*inodeOperations) - - // Unwrap the old parent to a *inodeOperations. - oldParentInodeOperations := oldParent.InodeOperations.(*inodeOperations) - - // Do the rename. - if err := i.fileState.file.rename(ctx, newParentInodeOperations.fileState.file, newName); err != nil { - return err - } - - // Is the renamed entity a directory? Fix link counts. - s := i.session() - if fs.IsDir(i.fileState.sattr) { - // Update cached state. - if s.cachePolicy.cacheUAttrs(oldParent) { - oldParentInodeOperations.cachingInodeOps.DecLinks(ctx) - } - if s.cachePolicy.cacheUAttrs(newParent) { - // Only IncLinks if there is a new addition to - // newParent. If this is replacement, then the total - // count remains the same. - if !replacement { - newParentInodeOperations.cachingInodeOps.IncLinks(ctx) - } - } - } - if s.cachePolicy.cacheReaddir() { - // Mark old directory dirty. - oldParentInodeOperations.markDirectoryDirty() - if oldParent != newParent { - // Mark new directory dirty. - newParentInodeOperations.markDirectoryDirty() - } - } - - // Rename always updates ctime. - if s.cachePolicy.cacheUAttrs(inode) { - i.cachingInodeOps.TouchStatusChangeTime(ctx) - } - return nil -} - -func (i *inodeOperations) touchModificationAndStatusChangeTime(ctx context.Context, inode *fs.Inode) { - s := i.session() - if s.cachePolicy.cacheUAttrs(inode) { - i.cachingInodeOps.TouchModificationAndStatusChangeTime(ctx) - } - if s.cachePolicy.cacheReaddir() { - // Invalidate readdir cache. - i.markDirectoryDirty() - } -} - -// markDirectoryDirty marks any cached data dirty for this directory. This is necessary in order -// to ensure that this node does not retain stale state throughout its lifetime across multiple -// open directory handles. -// -// Currently this means invalidating any readdir caches. -func (i *inodeOperations) markDirectoryDirty() { - i.readdirMu.Lock() - defer i.readdirMu.Unlock() - i.readdirCache = nil -} diff --git a/pkg/sentry/fs/gofer/session.go b/pkg/sentry/fs/gofer/session.go deleted file mode 100644 index 89db29dab..000000000 --- a/pkg/sentry/fs/gofer/session.go +++ /dev/null @@ -1,431 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/unet" -) - -// DefaultDirentCacheSize is the default dirent cache size for 9P mounts. It can -// be adjusted independently from the other dirent caches. -var DefaultDirentCacheSize uint64 = fs.DefaultDirentCacheSize - -// +stateify savable -type overrideInfo struct { - dirent *fs.Dirent - - // endpoint is set when dirent points to a socket. inode must not be set. - endpoint transport.BoundEndpoint - - // inode is set when dirent points to a pipe. endpoint must not be set. - inode *fs.Inode -} - -func (l *overrideInfo) inodeType() fs.InodeType { - switch { - case l.endpoint != nil: - return fs.Socket - case l.inode != nil: - return fs.Pipe - } - panic("endpoint or node must be set") -} - -// +stateify savable -type overrideMaps struct { - // mu protexts the keyMap, and the pathMap below. - mu sync.RWMutex `state:"nosave"` - - // keyMap links MultiDeviceKeys (containing inode IDs) to their sockets/pipes. - // It is not stored during save because the inode ID may change upon restore. - keyMap map[device.MultiDeviceKey]*overrideInfo `state:"nosave"` - - // pathMap links the sockets/pipes to their paths. - // It is filled before saving from the direntMap and is stored upon save. - // Upon restore, this map is used to re-populate the keyMap. - pathMap map[*overrideInfo]string -} - -// addBoundEndpoint adds the bound endpoint to the map. -// A reference is taken on the dirent argument. -// -// Precondition: maps must have been locked with 'lock'. -func (e *overrideMaps) addBoundEndpoint(key device.MultiDeviceKey, d *fs.Dirent, ep transport.BoundEndpoint) { - d.IncRef() - e.keyMap[key] = &overrideInfo{dirent: d, endpoint: ep} -} - -// addPipe adds the pipe inode to the map. -// A reference is taken on the dirent argument. -// -// Precondition: maps must have been locked with 'lock'. -func (e *overrideMaps) addPipe(key device.MultiDeviceKey, d *fs.Dirent, inode *fs.Inode) { - d.IncRef() - e.keyMap[key] = &overrideInfo{dirent: d, inode: inode} -} - -// remove deletes the key from the maps. -// -// Precondition: maps must have been locked with 'lock'. -func (e *overrideMaps) remove(ctx context.Context, key device.MultiDeviceKey) { - endpoint := e.keyMap[key] - delete(e.keyMap, key) - endpoint.dirent.DecRef(ctx) -} - -// lock blocks other addition and removal operations from happening while -// the backing file is being created or deleted. Returns a function that unlocks -// the endpoint map. -// +checklocksacquire:e.mu -func (e *overrideMaps) lock() { - e.mu.Lock() -} - -// +checklocksrelease:e.mu -func (e *overrideMaps) unlock() { - e.mu.Unlock() -} - -// getBoundEndpoint returns the bound endpoint mapped to the given key. -// -// Precondition: maps must have been locked. -func (e *overrideMaps) getBoundEndpoint(key device.MultiDeviceKey) transport.BoundEndpoint { - if v := e.keyMap[key]; v != nil { - return v.endpoint - } - return nil -} - -// getPipe returns the pipe inode mapped to the given key. -// -// Precondition: maps must have been locked. -func (e *overrideMaps) getPipe(key device.MultiDeviceKey) *fs.Inode { - if v := e.keyMap[key]; v != nil { - return v.inode - } - return nil -} - -// getType returns the inode type if there is a corresponding endpoint for the -// given key. Returns false otherwise. -func (e *overrideMaps) getType(key device.MultiDeviceKey) (fs.InodeType, bool) { - e.mu.Lock() - v := e.keyMap[key] - e.mu.Unlock() - - if v != nil { - return v.inodeType(), true - } - return 0, false -} - -// session holds state for each 9p session established during sys_mount. -// -// +stateify savable -type session struct { - refs.AtomicRefCount - - // msize is the value of the msize mount option, see fs/gofer/fs.go. - msize uint32 `state:"wait"` - - // version is the value of the version mount option, see fs/gofer/fs.go. - version string `state:"wait"` - - // cachePolicy is the cache policy. - cachePolicy cachePolicy `state:"wait"` - - // aname is the value of the aname mount option, see fs/gofer/fs.go. - aname string `state:"wait"` - - // The client associated with this session. This will be initialized lazily. - client *p9.Client `state:"nosave"` - - // The p9.File pointing to attachName via the client. This will be initialized - // lazily. - attach contextFile `state:"nosave"` - - // Flags provided to the mount. - superBlockFlags fs.MountSourceFlags `state:"wait"` - - // limitHostFDTranslation is the value used for - // CachingInodeOperationsOptions.LimitHostFDTranslation for all - // CachingInodeOperations created by the session. - limitHostFDTranslation bool - - // overlayfsStaleRead when set causes the readonly handle to be invalidated - // after file is open for write. - overlayfsStaleRead bool - - // connID is a unique identifier for the session connection. - connID string `state:"wait"` - - // inodeMappings contains mappings of fs.Inodes associated with this session - // to paths relative to the attach point, where inodeMappings is keyed by - // Inode.StableAttr.InodeID. - inodeMappings map[uint64]string `state:"wait"` - - // mounter is the EUID/EGID that mounted this file system. - mounter fs.FileOwner `state:"wait"` - - // overrides is used to map inodes that represent socket/pipes files to their - // corresponding endpoint/iops. These files are created as regular files in - // the gofer and their presence in this map indicate that they should indeed - // be socket/pipe files. This allows unix domain sockets and named pipes to - // be used with paths that belong to a gofer. - // - // There are a few possible races with someone stat'ing the file and another - // deleting it concurrently, where the file will not be reported as socket - // file. - overrides *overrideMaps `state:"wait"` -} - -// Destroy tears down the session. -func (s *session) Destroy(ctx context.Context) { - s.client.Close() -} - -// Revalidate implements MountSourceOperations.Revalidate. -func (s *session) Revalidate(ctx context.Context, name string, parent, child *fs.Inode) bool { - return s.cachePolicy.revalidate(ctx, name, parent, child) -} - -// Keep implements MountSourceOperations.Keep. -func (s *session) Keep(d *fs.Dirent) bool { - return s.cachePolicy.keep(d) -} - -// CacheReaddir implements MountSourceOperations.CacheReaddir. -func (s *session) CacheReaddir() bool { - return s.cachePolicy.cacheReaddir() -} - -// ResetInodeMappings implements fs.MountSourceOperations.ResetInodeMappings. -func (s *session) ResetInodeMappings() { - s.inodeMappings = make(map[uint64]string) -} - -// SaveInodeMapping implements fs.MountSourceOperations.SaveInodeMapping. -func (s *session) SaveInodeMapping(inode *fs.Inode, path string) { - // This is very unintuitive. We *CANNOT* trust the inode's StableAttrs, - // because overlay copyUp may have changed them out from under us. - // So much for "immutable". - switch iops := inode.InodeOperations.(type) { - case *inodeOperations: - s.inodeMappings[iops.fileState.sattr.InodeID] = path - case *fifo: - s.inodeMappings[iops.fileIops.fileState.sattr.InodeID] = path - default: - panic(fmt.Sprintf("Invalid type: %T", iops)) - } -} - -// newInodeOperations creates a new 9p fs.InodeOperations backed by a p9.File -// and attributes (p9.QID, p9.AttrMask, p9.Attr). -// -// Endpoints lock must not be held if socket == false. -func newInodeOperations(ctx context.Context, s *session, file contextFile, qid p9.QID, valid p9.AttrMask, attr p9.Attr) (fs.StableAttr, *inodeOperations) { - deviceKey := device.MultiDeviceKey{ - Device: attr.RDev, - SecondaryDevice: s.connID, - Inode: qid.Path, - } - - sattr := fs.StableAttr{ - Type: ntype(attr), - DeviceID: goferDevice.DeviceID(), - InodeID: goferDevice.Map(deviceKey), - BlockSize: bsize(attr), - } - - if s.overrides != nil && sattr.Type == fs.RegularFile { - // If overrides are allowed on this filesystem, check if this file is - // supposed to be of a different type, e.g. socket. - if t, ok := s.overrides.getType(deviceKey); ok { - sattr.Type = t - } - } - - fileState := &inodeFileState{ - s: s, - file: file, - sattr: sattr, - key: deviceKey, - } - if s.cachePolicy == cacheRemoteRevalidating && fs.IsFile(sattr) { - fileState.hostMappable = fsutil.NewHostMappable(fileState) - } - - uattr := unstable(ctx, valid, attr, s.mounter, s.client) - return sattr, &inodeOperations{ - fileState: fileState, - cachingInodeOps: fsutil.NewCachingInodeOperations(ctx, fileState, uattr, fsutil.CachingInodeOperationsOptions{ - ForcePageCache: s.superBlockFlags.ForcePageCache, - LimitHostFDTranslation: s.limitHostFDTranslation, - }), - } -} - -// Root returns the root of a 9p mount. This mount is bound to a 9p server -// based on conn. Otherwise configuration parameters are: -// -// - dev: connection id -// - filesystem: the filesystem backing the mount -// - superBlockFlags: the mount flags describing general mount options -// - opts: parsed 9p mount options -func Root(ctx context.Context, dev string, filesystem fs.Filesystem, superBlockFlags fs.MountSourceFlags, o opts) (*fs.Inode, error) { - // The mounting EUID/EGID will be cached by this file system. This will - // be used to assign ownership to files that the Gofer owns. - mounter := fs.FileOwnerFromContext(ctx) - - conn, err := unet.NewSocket(o.fd) - if err != nil { - return nil, err - } - - // Construct the session. - s := session{ - connID: dev, - msize: o.msize, - version: o.version, - cachePolicy: o.policy, - aname: o.aname, - superBlockFlags: superBlockFlags, - limitHostFDTranslation: o.limitHostFDTranslation, - overlayfsStaleRead: o.overlayfsStaleRead, - mounter: mounter, - } - s.EnableLeakCheck("gofer.session") - - if o.privateunixsocket { - s.overrides = newOverrideMaps() - } - - // Construct the MountSource with the session and superBlockFlags. - m := fs.NewMountSource(ctx, &s, filesystem, superBlockFlags) - - // Given that gofer files can consume host FDs, restrict the number - // of files that can be held by the cache. - m.SetDirentCacheMaxSize(DefaultDirentCacheSize) - m.SetDirentCacheLimiter(fs.DirentCacheLimiterFromContext(ctx)) - - // Send the Tversion request. - s.client, err = p9.NewClient(conn, s.msize, s.version) - if err != nil { - // Drop our reference on the session, it needs to be torn down. - s.DecRef(ctx) - return nil, err - } - - // Notify that we're about to call the Gofer and block. - ctx.UninterruptibleSleepStart(false) - // Send the Tattach request. - s.attach.file, err = s.client.Attach(s.aname) - ctx.UninterruptibleSleepFinish(false) - if err != nil { - // Same as above. - s.DecRef(ctx) - return nil, err - } - - qid, valid, attr, err := s.attach.getAttr(ctx, p9.AttrMaskAll()) - if err != nil { - s.attach.close(ctx) - // Same as above, but after we execute the Close request. - s.DecRef(ctx) - return nil, err - } - - sattr, iops := newInodeOperations(ctx, &s, s.attach, qid, valid, attr) - return fs.NewInode(ctx, iops, m, sattr), nil -} - -// newOverrideMaps creates a new overrideMaps. -func newOverrideMaps() *overrideMaps { - return &overrideMaps{ - keyMap: make(map[device.MultiDeviceKey]*overrideInfo), - pathMap: make(map[*overrideInfo]string), - } -} - -// fillKeyMap populates key and dirent maps upon restore from saved pathmap. -func (s *session) fillKeyMap(ctx context.Context) error { - s.overrides.lock() - defer s.overrides.unlock() - - for ep, dirPath := range s.overrides.pathMap { - _, file, err := s.attach.walk(ctx, splitAbsolutePath(dirPath)) - if err != nil { - return fmt.Errorf("error filling endpointmaps, failed to walk to %q: %v", dirPath, err) - } - - qid, _, attr, err := file.getAttr(ctx, p9.AttrMaskAll()) - if err != nil { - return fmt.Errorf("failed to get file attributes of %s: %v", dirPath, err) - } - - key := device.MultiDeviceKey{ - Device: attr.RDev, - SecondaryDevice: s.connID, - Inode: qid.Path, - } - - s.overrides.keyMap[key] = ep - } - return nil -} - -// fillPathMap populates paths for overrides from dirents in direntMap -// before save. -func (s *session) fillPathMap(ctx context.Context) error { - s.overrides.lock() - defer s.overrides.unlock() - - for _, endpoint := range s.overrides.keyMap { - mountRoot := endpoint.dirent.MountRoot() - defer mountRoot.DecRef(ctx) - dirPath, _ := endpoint.dirent.FullName(mountRoot) - if dirPath == "" { - return fmt.Errorf("error getting path from dirent") - } - s.overrides.pathMap[endpoint] = dirPath - } - return nil -} - -// restoreEndpointMaps recreates and fills the key and dirent maps. -func (s *session) restoreEndpointMaps(ctx context.Context) error { - // When restoring, only need to create the keyMap because the dirent and path - // maps got stored through the save. - s.overrides.keyMap = make(map[device.MultiDeviceKey]*overrideInfo) - if err := s.fillKeyMap(ctx); err != nil { - return fmt.Errorf("failed to insert sockets into endpoint map: %v", err) - } - - // Re-create pathMap because it can no longer be trusted as socket paths can - // change while process continues to run. Empty pathMap will be re-filled upon - // next save. - s.overrides.pathMap = make(map[*overrideInfo]string) - return nil -} diff --git a/pkg/sentry/fs/gofer/session_state.go b/pkg/sentry/fs/gofer/session_state.go deleted file mode 100644 index 48b423dd8..000000000 --- a/pkg/sentry/fs/gofer/session_state.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/unet" -) - -// beforeSave is invoked by stateify. -func (s *session) beforeSave() { - if s.overrides != nil { - ctx := &dummyClockContext{context.Background()} - if err := s.fillPathMap(ctx); err != nil { - panic("failed to save paths to override map before saving" + err.Error()) - } - } -} - -// afterLoad is invoked by stateify. -func (s *session) afterLoad() { - // The restore environment contains the 9p connection of this mount. - fsys := filesystem{} - env, ok := fs.CurrentRestoreEnvironment() - if !ok { - panic("failed to find restore environment") - } - mounts, ok := env.MountSources[fsys.Name()] - if !ok { - panic("failed to find mounts for filesystem type " + fsys.Name()) - } - var args fs.MountArgs - var found bool - for _, mount := range mounts { - if mount.Dev == s.connID { - args = mount - found = true - } - } - if !found { - panic(fmt.Sprintf("no connection for connection id %q", s.connID)) - } - - // Validate the mount flags and options. - opts, err := options(args.DataString) - if err != nil { - panic("failed to parse mount options: " + err.Error()) - } - if opts.msize != s.msize { - panic(fmt.Sprintf("new message size %v, want %v", opts.msize, s.msize)) - } - if opts.version != s.version { - panic(fmt.Sprintf("new version %v, want %v", opts.version, s.version)) - } - if opts.policy != s.cachePolicy { - panic(fmt.Sprintf("new cache policy %v, want %v", opts.policy, s.cachePolicy)) - } - if opts.aname != s.aname { - panic(fmt.Sprintf("new attach name %v, want %v", opts.aname, s.aname)) - } - - // Check if overrideMaps exist when uds sockets are enabled (only pathmaps - // will actually have been saved). - if opts.privateunixsocket != (s.overrides != nil) { - panic(fmt.Sprintf("new privateunixsocket option %v, want %v", opts.privateunixsocket, s.overrides != nil)) - } - if args.Flags != s.superBlockFlags { - panic(fmt.Sprintf("new mount flags %v, want %v", args.Flags, s.superBlockFlags)) - } - - // Manually restore the connection. - conn, err := unet.NewSocket(opts.fd) - if err != nil { - panic(fmt.Sprintf("failed to create Socket for FD %d: %v", opts.fd, err)) - } - - // Manually restore the client. - s.client, err = p9.NewClient(conn, s.msize, s.version) - if err != nil { - panic(fmt.Sprintf("failed to connect client to server: %v", err)) - } - - // Manually restore the attach point. - s.attach.file, err = s.client.Attach(s.aname) - if err != nil { - panic(fmt.Sprintf("failed to attach to aname: %v", err)) - } - - // If private unix sockets are enabled, create and fill the session's endpoint - // maps. - if opts.privateunixsocket { - ctx := &dummyClockContext{context.Background()} - - if err = s.restoreEndpointMaps(ctx); err != nil { - panic("failed to restore endpoint maps: " + err.Error()) - } - } -} diff --git a/pkg/sentry/fs/gofer/socket.go b/pkg/sentry/fs/gofer/socket.go deleted file mode 100644 index d47507f3f..000000000 --- a/pkg/sentry/fs/gofer/socket.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/host" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/waiter" -) - -// BoundEndpoint returns a gofer-backed transport.BoundEndpoint. -func (i *inodeOperations) BoundEndpoint(inode *fs.Inode, path string) transport.BoundEndpoint { - if !fs.IsSocket(i.fileState.sattr) { - return nil - } - - s := i.session() - if s.overrides != nil { - s.overrides.lock() - defer s.overrides.unlock() - ep := s.overrides.getBoundEndpoint(i.fileState.key) - if ep != nil { - return ep - } - - // Not found in overrides map, it may be a gofer backed unix socket... - } - - inode.IncRef() - return &endpoint{inode, i.fileState.file.file, path} -} - -// LINT.IfChange - -// endpoint is a Gofer-backed transport.BoundEndpoint. -// -// An endpoint's lifetime is the time between when InodeOperations.BoundEndpoint() -// is called and either BoundEndpoint.BidirectionalConnect or -// BoundEndpoint.UnidirectionalConnect is called. -type endpoint struct { - // inode is the filesystem inode which produced this endpoint. - inode *fs.Inode - - // file is the p9 file that contains a single unopened fid. - file p9.File - - // path is the sentry path where this endpoint is bound. - path string -} - -// BidirectionalConnect implements BoundEndpoint.BidirectionalConnect. -func (e *endpoint) BidirectionalConnect(ctx context.Context, ce transport.ConnectingEndpoint, returnConnect func(transport.Receiver, transport.ConnectedEndpoint)) *syserr.Error { - cf, ok := p9.SocketTypeFromLinux(ce.Type()) - if !ok { - return syserr.ErrConnectionRefused - } - - // No lock ordering required as only the ConnectingEndpoint has a mutex. - ce.Lock() - - // Check connecting state. - if ce.Connected() { - ce.Unlock() - return syserr.ErrAlreadyConnected - } - if ce.ListeningLocked() { - ce.Unlock() - return syserr.ErrInvalidEndpointState - } - - hostFile, err := e.file.Connect(cf) - if err != nil { - ce.Unlock() - return syserr.ErrConnectionRefused - } - - c, serr := host.NewConnectedEndpoint(ctx, hostFile, ce.WaiterQueue(), e.path) - if serr != nil { - ce.Unlock() - log.Warningf("Gofer returned invalid host socket for BidirectionalConnect; file %+v flags %+v: %v", e.file, cf, serr) - return serr - } - - returnConnect(c, c) - ce.Unlock() - c.Init() - - return nil -} - -// UnidirectionalConnect implements -// transport.BoundEndpoint.UnidirectionalConnect. -func (e *endpoint) UnidirectionalConnect(ctx context.Context) (transport.ConnectedEndpoint, *syserr.Error) { - hostFile, err := e.file.Connect(p9.DgramSocket) - if err != nil { - return nil, syserr.ErrConnectionRefused - } - - c, serr := host.NewConnectedEndpoint(ctx, hostFile, &waiter.Queue{}, e.path) - if serr != nil { - log.Warningf("Gofer returned invalid host socket for UnidirectionalConnect; file %+v: %v", e.file, serr) - return nil, serr - } - c.Init() - - // We don't need the receiver. - c.CloseRecv() - c.Release(ctx) - - return c, nil -} - -// Release implements transport.BoundEndpoint.Release. -func (e *endpoint) Release(ctx context.Context) { - e.inode.DecRef(ctx) -} - -// Passcred implements transport.BoundEndpoint.Passcred. -func (e *endpoint) Passcred() bool { - return false -} - -// LINT.ThenChange(../../fsimpl/gofer/socket.go) diff --git a/pkg/sentry/fs/gofer/util.go b/pkg/sentry/fs/gofer/util.go deleted file mode 100644 index 3cc6a7f7b..000000000 --- a/pkg/sentry/fs/gofer/util.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018 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. - -package gofer - -import ( - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/fs" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" -) - -func utimes(ctx context.Context, file contextFile, ts fs.TimeSpec) error { - if ts.ATimeOmit && ts.MTimeOmit { - return nil - } - - // Replace requests to use the "system time" with the current time to - // ensure that timestamps remain consistent with the remote - // filesystem. - now := ktime.NowFromContext(ctx) - if ts.ATimeSetSystemTime { - ts.ATime = now - } - if ts.MTimeSetSystemTime { - ts.MTime = now - } - mask := p9.SetAttrMask{ - ATime: !ts.ATimeOmit, - ATimeNotSystemTime: true, - MTime: !ts.MTimeOmit, - MTimeNotSystemTime: true, - } - as, ans := ts.ATime.Unix() - ms, mns := ts.MTime.Unix() - attr := p9.SetAttr{ - ATimeSeconds: uint64(as), - ATimeNanoSeconds: uint64(ans), - MTimeSeconds: uint64(ms), - MTimeNanoSeconds: uint64(mns), - } - // 9p2000.L SetAttr: "If a time bit is set without the corresponding SET bit, - // the current system time on the server is used instead of the value sent - // in the request." - return file.setAttr(ctx, mask, attr) -} - -func openFlagsFromPerms(p fs.PermMask) (p9.OpenFlags, error) { - switch { - case p.Read && p.Write: - return p9.ReadWrite, nil - case p.Write: - return p9.WriteOnly, nil - case p.Read: - return p9.ReadOnly, nil - default: - return 0, unix.EINVAL - } -} diff --git a/pkg/sentry/fs/host/BUILD b/pkg/sentry/fs/host/BUILD deleted file mode 100644 index 27feec9ab..000000000 --- a/pkg/sentry/fs/host/BUILD +++ /dev/null @@ -1,87 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "host", - srcs = [ - "control.go", - "descriptor.go", - "descriptor_state.go", - "device.go", - "file.go", - "host.go", - "inode.go", - "inode_state.go", - "ioctl_unsafe.go", - "socket.go", - "socket_iovec.go", - "socket_state.go", - "socket_unsafe.go", - "tty.go", - "util.go", - "util_amd64_unsafe.go", - "util_arm64_unsafe.go", - "util_unsafe.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/atomicbitops", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fd", - "//pkg/fdnotifier", - "//pkg/log", - "//pkg/marshal/primitive", - "//pkg/refs", - "//pkg/safemem", - "//pkg/secio", - "//pkg/sentry/arch", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/hostfd", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/socket/control", - "//pkg/sentry/socket/unix", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/unimpl", - "//pkg/sentry/uniqueid", - "//pkg/sync", - "//pkg/syserr", - "//pkg/tcpip", - "//pkg/unet", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "host_test", - size = "small", - srcs = [ - "descriptor_test.go", - "inode_test.go", - "socket_test.go", - "wait_test.go", - ], - library = ":host", - deps = [ - "//pkg/fd", - "//pkg/fdnotifier", - "//pkg/sentry/contexttest", - "//pkg/sentry/kernel/time", - "//pkg/sentry/socket", - "//pkg/sentry/socket/unix/transport", - "//pkg/syserr", - "//pkg/tcpip", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/pkg/sentry/fs/host/control.go b/pkg/sentry/fs/host/control.go deleted file mode 100644 index e53998be1..000000000 --- a/pkg/sentry/fs/host/control.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/socket/control" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" -) - -// LINT.IfChange - -type scmRights struct { - fds []int -} - -func newSCMRights(fds []int) control.SCMRights { - return &scmRights{fds} -} - -// Files implements control.SCMRights.Files. -func (c *scmRights) Files(ctx context.Context, max int) (control.RightsFiles, bool) { - n := max - var trunc bool - if l := len(c.fds); n > l { - n = l - } else if n < l { - trunc = true - } - - rf := control.RightsFiles(fdsToFiles(ctx, c.fds[:n])) - - // Only consume converted FDs (fdsToFiles may convert fewer than n FDs). - c.fds = c.fds[len(rf):] - return rf, trunc -} - -// Clone implements transport.RightsControlMessage.Clone. -func (c *scmRights) Clone() transport.RightsControlMessage { - // Host rights never need to be cloned. - return nil -} - -// Release implements transport.RightsControlMessage.Release. -func (c *scmRights) Release(ctx context.Context) { - for _, fd := range c.fds { - unix.Close(fd) - } - c.fds = nil -} - -// If an error is encountered, only files created before the error will be -// returned. This is what Linux does. -func fdsToFiles(ctx context.Context, fds []int) []*fs.File { - files := make([]*fs.File, 0, len(fds)) - for _, fd := range fds { - // Get flags. We do it here because they may be modified - // by subsequent functions. - fileFlags, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(fd), unix.F_GETFL, 0) - if errno != 0 { - ctx.Warningf("Error retrieving host FD flags: %v", error(errno)) - break - } - - // Create the file backed by hostFD. - file, err := NewFile(ctx, fd) - if err != nil { - ctx.Warningf("Error creating file from host FD: %v", err) - break - } - - // Set known flags. - file.SetFlags(fs.SettableFileFlags{ - NonBlocking: fileFlags&unix.O_NONBLOCK != 0, - }) - - files = append(files, file) - } - return files -} - -// LINT.ThenChange(../../fsimpl/host/control.go) diff --git a/pkg/sentry/fs/host/descriptor.go b/pkg/sentry/fs/host/descriptor.go deleted file mode 100644 index 5667b94a1..000000000 --- a/pkg/sentry/fs/host/descriptor.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/waiter" -) - -// descriptor wraps a host fd. -// -// +stateify savable -type descriptor struct { - // If origFD >= 0, it is the host fd that this file was originally created - // from, which must be available at time of restore. The FD can be closed - // after descriptor is created. - origFD int - - // wouldBlock is true if value (below) points to a file that can - // return EWOULDBLOCK for operations that would block. - wouldBlock bool - - // value is the wrapped host fd. It is never saved or restored - // directly. - value int `state:"nosave"` -} - -// newDescriptor returns a wrapped host file descriptor. On success, -// the descriptor is registered for event notifications with queue. -func newDescriptor(fd int, saveable bool, wouldBlock bool, queue *waiter.Queue) (*descriptor, error) { - ownedFD := fd - origFD := -1 - if saveable { - var err error - ownedFD, err = unix.Dup(fd) - if err != nil { - return nil, err - } - origFD = fd - } - if wouldBlock { - if err := unix.SetNonblock(ownedFD, true); err != nil { - return nil, err - } - if err := fdnotifier.AddFD(int32(ownedFD), queue); err != nil { - return nil, err - } - } - return &descriptor{ - origFD: origFD, - wouldBlock: wouldBlock, - value: ownedFD, - }, nil -} - -// initAfterLoad initializes the value of the descriptor after Load. -func (d *descriptor) initAfterLoad(id uint64, queue *waiter.Queue) error { - var err error - d.value, err = unix.Dup(d.origFD) - if err != nil { - return fmt.Errorf("failed to dup restored fd %d: %v", d.origFD, err) - } - if d.wouldBlock { - if err := unix.SetNonblock(d.value, true); err != nil { - return err - } - if err := fdnotifier.AddFD(int32(d.value), queue); err != nil { - return err - } - } - return nil -} - -// Release releases all resources held by descriptor. -func (d *descriptor) Release() { - if d.wouldBlock { - fdnotifier.RemoveFD(int32(d.value)) - } - if err := unix.Close(d.value); err != nil { - log.Warningf("error closing fd %d: %v", d.value, err) - } - d.value = -1 -} diff --git a/pkg/sentry/fs/host/descriptor_state.go b/pkg/sentry/fs/host/descriptor_state.go deleted file mode 100644 index e880582ab..000000000 --- a/pkg/sentry/fs/host/descriptor_state.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 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. - -package host - -// beforeSave is invoked by stateify. -func (d *descriptor) beforeSave() { - if d.origFD < 0 { - panic("donated file descriptor cannot be saved") - } -} - -// afterLoad is invoked by stateify. -func (d *descriptor) afterLoad() { - // value must be manually restored by the descriptor's parent using - // initAfterLoad. - d.value = -1 -} diff --git a/pkg/sentry/fs/host/descriptor_test.go b/pkg/sentry/fs/host/descriptor_test.go deleted file mode 100644 index cb809ab2d..000000000 --- a/pkg/sentry/fs/host/descriptor_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "io/ioutil" - "path/filepath" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/waiter" -) - -func TestDescriptorRelease(t *testing.T) { - for _, tc := range []struct { - name string - saveable bool - wouldBlock bool - }{ - {name: "all false"}, - {name: "saveable", saveable: true}, - {name: "wouldBlock", wouldBlock: true}, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir("", "descriptor_test") - if err != nil { - t.Fatal("ioutil.TempDir() failed:", err) - } - - fd, err := unix.Open(filepath.Join(dir, "file"), unix.O_RDWR|unix.O_CREAT, 0666) - if err != nil { - t.Fatal("failed to open temp file:", err) - } - - // FD ownership is transferred to the descritor. - queue := &waiter.Queue{} - d, err := newDescriptor(fd, tc.saveable, tc.wouldBlock, queue) - if err != nil { - unix.Close(fd) - t.Fatalf("newDescriptor(%d, %t, %t, queue) failed, err: %v", fd, tc.saveable, tc.wouldBlock, err) - } - if tc.saveable { - if d.origFD < 0 { - t.Errorf("saveable descriptor must preserve origFD, desc: %+v", d) - } - } - if tc.wouldBlock { - if !fdnotifier.HasFD(int32(d.value)) { - t.Errorf("FD not registered with notifier, desc: %+v", d) - } - } - - oldVal := d.value - d.Release() - if d.value != -1 { - t.Errorf("d.value want: -1, got: %d", d.value) - } - if tc.wouldBlock { - if fdnotifier.HasFD(int32(oldVal)) { - t.Errorf("FD not unregistered with notifier, desc: %+v", d) - } - } - }) - } -} diff --git a/pkg/sentry/fs/host/device.go b/pkg/sentry/fs/host/device.go deleted file mode 100644 index 484f0b58b..000000000 --- a/pkg/sentry/fs/host/device.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "gvisor.dev/gvisor/pkg/sentry/device" -) - -// hostFileDevice is the host file virtual device. -var hostFileDevice = device.NewAnonMultiDevice() - -// hostPipeDevice is the host pipe virtual device. -var hostPipeDevice = device.NewAnonDevice() diff --git a/pkg/sentry/fs/host/file.go b/pkg/sentry/fs/host/file.go deleted file mode 100644 index 83779ec38..000000000 --- a/pkg/sentry/fs/host/file.go +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/secio" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// fileOperations implements fs.FileOperations for a host file descriptor. -// -// +stateify savable -type fileOperations struct { - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoSplice `state:"nosplice"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - // iops are the Inode operations for this file. - iops *inodeOperations `state:"wait"` - - // a scratch buffer for reading directory entries. - dirinfo *dirInfo `state:"nosave"` - - // dirCursor is the directory cursor. - dirCursor string -} - -// fileOperations implements fs.FileOperations. -var _ fs.FileOperations = (*fileOperations)(nil) - -// NewFile creates a new File backed by the provided host file descriptor. If -// NewFile succeeds, ownership of the FD is transferred to the returned File. -// -// The returned File cannot be saved, since there is no guarantee that the same -// FD will exist or represent the same file at time of restore. If such a -// guarantee does exist, use ImportFile instead. -func NewFile(ctx context.Context, fd int) (*fs.File, error) { - return newFileFromDonatedFD(ctx, fd, false, false) -} - -// ImportFile creates a new File backed by the provided host file descriptor. -// Unlike NewFile, the file descriptor used by the File is duped from FD to -// ensure that later changes to FD are not reflected by the fs.File. -// -// If the returned file is saved, it will be restored by re-importing the FD -// originally passed to ImportFile. It is the restorer's responsibility to -// ensure that the FD represents the same file. -func ImportFile(ctx context.Context, fd int, isTTY bool) (*fs.File, error) { - return newFileFromDonatedFD(ctx, fd, true, isTTY) -} - -// newFileFromDonatedFD returns an fs.File from a donated FD. If the FD is -// saveable, then saveable is true. -func newFileFromDonatedFD(ctx context.Context, donated int, saveable, isTTY bool) (*fs.File, error) { - var s unix.Stat_t - if err := unix.Fstat(donated, &s); err != nil { - return nil, err - } - flags, err := fileFlagsFromDonatedFD(donated) - if err != nil { - return nil, err - } - switch s.Mode & unix.S_IFMT { - case unix.S_IFSOCK: - if isTTY { - return nil, fmt.Errorf("cannot import host socket as TTY") - } - - s, err := newSocket(ctx, donated, saveable) - if err != nil { - return nil, err - } - s.SetFlags(fs.SettableFileFlags{ - NonBlocking: flags.NonBlocking, - }) - return s, nil - default: - msrc := fs.NewNonCachingMountSource(ctx, &filesystem{}, fs.MountSourceFlags{}) - inode, err := newInode(ctx, msrc, donated, saveable) - if err != nil { - return nil, err - } - iops := inode.InodeOperations.(*inodeOperations) - - name := fmt.Sprintf("host:[%d]", inode.StableAttr.InodeID) - dirent := fs.NewDirent(ctx, inode, name) - defer dirent.DecRef(ctx) - - if isTTY { - return newTTYFile(ctx, dirent, flags, iops), nil - } - - return newFile(ctx, dirent, flags, iops), nil - } -} - -func fileFlagsFromDonatedFD(donated int) (fs.FileFlags, error) { - flags, _, errno := unix.Syscall(unix.SYS_FCNTL, uintptr(donated), unix.F_GETFL, 0) - if errno != 0 { - log.Warningf("Failed to get file flags for donated FD %d (errno=%d)", donated, errno) - return fs.FileFlags{}, unix.EIO - } - accmode := flags & unix.O_ACCMODE - return fs.FileFlags{ - Direct: flags&unix.O_DIRECT != 0, - NonBlocking: flags&unix.O_NONBLOCK != 0, - Sync: flags&unix.O_SYNC != 0, - Append: flags&unix.O_APPEND != 0, - Read: accmode == unix.O_RDONLY || accmode == unix.O_RDWR, - Write: accmode == unix.O_WRONLY || accmode == unix.O_RDWR, - }, nil -} - -// newFile returns a new fs.File. -func newFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags, iops *inodeOperations) *fs.File { - if !iops.ReturnsWouldBlock() { - // Allow reading/writing at an arbitrary offset for files - // that support it. - flags.Pread = true - flags.Pwrite = true - } - return fs.NewFile(ctx, dirent, flags, &fileOperations{iops: iops}) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (f *fileOperations) EventRegister(e *waiter.Entry) error { - f.iops.fileState.queue.EventRegister(e) - if err := fdnotifier.UpdateFD(int32(f.iops.fileState.FD())); err != nil { - f.iops.fileState.queue.EventUnregister(e) - return err - } - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (f *fileOperations) EventUnregister(e *waiter.Entry) { - f.iops.fileState.queue.EventUnregister(e) - if err := fdnotifier.UpdateFD(int32(f.iops.fileState.FD())); err != nil { - panic(fmt.Sprint("UpdateFD:", err)) - } -} - -// Readiness uses the poll() syscall to check the status of the underlying FD. -func (f *fileOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - return fdnotifier.NonBlockingPoll(int32(f.iops.fileState.FD()), mask) -} - -// Readdir implements fs.FileOperations.Readdir. -func (f *fileOperations) Readdir(ctx context.Context, file *fs.File, serializer fs.DentrySerializer) (int64, error) { - root := fs.RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - dirCtx := &fs.DirCtx{ - Serializer: serializer, - DirCursor: &f.dirCursor, - } - return fs.DirentReaddir(ctx, file.Dirent, f, root, dirCtx, file.Offset()) -} - -// IterateDir implements fs.DirIterator.IterateDir. -func (f *fileOperations) IterateDir(ctx context.Context, d *fs.Dirent, dirCtx *fs.DirCtx, offset int) (int, error) { - // We only support non-directory file descriptors that have been - // imported, so just claim that this isn't a directory, even if it is. - return offset, unix.ENOTDIR -} - -// Write implements fs.FileOperations.Write. -func (f *fileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - // Would this file block? - if f.iops.ReturnsWouldBlock() { - // These files can't be memory mapped, assert this. This also - // means that writes do not need to synchronize with memory - // mappings nor metadata cached by this file's fs.Inode. - if canMap(file.Dirent.Inode) { - panic("files that can return EWOULDBLOCK cannot be memory mapped") - } - // Ignore the offset, these files don't support writing at - // an arbitrary offset. - writer := fd.NewReadWriter(f.iops.fileState.FD()) - n, err := src.CopyInTo(ctx, safemem.FromIOWriter{writer}) - if isBlockError(err) { - err = linuxerr.ErrWouldBlock - } - return n, err - } - if !file.Dirent.Inode.MountSource.Flags.ForcePageCache { - writer := secio.NewOffsetWriter(fd.NewReadWriter(f.iops.fileState.FD()), offset) - return src.CopyInTo(ctx, safemem.FromIOWriter{writer}) - } - return f.iops.cachingInodeOps.Write(ctx, src, offset) -} - -// Read implements fs.FileOperations.Read. -func (f *fileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - // Would this file block? - if f.iops.ReturnsWouldBlock() { - // These files can't be memory mapped, assert this. This also - // means that reads do not need to synchronize with memory - // mappings nor metadata cached by this file's fs.Inode. - if canMap(file.Dirent.Inode) { - panic("files that can return EWOULDBLOCK cannot be memory mapped") - } - // Ignore the offset, these files don't support reading at - // an arbitrary offset. - reader := fd.NewReadWriter(f.iops.fileState.FD()) - n, err := dst.CopyOutFrom(ctx, safemem.FromIOReader{reader}) - if isBlockError(err) { - // If we got any data at all, return it as a "completed" partial read - // rather than retrying until complete. - if n != 0 { - err = nil - } else { - err = linuxerr.ErrWouldBlock - } - } - return n, err - } - if !file.Dirent.Inode.MountSource.Flags.ForcePageCache { - reader := secio.NewOffsetReader(fd.NewReadWriter(f.iops.fileState.FD()), offset) - return dst.CopyOutFrom(ctx, safemem.FromIOReader{reader}) - } - return f.iops.cachingInodeOps.Read(ctx, file, dst, offset) -} - -// Fsync implements fs.FileOperations.Fsync. -func (f *fileOperations) Fsync(ctx context.Context, file *fs.File, start int64, end int64, syncType fs.SyncType) error { - switch syncType { - case fs.SyncAll, fs.SyncData: - if err := file.Dirent.Inode.WriteOut(ctx); err != nil { - return err - } - fallthrough - case fs.SyncBackingStorage: - return unix.Fsync(f.iops.fileState.FD()) - } - panic("invalid sync type") -} - -// Flush implements fs.FileOperations.Flush. -func (f *fileOperations) Flush(context.Context, *fs.File) error { - // This is a no-op because flushing the resource backing this - // file would mean closing it. We can't do that because other - // open files may depend on the backing host FD. - return nil -} - -// ConfigureMMap implements fs.FileOperations.ConfigureMMap. -func (f *fileOperations) ConfigureMMap(ctx context.Context, file *fs.File, opts *memmap.MMapOpts) error { - if !canMap(file.Dirent.Inode) { - return linuxerr.ENODEV - } - return fsutil.GenericConfigureMMap(file, f.iops.cachingInodeOps, opts) -} - -// Seek implements fs.FileOperations.Seek. -func (f *fileOperations) Seek(ctx context.Context, file *fs.File, whence fs.SeekWhence, offset int64) (int64, error) { - return fsutil.SeekWithDirCursor(ctx, file, whence, offset, &f.dirCursor) -} diff --git a/pkg/sentry/fs/host/host.go b/pkg/sentry/fs/host/host.go deleted file mode 100644 index 2ab2560ac..000000000 --- a/pkg/sentry/fs/host/host.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020 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. - -// Package host supports file descriptors imported directly. -package host - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// filesystem is a host filesystem. -// -// +stateify savable -type filesystem struct{} - -func init() { - fs.RegisterFilesystem(&filesystem{}) -} - -// FilesystemName is the name under which the filesystem is registered. -const FilesystemName = "host" - -// Name is the name of the filesystem. -func (*filesystem) Name() string { - return FilesystemName -} - -// Mount returns an error. Mounting hostfs is not allowed. -func (*filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, dataObj any) (*fs.Inode, error) { - return nil, linuxerr.EPERM -} - -// AllowUserMount prohibits users from using mount(2) with this file system. -func (*filesystem) AllowUserMount() bool { - return false -} - -// AllowUserList prohibits this filesystem to be listed in /proc/filesystems. -func (*filesystem) AllowUserList() bool { - return false -} - -// Flags returns that there is nothing special about this file system. -func (*filesystem) Flags() fs.FilesystemFlags { - return 0 -} diff --git a/pkg/sentry/fs/host/inode.go b/pkg/sentry/fs/host/inode.go deleted file mode 100644 index 3c2c98527..000000000 --- a/pkg/sentry/fs/host/inode.go +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/secio" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/waiter" -) - -// inodeOperations implements fs.InodeOperations for an fs.Inodes backed -// by a host file descriptor. -// -// +stateify savable -type inodeOperations struct { - fsutil.InodeNotVirtual `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - - // fileState implements fs.CachedFileObject. It exists - // to break a circular load dependency between inodeOperations - // and cachingInodeOps (below). - fileState *inodeFileState `state:"wait"` - - // cachedInodeOps implements memmap.Mappable. - cachingInodeOps *fsutil.CachingInodeOperations - - // readdirMu protects the file offset on the host FD. This is needed - // for readdir because getdents must use the kernel offset, so - // concurrent readdirs must be exclusive. - // - // All read/write functions pass the offset directly to the kernel and - // thus don't need a lock. - readdirMu sync.Mutex `state:"nosave"` -} - -// inodeFileState implements fs.CachedFileObject and otherwise fully -// encapsulates state that needs to be manually loaded on restore for -// this file object. -// -// This unfortunate structure exists because fs.CachingInodeOperations -// defines afterLoad and therefore cannot be lazily loaded (to break a -// circular load dependency between it and inodeOperations). Even with -// lazy loading, this approach defines the dependencies between objects -// and the expected load behavior more concretely. -// -// +stateify savable -type inodeFileState struct { - // descriptor is the backing host FD. - descriptor *descriptor `state:"wait"` - - // Event queue for blocking operations. - queue waiter.Queue - - // sattr is used to restore the inodeOperations. - sattr fs.StableAttr `state:"wait"` - - // savedUAttr is only allocated during S/R. It points to the save-time - // unstable attributes and is used to validate restore-time ones. - // - // Note that these unstable attributes are only used to detect cross-S/R - // external file system metadata changes. They may differ from the - // cached unstable attributes in cachingInodeOps, as that might differ - // from the external file system attributes if there had been WriteOut - // failures. S/R is transparent to Sentry and the latter will continue - // using its cached values after restore. - savedUAttr *fs.UnstableAttr -} - -// ReadToBlocksAt implements fsutil.CachedFileObject.ReadToBlocksAt. -func (i *inodeFileState) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) { - // TODO(jamieliu): Using safemem.FromIOReader here is wasteful for two - // reasons: - // - // - Using preadv instead of iterated preads saves on host system calls. - // - // - Host system calls can handle destination memory that would fault in - // gr3 (i.e. they can accept safemem.Blocks with NeedSafecopy() == true), - // so the buffering performed by FromIOReader is unnecessary. - // - // This also applies to the write path below. - return safemem.FromIOReader{secio.NewOffsetReader(fd.NewReadWriter(i.FD()), int64(offset))}.ReadToBlocks(dsts) -} - -// WriteFromBlocksAt implements fsutil.CachedFileObject.WriteFromBlocksAt. -func (i *inodeFileState) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) { - return safemem.FromIOWriter{secio.NewOffsetWriter(fd.NewReadWriter(i.FD()), int64(offset))}.WriteFromBlocks(srcs) -} - -// SetMaskedAttributes implements fsutil.CachedFileObject.SetMaskedAttributes. -func (i *inodeFileState) SetMaskedAttributes(ctx context.Context, mask fs.AttrMask, attr fs.UnstableAttr, _ bool) error { - if mask.Empty() { - return nil - } - if mask.UID || mask.GID { - return linuxerr.EPERM - } - if mask.Perms { - if err := unix.Fchmod(i.FD(), uint32(attr.Perms.LinuxMode())); err != nil { - return err - } - } - if mask.Size { - if err := unix.Ftruncate(i.FD(), attr.Size); err != nil { - return err - } - } - if mask.AccessTime || mask.ModificationTime { - ts := fs.TimeSpec{ - ATime: attr.AccessTime, - ATimeOmit: !mask.AccessTime, - MTime: attr.ModificationTime, - MTimeOmit: !mask.ModificationTime, - } - if err := setTimestamps(i.FD(), ts); err != nil { - return err - } - } - return nil -} - -// Sync implements fsutil.CachedFileObject.Sync. -func (i *inodeFileState) Sync(ctx context.Context) error { - return unix.Fsync(i.FD()) -} - -// FD implements fsutil.CachedFileObject.FD. -func (i *inodeFileState) FD() int { - return i.descriptor.value -} - -func (i *inodeFileState) unstableAttr(ctx context.Context) (fs.UnstableAttr, error) { - var s unix.Stat_t - if err := unix.Fstat(i.FD(), &s); err != nil { - return fs.UnstableAttr{}, err - } - return unstableAttr(&s), nil -} - -// Allocate implements fsutil.CachedFileObject.Allocate. -func (i *inodeFileState) Allocate(_ context.Context, offset, length int64) error { - return unix.Fallocate(i.FD(), 0, offset, length) -} - -// inodeOperations implements fs.InodeOperations. -var _ fs.InodeOperations = (*inodeOperations)(nil) - -// newInode returns a new fs.Inode backed by the host FD. -func newInode(ctx context.Context, msrc *fs.MountSource, fd int, saveable bool) (*fs.Inode, error) { - // Retrieve metadata. - var s unix.Stat_t - err := unix.Fstat(fd, &s) - if err != nil { - return nil, err - } - - fileState := &inodeFileState{ - sattr: stableAttr(&s), - } - - // Initialize the wrapped host file descriptor. - fileState.descriptor, err = newDescriptor(fd, saveable, wouldBlock(&s), &fileState.queue) - if err != nil { - return nil, err - } - - // Build the fs.InodeOperations. - uattr := unstableAttr(&s) - iops := &inodeOperations{ - fileState: fileState, - cachingInodeOps: fsutil.NewCachingInodeOperations(ctx, fileState, uattr, fsutil.CachingInodeOperationsOptions{ - ForcePageCache: msrc.Flags.ForcePageCache, - }), - } - - // Return the fs.Inode. - return fs.NewInode(ctx, iops, msrc, fileState.sattr), nil -} - -// Mappable implements fs.InodeOperations.Mappable. -func (i *inodeOperations) Mappable(inode *fs.Inode) memmap.Mappable { - if !canMap(inode) { - return nil - } - return i.cachingInodeOps -} - -// ReturnsWouldBlock returns true if this host FD can return EWOULDBLOCK for -// operations that would block. -func (i *inodeOperations) ReturnsWouldBlock() bool { - return i.fileState.descriptor.wouldBlock -} - -// Release implements fs.InodeOperations.Release. -func (i *inodeOperations) Release(context.Context) { - i.fileState.descriptor.Release() - i.cachingInodeOps.Release() -} - -// Lookup implements fs.InodeOperations.Lookup. -func (i *inodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string) (*fs.Dirent, error) { - return nil, linuxerr.ENOENT -} - -// Create implements fs.InodeOperations.Create. -func (i *inodeOperations) Create(ctx context.Context, dir *fs.Inode, name string, flags fs.FileFlags, perm fs.FilePermissions) (*fs.File, error) { - return nil, linuxerr.EPERM - -} - -// CreateDirectory implements fs.InodeOperations.CreateDirectory. -func (i *inodeOperations) CreateDirectory(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions) error { - return linuxerr.EPERM -} - -// CreateLink implements fs.InodeOperations.CreateLink. -func (i *inodeOperations) CreateLink(ctx context.Context, dir *fs.Inode, oldname string, newname string) error { - return linuxerr.EPERM -} - -// CreateHardLink implements fs.InodeOperations.CreateHardLink. -func (*inodeOperations) CreateHardLink(context.Context, *fs.Inode, *fs.Inode, string) error { - return linuxerr.EPERM -} - -// CreateFifo implements fs.InodeOperations.CreateFifo. -func (*inodeOperations) CreateFifo(context.Context, *fs.Inode, string, fs.FilePermissions) error { - return linuxerr.EPERM -} - -// Remove implements fs.InodeOperations.Remove. -func (i *inodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string) error { - return linuxerr.EPERM -} - -// RemoveDirectory implements fs.InodeOperations.RemoveDirectory. -func (i *inodeOperations) RemoveDirectory(ctx context.Context, dir *fs.Inode, name string) error { - return linuxerr.EPERM -} - -// Rename implements fs.InodeOperations.Rename. -func (i *inodeOperations) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return linuxerr.EPERM -} - -// Bind implements fs.InodeOperations.Bind. -func (i *inodeOperations) Bind(ctx context.Context, dir *fs.Inode, name string, data transport.BoundEndpoint, perm fs.FilePermissions) (*fs.Dirent, error) { - return nil, linuxerr.EOPNOTSUPP -} - -// BoundEndpoint implements fs.InodeOperations.BoundEndpoint. -func (i *inodeOperations) BoundEndpoint(inode *fs.Inode, path string) transport.BoundEndpoint { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (i *inodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - if fs.IsSocket(d.Inode.StableAttr) { - return nil, linuxerr.ENXIO - } - - return newFile(ctx, d, flags, i), nil -} - -// canMap returns true if this fs.Inode can be memory mapped. -func canMap(inode *fs.Inode) bool { - // FIXME(b/38213152): Some obscure character devices can be mapped. - return fs.IsFile(inode.StableAttr) -} - -// UnstableAttr implements fs.InodeOperations.UnstableAttr. -func (i *inodeOperations) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - // When the kernel supports mapping host FDs, we do so to take - // advantage of the host page cache. We forego updating fs.Inodes - // because the host manages consistency of its own inode structures. - // - // For fs.Inodes that can never be mapped we take advantage of - // synchronizing metadata updates through host caches. - // - // So can we use host kernel metadata caches? - if !inode.MountSource.Flags.ForcePageCache || !canMap(inode) { - // Then just obtain the attributes. - return i.fileState.unstableAttr(ctx) - } - // No, we're maintaining consistency of metadata ourselves. - return i.cachingInodeOps.UnstableAttr(ctx, inode) -} - -// Check implements fs.InodeOperations.Check. -func (i *inodeOperations) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { - return fs.ContextCanAccessFile(ctx, inode, p) -} - -// SetOwner implements fs.InodeOperations.SetOwner. -func (i *inodeOperations) SetOwner(context.Context, *fs.Inode, fs.FileOwner) error { - return linuxerr.EPERM -} - -// SetPermissions implements fs.InodeOperations.SetPermissions. -func (i *inodeOperations) SetPermissions(ctx context.Context, inode *fs.Inode, f fs.FilePermissions) bool { - // Can we use host kernel metadata caches? - if !inode.MountSource.Flags.ForcePageCache || !canMap(inode) { - // Then just change the timestamps on the FD, the host - // will synchronize the metadata update with any host - // inode and page cache. - return unix.Fchmod(i.fileState.FD(), uint32(f.LinuxMode())) == nil - } - // Otherwise update our cached metadata. - return i.cachingInodeOps.SetPermissions(ctx, inode, f) -} - -// SetTimestamps implements fs.InodeOperations.SetTimestamps. -func (i *inodeOperations) SetTimestamps(ctx context.Context, inode *fs.Inode, ts fs.TimeSpec) error { - // Can we use host kernel metadata caches? - if !inode.MountSource.Flags.ForcePageCache || !canMap(inode) { - // Then just change the timestamps on the FD, the host - // will synchronize the metadata update with any host - // inode and page cache. - return setTimestamps(i.fileState.FD(), ts) - } - // Otherwise update our cached metadata. - return i.cachingInodeOps.SetTimestamps(ctx, inode, ts) -} - -// Truncate implements fs.InodeOperations.Truncate. -func (i *inodeOperations) Truncate(ctx context.Context, inode *fs.Inode, size int64) error { - // Is the file not memory-mappable? - if !canMap(inode) { - // Then just change the file size on the FD, the host - // will synchronize the metadata update with any host - // inode and page cache. - return unix.Ftruncate(i.fileState.FD(), size) - } - // Otherwise we need to go through cachingInodeOps, even if the host page - // cache is in use, to invalidate private copies of truncated pages. - return i.cachingInodeOps.Truncate(ctx, inode, size) -} - -// Allocate implements fs.InodeOperations.Allocate. -func (i *inodeOperations) Allocate(ctx context.Context, inode *fs.Inode, offset, length int64) error { - // Is the file not memory-mappable? - if !canMap(inode) { - // Then just send the call to the FD, the host will synchronize the metadata - // update with any host inode and page cache. - return i.fileState.Allocate(ctx, offset, length) - } - // Otherwise we need to go through cachingInodeOps, even if the host page - // cache is in use, to invalidate private copies of truncated pages. - return i.cachingInodeOps.Allocate(ctx, offset, length) -} - -// WriteOut implements fs.InodeOperations.WriteOut. -func (i *inodeOperations) WriteOut(ctx context.Context, inode *fs.Inode) error { - if inode.MountSource.Flags.ReadOnly { - return nil - } - // Have we been using host kernel metadata caches? - if !inode.MountSource.Flags.ForcePageCache || !canMap(inode) { - // Then the metadata is already up to date on the host. - return nil - } - // Otherwise we need to write out cached pages and attributes - // that are dirty. - return i.cachingInodeOps.WriteOut(ctx, inode) -} - -// Readlink implements fs.InodeOperations.Readlink. -func (i *inodeOperations) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - return readLink(i.fileState.FD()) -} - -// Getlink implements fs.InodeOperations.Getlink. -func (i *inodeOperations) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) { - if !fs.IsSymlink(i.fileState.sattr) { - return nil, linuxerr.ENOLINK - } - return nil, fs.ErrResolveViaReadlink -} - -// StatFS implements fs.InodeOperations.StatFS. -func (i *inodeOperations) StatFS(context.Context) (fs.Info, error) { - return fs.Info{}, linuxerr.ENOSYS -} - -// AddLink implements fs.InodeOperations.AddLink. -func (i *inodeOperations) AddLink() {} - -// DropLink implements fs.InodeOperations.DropLink. -func (i *inodeOperations) DropLink() {} - -// NotifyStatusChange implements fs.InodeOperations.NotifyStatusChange. -func (i *inodeOperations) NotifyStatusChange(ctx context.Context) {} diff --git a/pkg/sentry/fs/host/inode_state.go b/pkg/sentry/fs/host/inode_state.go deleted file mode 100644 index 03531b7e7..000000000 --- a/pkg/sentry/fs/host/inode_state.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// afterLoad is invoked by stateify. -func (i *inodeFileState) afterLoad() { - // Initialize the descriptor value. - if err := i.descriptor.initAfterLoad(i.sattr.InodeID, &i.queue); err != nil { - panic(fmt.Sprintf("failed to load value of descriptor: %v", err)) - } - - // Remap the inode number. - var s unix.Stat_t - if err := unix.Fstat(i.FD(), &s); err != nil { - panic(fs.ErrCorruption{fmt.Errorf("failed to get metadata for fd %d: %v", i.FD(), err)}) - } - key := device.MultiDeviceKey{ - Device: s.Dev, - Inode: s.Ino, - } - if !hostFileDevice.Load(key, i.sattr.InodeID) { - // This means there was a conflict at s.Dev and s.Ino with - // another inode mapping: two files that were unique on the - // saved filesystem are no longer unique on this filesystem. - // Since this violates the contract that filesystems cannot - // change across save and restore, error out. - panic(fs.ErrCorruption{fmt.Errorf("host %s conflict in host device mappings: %s", key, hostFileDevice)}) - } -} diff --git a/pkg/sentry/fs/host/inode_test.go b/pkg/sentry/fs/host/inode_test.go deleted file mode 100644 index 11738871b..000000000 --- a/pkg/sentry/fs/host/inode_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/sentry/contexttest" -) - -// TestCloseFD verifies fds will be closed. -func TestCloseFD(t *testing.T) { - var p [2]int - if err := unix.Pipe(p[0:]); err != nil { - t.Fatalf("Failed to create pipe %v", err) - } - defer unix.Close(p[0]) - defer unix.Close(p[1]) - - // Use the write-end because we will detect if it's closed on the read end. - ctx := contexttest.Context(t) - file, err := NewFile(ctx, p[1]) - if err != nil { - t.Fatalf("Failed to create File: %v", err) - } - file.DecRef(ctx) - - s := make([]byte, 10) - if c, err := unix.Read(p[0], s); c != 0 || err != nil { - t.Errorf("want 0, nil (EOF) from read end, got %v, %v", c, err) - } -} diff --git a/pkg/sentry/fs/host/ioctl_unsafe.go b/pkg/sentry/fs/host/ioctl_unsafe.go deleted file mode 100644 index 303ea4d9b..000000000 --- a/pkg/sentry/fs/host/ioctl_unsafe.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "unsafe" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" -) - -// LINT.IfChange - -func ioctlGetTermios(fd int) (*linux.Termios, error) { - var t linux.Termios - _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), linux.TCGETS, uintptr(unsafe.Pointer(&t))) - if errno != 0 { - return nil, errno - } - return &t, nil -} - -func ioctlSetTermios(fd int, req uint64, t *linux.Termios) error { - _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(t))) - if errno != 0 { - return errno - } - return nil -} - -func ioctlGetWinsize(fd int) (*linux.Winsize, error) { - var w linux.Winsize - _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), linux.TIOCGWINSZ, uintptr(unsafe.Pointer(&w))) - if errno != 0 { - return nil, errno - } - return &w, nil -} - -func ioctlSetWinsize(fd int, w *linux.Winsize) error { - _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), linux.TIOCSWINSZ, uintptr(unsafe.Pointer(w))) - if errno != 0 { - return errno - } - return nil -} - -// LINT.ThenChange(../../fsimpl/host/ioctl_unsafe.go) diff --git a/pkg/sentry/fs/host/socket.go b/pkg/sentry/fs/host/socket.go deleted file mode 100644 index d354f8d6c..000000000 --- a/pkg/sentry/fs/host/socket.go +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/socket/control" - unixsocket "gvisor.dev/gvisor/pkg/sentry/socket/unix" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sentry/uniqueid" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// ConnectedEndpoint is a host FD backed implementation of -// transport.ConnectedEndpoint and transport.Receiver. -// -// +stateify savable -type ConnectedEndpoint struct { - // ref keeps track of references to a connectedEndpoint. - ref refs.AtomicRefCount - - queue *waiter.Queue - path string - - // If srfd >= 0, it is the host FD that file was imported from. - srfd int `state:"wait"` - - // stype is the type of Unix socket. - stype linux.SockType - - // sndbuf is the size of the send buffer. - // - // N.B. When this is smaller than the host size, we present it via - // GetSockOpt and message splitting/rejection in SendMsg, but do not - // prevent lots of small messages from filling the real send buffer - // size on the host. - sndbuf atomicbitops.Int64 `state:"nosave"` - - // mu protects the fields below. - mu sync.RWMutex `state:"nosave"` - - // file is an *fd.FD containing the FD backing this endpoint. It must be - // set to nil if it has been closed. - file *fd.FD `state:"nosave"` -} - -// init performs initialization required for creating new ConnectedEndpoints and -// for restoring them. -func (c *ConnectedEndpoint) init() *syserr.Error { - family, err := unix.GetsockoptInt(c.file.FD(), unix.SOL_SOCKET, unix.SO_DOMAIN) - if err != nil { - return syserr.FromError(err) - } - - if family != unix.AF_UNIX { - // We only allow Unix sockets. - return syserr.ErrInvalidEndpointState - } - - stype, err := unix.GetsockoptInt(c.file.FD(), unix.SOL_SOCKET, unix.SO_TYPE) - if err != nil { - return syserr.FromError(err) - } - - if err := unix.SetNonblock(c.file.FD(), true); err != nil { - return syserr.FromError(err) - } - - sndbuf, err := unix.GetsockoptInt(c.file.FD(), unix.SOL_SOCKET, unix.SO_SNDBUF) - if err != nil { - return syserr.FromError(err) - } - - c.stype = linux.SockType(stype) - c.sndbuf = atomicbitops.FromInt64(int64(sndbuf)) - - return nil -} - -// NewConnectedEndpoint creates a new ConnectedEndpoint backed by a host FD -// that will pretend to be bound at a given sentry path. -// -// The caller is responsible for calling Init(). Additionaly, Release needs to -// be called twice because ConnectedEndpoint is both a transport.Receiver and -// transport.ConnectedEndpoint. -func NewConnectedEndpoint(ctx context.Context, file *fd.FD, queue *waiter.Queue, path string) (*ConnectedEndpoint, *syserr.Error) { - e := ConnectedEndpoint{ - path: path, - queue: queue, - file: file, - srfd: -1, - } - - if err := e.init(); err != nil { - return nil, err - } - - // AtomicRefCounters start off with a single reference. We need two. - e.ref.IncRef() - - e.ref.EnableLeakCheck("host.ConnectedEndpoint") - - return &e, nil -} - -// Init will do initialization required without holding other locks. -func (c *ConnectedEndpoint) Init() { - if err := fdnotifier.AddFD(int32(c.file.FD()), c.queue); err != nil { - panic(err) - } -} - -// NewSocketWithDirent allocates a new unix socket with host endpoint. -// -// This is currently only used by unsaveable Gofer nodes. -// -// NewSocketWithDirent takes ownership of f on success. -func NewSocketWithDirent(ctx context.Context, d *fs.Dirent, f *fd.FD, flags fs.FileFlags) (*fs.File, error) { - f2 := fd.New(f.FD()) - var q waiter.Queue - e, err := NewConnectedEndpoint(ctx, f2, &q, "" /* path */) - if err != nil { - f2.Release() - return nil, err.ToError() - } - - // Take ownship of the FD. - f.Release() - - e.Init() - ep := transport.NewExternal(e.stype, uniqueid.GlobalProviderFromContext(ctx), &q, e, e) - return unixsocket.NewWithDirent(ctx, d, ep, e.stype, flags), nil -} - -// newSocket allocates a new unix socket with host endpoint. -func newSocket(ctx context.Context, orgfd int, saveable bool) (*fs.File, error) { - ownedfd := orgfd - srfd := -1 - if saveable { - var err error - ownedfd, err = unix.Dup(orgfd) - if err != nil { - return nil, err - } - srfd = orgfd - } - f := fd.New(ownedfd) - var q waiter.Queue - e, err := NewConnectedEndpoint(ctx, f, &q, "" /* path */) - if err != nil { - if saveable { - f.Close() - } else { - f.Release() - } - return nil, err.ToError() - } - - e.srfd = srfd - e.Init() - ep := transport.NewExternal(e.stype, uniqueid.GlobalProviderFromContext(ctx), &q, e, e) - return unixsocket.New(ctx, ep, e.stype), nil -} - -// Send implements transport.ConnectedEndpoint.Send. -func (c *ConnectedEndpoint) Send(ctx context.Context, data [][]byte, controlMessages transport.ControlMessages, from tcpip.FullAddress) (int64, bool, *syserr.Error) { - c.mu.RLock() - defer c.mu.RUnlock() - - if !controlMessages.Empty() { - return 0, false, syserr.ErrInvalidEndpointState - } - - // Since stream sockets don't preserve message boundaries, we can write - // only as much of the message as fits in the send buffer. - truncate := c.stype == linux.SOCK_STREAM - - n, totalLen, err := fdWriteVec(c.file.FD(), data, c.SendMaxQueueSize(), truncate) - if n < totalLen && err == nil { - // The host only returns a short write if it would otherwise - // block (and only for stream sockets). - err = linuxerr.EAGAIN - } - if n > 0 && !linuxerr.Equals(linuxerr.EAGAIN, err) { - // The caller may need to block to send more data, but - // otherwise there isn't anything that can be done about an - // error with a partial write. - err = nil - } - - // There is no need for the callee to call SendNotify because fdWriteVec - // uses the host's sendmsg(2) and the host kernel's queue. - return n, false, syserr.FromError(err) -} - -// SendNotify implements transport.ConnectedEndpoint.SendNotify. -func (c *ConnectedEndpoint) SendNotify() {} - -// CloseSend implements transport.ConnectedEndpoint.CloseSend. -func (c *ConnectedEndpoint) CloseSend() { - c.mu.Lock() - defer c.mu.Unlock() - - if err := unix.Shutdown(c.file.FD(), unix.SHUT_WR); err != nil { - // A well-formed UDS shutdown can't fail. See - // net/unix/af_unix.c:unix_shutdown. - panic(fmt.Sprintf("failed write shutdown on host socket %+v: %v", c, err)) - } -} - -// CloseNotify implements transport.ConnectedEndpoint.CloseNotify. -func (c *ConnectedEndpoint) CloseNotify() {} - -// Writable implements transport.ConnectedEndpoint.Writable. -func (c *ConnectedEndpoint) Writable() bool { - c.mu.RLock() - defer c.mu.RUnlock() - - return fdnotifier.NonBlockingPoll(int32(c.file.FD()), waiter.WritableEvents)&waiter.WritableEvents != 0 -} - -// Passcred implements transport.ConnectedEndpoint.Passcred. -func (c *ConnectedEndpoint) Passcred() bool { - // We don't support credential passing for host sockets. - return false -} - -// GetLocalAddress implements transport.ConnectedEndpoint.GetLocalAddress. -func (c *ConnectedEndpoint) GetLocalAddress() (tcpip.FullAddress, tcpip.Error) { - return tcpip.FullAddress{Addr: tcpip.Address(c.path)}, nil -} - -// EventUpdate implements transport.ConnectedEndpoint.EventUpdate. -func (c *ConnectedEndpoint) EventUpdate() error { - c.mu.RLock() - defer c.mu.RUnlock() - if c.file.FD() != -1 { - if err := fdnotifier.UpdateFD(int32(c.file.FD())); err != nil { - return err - } - } - return nil -} - -// Recv implements transport.Receiver.Recv. -func (c *ConnectedEndpoint) Recv(ctx context.Context, data [][]byte, creds bool, numRights int, peek bool) (int64, int64, transport.ControlMessages, bool, tcpip.FullAddress, bool, *syserr.Error) { - c.mu.RLock() - defer c.mu.RUnlock() - - var cm unet.ControlMessage - if numRights > 0 { - cm.EnableFDs(int(numRights)) - } - - // N.B. Unix sockets don't have a receive buffer, the send buffer - // serves both purposes. - rl, ml, cl, cTrunc, err := fdReadVec(c.file.FD(), data, []byte(cm), peek, c.RecvMaxQueueSize()) - if rl > 0 && err != nil { - // We got some data, so all we need to do on error is return - // the data that we got. Short reads are fine, no need to - // block. - err = nil - } - if err != nil { - return 0, 0, transport.ControlMessages{}, false, tcpip.FullAddress{}, false, syserr.FromError(err) - } - - // There is no need for the callee to call RecvNotify because fdReadVec uses - // the host's recvmsg(2) and the host kernel's queue. - - // Trim the control data if we received less than the full amount. - if cl < uint64(len(cm)) { - cm = cm[:cl] - } - - // Avoid extra allocations in the case where there isn't any control data. - if len(cm) == 0 { - return rl, ml, transport.ControlMessages{}, cTrunc, tcpip.FullAddress{Addr: tcpip.Address(c.path)}, false, nil - } - - fds, err := cm.ExtractFDs() - if err != nil { - return 0, 0, transport.ControlMessages{}, false, tcpip.FullAddress{}, false, syserr.FromError(err) - } - - if len(fds) == 0 { - return rl, ml, transport.ControlMessages{}, cTrunc, tcpip.FullAddress{Addr: tcpip.Address(c.path)}, false, nil - } - return rl, ml, control.New(nil, nil, newSCMRights(fds)), cTrunc, tcpip.FullAddress{Addr: tcpip.Address(c.path)}, false, nil -} - -// close releases all resources related to the endpoint. -func (c *ConnectedEndpoint) close(context.Context) { - fdnotifier.RemoveFD(int32(c.file.FD())) - c.file.Close() - c.file = nil -} - -// RecvNotify implements transport.Receiver.RecvNotify. -func (c *ConnectedEndpoint) RecvNotify() {} - -// CloseRecv implements transport.Receiver.CloseRecv. -func (c *ConnectedEndpoint) CloseRecv() { - c.mu.Lock() - defer c.mu.Unlock() - - if err := unix.Shutdown(c.file.FD(), unix.SHUT_RD); err != nil { - // A well-formed UDS shutdown can't fail. See - // net/unix/af_unix.c:unix_shutdown. - panic(fmt.Sprintf("failed read shutdown on host socket %+v: %v", c, err)) - } -} - -// Readable implements transport.Receiver.Readable. -func (c *ConnectedEndpoint) Readable() bool { - c.mu.RLock() - defer c.mu.RUnlock() - - return fdnotifier.NonBlockingPoll(int32(c.file.FD()), waiter.ReadableEvents)&waiter.ReadableEvents != 0 -} - -// SendQueuedSize implements transport.Receiver.SendQueuedSize. -func (c *ConnectedEndpoint) SendQueuedSize() int64 { - // TODO(gvisor.dev/issue/273): SendQueuedSize isn't supported for host - // sockets because we don't allow the sentry to call ioctl(2). - return -1 -} - -// RecvQueuedSize implements transport.Receiver.RecvQueuedSize. -func (c *ConnectedEndpoint) RecvQueuedSize() int64 { - // TODO(gvisor.dev/issue/273): RecvQueuedSize isn't supported for host - // sockets because we don't allow the sentry to call ioctl(2). - return -1 -} - -// SendMaxQueueSize implements transport.Receiver.SendMaxQueueSize. -func (c *ConnectedEndpoint) SendMaxQueueSize() int64 { - return c.sndbuf.Load() -} - -// RecvMaxQueueSize implements transport.Receiver.RecvMaxQueueSize. -func (c *ConnectedEndpoint) RecvMaxQueueSize() int64 { - // N.B. Unix sockets don't use the receive buffer. We'll claim it is - // the same size as the send buffer. - return c.sndbuf.Load() -} - -// Release implements transport.ConnectedEndpoint.Release and transport.Receiver.Release. -func (c *ConnectedEndpoint) Release(ctx context.Context) { - c.ref.DecRefWithDestructor(ctx, c.close) -} - -// CloseUnread implements transport.ConnectedEndpoint.CloseUnread. -func (c *ConnectedEndpoint) CloseUnread() {} - -// SetSendBufferSize implements transport.ConnectedEndpoint.SetSendBufferSize. -func (c *ConnectedEndpoint) SetSendBufferSize(v int64) (newSz int64) { - // gVisor does not permit setting of SO_SNDBUF for host backed unix - // domain sockets. - return c.sndbuf.Load() -} - -// SetReceiveBufferSize implements transport.ConnectedEndpoint.SetReceiveBufferSize. -func (c *ConnectedEndpoint) SetReceiveBufferSize(v int64) (newSz int64) { - // gVisor does not permit setting of SO_RCVBUF for host backed unix - // domain sockets. Receive buffer does not have any effect for unix - // sockets and we claim to be the same as send buffer. - return c.sndbuf.Load() -} - -// LINT.ThenChange(../../socket/unix/transport/host.go) diff --git a/pkg/sentry/fs/host/socket_iovec.go b/pkg/sentry/fs/host/socket_iovec.go deleted file mode 100644 index d98e3c6d1..000000000 --- a/pkg/sentry/fs/host/socket_iovec.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/hostfd" -) - -// LINT.IfChange - -// copyToMulti copies as many bytes from src to dst as possible. -func copyToMulti(dst [][]byte, src []byte) { - for _, d := range dst { - done := copy(d, src) - src = src[done:] - if len(src) == 0 { - break - } - } -} - -// copyFromMulti copies as many bytes from src to dst as possible. -func copyFromMulti(dst []byte, src [][]byte) { - for _, s := range src { - done := copy(dst, s) - dst = dst[done:] - if len(dst) == 0 { - break - } - } -} - -// buildIovec builds an iovec slice from the given []byte slice. -// -// If truncate, truncate bufs > maxlen. Otherwise, immediately return an error. -// -// If length < the total length of bufs, err indicates why, even when returning -// a truncated iovec. -// -// If intermediate != nil, iovecs references intermediate rather than bufs and -// the caller must copy to/from bufs as necessary. -func buildIovec(bufs [][]byte, maxlen int64, truncate bool) (length int64, iovecs []unix.Iovec, intermediate []byte, err error) { - var iovsRequired int - for _, b := range bufs { - length += int64(len(b)) - if len(b) > 0 { - iovsRequired++ - } - } - - stopLen := length - if length > maxlen { - if truncate { - stopLen = maxlen - err = linuxerr.EAGAIN - } else { - return 0, nil, nil, linuxerr.EMSGSIZE - } - } - - if iovsRequired > hostfd.MaxSendRecvMsgIov { - // The kernel will reject our call if we pass this many iovs. - // Use a single intermediate buffer instead. - b := make([]byte, stopLen) - - return stopLen, []unix.Iovec{{ - Base: &b[0], - Len: uint64(stopLen), - }}, b, err - } - - var total int64 - iovecs = make([]unix.Iovec, 0, iovsRequired) - for i := range bufs { - l := len(bufs[i]) - if l == 0 { - continue - } - - stop := int64(l) - if total+stop > stopLen { - stop = stopLen - total - } - - iovecs = append(iovecs, unix.Iovec{ - Base: &bufs[i][0], - Len: uint64(stop), - }) - - total += stop - if total >= stopLen { - break - } - } - - return total, iovecs, nil, err -} - -// LINT.ThenChange(../../fsimpl/host/socket_iovec.go) diff --git a/pkg/sentry/fs/host/socket_state.go b/pkg/sentry/fs/host/socket_state.go deleted file mode 100644 index 1d3e5298a..000000000 --- a/pkg/sentry/fs/host/socket_state.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/fd" -) - -// beforeSave is invoked by stateify. -func (c *ConnectedEndpoint) beforeSave() { - if c.srfd < 0 { - panic("only host file descriptors provided at sentry startup can be saved") - } -} - -// afterLoad is invoked by stateify. -func (c *ConnectedEndpoint) afterLoad() { - f, err := unix.Dup(c.srfd) - if err != nil { - panic(fmt.Sprintf("failed to dup restored FD %d: %v", c.srfd, err)) - } - c.file = fd.New(f) - if err := c.init(); err != nil { - panic(fmt.Sprintf("Could not restore host socket FD %d: %v", c.srfd, err)) - } - c.Init() -} diff --git a/pkg/sentry/fs/host/socket_test.go b/pkg/sentry/fs/host/socket_test.go deleted file mode 100644 index f7014b6b1..000000000 --- a/pkg/sentry/fs/host/socket_test.go +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "reflect" - "testing" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -var ( - // Make sure that ConnectedEndpoint implements transport.ConnectedEndpoint. - _ = transport.ConnectedEndpoint(new(ConnectedEndpoint)) - - // Make sure that ConnectedEndpoint implements transport.Receiver. - _ = transport.Receiver(new(ConnectedEndpoint)) -) - -func getFl(fd int) (uint32, error) { - fl, _, err := unix.RawSyscall(unix.SYS_FCNTL, uintptr(fd), unix.F_GETFL, 0) - if err == 0 { - return uint32(fl), nil - } - return 0, err -} - -func TestSocketIsBlocking(t *testing.T) { - // Using socketpair here because it's already connected. - pair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) - if err != nil { - t.Fatalf("host socket creation failed: %v", err) - } - - fl, err := getFl(pair[0]) - if err != nil { - t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[0], err) - } - if fl&unix.O_NONBLOCK == unix.O_NONBLOCK { - t.Fatalf("Expected socket %v to be blocking", pair[0]) - } - if fl, err = getFl(pair[1]); err != nil { - t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[1], err) - } - if fl&unix.O_NONBLOCK == unix.O_NONBLOCK { - t.Fatalf("Expected socket %v to be blocking", pair[1]) - } - ctx := contexttest.Context(t) - sock, err := newSocket(ctx, pair[0], false) - if err != nil { - t.Fatalf("newSocket(%v) failed => %v", pair[0], err) - } - defer sock.DecRef(ctx) - // Test that the socket now is non-blocking. - if fl, err = getFl(pair[0]); err != nil { - t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[0], err) - } - if fl&unix.O_NONBLOCK != unix.O_NONBLOCK { - t.Errorf("Expected socket %v to have become non-blocking", pair[0]) - } - if fl, err = getFl(pair[1]); err != nil { - t.Fatalf("getFl: fcntl(%v, GETFL) => %v", pair[1], err) - } - if fl&unix.O_NONBLOCK == unix.O_NONBLOCK { - t.Errorf("Did not expect socket %v to become non-blocking", pair[1]) - } -} - -func TestSocketWritev(t *testing.T) { - // Using socketpair here because it's already connected. - pair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) - if err != nil { - t.Fatalf("host socket creation failed: %v", err) - } - ctx := contexttest.Context(t) - socket, err := newSocket(ctx, pair[0], false) - if err != nil { - t.Fatalf("newSocket(%v) => %v", pair[0], err) - } - defer socket.DecRef(ctx) - buf := []byte("hello world\n") - n, err := socket.Writev(contexttest.Context(t), usermem.BytesIOSequence(buf)) - if err != nil { - t.Fatalf("socket writev failed: %v", err) - } - - if n != int64(len(buf)) { - t.Fatalf("socket writev wrote incorrect bytes: %d", n) - } -} - -func TestSocketWritevLen0(t *testing.T) { - // Using socketpair here because it's already connected. - pair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) - if err != nil { - t.Fatalf("host socket creation failed: %v", err) - } - ctx := contexttest.Context(t) - socket, err := newSocket(ctx, pair[0], false) - if err != nil { - t.Fatalf("newSocket(%v) => %v", pair[0], err) - } - defer socket.DecRef(ctx) - n, err := socket.Writev(contexttest.Context(t), usermem.BytesIOSequence(nil)) - if err != nil { - t.Fatalf("socket writev failed: %v", err) - } - - if n != 0 { - t.Fatalf("socket writev wrote incorrect bytes: %d", n) - } -} - -func TestSocketSendMsgLen0(t *testing.T) { - // Using socketpair here because it's already connected. - pair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) - if err != nil { - t.Fatalf("host socket creation failed: %v", err) - } - ctx := contexttest.Context(t) - sfile, err := newSocket(ctx, pair[0], false) - if err != nil { - t.Fatalf("newSocket(%v) => %v", pair[0], err) - } - defer sfile.DecRef(ctx) - - s := sfile.FileOperations.(socket.Socket) - n, terr := s.SendMsg(nil, usermem.BytesIOSequence(nil), []byte{}, 0, false, ktime.Time{}, socket.ControlMessages{}) - if n != 0 { - t.Fatalf("socket sendmsg() failed: %v wrote: %d", terr, n) - } - - if terr != nil { - t.Fatalf("socket sendmsg() failed: %v", terr) - } -} - -func TestListen(t *testing.T) { - pair, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) - if err != nil { - t.Fatalf("unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0) => %v", err) - } - ctx := contexttest.Context(t) - sfile1, err := newSocket(ctx, pair[0], false) - if err != nil { - t.Fatalf("newSocket(%v) => %v", pair[0], err) - } - defer sfile1.DecRef(ctx) - socket1 := sfile1.FileOperations.(socket.Socket) - - sfile2, err := newSocket(ctx, pair[1], false) - if err != nil { - t.Fatalf("newSocket(%v) => %v", pair[1], err) - } - defer sfile2.DecRef(ctx) - socket2 := sfile2.FileOperations.(socket.Socket) - - // Socketpairs can not be listened to. - if err := socket1.Listen(nil, 64); err != syserr.ErrInvalidEndpointState { - t.Fatalf("socket1.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err) - } - if err := socket2.Listen(nil, 64); err != syserr.ErrInvalidEndpointState { - t.Fatalf("socket2.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err) - } - - // Create a Unix socket, do not bind it. - sock, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0) - if err != nil { - t.Fatalf("unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0) => %v", err) - } - sfile3, err := newSocket(ctx, sock, false) - if err != nil { - t.Fatalf("newSocket(%v) => %v", sock, err) - } - defer sfile3.DecRef(ctx) - socket3 := sfile3.FileOperations.(socket.Socket) - - // This socket is not bound so we can't listen on it. - if err := socket3.Listen(nil, 64); err != syserr.ErrInvalidEndpointState { - t.Fatalf("socket3.Listen(nil, 64) => %v, want syserr.ErrInvalidEndpointState", err) - } -} - -func TestPasscred(t *testing.T) { - e := &ConnectedEndpoint{} - if got, want := e.Passcred(), false; got != want { - t.Errorf("Got %#v.Passcred() = %t, want = %t", e, got, want) - } -} - -func TestGetLocalAddress(t *testing.T) { - e := &ConnectedEndpoint{path: "foo"} - want := tcpip.FullAddress{Addr: tcpip.Address("foo")} - if got, err := e.GetLocalAddress(); err != nil || got != want { - t.Errorf("Got %#v.GetLocalAddress() = %#v, %v, want = %#v, %v", e, got, err, want, nil) - } -} - -func TestQueuedSize(t *testing.T) { - e := &ConnectedEndpoint{} - tests := []struct { - name string - f func() int64 - }{ - {"SendQueuedSize", e.SendQueuedSize}, - {"RecvQueuedSize", e.RecvQueuedSize}, - } - - for _, test := range tests { - if got, want := test.f(), int64(-1); got != want { - t.Errorf("Got %#v.%s() = %d, want = %d", e, test.name, got, want) - } - } -} - -func TestRelease(t *testing.T) { - f, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0) - if err != nil { - t.Fatal("Creating socket:", err) - } - c := &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f)} - want := &ConnectedEndpoint{queue: c.queue} - ctx := contexttest.Context(t) - want.ref.DecRef(ctx) - fdnotifier.AddFD(int32(c.file.FD()), nil) - c.Release(ctx) - if !reflect.DeepEqual(c, want) { - t.Errorf("got = %#v, want = %#v", c, want) - } -} diff --git a/pkg/sentry/fs/host/socket_unsafe.go b/pkg/sentry/fs/host/socket_unsafe.go deleted file mode 100644 index 552b0d334..000000000 --- a/pkg/sentry/fs/host/socket_unsafe.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "unsafe" - - "golang.org/x/sys/unix" -) - -// LINT.IfChange - -// fdReadVec receives from fd to bufs. -// -// If the total length of bufs is > maxlen, fdReadVec will do a partial read -// and err will indicate why the message was truncated. -func fdReadVec(fd int, bufs [][]byte, control []byte, peek bool, maxlen int64) (readLen int64, msgLen int64, controlLen uint64, controlTrunc bool, err error) { - flags := uintptr(unix.MSG_DONTWAIT | unix.MSG_TRUNC) - if peek { - flags |= unix.MSG_PEEK - } - - // Always truncate the receive buffer. All socket types will truncate - // received messages. - length, iovecs, intermediate, err := buildIovec(bufs, maxlen, true) - if err != nil && len(iovecs) == 0 { - // No partial write to do, return error immediately. - return 0, 0, 0, false, err - } - - var msg unix.Msghdr - if len(control) != 0 { - msg.Control = &control[0] - msg.SetControllen(len(control)) - } - - if len(iovecs) != 0 { - msg.Iov = &iovecs[0] - msg.SetIovlen(len(iovecs)) - } - - rawN, _, e := unix.RawSyscall(unix.SYS_RECVMSG, uintptr(fd), uintptr(unsafe.Pointer(&msg)), flags) - if e != 0 { - // N.B. prioritize the syscall error over the buildIovec error. - return 0, 0, 0, false, e - } - n := int64(rawN) - - // Copy data back to bufs. - if intermediate != nil { - copyToMulti(bufs, intermediate) - } - - controlTrunc = msg.Flags&unix.MSG_CTRUNC == unix.MSG_CTRUNC - - if n > length { - return length, n, uint64(msg.Controllen), controlTrunc, nil - } - - return n, n, uint64(msg.Controllen), controlTrunc, nil -} - -// fdWriteVec sends from bufs to fd. -// -// If the total length of bufs is > maxlen && truncate, fdWriteVec will do a -// partial write and err will indicate why the message was truncated. -func fdWriteVec(fd int, bufs [][]byte, maxlen int64, truncate bool) (int64, int64, error) { - length, iovecs, intermediate, err := buildIovec(bufs, maxlen, truncate) - if err != nil && len(iovecs) == 0 { - // No partial write to do, return error immediately. - return 0, length, err - } - - // Copy data to intermediate buf. - if intermediate != nil { - copyFromMulti(intermediate, bufs) - } - - var msg unix.Msghdr - if len(iovecs) > 0 { - msg.Iov = &iovecs[0] - msg.SetIovlen(len(iovecs)) - } - - n, _, e := unix.RawSyscall(unix.SYS_SENDMSG, uintptr(fd), uintptr(unsafe.Pointer(&msg)), unix.MSG_DONTWAIT|unix.MSG_NOSIGNAL) - if e != 0 { - // N.B. prioritize the syscall error over the buildIovec error. - return 0, length, e - } - - return int64(n), length, err -} - -// LINT.ThenChange(../../fsimpl/host/socket_unsafe.go) diff --git a/pkg/sentry/fs/host/tty.go b/pkg/sentry/fs/host/tty.go deleted file mode 100644 index 4e561c5ed..000000000 --- a/pkg/sentry/fs/host/tty.go +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/unimpl" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -// LINT.IfChange - -// TTYFileOperations implements fs.FileOperations for a host file descriptor -// that wraps a TTY FD. -// -// +stateify savable -type TTYFileOperations struct { - fileOperations - - // mu protects the fields below. - mu sync.Mutex `state:"nosave"` - - // session is the session attached to this TTYFileOperations. - session *kernel.Session - - // fgProcessGroup is the foreground process group that is currently - // connected to this TTY. - fgProcessGroup *kernel.ProcessGroup - - // termios contains the terminal attributes for this TTY. - termios linux.KernelTermios -} - -// newTTYFile returns a new fs.File that wraps a TTY FD. -func newTTYFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags, iops *inodeOperations) *fs.File { - return fs.NewFile(ctx, dirent, flags, &TTYFileOperations{ - fileOperations: fileOperations{iops: iops}, - termios: linux.DefaultReplicaTermios, - }) -} - -// InitForegroundProcessGroup sets the foreground process group and session for -// the TTY. This should only be called once, after the foreground process group -// has been created, but before it has started running. -func (t *TTYFileOperations) InitForegroundProcessGroup(pg *kernel.ProcessGroup) { - t.mu.Lock() - defer t.mu.Unlock() - if t.fgProcessGroup != nil { - panic("foreground process group is already set") - } - t.fgProcessGroup = pg - t.session = pg.Session() -} - -// ForegroundProcessGroup returns the foreground process for the TTY. -func (t *TTYFileOperations) ForegroundProcessGroup() *kernel.ProcessGroup { - t.mu.Lock() - defer t.mu.Unlock() - return t.fgProcessGroup -} - -// Read implements fs.FileOperations.Read. -// -// Reading from a TTY is only allowed for foreground process groups. Background -// process groups will either get EIO or a SIGTTIN. -// -// See drivers/tty/n_tty.c:n_tty_read()=>job_control(). -func (t *TTYFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - t.mu.Lock() - defer t.mu.Unlock() - - // Are we allowed to do the read? - // drivers/tty/n_tty.c:n_tty_read()=>job_control()=>tty_check_change(). - if err := t.checkChange(ctx, linux.SIGTTIN); err != nil { - return 0, err - } - - // Do the read. - return t.fileOperations.Read(ctx, file, dst, offset) -} - -// Write implements fs.FileOperations.Write. -func (t *TTYFileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - t.mu.Lock() - defer t.mu.Unlock() - - // Check whether TOSTOP is enabled. This corresponds to the check in - // drivers/tty/n_tty.c:n_tty_write(). - if t.termios.LEnabled(linux.TOSTOP) { - if err := t.checkChange(ctx, linux.SIGTTOU); err != nil { - return 0, err - } - } - return t.fileOperations.Write(ctx, file, src, offset) -} - -// Release implements fs.FileOperations.Release. -func (t *TTYFileOperations) Release(ctx context.Context) { - t.mu.Lock() - t.fgProcessGroup = nil - t.mu.Unlock() - - t.fileOperations.Release(ctx) -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (t *TTYFileOperations) Ioctl(ctx context.Context, _ *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - task := kernel.TaskFromContext(ctx) - if task == nil { - return 0, linuxerr.ENOTTY - } - - // Ignore arg[0]. This is the real FD: - fd := t.fileOperations.iops.fileState.FD() - ioctl := args[1].Uint64() - switch ioctl { - case linux.TCGETS: - termios, err := ioctlGetTermios(fd) - if err != nil { - return 0, err - } - _, err = termios.CopyOut(task, args[2].Pointer()) - return 0, err - - case linux.TCSETS, linux.TCSETSW, linux.TCSETSF: - t.mu.Lock() - defer t.mu.Unlock() - - if err := t.checkChange(ctx, linux.SIGTTOU); err != nil { - return 0, err - } - - var termios linux.Termios - if _, err := termios.CopyIn(task, args[2].Pointer()); err != nil { - return 0, err - } - err := ioctlSetTermios(fd, ioctl, &termios) - if err == nil { - t.termios.FromTermios(termios) - } - return 0, err - - case linux.TIOCGPGRP: - // Args: pid_t *argp - // When successful, equivalent to *argp = tcgetpgrp(fd). - // Get the process group ID of the foreground process group on - // this terminal. - - pidns := kernel.PIDNamespaceFromContext(ctx) - if pidns == nil { - return 0, linuxerr.ENOTTY - } - - t.mu.Lock() - defer t.mu.Unlock() - - // Map the ProcessGroup into a ProcessGroupID in the task's PID - // namespace. - pgID := primitive.Int32(pidns.IDOfProcessGroup(t.fgProcessGroup)) - _, err := pgID.CopyOut(task, args[2].Pointer()) - return 0, err - - case linux.TIOCSPGRP: - // Args: const pid_t *argp - // Equivalent to tcsetpgrp(fd, *argp). - // Set the foreground process group ID of this terminal. - - t.mu.Lock() - defer t.mu.Unlock() - - // Check that we are allowed to set the process group. - if err := t.checkChange(ctx, linux.SIGTTOU); err != nil { - // drivers/tty/tty_io.c:tiocspgrp() converts -EIO from - // tty_check_change() to -ENOTTY. - if linuxerr.Equals(linuxerr.EIO, err) { - return 0, linuxerr.ENOTTY - } - return 0, err - } - - // Check that calling task's process group is in the TTY - // session. - if task.ThreadGroup().Session() != t.session { - return 0, linuxerr.ENOTTY - } - - var pgIDP primitive.Int32 - if _, err := pgIDP.CopyIn(task, args[2].Pointer()); err != nil { - return 0, err - } - pgID := kernel.ProcessGroupID(pgIDP) - - // pgID must be non-negative. - if pgID < 0 { - return 0, linuxerr.EINVAL - } - - // Process group with pgID must exist in this PID namespace. - pidns := task.PIDNamespace() - pg := pidns.ProcessGroupWithID(pgID) - if pg == nil { - return 0, linuxerr.ESRCH - } - - // Check that new process group is in the TTY session. - if pg.Session() != t.session { - return 0, linuxerr.EPERM - } - - t.fgProcessGroup = pg - return 0, nil - - case linux.TIOCGWINSZ: - // Args: struct winsize *argp - // Get window size. - winsize, err := ioctlGetWinsize(fd) - if err != nil { - return 0, err - } - _, err = winsize.CopyOut(task, args[2].Pointer()) - return 0, err - - case linux.TIOCSWINSZ: - // Args: const struct winsize *argp - // Set window size. - - // Unlike setting the termios, any process group (even - // background ones) can set the winsize. - - var winsize linux.Winsize - if _, err := winsize.CopyIn(task, args[2].Pointer()); err != nil { - return 0, err - } - err := ioctlSetWinsize(fd, &winsize) - return 0, err - - // Unimplemented commands. - case linux.TIOCSETD, - linux.TIOCSBRK, - linux.TIOCCBRK, - linux.TCSBRK, - linux.TCSBRKP, - linux.TIOCSTI, - linux.TIOCCONS, - linux.FIONBIO, - linux.TIOCEXCL, - linux.TIOCNXCL, - linux.TIOCGEXCL, - linux.TIOCNOTTY, - linux.TIOCSCTTY, - linux.TIOCGSID, - linux.TIOCGETD, - linux.TIOCVHANGUP, - linux.TIOCGDEV, - linux.TIOCMGET, - linux.TIOCMSET, - linux.TIOCMBIC, - linux.TIOCMBIS, - linux.TIOCGICOUNT, - linux.TCFLSH, - linux.TIOCSSERIAL, - linux.TIOCGPTPEER: - - unimpl.EmitUnimplementedEvent(ctx) - fallthrough - default: - return 0, linuxerr.ENOTTY - } -} - -// checkChange checks that the process group is allowed to read, write, or -// change the state of the TTY. -// -// This corresponds to Linux drivers/tty/tty_io.c:tty_check_change(). The logic -// is a bit convoluted, but documented inline. -// -// Preconditions: t.mu must be held. -func (t *TTYFileOperations) checkChange(ctx context.Context, sig linux.Signal) error { - task := kernel.TaskFromContext(ctx) - if task == nil { - // No task? Linux does not have an analog for this case, but - // tty_check_change only blocks specific cases and is - // surprisingly permissive. Allowing the change seems - // appropriate. - return nil - } - - tg := task.ThreadGroup() - pg := tg.ProcessGroup() - - // If the session for the task is different than the session for the - // controlling TTY, then the change is allowed. Seems like a bad idea, - // but that's exactly what linux does. - if tg.Session() != t.fgProcessGroup.Session() { - return nil - } - - // If we are the foreground process group, then the change is allowed. - if pg == t.fgProcessGroup { - return nil - } - - // We are not the foreground process group. - - // Is the provided signal blocked or ignored? - if (task.SignalMask()&linux.SignalSetOf(sig) != 0) || tg.SignalHandlers().IsIgnored(sig) { - // If the signal is SIGTTIN, then we are attempting to read - // from the TTY. Don't send the signal and return EIO. - if sig == linux.SIGTTIN { - return linuxerr.EIO - } - - // Otherwise, we are writing or changing terminal state. This is allowed. - return nil - } - - // If the process group is an orphan, return EIO. - if pg.IsOrphan() { - return linuxerr.EIO - } - - // Otherwise, send the signal to the process group and return ERESTARTSYS. - // - // Note that Linux also unconditionally sets TIF_SIGPENDING on current, - // but this isn't necessary in gVisor because the rationale given in - // 040b6362d58f "tty: fix leakage of -ERESTARTSYS to userland" doesn't - // apply: the sentry will handle -ERESTARTSYS in - // kernel.runApp.execute() even if the kernel.Task isn't interrupted. - // - // Linux ignores the result of kill_pgrp(). - _ = pg.SendSignal(kernel.SignalInfoPriv(sig)) - return linuxerr.ERESTARTSYS -} - -// LINT.ThenChange(../../fsimpl/host/tty.go) diff --git a/pkg/sentry/fs/host/util.go b/pkg/sentry/fs/host/util.go deleted file mode 100644 index f2a33cc14..000000000 --- a/pkg/sentry/fs/host/util.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "os" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" -) - -func nodeType(s *unix.Stat_t) fs.InodeType { - switch x := (s.Mode & unix.S_IFMT); x { - case unix.S_IFLNK: - return fs.Symlink - case unix.S_IFIFO: - return fs.Pipe - case unix.S_IFCHR: - return fs.CharacterDevice - case unix.S_IFBLK: - return fs.BlockDevice - case unix.S_IFSOCK: - return fs.Socket - case unix.S_IFDIR: - return fs.Directory - case unix.S_IFREG: - return fs.RegularFile - default: - // This shouldn't happen, but just in case... - log.Warningf("unknown host file type %d: assuming regular", x) - return fs.RegularFile - } -} - -func wouldBlock(s *unix.Stat_t) bool { - typ := nodeType(s) - return typ == fs.Pipe || typ == fs.Socket || typ == fs.CharacterDevice -} - -func stableAttr(s *unix.Stat_t) fs.StableAttr { - return fs.StableAttr{ - Type: nodeType(s), - DeviceID: hostFileDevice.DeviceID(), - InodeID: hostFileDevice.Map(device.MultiDeviceKey{ - Device: s.Dev, - Inode: s.Ino, - }), - BlockSize: int64(s.Blksize), - } -} - -func owner(s *unix.Stat_t) fs.FileOwner { - return fs.FileOwner{ - UID: auth.KUID(s.Uid), - GID: auth.KGID(s.Gid), - } -} - -func unstableAttr(s *unix.Stat_t) fs.UnstableAttr { - return fs.UnstableAttr{ - Size: s.Size, - Usage: s.Blocks * 512, - Perms: fs.FilePermsFromMode(linux.FileMode(s.Mode)), - Owner: owner(s), - AccessTime: ktime.FromUnix(s.Atim.Sec, s.Atim.Nsec), - ModificationTime: ktime.FromUnix(s.Mtim.Sec, s.Mtim.Nsec), - StatusChangeTime: ktime.FromUnix(s.Ctim.Sec, s.Ctim.Nsec), - Links: uint64(s.Nlink), - } -} - -type dirInfo struct { - buf []byte // buffer for directory I/O. - nbuf int // length of buf; return value from ReadDirent. - bufp int // location of next record in buf. -} - -// LINT.IfChange - -// isBlockError unwraps os errors and checks if they are caused by EAGAIN or -// EWOULDBLOCK. This is so they can be transformed into linuxerr.ErrWouldBlock. -func isBlockError(err error) bool { - if linuxerr.Equals(linuxerr.EAGAIN, err) || linuxerr.Equals(linuxerr.EWOULDBLOCK, err) { - return true - } - if pe, ok := err.(*os.PathError); ok { - return isBlockError(pe.Err) - } - return false -} - -// LINT.ThenChange(../../fsimpl/host/util.go) - -func hostEffectiveKIDs() (uint32, []uint32, error) { - gids, err := os.Getgroups() - if err != nil { - return 0, nil, err - } - egids := make([]uint32, len(gids)) - for i, gid := range gids { - egids[i] = uint32(gid) - } - return uint32(os.Geteuid()), append(egids, uint32(os.Getegid())), nil -} - -var hostUID uint32 -var hostGIDs []uint32 - -func init() { - hostUID, hostGIDs, _ = hostEffectiveKIDs() -} diff --git a/pkg/sentry/fs/host/util_amd64_unsafe.go b/pkg/sentry/fs/host/util_amd64_unsafe.go deleted file mode 100644 index e90629f4e..000000000 --- a/pkg/sentry/fs/host/util_amd64_unsafe.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 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. - -//go:build amd64 -// +build amd64 - -package host - -import ( - "unsafe" - - "golang.org/x/sys/unix" -) - -func fstatat(fd int, name string, flags int) (unix.Stat_t, error) { - var stat unix.Stat_t - namePtr, err := unix.BytePtrFromString(name) - if err != nil { - return stat, err - } - _, _, errno := unix.Syscall6( - unix.SYS_NEWFSTATAT, - uintptr(fd), - uintptr(unsafe.Pointer(namePtr)), - uintptr(unsafe.Pointer(&stat)), - uintptr(flags), - 0, 0) - if errno != 0 { - return stat, errno - } - return stat, nil -} diff --git a/pkg/sentry/fs/host/util_arm64_unsafe.go b/pkg/sentry/fs/host/util_arm64_unsafe.go deleted file mode 100644 index 9fbb93726..000000000 --- a/pkg/sentry/fs/host/util_arm64_unsafe.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 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. - -//go:build arm64 -// +build arm64 - -package host - -import ( - "unsafe" - - "golang.org/x/sys/unix" -) - -func fstatat(fd int, name string, flags int) (unix.Stat_t, error) { - var stat unix.Stat_t - namePtr, err := unix.BytePtrFromString(name) - if err != nil { - return stat, err - } - _, _, errno := unix.Syscall6( - unix.SYS_FSTATAT, - uintptr(fd), - uintptr(unsafe.Pointer(namePtr)), - uintptr(unsafe.Pointer(&stat)), - uintptr(flags), - 0, 0) - if errno != 0 { - return stat, errno - } - return stat, nil -} diff --git a/pkg/sentry/fs/host/util_unsafe.go b/pkg/sentry/fs/host/util_unsafe.go deleted file mode 100644 index 3c5e3ed6d..000000000 --- a/pkg/sentry/fs/host/util_unsafe.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "unsafe" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/fs" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" -) - -// NulByte is a single NUL byte. It is passed to readlinkat as an empty string. -var NulByte byte = '\x00' - -func readLink(fd int) (string, error) { - // Buffer sizing copied from os.Readlink. - for l := 128; ; l *= 2 { - b := make([]byte, l) - n, _, errno := unix.Syscall6( - unix.SYS_READLINKAT, - uintptr(fd), - uintptr(unsafe.Pointer(&NulByte)), // "" - uintptr(unsafe.Pointer(&b[0])), - uintptr(l), - 0, 0) - if errno != 0 { - return "", errno - } - if n < uintptr(l) { - return string(b[:n]), nil - } - } -} - -func timespecFromTimestamp(t ktime.Time, omit, setSysTime bool) unix.Timespec { - if omit { - return unix.Timespec{0, linux.UTIME_OMIT} - } - if setSysTime { - return unix.Timespec{0, linux.UTIME_NOW} - } - return unix.NsecToTimespec(t.Nanoseconds()) -} - -func setTimestamps(fd int, ts fs.TimeSpec) error { - if ts.ATimeOmit && ts.MTimeOmit { - return nil - } - var sts [2]unix.Timespec - sts[0] = timespecFromTimestamp(ts.ATime, ts.ATimeOmit, ts.ATimeSetSystemTime) - sts[1] = timespecFromTimestamp(ts.MTime, ts.MTimeOmit, ts.MTimeSetSystemTime) - _, _, errno := unix.Syscall6( - unix.SYS_UTIMENSAT, - uintptr(fd), - 0, /* path */ - uintptr(unsafe.Pointer(&sts)), - 0, /* flags */ - 0, 0) - if errno != 0 { - return errno - } - return nil -} diff --git a/pkg/sentry/fs/host/wait_test.go b/pkg/sentry/fs/host/wait_test.go deleted file mode 100644 index db4590ec6..000000000 --- a/pkg/sentry/fs/host/wait_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2018 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. - -package host - -import ( - "testing" - "time" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/waiter" -) - -func TestWait(t *testing.T) { - var fds [2]int - err := unix.Pipe(fds[:]) - if err != nil { - t.Fatalf("Unable to create pipe: %v", err) - } - - defer unix.Close(fds[1]) - - ctx := contexttest.Context(t) - file, err := NewFile(ctx, fds[0]) - if err != nil { - unix.Close(fds[0]) - t.Fatalf("NewFile failed: %v", err) - } - - defer file.DecRef(ctx) - - r := file.Readiness(waiter.ReadableEvents) - if r != 0 { - t.Fatalf("File is ready for read when it shouldn't be.") - } - - e, ch := waiter.NewChannelEntry(waiter.ReadableEvents) - file.EventRegister(&e) - defer file.EventUnregister(&e) - - // Check that there are no notifications yet. - if len(ch) != 0 { - t.Fatalf("Channel is non-empty") - } - - // Write to the pipe, so it should be writable now. - unix.Write(fds[1], []byte{1}) - - // Check that we get a notification. We need to yield the current thread - // so that the fdnotifier can deliver notifications, so we use a - // 1-second timeout instead of just checking the length of the channel. - select { - case <-ch: - case <-time.After(1 * time.Second): - t.Fatalf("Channel not notified") - } -} diff --git a/pkg/sentry/fs/inode.go b/pkg/sentry/fs/inode.go index f280a6395..61cbfadd7 100644 --- a/pkg/sentry/fs/inode.go +++ b/pkg/sentry/fs/inode.go @@ -20,7 +20,7 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/fsmetric" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/memmap" diff --git a/pkg/sentry/fs/inode_overlay_test.go b/pkg/sentry/fs/inode_overlay_test.go deleted file mode 100644 index a3800d700..000000000 --- a/pkg/sentry/fs/inode_overlay_test.go +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright 2018 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. - -package fs_test - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest" -) - -func TestLookup(t *testing.T) { - ctx := contexttest.Context(t) - for _, test := range []struct { - // Test description. - desc string - - // Lookup parameters. - dir *fs.Inode - name string - - // Want from lookup. - found bool - hasUpper bool - hasLower bool - }{ - { - desc: "no upper, lower has name", - dir: fs.NewTestOverlayDir(ctx, - nil, /* upper */ - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* lower */ - false /* revalidate */), - name: "a", - found: true, - hasUpper: false, - hasLower: true, - }, - { - desc: "no lower, upper has name", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* upper */ - nil, /* lower */ - false /* revalidate */), - name: "a", - found: true, - hasUpper: true, - hasLower: false, - }, - { - desc: "upper and lower, only lower has name", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - { - name: "b", - dir: false, - }, - }, nil), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* lower */ - false /* revalidate */), - name: "a", - found: true, - hasUpper: false, - hasLower: true, - }, - { - desc: "upper and lower, only upper has name", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - { - name: "b", - dir: false, - }, - }, nil), /* lower */ - false /* revalidate */), - name: "a", - found: true, - hasUpper: true, - hasLower: false, - }, - { - desc: "upper and lower, both have file", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* lower */ - false /* revalidate */), - name: "a", - found: true, - hasUpper: true, - hasLower: false, - }, - { - desc: "upper and lower, both have directory", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: true, - }, - }, nil), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: true, - }, - }, nil), /* lower */ - false /* revalidate */), - name: "a", - found: true, - hasUpper: true, - hasLower: true, - }, - { - desc: "upper and lower, upper negative masks lower file", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, nil, []string{"a"}), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* lower */ - false /* revalidate */), - name: "a", - found: false, - hasUpper: false, - hasLower: false, - }, - { - desc: "upper and lower, upper negative does not mask lower file", - dir: fs.NewTestOverlayDir(ctx, - newTestRamfsDir(ctx, nil, []string{"b"}), /* upper */ - newTestRamfsDir(ctx, []dirContent{ - { - name: "a", - dir: false, - }, - }, nil), /* lower */ - false /* revalidate */), - name: "a", - found: true, - hasUpper: false, - hasLower: true, - }, - } { - t.Run(test.desc, func(t *testing.T) { - dirent, err := test.dir.Lookup(ctx, test.name) - if test.found && (linuxerr.Equals(linuxerr.ENOENT, err) || dirent.IsNegative()) { - t.Fatalf("lookup %q expected to find positive dirent, got dirent %v err %v", test.name, dirent, err) - } - if !test.found { - if !linuxerr.Equals(linuxerr.ENOENT, err) && !dirent.IsNegative() { - t.Errorf("lookup %q expected to return ENOENT or negative dirent, got dirent %v err %v", test.name, dirent, err) - } - // Nothing more to check. - return - } - if hasUpper := dirent.Inode.TestHasUpperFS(); hasUpper != test.hasUpper { - t.Fatalf("lookup got upper filesystem %v, want %v", hasUpper, test.hasUpper) - } - if hasLower := dirent.Inode.TestHasLowerFS(); hasLower != test.hasLower { - t.Errorf("lookup got lower filesystem %v, want %v", hasLower, test.hasLower) - } - }) - } -} - -func TestLookupRevalidation(t *testing.T) { - // File name used in the tests. - fileName := "foofile" - ctx := contexttest.Context(t) - for _, tc := range []struct { - // Test description. - desc string - - // Upper and lower fs for the overlay. - upper *fs.Inode - lower *fs.Inode - - // Whether the upper requires revalidation. - revalidate bool - - // Whether we should get the same dirent on second lookup. - wantSame bool - }{ - { - desc: "file from upper with no revalidation", - upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - lower: newTestRamfsDir(ctx, nil, nil), - revalidate: false, - wantSame: true, - }, - { - desc: "file from upper with revalidation", - upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - lower: newTestRamfsDir(ctx, nil, nil), - revalidate: true, - wantSame: false, - }, - { - desc: "file from lower with no revalidation", - upper: newTestRamfsDir(ctx, nil, nil), - lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - revalidate: false, - wantSame: true, - }, - { - desc: "file from lower with revalidation", - upper: newTestRamfsDir(ctx, nil, nil), - lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - revalidate: true, - // The file does not exist in the upper, so we do not - // need to revalidate it. - wantSame: true, - }, - { - desc: "file from upper and lower with no revalidation", - upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - revalidate: false, - wantSame: true, - }, - { - desc: "file from upper and lower with revalidation", - upper: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - lower: newTestRamfsDir(ctx, []dirContent{{name: fileName}}, nil), - revalidate: true, - wantSame: false, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - root := fs.NewDirent(ctx, newTestRamfsDir(ctx, nil, nil), "root") - ctx = &rootContext{ - Context: ctx, - root: root, - } - overlay := fs.NewDirent(ctx, fs.NewTestOverlayDir(ctx, tc.upper, tc.lower, tc.revalidate), "overlay") - // Lookup the file twice through the overlay. - first, err := overlay.Walk(ctx, root, fileName) - if err != nil { - t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err) - } - second, err := overlay.Walk(ctx, root, fileName) - if err != nil { - t.Fatalf("overlay.Walk(%q) failed: %v", fileName, err) - } - - if tc.wantSame && first != second { - t.Errorf("dirent lookup got different dirents, wanted same\nfirst=%+v\nsecond=%+v", first, second) - } else if !tc.wantSame && first == second { - t.Errorf("dirent lookup got the same dirent, wanted different: %+v", first) - } - }) - } -} - -func TestCacheFlush(t *testing.T) { - ctx := contexttest.Context(t) - - // Upper and lower each have a file. - upperFileName := "file-from-upper" - lowerFileName := "file-from-lower" - upper := newTestRamfsDir(ctx, []dirContent{{name: upperFileName}}, nil) - lower := newTestRamfsDir(ctx, []dirContent{{name: lowerFileName}}, nil) - - overlay := fs.NewTestOverlayDir(ctx, upper, lower, true /* revalidate */) - - mns, err := fs.NewMountNamespace(ctx, overlay) - if err != nil { - t.Fatalf("NewMountNamespace failed: %v", err) - } - root := mns.Root() - defer root.DecRef(ctx) - - ctx = &rootContext{ - Context: ctx, - root: root, - } - - for _, fileName := range []string{upperFileName, lowerFileName} { - // Walk to the file. - maxTraversals := uint(0) - dirent, err := mns.FindInode(ctx, root, nil, fileName, &maxTraversals) - if err != nil { - t.Fatalf("FindInode(%q) failed: %v", fileName, err) - } - - // Get a file from the dirent. - file, err := dirent.Inode.GetFile(ctx, dirent, fs.FileFlags{Read: true}) - if err != nil { - t.Fatalf("GetFile() failed: %v", err) - } - - // The dirent should have 3 refs, one from us, one from the - // file, and one from the dirent cache. - // dirent cache. - if got, want := dirent.ReadRefs(), 3; int(got) != want { - t.Errorf("dirent.ReadRefs() got %d want %d", got, want) - } - - // Drop the file reference. - file.DecRef(ctx) - - // Dirent should have 2 refs left. - if got, want := dirent.ReadRefs(), 2; int(got) != want { - t.Errorf("dirent.ReadRefs() got %d want %d", got, want) - } - - // Flush the dirent cache. - mns.FlushMountSourceRefs() - - // Dirent should have 1 ref left from the dirent cache. - if got, want := dirent.ReadRefs(), 1; int(got) != want { - t.Errorf("dirent.ReadRefs() got %d want %d", got, want) - } - - // Drop our ref. - dirent.DecRef(ctx) - - // We should be back to zero refs. - if got, want := dirent.ReadRefs(), 0; int(got) != want { - t.Errorf("dirent.ReadRefs() got %d want %d", got, want) - } - } - -} - -type dir struct { - fs.InodeOperations - - // List of negative child names. - negative []string - - // ReaddirCalled records whether Readdir was called on a file - // corresponding to this inode. - ReaddirCalled bool -} - -// GetXattr implements InodeOperations.GetXattr. -func (d *dir) GetXattr(_ context.Context, _ *fs.Inode, name string, _ uint64) (string, error) { - for _, n := range d.negative { - if name == fs.XattrOverlayWhiteout(n) { - return "y", nil - } - } - return "", linuxerr.ENOATTR -} - -// GetFile implements InodeOperations.GetFile. -func (d *dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - file, err := d.InodeOperations.GetFile(ctx, dirent, flags) - if err != nil { - return nil, err - } - defer file.DecRef(ctx) - // Wrap the file's FileOperations in a dirFile. - fops := &dirFile{ - FileOperations: file.FileOperations, - inode: d, - } - return fs.NewFile(ctx, dirent, flags, fops), nil -} - -type dirContent struct { - name string - dir bool -} - -type dirFile struct { - fs.FileOperations - inode *dir -} - -type inode struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotAllocatable `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeNotTruncatable `state:"nosave"` - fsutil.InodeNotVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - fsutil.InodeStaticFileGetter -} - -// Readdir implements fs.FileOperations.Readdir. It sets the ReaddirCalled -// field on the inode. -func (f *dirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) { - f.inode.ReaddirCalled = true - return f.FileOperations.Readdir(ctx, file, ser) -} - -func newTestRamfsInode(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - inode := fs.NewInode(ctx, &inode{ - InodeStaticFileGetter: fsutil.InodeStaticFileGetter{ - Contents: []byte("foobar"), - }, - }, msrc, fs.StableAttr{Type: fs.RegularFile}) - return inode -} - -func newTestRamfsDir(ctx context.Context, contains []dirContent, negative []string) *fs.Inode { - msrc := fs.NewPseudoMountSource(ctx) - contents := make(map[string]*fs.Inode) - for _, c := range contains { - if c.dir { - contents[c.name] = newTestRamfsDir(ctx, nil, nil) - } else { - contents[c.name] = newTestRamfsInode(ctx, msrc) - } - } - dops := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermissions{ - User: fs.PermMask{Read: true, Execute: true}, - }) - return fs.NewInode(ctx, &dir{ - InodeOperations: dops, - negative: negative, - }, msrc, fs.StableAttr{Type: fs.Directory}) -} diff --git a/pkg/sentry/fs/mount_test.go b/pkg/sentry/fs/mount_test.go deleted file mode 100644 index 6c296f5d0..000000000 --- a/pkg/sentry/fs/mount_test.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright 2018 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. - -package fs - -import ( - "fmt" - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/contexttest" -) - -// cacheReallyContains iterates through the dirent cache to determine whether -// it contains the given dirent. -func cacheReallyContains(cache *DirentCache, d *Dirent) bool { - for i := cache.list.Front(); i != nil; i = i.Next() { - if i == d { - return true - } - } - return false -} - -func mountPathsAre(ctx context.Context, root *Dirent, got []*Mount, want ...string) error { - gotPaths := make(map[string]struct{}, len(got)) - gotStr := make([]string, len(got)) - for i, g := range got { - if groot := g.Root(); groot != nil { - name, _ := groot.FullName(root) - groot.DecRef(ctx) - gotStr[i] = name - gotPaths[name] = struct{}{} - } - } - if len(got) != len(want) { - return fmt.Errorf("mount paths are different, got: %q, want: %q", gotStr, want) - } - for _, w := range want { - if _, ok := gotPaths[w]; !ok { - return fmt.Errorf("no mount with path %q found", w) - } - } - return nil -} - -// TestMountSourceOnlyCachedOnce tests that a Dirent that is mounted over only ends -// up in a single Dirent Cache. NOTE(b/63848693): Having a dirent in multiple -// caches causes major consistency issues. -func TestMountSourceOnlyCachedOnce(t *testing.T) { - ctx := contexttest.Context(t) - - rootCache := NewDirentCache(100) - rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{ - Type: Directory, - }) - mm, err := NewMountNamespace(ctx, rootInode) - if err != nil { - t.Fatalf("NewMountNamespace failed: %v", err) - } - rootDirent := mm.Root() - defer rootDirent.DecRef(ctx) - - // Get a child of the root which we will mount over. Note that the - // MockInodeOperations causes Walk to always succeed. - child, err := rootDirent.Walk(ctx, rootDirent, "child") - if err != nil { - t.Fatalf("failed to walk to child dirent: %v", err) - } - child.maybeExtendReference() // Cache. - - // Ensure that the root cache contains the child. - if !cacheReallyContains(rootCache, child) { - t.Errorf("wanted rootCache to contain child dirent, but it did not") - } - - // Create a new cache and inode, and mount it over child. - submountCache := NewDirentCache(100) - submountInode := NewMockInode(ctx, NewMockMountSource(submountCache), StableAttr{ - Type: Directory, - }) - if err := mm.Mount(ctx, child, submountInode); err != nil { - t.Fatalf("failed to mount over child: %v", err) - } - - // Walk to the child again. - child2, err := rootDirent.Walk(ctx, rootDirent, "child") - if err != nil { - t.Fatalf("failed to walk to child dirent: %v", err) - } - - // Should have a different Dirent than before. - if child == child2 { - t.Fatalf("expected %v not equal to %v, but they are the same", child, child2) - } - - // Neither of the caches should no contain the child. - if cacheReallyContains(rootCache, child) { - t.Errorf("wanted rootCache not to contain child dirent, but it did") - } - if cacheReallyContains(submountCache, child) { - t.Errorf("wanted submountCache not to contain child dirent, but it did") - } -} - -func TestAllMountsUnder(t *testing.T) { - ctx := contexttest.Context(t) - - rootCache := NewDirentCache(100) - rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{ - Type: Directory, - }) - mm, err := NewMountNamespace(ctx, rootInode) - if err != nil { - t.Fatalf("NewMountNamespace failed: %v", err) - } - rootDirent := mm.Root() - defer rootDirent.DecRef(ctx) - - // Add mounts at the following paths: - paths := []string{ - "/foo", - "/foo/bar", - "/foo/bar/baz", - "/foo/qux", - "/waldo", - } - - var maxTraversals uint - for _, p := range paths { - maxTraversals = 0 - d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals) - if err != nil { - t.Fatalf("could not find path %q in mount manager: %v", p, err) - } - - submountInode := NewMockInode(ctx, NewMockMountSource(nil), StableAttr{ - Type: Directory, - }) - if err := mm.Mount(ctx, d, submountInode); err != nil { - t.Fatalf("could not mount at %q: %v", p, err) - } - d.DecRef(ctx) - } - - // mm root should contain all submounts (and does not include the root mount). - rootMnt := mm.FindMount(rootDirent) - submounts := mm.AllMountsUnder(rootMnt) - allPaths := append(paths, "/") - if err := mountPathsAre(ctx, rootDirent, submounts, allPaths...); err != nil { - t.Error(err) - } - - // Each mount should have a unique ID. - foundIDs := make(map[uint64]struct{}) - for _, m := range submounts { - if _, ok := foundIDs[m.ID]; ok { - t.Errorf("got multiple mounts with id %d", m.ID) - } - foundIDs[m.ID] = struct{}{} - } - - // Root mount should have no parent. - if p := rootMnt.ParentID; p != invalidMountID { - t.Errorf("root.Parent got %v wanted nil", p) - } - - // Check that "foo" mount has 3 children. - maxTraversals = 0 - d, err := mm.FindLink(ctx, rootDirent, nil, "/foo", &maxTraversals) - if err != nil { - t.Fatalf("could not find path %q in mount manager: %v", "/foo", err) - } - defer d.DecRef(ctx) - submounts = mm.AllMountsUnder(mm.FindMount(d)) - if err := mountPathsAre(ctx, rootDirent, submounts, "/foo", "/foo/bar", "/foo/qux", "/foo/bar/baz"); err != nil { - t.Error(err) - } - - // "waldo" mount should have no children. - maxTraversals = 0 - waldo, err := mm.FindLink(ctx, rootDirent, nil, "/waldo", &maxTraversals) - if err != nil { - t.Fatalf("could not find path %q in mount manager: %v", "/waldo", err) - } - defer waldo.DecRef(ctx) - submounts = mm.AllMountsUnder(mm.FindMount(waldo)) - if err := mountPathsAre(ctx, rootDirent, submounts, "/waldo"); err != nil { - t.Error(err) - } -} - -func TestUnmount(t *testing.T) { - ctx := contexttest.Context(t) - - rootCache := NewDirentCache(100) - rootInode := NewMockInode(ctx, NewMockMountSource(rootCache), StableAttr{ - Type: Directory, - }) - mm, err := NewMountNamespace(ctx, rootInode) - if err != nil { - t.Fatalf("NewMountNamespace failed: %v", err) - } - rootDirent := mm.Root() - defer rootDirent.DecRef(ctx) - - // Add mounts at the following paths: - paths := []string{ - "/foo", - "/foo/bar", - "/foo/bar/goo", - "/foo/bar/goo/abc", - "/foo/abc", - "/foo/def", - "/waldo", - "/wally", - } - - var maxTraversals uint - for _, p := range paths { - maxTraversals = 0 - d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals) - if err != nil { - t.Fatalf("could not find path %q in mount manager: %v", p, err) - } - - submountInode := NewMockInode(ctx, NewMockMountSource(nil), StableAttr{ - Type: Directory, - }) - if err := mm.Mount(ctx, d, submountInode); err != nil { - t.Fatalf("could not mount at %q: %v", p, err) - } - d.DecRef(ctx) - } - - allPaths := make([]string, len(paths)+1) - allPaths[0] = "/" - copy(allPaths[1:], paths) - - rootMnt := mm.FindMount(rootDirent) - for i := len(paths) - 1; i >= 0; i-- { - maxTraversals = 0 - p := paths[i] - d, err := mm.FindLink(ctx, rootDirent, nil, p, &maxTraversals) - if err != nil { - t.Fatalf("could not find path %q in mount manager: %v", p, err) - } - - if err := mm.Unmount(ctx, d, false); err != nil { - t.Fatalf("could not unmount at %q: %v", p, err) - } - d.DecRef(ctx) - - // Remove the path that has been unmounted and the check that the remaining - // mounts are still there. - allPaths = allPaths[:len(allPaths)-1] - submounts := mm.AllMountsUnder(rootMnt) - if err := mountPathsAre(ctx, rootDirent, submounts, allPaths...); err != nil { - t.Error(err) - } - } -} diff --git a/pkg/sentry/fs/mounts_test.go b/pkg/sentry/fs/mounts_test.go deleted file mode 100644 index b825b786a..000000000 --- a/pkg/sentry/fs/mounts_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 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. - -package fs_test - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest" -) - -// Creates a new MountNamespace with filesystem: -// -// / (root dir) -// |-foo (dir) -// |-bar (file) -func createMountNamespace(ctx context.Context) (*fs.MountNamespace, error) { - perms := fs.FilePermsFromMode(0777) - m := fs.NewPseudoMountSource(ctx) - - barFile := fsutil.NewSimpleFileInode(ctx, fs.RootOwner, perms, 0) - fooDir := ramfs.NewDir(ctx, map[string]*fs.Inode{ - "bar": fs.NewInode(ctx, barFile, m, fs.StableAttr{Type: fs.RegularFile}), - }, fs.RootOwner, perms) - rootDir := ramfs.NewDir(ctx, map[string]*fs.Inode{ - "foo": fs.NewInode(ctx, fooDir, m, fs.StableAttr{Type: fs.Directory}), - }, fs.RootOwner, perms) - - return fs.NewMountNamespace(ctx, fs.NewInode(ctx, rootDir, m, fs.StableAttr{Type: fs.Directory})) -} - -func TestFindLink(t *testing.T) { - ctx := contexttest.Context(t) - mm, err := createMountNamespace(ctx) - if err != nil { - t.Fatalf("createMountNamespace failed: %v", err) - } - - root := mm.Root() - defer root.DecRef(ctx) - foo, err := root.Walk(ctx, root, "foo") - if err != nil { - t.Fatalf("Error walking to foo: %v", err) - } - - // Positive cases. - for _, tc := range []struct { - findPath string - wd *fs.Dirent - wantPath string - }{ - {".", root, "/"}, - {".", foo, "/foo"}, - {"..", foo, "/"}, - {"../../..", foo, "/"}, - {"///foo", foo, "/foo"}, - {"/foo", foo, "/foo"}, - {"/foo/bar", foo, "/foo/bar"}, - {"/foo/.///./bar", foo, "/foo/bar"}, - {"/foo///bar", foo, "/foo/bar"}, - {"/foo/../foo/bar", foo, "/foo/bar"}, - {"foo/bar", root, "/foo/bar"}, - {"foo////bar", root, "/foo/bar"}, - {"bar", foo, "/foo/bar"}, - } { - wdPath, _ := tc.wd.FullName(root) - maxTraversals := uint(0) - if d, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err != nil { - t.Errorf("FindLink(%q, wd=%q) failed: %v", tc.findPath, wdPath, err) - } else if got, _ := d.FullName(root); got != tc.wantPath { - t.Errorf("FindLink(%q, wd=%q) got dirent %q, want %q", tc.findPath, wdPath, got, tc.wantPath) - } - } - - // Negative cases. - for _, tc := range []struct { - findPath string - wd *fs.Dirent - }{ - {"bar", root}, - {"/bar", root}, - {"/foo/../../bar", root}, - {"foo", foo}, - } { - wdPath, _ := tc.wd.FullName(root) - maxTraversals := uint(0) - if _, err := mm.FindLink(ctx, root, tc.wd, tc.findPath, &maxTraversals); err == nil { - t.Errorf("FindLink(%q, wd=%q) did not return error", tc.findPath, wdPath) - } - } -} diff --git a/pkg/sentry/fs/path_test.go b/pkg/sentry/fs/path_test.go deleted file mode 100644 index e6f57ebba..000000000 --- a/pkg/sentry/fs/path_test.go +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2018 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. - -package fs - -import ( - "testing" -) - -// TestSplitLast tests variants of path splitting. -func TestSplitLast(t *testing.T) { - cases := []struct { - path string - dir string - file string - }{ - {path: "/", dir: "/", file: "."}, - {path: "/.", dir: "/", file: "."}, - {path: "/./", dir: "/", file: "."}, - {path: "/./.", dir: "/.", file: "."}, - {path: "/././", dir: "/.", file: "."}, - {path: "/./..", dir: "/.", file: ".."}, - {path: "/./../", dir: "/.", file: ".."}, - {path: "/..", dir: "/", file: ".."}, - {path: "/../", dir: "/", file: ".."}, - {path: "/../.", dir: "/..", file: "."}, - {path: "/.././", dir: "/..", file: "."}, - {path: "/../..", dir: "/..", file: ".."}, - {path: "/../../", dir: "/..", file: ".."}, - - {path: "", dir: ".", file: "."}, - {path: ".", dir: ".", file: "."}, - {path: "./", dir: ".", file: "."}, - {path: "./.", dir: ".", file: "."}, - {path: "././", dir: ".", file: "."}, - {path: "./..", dir: ".", file: ".."}, - {path: "./../", dir: ".", file: ".."}, - {path: "..", dir: ".", file: ".."}, - {path: "../", dir: ".", file: ".."}, - {path: "../.", dir: "..", file: "."}, - {path: ".././", dir: "..", file: "."}, - {path: "../..", dir: "..", file: ".."}, - {path: "../../", dir: "..", file: ".."}, - - {path: "/foo", dir: "/", file: "foo"}, - {path: "/foo/", dir: "/", file: "foo"}, - {path: "/foo/.", dir: "/foo", file: "."}, - {path: "/foo/./", dir: "/foo", file: "."}, - {path: "/foo/./.", dir: "/foo/.", file: "."}, - {path: "/foo/./..", dir: "/foo/.", file: ".."}, - {path: "/foo/..", dir: "/foo", file: ".."}, - {path: "/foo/../", dir: "/foo", file: ".."}, - {path: "/foo/../.", dir: "/foo/..", file: "."}, - {path: "/foo/../..", dir: "/foo/..", file: ".."}, - - {path: "/foo/bar", dir: "/foo", file: "bar"}, - {path: "/foo/bar/", dir: "/foo", file: "bar"}, - {path: "/foo/bar/.", dir: "/foo/bar", file: "."}, - {path: "/foo/bar/./", dir: "/foo/bar", file: "."}, - {path: "/foo/bar/./.", dir: "/foo/bar/.", file: "."}, - {path: "/foo/bar/./..", dir: "/foo/bar/.", file: ".."}, - {path: "/foo/bar/..", dir: "/foo/bar", file: ".."}, - {path: "/foo/bar/../", dir: "/foo/bar", file: ".."}, - {path: "/foo/bar/../.", dir: "/foo/bar/..", file: "."}, - {path: "/foo/bar/../..", dir: "/foo/bar/..", file: ".."}, - - {path: "foo", dir: ".", file: "foo"}, - {path: "foo", dir: ".", file: "foo"}, - {path: "foo/", dir: ".", file: "foo"}, - {path: "foo/.", dir: "foo", file: "."}, - {path: "foo/./", dir: "foo", file: "."}, - {path: "foo/./.", dir: "foo/.", file: "."}, - {path: "foo/./..", dir: "foo/.", file: ".."}, - {path: "foo/..", dir: "foo", file: ".."}, - {path: "foo/../", dir: "foo", file: ".."}, - {path: "foo/../.", dir: "foo/..", file: "."}, - {path: "foo/../..", dir: "foo/..", file: ".."}, - {path: "foo/", dir: ".", file: "foo"}, - {path: "foo/.", dir: "foo", file: "."}, - - {path: "foo/bar", dir: "foo", file: "bar"}, - {path: "foo/bar/", dir: "foo", file: "bar"}, - {path: "foo/bar/.", dir: "foo/bar", file: "."}, - {path: "foo/bar/./", dir: "foo/bar", file: "."}, - {path: "foo/bar/./.", dir: "foo/bar/.", file: "."}, - {path: "foo/bar/./..", dir: "foo/bar/.", file: ".."}, - {path: "foo/bar/..", dir: "foo/bar", file: ".."}, - {path: "foo/bar/../", dir: "foo/bar", file: ".."}, - {path: "foo/bar/../.", dir: "foo/bar/..", file: "."}, - {path: "foo/bar/../..", dir: "foo/bar/..", file: ".."}, - {path: "foo/bar/", dir: "foo", file: "bar"}, - {path: "foo/bar/.", dir: "foo/bar", file: "."}, - } - - for _, c := range cases { - dir, file := SplitLast(c.path) - if dir != c.dir || file != c.file { - t.Errorf("SplitLast(%q) got (%q, %q), expected (%q, %q)", c.path, dir, file, c.dir, c.file) - } - } -} - -// TestSplitFirst tests variants of path splitting. -func TestSplitFirst(t *testing.T) { - cases := []struct { - path string - first string - remainder string - }{ - {path: "/", first: "/", remainder: ""}, - {path: "/.", first: "/", remainder: "."}, - {path: "///.", first: "/", remainder: "//."}, - {path: "/.///", first: "/", remainder: "."}, - {path: "/./.", first: "/", remainder: "./."}, - {path: "/././", first: "/", remainder: "./."}, - {path: "/./..", first: "/", remainder: "./.."}, - {path: "/./../", first: "/", remainder: "./.."}, - {path: "/..", first: "/", remainder: ".."}, - {path: "/../", first: "/", remainder: ".."}, - {path: "/../.", first: "/", remainder: "../."}, - {path: "/.././", first: "/", remainder: "../."}, - {path: "/../..", first: "/", remainder: "../.."}, - {path: "/../../", first: "/", remainder: "../.."}, - - {path: "", first: ".", remainder: ""}, - {path: ".", first: ".", remainder: ""}, - {path: "./", first: ".", remainder: ""}, - {path: ".///", first: ".", remainder: ""}, - {path: "./.", first: ".", remainder: "."}, - {path: "././", first: ".", remainder: "."}, - {path: "./..", first: ".", remainder: ".."}, - {path: "./../", first: ".", remainder: ".."}, - {path: "..", first: "..", remainder: ""}, - {path: "../", first: "..", remainder: ""}, - {path: "../.", first: "..", remainder: "."}, - {path: ".././", first: "..", remainder: "."}, - {path: "../..", first: "..", remainder: ".."}, - {path: "../../", first: "..", remainder: ".."}, - - {path: "/foo", first: "/", remainder: "foo"}, - {path: "/foo/", first: "/", remainder: "foo"}, - {path: "/foo///", first: "/", remainder: "foo"}, - {path: "/foo/.", first: "/", remainder: "foo/."}, - {path: "/foo/./", first: "/", remainder: "foo/."}, - {path: "/foo/./.", first: "/", remainder: "foo/./."}, - {path: "/foo/./..", first: "/", remainder: "foo/./.."}, - {path: "/foo/..", first: "/", remainder: "foo/.."}, - {path: "/foo/../", first: "/", remainder: "foo/.."}, - {path: "/foo/../.", first: "/", remainder: "foo/../."}, - {path: "/foo/../..", first: "/", remainder: "foo/../.."}, - - {path: "/foo/bar", first: "/", remainder: "foo/bar"}, - {path: "///foo/bar", first: "/", remainder: "//foo/bar"}, - {path: "/foo///bar", first: "/", remainder: "foo///bar"}, - {path: "/foo/bar/.", first: "/", remainder: "foo/bar/."}, - {path: "/foo/bar/./", first: "/", remainder: "foo/bar/."}, - {path: "/foo/bar/./.", first: "/", remainder: "foo/bar/./."}, - {path: "/foo/bar/./..", first: "/", remainder: "foo/bar/./.."}, - {path: "/foo/bar/..", first: "/", remainder: "foo/bar/.."}, - {path: "/foo/bar/../", first: "/", remainder: "foo/bar/.."}, - {path: "/foo/bar/../.", first: "/", remainder: "foo/bar/../."}, - {path: "/foo/bar/../..", first: "/", remainder: "foo/bar/../.."}, - - {path: "foo", first: "foo", remainder: ""}, - {path: "foo", first: "foo", remainder: ""}, - {path: "foo/", first: "foo", remainder: ""}, - {path: "foo///", first: "foo", remainder: ""}, - {path: "foo/.", first: "foo", remainder: "."}, - {path: "foo/./", first: "foo", remainder: "."}, - {path: "foo/./.", first: "foo", remainder: "./."}, - {path: "foo/./..", first: "foo", remainder: "./.."}, - {path: "foo/..", first: "foo", remainder: ".."}, - {path: "foo/../", first: "foo", remainder: ".."}, - {path: "foo/../.", first: "foo", remainder: "../."}, - {path: "foo/../..", first: "foo", remainder: "../.."}, - {path: "foo/", first: "foo", remainder: ""}, - {path: "foo/.", first: "foo", remainder: "."}, - - {path: "foo/bar", first: "foo", remainder: "bar"}, - {path: "foo///bar", first: "foo", remainder: "bar"}, - {path: "foo/bar/", first: "foo", remainder: "bar"}, - {path: "foo/bar/.", first: "foo", remainder: "bar/."}, - {path: "foo/bar/./", first: "foo", remainder: "bar/."}, - {path: "foo/bar/./.", first: "foo", remainder: "bar/./."}, - {path: "foo/bar/./..", first: "foo", remainder: "bar/./.."}, - {path: "foo/bar/..", first: "foo", remainder: "bar/.."}, - {path: "foo/bar/../", first: "foo", remainder: "bar/.."}, - {path: "foo/bar/../.", first: "foo", remainder: "bar/../."}, - {path: "foo/bar/../..", first: "foo", remainder: "bar/../.."}, - {path: "foo/bar/", first: "foo", remainder: "bar"}, - {path: "foo/bar/.", first: "foo", remainder: "bar/."}, - } - - for _, c := range cases { - first, remainder := SplitFirst(c.path) - if first != c.first || remainder != c.remainder { - t.Errorf("SplitFirst(%q) got (%q, %q), expected (%q, %q)", c.path, first, remainder, c.first, c.remainder) - } - } -} - -// TestIsSubpath tests the IsSubpath method. -func TestIsSubpath(t *testing.T) { - tcs := []struct { - // Two absolute paths. - pathA string - pathB string - - // Whether pathA is a subpath of pathB. - wantIsSubpath bool - - // Relative path from pathA to pathB. Only checked if - // wantIsSubpath is true. - wantRelpath string - }{ - { - pathA: "/foo/bar/baz", - pathB: "/foo", - wantIsSubpath: true, - wantRelpath: "bar/baz", - }, - { - pathA: "/foo", - pathB: "/foo/bar/baz", - wantIsSubpath: false, - }, - { - pathA: "/foo", - pathB: "/foo", - wantIsSubpath: false, - }, - { - pathA: "/foobar", - pathB: "/foo", - wantIsSubpath: false, - }, - { - pathA: "/foo", - pathB: "/foobar", - wantIsSubpath: false, - }, - { - pathA: "/foo", - pathB: "/foobar", - wantIsSubpath: false, - }, - { - pathA: "/", - pathB: "/foo", - wantIsSubpath: false, - }, - { - pathA: "/foo", - pathB: "/", - wantIsSubpath: true, - wantRelpath: "foo", - }, - { - pathA: "/foo/bar/../bar", - pathB: "/foo", - wantIsSubpath: true, - wantRelpath: "bar", - }, - { - pathA: "/foo/bar", - pathB: "/foo/../foo", - wantIsSubpath: true, - wantRelpath: "bar", - }, - } - - for _, tc := range tcs { - gotRelpath, gotIsSubpath := IsSubpath(tc.pathA, tc.pathB) - if gotRelpath != tc.wantRelpath || gotIsSubpath != tc.wantIsSubpath { - t.Errorf("IsSubpath(%q, %q) got %q %t, want %q %t", tc.pathA, tc.pathB, gotRelpath, gotIsSubpath, tc.wantRelpath, tc.wantIsSubpath) - } - } -} diff --git a/pkg/sentry/fs/proc/BUILD b/pkg/sentry/fs/proc/BUILD deleted file mode 100644 index bc75ae505..000000000 --- a/pkg/sentry/fs/proc/BUILD +++ /dev/null @@ -1,74 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "proc", - srcs = [ - "cgroup.go", - "cpuinfo.go", - "exec_args.go", - "fds.go", - "filesystems.go", - "fs.go", - "inode.go", - "loadavg.go", - "meminfo.go", - "mounts.go", - "net.go", - "proc.go", - "stat.go", - "sys.go", - "sys_net.go", - "sys_net_state.go", - "task.go", - "uid_gid_map.go", - "uptime.go", - "version.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/log", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/proc/device", - "//pkg/sentry/fs/proc/seqfile", - "//pkg/sentry/fs/ramfs", - "//pkg/sentry/fsbridge", - "//pkg/sentry/inet", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/time", - "//pkg/sentry/limits", - "//pkg/sentry/mm", - "//pkg/sentry/socket", - "//pkg/sentry/socket/unix", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/usage", - "//pkg/sync", - "//pkg/tcpip/header", - "//pkg/tcpip/network/ipv4", - "//pkg/usermem", - "//pkg/waiter", - ], -) - -go_test( - name = "proc_test", - size = "small", - srcs = [ - "net_test.go", - "sys_net_test.go", - ], - library = ":proc", - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/sentry/inet", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fs/proc/README.md b/pkg/sentry/fs/proc/README.md deleted file mode 100644 index 6667a0916..000000000 --- a/pkg/sentry/fs/proc/README.md +++ /dev/null @@ -1,336 +0,0 @@ -This document tracks what is implemented in procfs. Refer to -Documentation/filesystems/proc.txt in the Linux project for information about -procfs generally. - -**NOTE**: This document is not guaranteed to be up to date. If you find an -inconsistency, please file a bug. - -[TOC] - -## Kernel data - -The following files are implemented: - - - -| File /proc/ | Content | -| :------------------------ | :---------------------------------------------------- | -| [cpuinfo](#cpuinfo) | Info about the CPU | -| [filesystems](#filesystems) | Supported filesystems | -| [loadavg](#loadavg) | Load average of last 1, 5 & 15 minutes | -| [meminfo](#meminfo) | Overall memory info | -| [stat](#stat) | Overall kernel statistics | -| [sys](#sys) | Change parameters within the kernel | -| [uptime](#uptime) | Wall clock since boot, combined idle time of all cpus | -| [version](#version) | Kernel version | - - - -### cpuinfo - -```bash -$ cat /proc/cpuinfo -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 45 -model name : unknown -stepping : unknown -cpu MHz : 1234.588 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx xsaveopt -bogomips : 1234.59 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -... -``` - -Notable divergences: - -Field name | Notes -:--------------- | :--------------------------------------- -model name | Always unknown -stepping | Always unknown -fpu | Always yes -fpu_exception | Always yes -wp | Always yes -bogomips | Bogus value (matches cpu MHz) -clflush size | Always 64 -cache_alignment | Always 64 -address sizes | Always 46 bits physical, 48 bits virtual -power management | Always blank - -Otherwise fields are derived from the sentry configuration. - -### filesystems - -```bash -$ cat /proc/filesystems -nodev 9p -nodev devpts -nodev devtmpfs -nodev proc -nodev sysfs -nodev tmpfs -``` - -### loadavg - -```bash -$ cat /proc/loadavg -0.00 0.00 0.00 0/0 0 -``` - -Column | Notes -:------------------------------------ | :---------- -CPU.IO utilization in last 1 minute | Always zero -CPU.IO utilization in last 5 minutes | Always zero -CPU.IO utilization in last 10 minutes | Always zero -Num currently running processes | Always zero -Total num processes | Always zero - -TODO(b/62345059): Populate the columns with accurate statistics. - -### meminfo - -```bash -$ cat /proc/meminfo -MemTotal: 2097152 kB -MemFree: 2083540 kB -MemAvailable: 2083540 kB -Buffers: 0 kB -Cached: 4428 kB -SwapCache: 0 kB -Active: 10812 kB -Inactive: 2216 kB -Active(anon): 8600 kB -Inactive(anon): 0 kB -Active(file): 2212 kB -Inactive(file): 2216 kB -Unevictable: 0 kB -Mlocked: 0 kB -SwapTotal: 0 kB -SwapFree: 0 kB -Dirty: 0 kB -Writeback: 0 kB -AnonPages: 8600 kB -Mapped: 4428 kB -Shmem: 0 kB - -``` - -Notable divergences: - -Field name | Notes -:---------------- | :----------------------------------------------------- -Buffers | Always zero, no block devices -SwapCache | Always zero, no swap -Inactive(anon) | Always zero, see SwapCache -Unevictable | Always zero TODO(b/31823263) -Mlocked | Always zero TODO(b/31823263) -SwapTotal | Always zero, no swap -SwapFree | Always zero, no swap -Dirty | Always zero TODO(b/31823263) -Writeback | Always zero TODO(b/31823263) -MemAvailable | Uses the same value as MemFree since there is no swap. -Slab | Missing -SReclaimable | Missing -SUnreclaim | Missing -KernelStack | Missing -PageTables | Missing -NFS_Unstable | Missing -Bounce | Missing -WritebackTmp | Missing -CommitLimit | Missing -Committed_AS | Missing -VmallocTotal | Missing -VmallocUsed | Missing -VmallocChunk | Missing -HardwareCorrupted | Missing -AnonHugePages | Missing -ShmemHugePages | Missing -ShmemPmdMapped | Missing -HugePages_Total | Missing -HugePages_Free | Missing -HugePages_Rsvd | Missing -HugePages_Surp | Missing -Hugepagesize | Missing -DirectMap4k | Missing -DirectMap2M | Missing -DirectMap1G | Missing - -### stat - -```bash -$ cat /proc/stat -cpu 0 0 0 0 0 0 0 0 0 0 -cpu0 0 0 0 0 0 0 0 0 0 0 -cpu1 0 0 0 0 0 0 0 0 0 0 -cpu2 0 0 0 0 0 0 0 0 0 0 -cpu3 0 0 0 0 0 0 0 0 0 0 -cpu4 0 0 0 0 0 0 0 0 0 0 -cpu5 0 0 0 0 0 0 0 0 0 0 -cpu6 0 0 0 0 0 0 0 0 0 0 -cpu7 0 0 0 0 0 0 0 0 0 0 -intr 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -ctxt 0 -btime 1504040968 -processes 0 -procs_running 0 -procs_blokkcked 0 -softirq 0 0 0 0 0 0 0 0 0 0 0 -``` - -All fields except for `btime` are always zero. - -TODO(b/37226836): Populate with accurate fields. - -### sys - -```bash -$ ls /proc/sys -kernel vm -``` - -Directory | Notes -:-------- | :---------------------------- -abi | Missing -debug | Missing -dev | Missing -fs | Missing -kernel | Contains hostname (only) -net | Missing -user | Missing -vm | Contains mmap_min_addr (only) - -### uptime - -```bash -$ cat /proc/uptime -3204.62 0.00 -``` - -Column | Notes -:------------------------------- | :---------------------------- -Total num seconds system running | Time since procfs was mounted -Number of seconds idle | Always zero - -### version - -```bash -$ cat /proc/version -Linux version 4.4 #1 SMP Sun Jan 10 15:06:54 PST 2016 -``` - -## Process-specific data - -The following files are implemented: - -File /proc/PID | Content -:---------------------- | :--------------------------------------------------- -[auxv](#auxv) | Copy of auxiliary vector for the process -[cmdline](#cmdline) | Command line arguments -[comm](#comm) | Command name associated with the process -[environ](#environ) | Process environment -[exe](#exe) | Symlink to the process's executable -[fd](#fd) | Directory containing links to open file descriptors -[fdinfo](#fdinfo) | Information associated with open file descriptors -[gid_map](#gid_map) | Mappings for group IDs inside the user namespace -[io](#io) | IO statistics -[maps](#maps) | Memory mappings (anon, executables, library files) -[mounts](#mounts) | Mounted filesystems -[mountinfo](#mountinfo) | Information about mounts -[ns](#ns) | Directory containing info about supported namespaces -[stat](#stat) | Process statistics -[statm](#statm) | Process memory statistics -[status](#status) | Process status in human readable format -[task](#task) | Directory containing info about running threads -[uid_map](#uid_map) | Mappings for user IDs inside the user namespace - -### auxv - -TODO - -### cmdline - -TODO - -### comm - -TODO - -### environment - -TODO - -### exe - -TODO - -### fd - -TODO - -### fdinfo - -TODO - -### gid_map - -TODO - -### io - -Only has data for rchar, wchar, syscr, and syscw. - -TODO: add more detail. - -### maps - -TODO - -### mounts - -TODO - -### mountinfo - -TODO - -### ns - -TODO - -### stat - -Only has data for pid, comm, state, ppid, utime, stime, cutime, cstime, -num_threads, and exit_signal. - -TODO: add more detail. - -### statm - -Only has data for vss and rss. - -TODO: add more detail. - -### status - -Contains data for Name, State, Tgid, Pid, Ppid, TracerPid, FDSize, VmSize, -VmRSS, Threads, CapInh, CapPrm, CapEff, CapBnd, Seccomp. - -TODO: add more detail. - -### task - -TODO - -### uid_map - -TODO diff --git a/pkg/sentry/fs/proc/cgroup.go b/pkg/sentry/fs/proc/cgroup.go deleted file mode 100644 index 7c1d9e7e9..000000000 --- a/pkg/sentry/fs/proc/cgroup.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// LINT.IfChange - -func newCGroupInode(ctx context.Context, msrc *fs.MountSource, cgroupControllers map[string]string) *fs.Inode { - // From man 7 cgroups: "For each cgroup hierarchy of which the process - // is a member, there is one entry containing three colon-separated - // fields: hierarchy-ID:controller-list:cgroup-path" - - // The hierarchy ids must be positive integers (for cgroup v1), but the - // exact number does not matter, so long as they are unique. We can - // just use a counter, but since linux sorts this file in descending - // order, we must count down to perserve this behavior. - i := len(cgroupControllers) - var data string - for name, dir := range cgroupControllers { - data += fmt.Sprintf("%d:%s:%s\n", i, name, dir) - i-- - } - - return newStaticProcInode(ctx, msrc, []byte(data)) -} - -// LINT.ThenChange(../../fsimpl/proc/tasks_files.go) diff --git a/pkg/sentry/fs/proc/cpuinfo.go b/pkg/sentry/fs/proc/cpuinfo.go deleted file mode 100644 index d14e9441f..000000000 --- a/pkg/sentry/fs/proc/cpuinfo.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -func newCPUInfo(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - k := kernel.KernelFromContext(ctx) - features := k.FeatureSet() - var buf bytes.Buffer - for i, max := uint(0), k.ApplicationCores(); i < max; i++ { - features.WriteCPUInfoTo(i, &buf) - } - return newStaticProcInode(ctx, msrc, buf.Bytes()) -} - -// LINT.ThenChange(../../fsimpl/proc/tasks.go) diff --git a/pkg/sentry/fs/proc/device/BUILD b/pkg/sentry/fs/proc/device/BUILD deleted file mode 100644 index 52c9aa93d..000000000 --- a/pkg/sentry/fs/proc/device/BUILD +++ /dev/null @@ -1,10 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "device", - srcs = ["device.go"], - visibility = ["//pkg/sentry:internal"], - deps = ["//pkg/sentry/device"], -) diff --git a/pkg/sentry/fs/proc/device/device.go b/pkg/sentry/fs/proc/device/device.go deleted file mode 100644 index bbe66e796..000000000 --- a/pkg/sentry/fs/proc/device/device.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 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. - -// Package device contains the proc device to avoid dependency loops. -package device - -import ( - "gvisor.dev/gvisor/pkg/sentry/device" -) - -// ProcDevice is the kernel proc device. -var ProcDevice = device.NewAnonDevice() diff --git a/pkg/sentry/fs/proc/exec_args.go b/pkg/sentry/fs/proc/exec_args.go deleted file mode 100644 index 75dc5d204..000000000 --- a/pkg/sentry/fs/proc/exec_args.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - "io" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// execArgType enumerates the types of exec arguments that are exposed through -// proc. -type execArgType int - -const ( - cmdlineExecArg execArgType = iota - environExecArg -) - -// execArgInode is a inode containing the exec args (either cmdline or environ) -// for a given task. -// -// +stateify savable -type execArgInode struct { - fsutil.SimpleFileInode - - // arg is the type of exec argument this file contains. - arg execArgType - - // t is the Task to read the exec arg line from. - t *kernel.Task -} - -var _ fs.InodeOperations = (*execArgInode)(nil) - -// newExecArgFile creates a file containing the exec args of the given type. -func newExecArgInode(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, arg execArgType) *fs.Inode { - if arg != cmdlineExecArg && arg != environExecArg { - panic(fmt.Sprintf("unknown exec arg type %v", arg)) - } - f := &execArgInode{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - arg: arg, - t: t, - } - return newProcInode(ctx, f, msrc, fs.SpecialFile, t) -} - -// GetFile implements fs.InodeOperations.GetFile. -func (i *execArgInode) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &execArgFile{ - arg: i.arg, - t: i.t, - }), nil -} - -// +stateify savable -type execArgFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopWrite `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - // arg is the type of exec argument this file contains. - arg execArgType - - // t is the Task to read the exec arg line from. - t *kernel.Task -} - -var _ fs.FileOperations = (*execArgFile)(nil) - -// Read reads the exec arg from the process's address space.. -func (f *execArgFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset < 0 { - return 0, linuxerr.EINVAL - } - - m, err := getTaskMMIncRef(f.t) - if err != nil { - return 0, err - } - defer m.DecUsers(ctx) - - // Figure out the bounds of the exec arg we are trying to read. - var execArgStart, execArgEnd hostarch.Addr - switch f.arg { - case cmdlineExecArg: - execArgStart, execArgEnd = m.ArgvStart(), m.ArgvEnd() - case environExecArg: - execArgStart, execArgEnd = m.EnvvStart(), m.EnvvEnd() - default: - panic(fmt.Sprintf("unknown exec arg type %v", f.arg)) - } - if execArgStart == 0 || execArgEnd == 0 { - // Don't attempt to read before the start/end are set up. - return 0, io.EOF - } - - start, ok := execArgStart.AddLength(uint64(offset)) - if !ok { - return 0, io.EOF - } - if start >= execArgEnd { - return 0, io.EOF - } - - length := int(execArgEnd - start) - if dstlen := dst.NumBytes(); int64(length) > dstlen { - length = int(dstlen) - } - - buf := make([]byte, length) - // N.B. Technically this should be usermem.IOOpts.IgnorePermissions = true - // until Linux 4.9 (272ddc8b3735 "proc: don't use FOLL_FORCE for reading - // cmdline and environment"). - copyN, err := m.CopyIn(ctx, start, buf, usermem.IOOpts{}) - if copyN == 0 { - // Nothing to copy. - return 0, err - } - buf = buf[:copyN] - - // On Linux, if the NUL byte at the end of the argument vector has been - // overwritten, it continues reading the environment vector as part of - // the argument vector. - - if f.arg == cmdlineExecArg && buf[copyN-1] != 0 { - // Linux will limit the return up to and including the first null character in argv - - copyN = bytes.IndexByte(buf, 0) - if copyN == -1 { - copyN = len(buf) - } - // If we found a NUL character in argv, return upto and including that character. - if copyN < len(buf) { - buf = buf[:copyN] - } else { // Otherwise return into envp. - lengthEnvv := int(m.EnvvEnd() - m.EnvvStart()) - - // Upstream limits the returned amount to one page of slop. - // https://elixir.bootlin.com/linux/v4.20/source/fs/proc/base.c#L208 - // we'll return one page total between argv and envp because of the - // above page restrictions. - if lengthEnvv > hostarch.PageSize-len(buf) { - lengthEnvv = hostarch.PageSize - len(buf) - } - // Make a new buffer to fit the whole thing - tmp := make([]byte, length+lengthEnvv) - copyNE, err := m.CopyIn(ctx, m.EnvvStart(), tmp[copyN:], usermem.IOOpts{}) - if err != nil { - return 0, err - } - - // Linux will return envp up to and including the first NUL character, so find it. - for i, c := range tmp[copyN:] { - if c == 0 { - copyNE = i - break - } - } - - copy(tmp, buf) - buf = tmp[:copyN+copyNE] - - } - - } - - n, dstErr := dst.CopyOut(ctx, buf) - if dstErr != nil { - return int64(n), dstErr - } - return int64(n), err -} - -// LINT.ThenChange(../../fsimpl/proc/task.go) diff --git a/pkg/sentry/fs/proc/fds.go b/pkg/sentry/fs/proc/fds.go deleted file mode 100644 index e68bb46c0..000000000 --- a/pkg/sentry/fs/proc/fds.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - "sort" - "strconv" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/device" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -// walkDescriptors finds the descriptor (file-flag pair) for the fd identified -// by p, and calls the toInodeOperations callback with that descriptor. This is a helper -// method for implementing fs.InodeOperations.Lookup. -func walkDescriptors(t *kernel.Task, p string, toInode func(*fs.File, kernel.FDFlags) *fs.Inode) (*fs.Inode, error) { - n, err := strconv.ParseUint(p, 10, 64) - if err != nil { - // Not found. - return nil, linuxerr.ENOENT - } - - var file *fs.File - var fdFlags kernel.FDFlags - t.WithMuLocked(func(t *kernel.Task) { - if fdTable := t.FDTable(); fdTable != nil { - file, fdFlags = fdTable.Get(int32(n)) - } - }) - if file == nil { - return nil, linuxerr.ENOENT - } - return toInode(file, fdFlags), nil -} - -// readDescriptors reads fds in the task starting at offset, and calls the -// toDentAttr callback for each to get a DentAttr, which it then emits. This is -// a helper for implementing fs.InodeOperations.Readdir. -func readDescriptors(ctx context.Context, t *kernel.Task, c *fs.DirCtx, offset int64, toDentAttr func(int) fs.DentAttr) (int64, error) { - var fds []int32 - t.WithMuLocked(func(t *kernel.Task) { - if fdTable := t.FDTable(); fdTable != nil { - fds = fdTable.GetFDs(ctx) - } - }) - - // Find the appropriate starting point. - idx := sort.Search(len(fds), func(i int) bool { return fds[i] >= int32(offset) }) - if idx == len(fds) { - return offset, nil - } - fds = fds[idx:] - - // Serialize all FDs. - for _, fd := range fds { - name := strconv.FormatUint(uint64(fd), 10) - if err := c.DirEmit(name, toDentAttr(int(fd))); err != nil { - // Returned offset is the next fd to serialize. - return int64(fd), err - } - } - // We serialized them all. Next offset should be higher than last - // serialized fd. - return int64(fds[len(fds)-1] + 1), nil -} - -// fd implements fs.InodeOperations for a file in /proc/TID/fd/. -type fd struct { - ramfs.Symlink - file *fs.File -} - -var _ fs.InodeOperations = (*fd)(nil) - -// newFd returns a new fd based on an existing file. -// -// This inherits one reference to the file. -func newFd(ctx context.Context, t *kernel.Task, f *fs.File, msrc *fs.MountSource) *fs.Inode { - fd := &fd{ - // RootOwner overridden by taskOwnedInodeOps.UnstableAttrs(). - Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, ""), - file: f, - } - return newProcInode(ctx, fd, msrc, fs.Symlink, t) -} - -// GetFile returns the fs.File backing this fd. The dirent and flags -// arguments are ignored. -func (f *fd) GetFile(context.Context, *fs.Dirent, fs.FileFlags) (*fs.File, error) { - // Take a reference on the fs.File. - f.file.IncRef() - return f.file, nil -} - -// Readlink returns the current target. -func (f *fd) Readlink(ctx context.Context, _ *fs.Inode) (string, error) { - root := fs.RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - n, _ := f.file.Dirent.FullName(root) - return n, nil -} - -// Getlink implements fs.InodeOperations.Getlink. -func (f *fd) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) { - f.file.Dirent.IncRef() - return f.file.Dirent, nil -} - -// Truncate is ignored. -func (f *fd) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -func (f *fd) Release(ctx context.Context) { - f.Symlink.Release(ctx) - f.file.DecRef(ctx) -} - -// fdDir is an InodeOperations for /proc/TID/fd. -// -// +stateify savable -type fdDir struct { - ramfs.Dir - - // We hold a reference on the task's FDTable but only keep an indirect - // task pointer to avoid Dirent loading circularity caused by the - // table's back pointers into the dirent tree. - t *kernel.Task -} - -var _ fs.InodeOperations = (*fdDir)(nil) - -// newFdDir creates a new fdDir. -func newFdDir(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - f := &fdDir{ - Dir: *ramfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermissions{User: fs.PermMask{Read: true, Execute: true}}), - t: t, - } - return newProcInode(ctx, f, msrc, fs.SpecialDirectory, t) -} - -// Check implements InodeOperations.Check. -// -// This is to match Linux, which uses a special permission handler to guarantee -// that a process can still access /proc/self/fd after it has executed -// setuid. See fs/proc/fd.c:proc_fd_permission. -func (f *fdDir) Check(ctx context.Context, inode *fs.Inode, req fs.PermMask) bool { - if fs.ContextCanAccessFile(ctx, inode, req) { - return true - } - if t := kernel.TaskFromContext(ctx); t != nil { - // Allow access if the task trying to access it is in the - // thread group corresponding to this directory. - if f.t.ThreadGroup() == t.ThreadGroup() { - return true - } - } - return false -} - -// Lookup loads an Inode in /proc/TID/fd into a Dirent. -func (f *fdDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) { - n, err := walkDescriptors(f.t, p, func(file *fs.File, _ kernel.FDFlags) *fs.Inode { - return newFd(ctx, f.t, file, dir.MountSource) - }) - if err != nil { - return nil, err - } - return fs.NewDirent(ctx, n, p), nil -} - -// GetFile implements fs.FileOperations.GetFile. -func (f *fdDir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - fops := &fdDirFile{ - isInfoFile: false, - t: f.t, - } - return fs.NewFile(ctx, dirent, flags, fops), nil -} - -// +stateify savable -type fdDirFile struct { - fsutil.DirFileOperations `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - isInfoFile bool - - t *kernel.Task -} - -var _ fs.FileOperations = (*fdDirFile)(nil) - -// Readdir implements fs.FileOperations.Readdir. -func (f *fdDirFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) { - dirCtx := &fs.DirCtx{ - Serializer: ser, - } - typ := fs.RegularFile - if f.isInfoFile { - typ = fs.Symlink - } - return readDescriptors(ctx, f.t, dirCtx, file.Offset(), func(fd int) fs.DentAttr { - return fs.GenericDentAttr(typ, device.ProcDevice) - }) -} - -// fdInfoDir implements /proc/TID/fdinfo. It embeds an fdDir, but overrides -// Lookup and Readdir. -// -// +stateify savable -type fdInfoDir struct { - ramfs.Dir - - t *kernel.Task -} - -// newFdInfoDir creates a new fdInfoDir. -func newFdInfoDir(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - fdid := &fdInfoDir{ - Dir: *ramfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermsFromMode(0500)), - t: t, - } - return newProcInode(ctx, fdid, msrc, fs.SpecialDirectory, t) -} - -// Lookup loads an fd in /proc/TID/fdinfo into a Dirent. -func (fdid *fdInfoDir) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) { - inode, err := walkDescriptors(fdid.t, p, func(file *fs.File, fdFlags kernel.FDFlags) *fs.Inode { - // TODO(b/121266871): Using a static inode here means that the - // data can be out-of-date if, for instance, the flags on the - // FD change before we read this file. We should switch to - // generating the data on Read(). Also, we should include pos, - // locks, and other data. For now we only have flags. - // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt - flags := file.Flags().ToLinux() | fdFlags.ToLinuxFileFlags() - file.DecRef(ctx) - contents := []byte(fmt.Sprintf("flags:\t0%o\n", flags)) - return newStaticProcInode(ctx, dir.MountSource, contents) - }) - if err != nil { - return nil, err - } - return fs.NewDirent(ctx, inode, p), nil -} - -// GetFile implements fs.FileOperations.GetFile. -func (fdid *fdInfoDir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - fops := &fdDirFile{ - isInfoFile: true, - t: fdid.t, - } - return fs.NewFile(ctx, dirent, flags, fops), nil -} - -// LINT.ThenChange(../../fsimpl/proc/task_files.go) diff --git a/pkg/sentry/fs/proc/filesystems.go b/pkg/sentry/fs/proc/filesystems.go deleted file mode 100644 index 0a58ac34c..000000000 --- a/pkg/sentry/fs/proc/filesystems.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" -) - -// LINT.IfChange - -// filesystemsData backs /proc/filesystems. -// -// +stateify savable -type filesystemsData struct{} - -// NeedsUpdate returns true on the first generation. The set of registered file -// systems doesn't change so there's no need to generate SeqData more than once. -func (*filesystemsData) NeedsUpdate(generation int64) bool { - return generation == 0 -} - -// ReadSeqFileData returns data for the SeqFile reader. -// SeqData, the current generation and where in the file the handle corresponds to. -func (*filesystemsData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - // We don't ever expect to see a non-nil SeqHandle. - if h != nil { - return nil, 0 - } - - // Generate the file contents. - var buf bytes.Buffer - for _, sys := range fs.GetFilesystems() { - if !sys.AllowUserList() { - continue - } - nodev := "nodev" - if sys.Flags()&fs.FilesystemRequiresDev != 0 { - nodev = "" - } - // Matches the format of fs/filesystems.c:filesystems_proc_show. - fmt.Fprintf(&buf, "%s\t%s\n", nodev, sys.Name()) - } - - // Return the SeqData and advance the generation counter. - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*filesystemsData)(nil)}}, 1 -} - -// LINT.ThenChange(../../fsimpl/proc/filesystem.go) diff --git a/pkg/sentry/fs/proc/fs.go b/pkg/sentry/fs/proc/fs.go deleted file mode 100644 index 52b6ccbcc..000000000 --- a/pkg/sentry/fs/proc/fs.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// LINT.IfChange - -// filesystem is a procfs. -// -// +stateify savable -type filesystem struct{} - -func init() { - fs.RegisterFilesystem(&filesystem{}) -} - -// FilesystemName is the name under which the filesystem is registered. -// Name matches fs/proc/root.c:proc_fs_type.name. -const FilesystemName = "proc" - -// Name is the name of the file system. -func (*filesystem) Name() string { - return FilesystemName -} - -// AllowUserMount allows users to mount(2) this file system. -func (*filesystem) AllowUserMount() bool { - return true -} - -// AllowUserList allows this filesystem to be listed in /proc/filesystems. -func (*filesystem) AllowUserList() bool { - return true -} - -// Flags returns that there is nothing special about this file system. -// -// In Linux, proc returns FS_USERNS_VISIBLE | FS_USERNS_MOUNT, see fs/proc/root.c. -func (*filesystem) Flags() fs.FilesystemFlags { - return 0 -} - -// Mount returns the root of a procfs that can be positioned in the vfs. -func (f *filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, cgroupsInt any) (*fs.Inode, error) { - // device is always ignored. - - // Parse generic comma-separated key=value options, this file system expects them. - options := fs.GenericMountSourceOptions(data) - - // Proc options parsing checks for either a gid= or hidepid= and barfs on - // anything else, see fs/proc/root.c:proc_parse_options. Since we don't know - // what to do with gid= or hidepid=, we blow up if we get any options. - if len(options) > 0 { - return nil, fmt.Errorf("unsupported mount options: %v", options) - } - - var cgroups map[string]string - if cgroupsInt != nil { - cgroups = cgroupsInt.(map[string]string) - } - - // Construct the procfs root. Since procfs files are all virtual, we - // never want them cached. - return New(ctx, fs.NewNonCachingMountSource(ctx, f, flags), cgroups) -} - -// LINT.ThenChange(../../fsimpl/proc/filesystem.go) diff --git a/pkg/sentry/fs/proc/inode.go b/pkg/sentry/fs/proc/inode.go deleted file mode 100644 index 78132f7a5..000000000 --- a/pkg/sentry/fs/proc/inode.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/device" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/mm" -) - -// LINT.IfChange - -// taskOwnedInodeOps wraps an fs.InodeOperations and overrides the UnstableAttr -// method to return either the task or root as the owner, depending on the -// task's dumpability. -// -// +stateify savable -type taskOwnedInodeOps struct { - fs.InodeOperations - - // t is the task that owns this file. - t *kernel.Task -} - -// UnstableAttr implement fs.InodeOperations.UnstableAttr. -func (i *taskOwnedInodeOps) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - uattr, err := i.InodeOperations.UnstableAttr(ctx, inode) - if err != nil { - return fs.UnstableAttr{}, err - } - - // By default, set the task owner as the file owner. - creds := i.t.Credentials() - uattr.Owner = fs.FileOwner{creds.EffectiveKUID, creds.EffectiveKGID} - - // Linux doesn't apply dumpability adjustments to world - // readable/executable directories so that applications can stat - // /proc/PID to determine the effective UID of a process. See - // fs/proc/base.c:task_dump_owner. - if fs.IsDir(inode.StableAttr) && uattr.Perms == fs.FilePermsFromMode(0555) { - return uattr, nil - } - - // If the task is not dumpable, then root (in the namespace preferred) - // owns the file. - var m *mm.MemoryManager - i.t.WithMuLocked(func(t *kernel.Task) { - m = t.MemoryManager() - }) - - if m == nil { - uattr.Owner.UID = auth.RootKUID - uattr.Owner.GID = auth.RootKGID - } else if m.Dumpability() != mm.UserDumpable { - if kuid := creds.UserNamespace.MapToKUID(auth.RootUID); kuid.Ok() { - uattr.Owner.UID = kuid - } else { - uattr.Owner.UID = auth.RootKUID - } - if kgid := creds.UserNamespace.MapToKGID(auth.RootGID); kgid.Ok() { - uattr.Owner.GID = kgid - } else { - uattr.Owner.GID = auth.RootKGID - } - } - - return uattr, nil -} - -// staticFileInodeOps is an InodeOperations implementation that can be used to -// return file contents which are constant. This file is not writable and will -// always have mode 0444. -// -// +stateify savable -type staticFileInodeOps struct { - fsutil.InodeDenyWriteChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopAllocate `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - fsutil.InodeStaticFileGetter -} - -var _ fs.InodeOperations = (*staticFileInodeOps)(nil) - -// newStaticFileInode returns a procfs InodeOperations with static contents. -func newStaticProcInode(ctx context.Context, msrc *fs.MountSource, contents []byte) *fs.Inode { - iops := &staticFileInodeOps{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - InodeStaticFileGetter: fsutil.InodeStaticFileGetter{ - Contents: contents, - }, - } - return newProcInode(ctx, iops, msrc, fs.SpecialFile, nil) -} - -// newProcInode creates a new inode from the given inode operations. -func newProcInode(ctx context.Context, iops fs.InodeOperations, msrc *fs.MountSource, typ fs.InodeType, t *kernel.Task) *fs.Inode { - sattr := fs.StableAttr{ - DeviceID: device.ProcDevice.DeviceID(), - InodeID: device.ProcDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: typ, - } - if t != nil { - iops = &taskOwnedInodeOps{iops, t} - } - return fs.NewInode(ctx, iops, msrc, sattr) -} - -// LINT.ThenChange(../../fsimpl/proc/tasks.go) diff --git a/pkg/sentry/fs/proc/loadavg.go b/pkg/sentry/fs/proc/loadavg.go deleted file mode 100644 index 139d49c34..000000000 --- a/pkg/sentry/fs/proc/loadavg.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" -) - -// LINT.IfChange - -// loadavgData backs /proc/loadavg. -// -// +stateify savable -type loadavgData struct{} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*loadavgData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (d *loadavgData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - var buf bytes.Buffer - - // TODO(b/62345059): Include real data in fields. - // Column 1-3: CPU and IO utilization of the last 1, 5, and 10 minute periods. - // Column 4-5: currently running processes and the total number of processes. - // Column 6: the last process ID used. - fmt.Fprintf(&buf, "%.2f %.2f %.2f %d/%d %d\n", 0.00, 0.00, 0.00, 0, 0, 0) - - return []seqfile.SeqData{ - { - Buf: buf.Bytes(), - Handle: (*loadavgData)(nil), - }, - }, 0 -} - -// LINT.ThenChange(../../fsimpl/proc/tasks_files.go) diff --git a/pkg/sentry/fs/proc/meminfo.go b/pkg/sentry/fs/proc/meminfo.go deleted file mode 100644 index 7d975d333..000000000 --- a/pkg/sentry/fs/proc/meminfo.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/usage" -) - -// LINT.IfChange - -// meminfoData backs /proc/meminfo. -// -// +stateify savable -type meminfoData struct { - // k is the owning Kernel. - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*meminfoData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (d *meminfoData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - mf := d.k.MemoryFile() - mf.UpdateUsage() - snapshot, totalUsage := usage.MemoryAccounting.Copy() - totalSize := usage.TotalMemory(mf.TotalSize(), totalUsage) - anon := snapshot.Anonymous + snapshot.Tmpfs - file := snapshot.PageCache + snapshot.Mapped - // We don't actually have active/inactive LRUs, so just make up numbers. - activeFile := (file / 2) &^ (hostarch.PageSize - 1) - inactiveFile := file - activeFile - - var buf bytes.Buffer - fmt.Fprintf(&buf, "MemTotal: %8d kB\n", totalSize/1024) - memFree := totalSize - totalUsage - if memFree > totalSize { - // Underflow. - memFree = 0 - } - // We use MemFree as MemAvailable because we don't swap. - // TODO(rahat): When reclaim is implemented the value of MemAvailable - // should change. - fmt.Fprintf(&buf, "MemFree: %8d kB\n", memFree/1024) - fmt.Fprintf(&buf, "MemAvailable: %8d kB\n", memFree/1024) - fmt.Fprintf(&buf, "Buffers: 0 kB\n") // memory usage by block devices - fmt.Fprintf(&buf, "Cached: %8d kB\n", (file+snapshot.Tmpfs)/1024) - // Emulate a system with no swap, which disables inactivation of anon pages. - fmt.Fprintf(&buf, "SwapCache: 0 kB\n") - fmt.Fprintf(&buf, "Active: %8d kB\n", (anon+activeFile)/1024) - fmt.Fprintf(&buf, "Inactive: %8d kB\n", inactiveFile/1024) - fmt.Fprintf(&buf, "Active(anon): %8d kB\n", anon/1024) - fmt.Fprintf(&buf, "Inactive(anon): 0 kB\n") - fmt.Fprintf(&buf, "Active(file): %8d kB\n", activeFile/1024) - fmt.Fprintf(&buf, "Inactive(file): %8d kB\n", inactiveFile/1024) - fmt.Fprintf(&buf, "Unevictable: 0 kB\n") // TODO(b/31823263) - fmt.Fprintf(&buf, "Mlocked: 0 kB\n") // TODO(b/31823263) - fmt.Fprintf(&buf, "SwapTotal: 0 kB\n") - fmt.Fprintf(&buf, "SwapFree: 0 kB\n") - fmt.Fprintf(&buf, "Dirty: 0 kB\n") - fmt.Fprintf(&buf, "Writeback: 0 kB\n") - fmt.Fprintf(&buf, "AnonPages: %8d kB\n", anon/1024) - fmt.Fprintf(&buf, "Mapped: %8d kB\n", file/1024) // doesn't count mapped tmpfs, which we don't know - fmt.Fprintf(&buf, "Shmem: %8d kB\n", snapshot.Tmpfs/1024) - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*meminfoData)(nil)}}, 0 -} - -// LINT.ThenChange(../../fsimpl/proc/tasks_files.go) diff --git a/pkg/sentry/fs/proc/mounts.go b/pkg/sentry/fs/proc/mounts.go deleted file mode 100644 index 6a63c47b3..000000000 --- a/pkg/sentry/fs/proc/mounts.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - "sort" - "strings" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -// forEachMountSource runs f for the process root mount and each mount that is a -// descendant of the root. -func forEachMount(t *kernel.Task, fn func(string, *fs.Mount)) { - var fsctx *kernel.FSContext - t.WithMuLocked(func(t *kernel.Task) { - fsctx = t.FSContext() - }) - if fsctx == nil { - // The task has been destroyed. Nothing to show here. - return - } - - // All mount points must be relative to the rootDir, and mounts outside - // will be excluded. - rootDir := fsctx.RootDirectory() - if rootDir == nil { - // The task has been destroyed. Nothing to show here. - return - } - defer rootDir.DecRef(t) - - mnt := t.MountNamespace().FindMount(rootDir) - if mnt == nil { - // Has it just been unmounted? - return - } - ms := t.MountNamespace().AllMountsUnder(mnt) - sort.Slice(ms, func(i, j int) bool { - return ms[i].ID < ms[j].ID - }) - for _, m := range ms { - mroot := m.Root() - if mroot == nil { - continue // No longer valid. - } - mountPath, desc := mroot.FullName(rootDir) - mroot.DecRef(t) - if !desc { - // MountSources that are not descendants of the chroot jail are ignored. - continue - } - fn(mountPath, m) - } -} - -// mountInfoFile is used to implement /proc/[pid]/mountinfo. -// -// +stateify savable -type mountInfoFile struct { - t *kernel.Task -} - -// NeedsUpdate implements SeqSource.NeedsUpdate. -func (mif *mountInfoFile) NeedsUpdate(_ int64) bool { - return true -} - -// ReadSeqFileData implements SeqSource.ReadSeqFileData. -func (mif *mountInfoFile) ReadSeqFileData(ctx context.Context, handle seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if handle != nil { - return nil, 0 - } - - var buf bytes.Buffer - forEachMount(mif.t, func(mountPath string, m *fs.Mount) { - mroot := m.Root() - if mroot == nil { - return // No longer valid. - } - defer mroot.DecRef(ctx) - - // Format: - // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) - - // (1) MountSource ID. - fmt.Fprintf(&buf, "%d ", m.ID) - - // (2) Parent ID (or this ID if there is no parent). - pID := m.ID - if !m.IsRoot() && !m.IsUndo() { - pID = m.ParentID - } - fmt.Fprintf(&buf, "%d ", pID) - - // (3) Major:Minor device ID. We don't have a superblock, so we - // just use the root inode device number. - sa := mroot.Inode.StableAttr - fmt.Fprintf(&buf, "%d:%d ", sa.DeviceFileMajor, sa.DeviceFileMinor) - - // (4) Root: the pathname of the directory in the filesystem - // which forms the root of this mount. - // - // NOTE(b/78135857): This will always be "/" until we implement - // bind mounts. - fmt.Fprintf(&buf, "/ ") - - // (5) Mount point (relative to process root). - fmt.Fprintf(&buf, "%s ", mountPath) - - // (6) Mount options. - flags := mroot.Inode.MountSource.Flags - opts := "rw" - if flags.ReadOnly { - opts = "ro" - } - if flags.NoAtime { - opts += ",noatime" - } - if flags.NoExec { - opts += ",noexec" - } - fmt.Fprintf(&buf, "%s ", opts) - - // (7) Optional fields: zero or more fields of the form "tag[:value]". - // (8) Separator: the end of the optional fields is marked by a single hyphen. - fmt.Fprintf(&buf, "- ") - - // (9) Filesystem type. - fmt.Fprintf(&buf, "%s ", mroot.Inode.MountSource.FilesystemType) - - // (10) Mount source: filesystem-specific information or "none". - fmt.Fprintf(&buf, "none ") - - // (11) Superblock options, and final newline. - fmt.Fprintf(&buf, "%s\n", superBlockOpts(mountPath, mroot.Inode.MountSource)) - }) - - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*mountInfoFile)(nil)}}, 0 -} - -func superBlockOpts(mountPath string, msrc *fs.MountSource) string { - // gVisor doesn't (yet) have a concept of super block options, so we - // use the ro/rw bit from the mount flag. - opts := "rw" - if msrc.Flags.ReadOnly { - opts = "ro" - } - - // NOTE(b/147673608): If the mount is a cgroup, we also need to include - // the cgroup name in the options. For now we just read that from the - // path. - // - // TODO(gvisor.dev/issue/190): Once gVisor has full cgroup support, we - // should get this value from the cgroup itself, and not rely on the - // path. - if msrc.FilesystemType == "cgroup" { - splitPath := strings.Split(mountPath, "/") - cgroupType := splitPath[len(splitPath)-1] - opts += "," + cgroupType - } - return opts -} - -// mountsFile is used to implement /proc/[pid]/mounts. -// -// +stateify savable -type mountsFile struct { - t *kernel.Task -} - -// NeedsUpdate implements SeqSource.NeedsUpdate. -func (mf *mountsFile) NeedsUpdate(_ int64) bool { - return true -} - -// ReadSeqFileData implements SeqSource.ReadSeqFileData. -func (mf *mountsFile) ReadSeqFileData(ctx context.Context, handle seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if handle != nil { - return nil, 0 - } - - var buf bytes.Buffer - forEachMount(mf.t, func(mountPath string, m *fs.Mount) { - // Format: - // - // - // We use the filesystem name as the first field, since there - // is no real block device we can point to, and we also should - // not expose anything about the remote filesystem. - // - // Only ro/rw option is supported for now. - // - // The "needs dump"and fsck flags are always 0, which is allowed. - root := m.Root() - if root == nil { - return // No longer valid. - } - defer root.DecRef(ctx) - - flags := root.Inode.MountSource.Flags - opts := "rw" - if flags.ReadOnly { - opts = "ro" - } - fmt.Fprintf(&buf, "%s %s %s %s %d %d\n", "none", mountPath, root.Inode.MountSource.FilesystemType, opts, 0, 0) - }) - - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*mountsFile)(nil)}}, 0 -} - -// LINT.ThenChange(../../fsimpl/proc/tasks_files.go) diff --git a/pkg/sentry/fs/proc/net.go b/pkg/sentry/fs/proc/net.go deleted file mode 100644 index 6d5f4e5e1..000000000 --- a/pkg/sentry/fs/proc/net.go +++ /dev/null @@ -1,865 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - "io" - "reflect" - "time" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/inet" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/socket/unix" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/tcpip/header" -) - -// LINT.IfChange - -// newNetDir creates a new proc net entry. -func newNetDir(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - k := t.Kernel() - - var contents map[string]*fs.Inode - if s := t.NetworkNamespace().Stack(); s != nil { - // TODO(gvisor.dev/issue/1833): Make sure file contents reflect the task - // network namespace. - contents = map[string]*fs.Inode{ - "dev": seqfile.NewSeqFileInode(ctx, &netDev{s: s}, msrc), - "snmp": seqfile.NewSeqFileInode(ctx, &netSnmp{s: s}, msrc), - - // The following files are simple stubs until they are - // implemented in netstack, if the file contains a - // header the stub is just the header otherwise it is - // an empty file. - "arp": newStaticProcInode(ctx, msrc, []byte("IP address HW type Flags HW address Mask Device\n")), - - "netlink": newStaticProcInode(ctx, msrc, []byte("sk Eth Pid Groups Rmem Wmem Dump Locks Drops Inode\n")), - "netstat": newStaticProcInode(ctx, msrc, []byte("TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed EmbryonicRsts PruneCalled RcvPruned OfoPruned OutOfWindowIcmps LockDroppedIcmps ArpFilter TW TWRecycled TWKilled PAWSPassive PAWSActive PAWSEstab DelayedACKs DelayedACKLocked DelayedACKLost ListenOverflows ListenDrops TCPPrequeued TCPDirectCopyFromBacklog TCPDirectCopyFromPrequeue TCPPrequeueDropped TCPHPHits TCPHPHitsToUser TCPPureAcks TCPHPAcks TCPRenoRecovery TCPSackRecovery TCPSACKReneging TCPFACKReorder TCPSACKReorder TCPRenoReorder TCPTSReorder TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo TCPLostRetransmit TCPRenoFailures TCPSackFailures TCPLossFailures TCPFastRetrans TCPForwardRetrans TCPSlowStartRetrans TCPTimeouts TCPLossProbes TCPLossProbeRecovery TCPRenoRecoveryFail TCPSackRecoveryFail TCPSchedulerFailed TCPRcvCollapsed TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv TCPAbortOnData TCPAbortOnClose TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger TCPAbortFailed TCPMemoryPressures TCPSACKDiscard TCPDSACKIgnoredOld TCPDSACKIgnoredNoUndo TCPSpuriousRTOs TCPMD5NotFound TCPMD5Unexpected TCPMD5Failure TCPSackShifted TCPSackMerged TCPSackShiftFallback TCPBacklogDrop TCPMinTTLDrop TCPDeferAcceptDrop IPReversePathFilter TCPTimeWaitOverflow TCPReqQFullDoCookies TCPReqQFullDrop TCPRetransFail TCPRcvCoalesce TCPOFOQueue TCPOFODrop TCPOFOMerge TCPChallengeACK TCPSYNChallenge TCPFastOpenActive TCPFastOpenActiveFail TCPFastOpenPassive TCPFastOpenPassiveFail TCPFastOpenListenOverflow TCPFastOpenCookieReqd TCPSpuriousRtxHostQueues BusyPollRxPackets TCPAutoCorking TCPFromZeroWindowAdv TCPToZeroWindowAdv TCPWantZeroWindowAdv TCPSynRetrans TCPOrigDataSent TCPHystartTrainDetect TCPHystartTrainCwnd TCPHystartDelayDetect TCPHystartDelayCwnd TCPACKSkippedSynRecv TCPACKSkippedPAWS TCPACKSkippedSeq TCPACKSkippedFinWait2 TCPACKSkippedTimeWait TCPACKSkippedChallenge TCPWinProbe TCPKeepAlive TCPMTUPFail TCPMTUPSuccess\n")), - "packet": newStaticProcInode(ctx, msrc, []byte("sk RefCnt Type Proto Iface R Rmem User Inode\n")), - "protocols": newStaticProcInode(ctx, msrc, []byte("protocol size sockets memory press maxhdr slab module cl co di ac io in de sh ss gs se re sp bi br ha uh gp em\n")), - // Linux sets psched values to: nsec per usec, psched - // tick in ns, 1000000, high res timer ticks per sec - // (ClockGetres returns 1ns resolution). - "psched": newStaticProcInode(ctx, msrc, []byte(fmt.Sprintf("%08x %08x %08x %08x\n", uint64(time.Microsecond/time.Nanosecond), 64, 1000000, uint64(time.Second/time.Nanosecond)))), - "ptype": newStaticProcInode(ctx, msrc, []byte("Type Device Function\n")), - "route": seqfile.NewSeqFileInode(ctx, &netRoute{s: s}, msrc), - "tcp": seqfile.NewSeqFileInode(ctx, &netTCP{k: k}, msrc), - "udp": seqfile.NewSeqFileInode(ctx, &netUDP{k: k}, msrc), - "unix": seqfile.NewSeqFileInode(ctx, &netUnix{k: k}, msrc), - } - - if s.SupportsIPv6() { - contents["if_inet6"] = seqfile.NewSeqFileInode(ctx, &ifinet6{s: s}, msrc) - contents["ipv6_route"] = newStaticProcInode(ctx, msrc, []byte("")) - contents["tcp6"] = seqfile.NewSeqFileInode(ctx, &netTCP6{k: k}, msrc) - contents["udp6"] = newStaticProcInode(ctx, msrc, []byte(" sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n")) - } - } - d := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, t) -} - -// ifinet6 implements seqfile.SeqSource for /proc/net/if_inet6. -// -// +stateify savable -type ifinet6 struct { - s inet.Stack -} - -func (n *ifinet6) contents() []string { - var lines []string - nics := n.s.Interfaces() - for id, naddrs := range n.s.InterfaceAddrs() { - nic, ok := nics[id] - if !ok { - // NIC was added after NICNames was called. We'll just - // ignore it. - continue - } - - for _, a := range naddrs { - // IPv6 only. - if a.Family != linux.AF_INET6 { - continue - } - - // Fields: - // IPv6 address displayed in 32 hexadecimal chars without colons - // Netlink device number (interface index) in hexadecimal (use nic id) - // Prefix length in hexadecimal - // Scope value (use 0) - // Interface flags - // Device name - lines = append(lines, fmt.Sprintf("%032x %02x %02x %02x %02x %8s\n", a.Addr, id, a.PrefixLen, 0, a.Flags, nic.Name)) - } - } - return lines -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*ifinet6) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (n *ifinet6) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - contents := n.contents() - minI := 0 - if h != nil { - minI = h.(int) + 1 - if minI > len(contents) { - minI = len(contents) - } - } - var data []seqfile.SeqData - for i, l := range contents[minI:] { - data = append(data, seqfile.SeqData{Buf: []byte(l), Handle: i + minI}) - } - - return data, 0 -} - -// netDev implements seqfile.SeqSource for /proc/net/dev. -// -// +stateify savable -type netDev struct { - s inet.Stack -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (n *netDev) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. See Linux's -// net/core/net-procfs.c:dev_seq_show. -func (n *netDev) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - interfaces := n.s.Interfaces() - contents := make([]string, 2, 2+len(interfaces)) - // Add the table header. From net/core/net-procfs.c:dev_seq_show. - contents[0] = "Inter-| Receive | Transmit\n" - contents[1] = " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n" - - for _, i := range interfaces { - // Implements the same format as - // net/core/net-procfs.c:dev_seq_printf_stats. - var stats inet.StatDev - if err := n.s.Statistics(&stats, i.Name); err != nil { - log.Warningf("Failed to retrieve interface statistics for %v: %v", i.Name, err) - continue - } - l := fmt.Sprintf( - "%6s: %7d %7d %4d %4d %4d %5d %10d %9d %8d %7d %4d %4d %4d %5d %7d %10d\n", - i.Name, - // Received - stats[0], // bytes - stats[1], // packets - stats[2], // errors - stats[3], // dropped - stats[4], // fifo - stats[5], // frame - stats[6], // compressed - stats[7], // multicast - // Transmitted - stats[8], // bytes - stats[9], // packets - stats[10], // errors - stats[11], // dropped - stats[12], // fifo - stats[13], // frame - stats[14], // compressed - stats[15]) // multicast - contents = append(contents, l) - } - - minI := 0 - if h != nil { - minI = h.(int) + 1 - if minI > len(contents) { - minI = len(contents) - } - } - var data []seqfile.SeqData - for i, l := range contents[minI:] { - data = append(data, seqfile.SeqData{Buf: []byte(l), Handle: i + minI}) - } - - return data, 0 -} - -// netSnmp implements seqfile.SeqSource for /proc/net/snmp. -// -// +stateify savable -type netSnmp struct { - s inet.Stack -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (n *netSnmp) NeedsUpdate(generation int64) bool { - return true -} - -type snmpLine struct { - prefix string - header string -} - -var snmp = []snmpLine{ - { - prefix: "Ip", - header: "Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates", - }, - { - prefix: "Icmp", - header: "InMsgs InErrors InCsumErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskReps", - }, - { - prefix: "IcmpMsg", - }, - { - prefix: "Tcp", - header: "RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrors", - }, - { - prefix: "Udp", - header: "InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti", - }, - { - prefix: "UdpLite", - header: "InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti", - }, -} - -func toSlice(a any) []uint64 { - v := reflect.Indirect(reflect.ValueOf(a)) - return v.Slice(0, v.Len()).Interface().([]uint64) -} - -func sprintSlice(s []uint64) string { - if len(s) == 0 { - return "" - } - r := fmt.Sprint(s) - return r[1 : len(r)-1] // Remove "[]" introduced by fmt of slice. -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. See Linux's -// net/core/net-procfs.c:dev_seq_show. -func (n *netSnmp) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - contents := make([]string, 0, len(snmp)*2) - types := []any{ - &inet.StatSNMPIP{}, - &inet.StatSNMPICMP{}, - nil, // TODO(gvisor.dev/issue/628): Support IcmpMsg stats. - &inet.StatSNMPTCP{}, - &inet.StatSNMPUDP{}, - &inet.StatSNMPUDPLite{}, - } - for i, stat := range types { - line := snmp[i] - if stat == nil { - contents = append( - contents, - fmt.Sprintf("%s:\n", line.prefix), - fmt.Sprintf("%s:\n", line.prefix), - ) - continue - } - if err := n.s.Statistics(stat, line.prefix); err != nil { - if linuxerr.Equals(linuxerr.EOPNOTSUPP, err) { - log.Infof("Failed to retrieve %s of /proc/net/snmp: %v", line.prefix, err) - } else { - log.Warningf("Failed to retrieve %s of /proc/net/snmp: %v", line.prefix, err) - } - } - var values string - if line.prefix == "Tcp" { - tcp := stat.(*inet.StatSNMPTCP) - // "Tcp" needs special processing because MaxConn is signed. RFC 2012. - values = fmt.Sprintf("%s %d %s", sprintSlice(tcp[:3]), int64(tcp[3]), sprintSlice(tcp[4:])) - } else { - values = sprintSlice(toSlice(stat)) - } - contents = append( - contents, - fmt.Sprintf("%s: %s\n", line.prefix, line.header), - fmt.Sprintf("%s: %s\n", line.prefix, values), - ) - } - - minI := 0 - if h != nil { - minI = h.(int) + 1 - if minI > len(contents) { - minI = len(contents) - } - } - data := make([]seqfile.SeqData, 0, len(snmp)*2) - for i, l := range contents[minI:] { - data = append(data, seqfile.SeqData{Buf: []byte(l), Handle: i + minI}) - } - - return data, 0 -} - -// netRoute implements seqfile.SeqSource for /proc/net/route. -// -// +stateify savable -type netRoute struct { - s inet.Stack -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (n *netRoute) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -// See Linux's net/ipv4/fib_trie.c:fib_route_seq_show. -func (n *netRoute) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - interfaces := n.s.Interfaces() - contents := []string{"Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT"} - for _, rt := range n.s.RouteTable() { - // /proc/net/route only includes ipv4 routes. - if rt.Family != linux.AF_INET { - continue - } - - // /proc/net/route does not include broadcast or multicast routes. - if rt.Type == linux.RTN_BROADCAST || rt.Type == linux.RTN_MULTICAST { - continue - } - - iface, ok := interfaces[rt.OutputInterface] - if !ok || iface.Name == "lo" { - continue - } - - var ( - gw uint32 - prefix uint32 - flags = linux.RTF_UP - ) - if len(rt.GatewayAddr) == header.IPv4AddressSize { - flags |= linux.RTF_GATEWAY - gw = hostarch.ByteOrder.Uint32(rt.GatewayAddr) - } - if len(rt.DstAddr) == header.IPv4AddressSize { - prefix = hostarch.ByteOrder.Uint32(rt.DstAddr) - } - l := fmt.Sprintf( - "%s\t%08X\t%08X\t%04X\t%d\t%d\t%d\t%08X\t%d\t%d\t%d", - iface.Name, - prefix, - gw, - flags, - 0, // RefCnt. - 0, // Use. - 0, // Metric. - (uint32(1)< len(contents) { - minI = len(contents) - } - } - var data []seqfile.SeqData - for i, l := range contents[minI:] { - l = fmt.Sprintf("%-127s\n", l) - data = append(data, seqfile.SeqData{Buf: []byte(l), Handle: i + minI}) - } - - return data, 0 -} - -// netUnix implements seqfile.SeqSource for /proc/net/unix. -// -// +stateify savable -type netUnix struct { - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*netUnix) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (n *netUnix) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - var buf bytes.Buffer - for _, se := range n.k.ListSockets() { - s := se.Sock.Get() - if s == nil { - log.Debugf("Couldn't resolve weakref with ID %v in socket table, racing with destruction?", se.ID) - continue - } - sfile := s.(*fs.File) - if family, _, _ := sfile.FileOperations.(socket.Socket).Type(); family != linux.AF_UNIX { - s.DecRef(ctx) - // Not a unix socket. - continue - } - sops := sfile.FileOperations.(*unix.SocketOperations) - - addr, err := sops.Endpoint().GetLocalAddress() - if err != nil { - log.Warningf("Failed to retrieve socket name from %+v: %v", sfile, err) - addr.Addr = "" - } - - sockFlags := 0 - if ce, ok := sops.Endpoint().(transport.ConnectingEndpoint); ok { - ce.Lock() - if ce.ListeningLocked() { - // For unix domain sockets, linux reports a single flag - // value if the socket is listening, of __SO_ACCEPTCON. - sockFlags = linux.SO_ACCEPTCON - } - ce.Unlock() - } - - // In the socket entry below, the value for the 'Num' field requires - // some consideration. Linux prints the address to the struct - // unix_sock representing a socket in the kernel, but may redact the - // value for unprivileged users depending on the kptr_restrict - // sysctl. - // - // One use for this field is to allow a privileged user to - // introspect into the kernel memory to determine information about - // a socket not available through procfs, such as the socket's peer. - // - // On gvisor, returning a pointer to our internal structures would - // be pointless, as it wouldn't match the memory layout for struct - // unix_sock, making introspection difficult. We could populate a - // struct unix_sock with the appropriate data, but even that - // requires consideration for which kernel version to emulate, as - // the definition of this struct changes over time. - // - // For now, we always redact this pointer. - fmt.Fprintf(&buf, "%#016p: %08X %08X %08X %04X %02X %5d", - (*unix.SocketOperations)(nil), // Num, pointer to kernel socket struct. - sfile.ReadRefs()-1, // RefCount, don't count our own ref. - 0, // Protocol, always 0 for UDS. - sockFlags, // Flags. - sops.Endpoint().Type(), // Type. - sops.State(), // State. - sfile.InodeID(), // Inode. - ) - - // Path - if len(addr.Addr) != 0 { - if addr.Addr[0] == 0 { - // Abstract path. - fmt.Fprintf(&buf, " @%s", string(addr.Addr[1:])) - } else { - fmt.Fprintf(&buf, " %s", string(addr.Addr)) - } - } - fmt.Fprintf(&buf, "\n") - - s.DecRef(ctx) - } - - minI := 0 - if h != nil { - minI = h.(int) + 1 - } - var data []seqfile.SeqData - if minI <= 0 { - data = append(data, seqfile.SeqData{ - Buf: []byte("Num RefCount Protocol Flags Type St Inode Path\n"), - Handle: 0, - }) - } - if minI <= 1 { - data = append(data, seqfile.SeqData{ - Buf: buf.Bytes(), - Handle: 1, - }) - } - return data, 0 -} - -func networkToHost16(n uint16) uint16 { - // n is in network byte order, so is big-endian. The most-significant byte - // should be stored in the lower address. - // - // We manually inline binary.BigEndian.Uint16() because Go does not support - // non-primitive consts, so binary.BigEndian is a (mutable) var, so calls to - // binary.BigEndian.Uint16() require a read of binary.BigEndian and an - // interface method call, defeating inlining. - buf := [2]byte{byte(n >> 8 & 0xff), byte(n & 0xff)} - return hostarch.ByteOrder.Uint16(buf[:]) -} - -func writeInetAddr(w io.Writer, family int, i linux.SockAddr) { - switch family { - case linux.AF_INET: - var a linux.SockAddrInet - if i != nil { - a = *i.(*linux.SockAddrInet) - } - - // linux.SockAddrInet.Port is stored in the network byte order and is - // printed like a number in host byte order. Note that all numbers in host - // byte order are printed with the most-significant byte first when - // formatted with %X. See get_tcp4_sock() and udp4_format_sock() in Linux. - port := networkToHost16(a.Port) - - // linux.SockAddrInet.Addr is stored as a byte slice in big-endian order - // (i.e. most-significant byte in index 0). Linux represents this as a - // __be32 which is a typedef for an unsigned int, and is printed with - // %X. This means that for a little-endian machine, Linux prints the - // least-significant byte of the address first. To emulate this, we first - // invert the byte order for the address using hostarch.ByteOrder.Uint32, - // which makes it have the equivalent encoding to a __be32 on a little - // endian machine. Note that this operation is a no-op on a big endian - // machine. Then similar to Linux, we format it with %X, which will print - // the most-significant byte of the __be32 address first, which is now - // actually the least-significant byte of the original address in - // linux.SockAddrInet.Addr on little endian machines, due to the conversion. - addr := hostarch.ByteOrder.Uint32(a.Addr[:]) - - fmt.Fprintf(w, "%08X:%04X ", addr, port) - case linux.AF_INET6: - var a linux.SockAddrInet6 - if i != nil { - a = *i.(*linux.SockAddrInet6) - } - - port := networkToHost16(a.Port) - addr0 := hostarch.ByteOrder.Uint32(a.Addr[0:4]) - addr1 := hostarch.ByteOrder.Uint32(a.Addr[4:8]) - addr2 := hostarch.ByteOrder.Uint32(a.Addr[8:12]) - addr3 := hostarch.ByteOrder.Uint32(a.Addr[12:16]) - fmt.Fprintf(w, "%08X%08X%08X%08X:%04X ", addr0, addr1, addr2, addr3, port) - } -} - -func commonReadSeqFileDataTCP(ctx context.Context, n seqfile.SeqHandle, k *kernel.Kernel, h seqfile.SeqHandle, fa int, header []byte) ([]seqfile.SeqData, int64) { - // t may be nil here if our caller is not part of a task goroutine. This can - // happen for example if we're here for "sentryctl cat". When t is nil, - // degrade gracefully and retrieve what we can. - t := kernel.TaskFromContext(ctx) - - var buf bytes.Buffer - for _, se := range k.ListSockets() { - s := se.Sock.Get() - if s == nil { - log.Debugf("Couldn't resolve weakref with ID %v in socket table, racing with destruction?", se.ID) - continue - } - sfile := s.(*fs.File) - sops, ok := sfile.FileOperations.(socket.Socket) - if !ok { - panic(fmt.Sprintf("Found non-socket file in socket table: %+v", sfile)) - } - if family, stype, _ := sops.Type(); !(family == fa && stype == linux.SOCK_STREAM) { - s.DecRef(ctx) - // Not tcp4 sockets. - continue - } - - // Linux's documentation for the fields below can be found at - // https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt. - // For Linux's implementation, see net/ipv4/tcp_ipv4.c:get_tcp4_sock(). - // Note that the header doesn't contain labels for all the fields. - - // Field: sl; entry number. - fmt.Fprintf(&buf, "%4d: ", se.ID) - - // Field: local_adddress. - var localAddr linux.SockAddr - if t != nil { - if local, _, err := sops.GetSockName(t); err == nil { - localAddr = local - } - } - writeInetAddr(&buf, fa, localAddr) - - // Field: rem_address. - var remoteAddr linux.SockAddr - if t != nil { - if remote, _, err := sops.GetPeerName(t); err == nil { - remoteAddr = remote - } - } - writeInetAddr(&buf, fa, remoteAddr) - - // Field: state; socket state. - fmt.Fprintf(&buf, "%02X ", sops.State()) - - // Field: tx_queue, rx_queue; number of packets in the transmit and - // receive queue. Unimplemented. - fmt.Fprintf(&buf, "%08X:%08X ", 0, 0) - - // Field: tr, tm->when; timer active state and number of jiffies - // until timer expires. Unimplemented. - fmt.Fprintf(&buf, "%02X:%08X ", 0, 0) - - // Field: retrnsmt; number of unrecovered RTO timeouts. - // Unimplemented. - fmt.Fprintf(&buf, "%08X ", 0) - - // Field: uid. - uattr, err := sfile.Dirent.Inode.UnstableAttr(ctx) - if err != nil { - log.Warningf("Failed to retrieve unstable attr for socket file: %v", err) - fmt.Fprintf(&buf, "%5d ", 0) - } else { - creds := auth.CredentialsFromContext(ctx) - fmt.Fprintf(&buf, "%5d ", uint32(uattr.Owner.UID.In(creds.UserNamespace).OrOverflow())) - } - - // Field: timeout; number of unanswered 0-window probes. - // Unimplemented. - fmt.Fprintf(&buf, "%8d ", 0) - - // Field: inode. - fmt.Fprintf(&buf, "%8d ", sfile.InodeID()) - - // Field: refcount. Don't count the ref we obtain while deferencing - // the weakref to this socket. - fmt.Fprintf(&buf, "%d ", sfile.ReadRefs()-1) - - // Field: Socket struct address. Redacted due to the same reason as - // the 'Num' field in /proc/net/unix, see netUnix.ReadSeqFileData. - fmt.Fprintf(&buf, "%#016p ", (*socket.Socket)(nil)) - - // Field: retransmit timeout. Unimplemented. - fmt.Fprintf(&buf, "%d ", 0) - - // Field: predicted tick of soft clock (delayed ACK control data). - // Unimplemented. - fmt.Fprintf(&buf, "%d ", 0) - - // Field: (ack.quick<<1)|ack.pingpong, Unimplemented. - fmt.Fprintf(&buf, "%d ", 0) - - // Field: sending congestion window, Unimplemented. - fmt.Fprintf(&buf, "%d ", 0) - - // Field: Slow start size threshold, -1 if threshold >= 0xFFFF. - // Unimplemented, report as large threshold. - fmt.Fprintf(&buf, "%d", -1) - - fmt.Fprintf(&buf, "\n") - - s.DecRef(ctx) - } - - minI := 0 - if h != nil { - minI = h.(int) + 1 - } - var data []seqfile.SeqData - if minI <= 0 { - data = append(data, seqfile.SeqData{ - Buf: header, - Handle: 0, - }) - } - if minI <= 1 { - data = append(data, seqfile.SeqData{ - Buf: buf.Bytes(), - Handle: 1, - }) - } - return data, 0 -} - -// netTCP implements seqfile.SeqSource for /proc/net/tcp. -// -// +stateify savable -type netTCP struct { - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*netTCP) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - header := []byte(" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode \n") - return commonReadSeqFileDataTCP(ctx, n, n.k, h, linux.AF_INET, header) -} - -// netTCP6 implements seqfile.SeqSource for /proc/net/tcp6. -// -// +stateify savable -type netTCP6 struct { - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*netTCP6) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (n *netTCP6) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - header := []byte(" sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n") - return commonReadSeqFileDataTCP(ctx, n, n.k, h, linux.AF_INET6, header) -} - -// netUDP implements seqfile.SeqSource for /proc/net/udp. -// -// +stateify savable -type netUDP struct { - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*netUDP) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (n *netUDP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - // t may be nil here if our caller is not part of a task goroutine. This can - // happen for example if we're here for "sentryctl cat". When t is nil, - // degrade gracefully and retrieve what we can. - t := kernel.TaskFromContext(ctx) - - var buf bytes.Buffer - for _, se := range n.k.ListSockets() { - s := se.Sock.Get() - if s == nil { - log.Debugf("Couldn't resolve weakref with ID %v in socket table, racing with destruction?", se.ID) - continue - } - sfile := s.(*fs.File) - sops, ok := sfile.FileOperations.(socket.Socket) - if !ok { - panic(fmt.Sprintf("Found non-socket file in socket table: %+v", sfile)) - } - if family, stype, _ := sops.Type(); family != linux.AF_INET || stype != linux.SOCK_DGRAM { - s.DecRef(ctx) - // Not udp4 socket. - continue - } - - // For Linux's implementation, see net/ipv4/udp.c:udp4_format_sock(). - - // Field: sl; entry number. - fmt.Fprintf(&buf, "%5d: ", se.ID) - - // Field: local_adddress. - var localAddr linux.SockAddrInet - if t != nil { - if local, _, err := sops.GetSockName(t); err == nil { - localAddr = *local.(*linux.SockAddrInet) - } - } - writeInetAddr(&buf, linux.AF_INET, &localAddr) - - // Field: rem_address. - var remoteAddr linux.SockAddrInet - if t != nil { - if remote, _, err := sops.GetPeerName(t); err == nil { - remoteAddr = *remote.(*linux.SockAddrInet) - } - } - writeInetAddr(&buf, linux.AF_INET, &remoteAddr) - - // Field: state; socket state. - fmt.Fprintf(&buf, "%02X ", sops.State()) - - // Field: tx_queue, rx_queue; number of packets in the transmit and - // receive queue. Unimplemented. - fmt.Fprintf(&buf, "%08X:%08X ", 0, 0) - - // Field: tr, tm->when. Always 0 for UDP. - fmt.Fprintf(&buf, "%02X:%08X ", 0, 0) - - // Field: retrnsmt. Always 0 for UDP. - fmt.Fprintf(&buf, "%08X ", 0) - - // Field: uid. - uattr, err := sfile.Dirent.Inode.UnstableAttr(ctx) - if err != nil { - log.Warningf("Failed to retrieve unstable attr for socket file: %v", err) - fmt.Fprintf(&buf, "%5d ", 0) - } else { - creds := auth.CredentialsFromContext(ctx) - fmt.Fprintf(&buf, "%5d ", uint32(uattr.Owner.UID.In(creds.UserNamespace).OrOverflow())) - } - - // Field: timeout. Always 0 for UDP. - fmt.Fprintf(&buf, "%8d ", 0) - - // Field: inode. - fmt.Fprintf(&buf, "%8d ", sfile.InodeID()) - - // Field: ref; reference count on the socket inode. Don't count the ref - // we obtain while deferencing the weakref to this socket. - fmt.Fprintf(&buf, "%d ", sfile.ReadRefs()-1) - - // Field: Socket struct address. Redacted due to the same reason as - // the 'Num' field in /proc/net/unix, see netUnix.ReadSeqFileData. - fmt.Fprintf(&buf, "%#016p ", (*socket.Socket)(nil)) - - // Field: drops; number of dropped packets. Unimplemented. - fmt.Fprintf(&buf, "%d", 0) - - fmt.Fprintf(&buf, "\n") - - s.DecRef(ctx) - } - - minI := 0 - if h != nil { - minI = h.(int) + 1 - } - var data []seqfile.SeqData - if minI <= 0 { - data = append(data, seqfile.SeqData{ - Buf: []byte(" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops \n"), - Handle: 0, - }) - } - if minI <= 1 { - data = append(data, seqfile.SeqData{ - Buf: buf.Bytes(), - Handle: 1, - }) - } - return data, 0 -} - -// LINT.ThenChange(../../fsimpl/proc/task_net.go) diff --git a/pkg/sentry/fs/proc/net_test.go b/pkg/sentry/fs/proc/net_test.go deleted file mode 100644 index f18681405..000000000 --- a/pkg/sentry/fs/proc/net_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "reflect" - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/inet" -) - -func newIPv6TestStack() *inet.TestStack { - s := inet.NewTestStack() - s.SupportsIPv6Flag = true - return s -} - -func TestIfinet6NoAddresses(t *testing.T) { - n := &ifinet6{s: newIPv6TestStack()} - if got := n.contents(); got != nil { - t.Errorf("Got n.contents() = %v, want = %v", got, nil) - } -} - -func TestIfinet6(t *testing.T) { - s := newIPv6TestStack() - s.InterfacesMap[1] = inet.Interface{Name: "eth0"} - s.InterfaceAddrsMap[1] = []inet.InterfaceAddr{ - { - Family: linux.AF_INET6, - PrefixLen: 128, - Addr: []byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"), - }, - } - s.InterfacesMap[2] = inet.Interface{Name: "eth1"} - s.InterfaceAddrsMap[2] = []inet.InterfaceAddr{ - { - Family: linux.AF_INET6, - PrefixLen: 128, - Addr: []byte("\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"), - }, - } - want := map[string]struct{}{ - "000102030405060708090a0b0c0d0e0f 01 80 00 00 eth0\n": {}, - "101112131415161718191a1b1c1d1e1f 02 80 00 00 eth1\n": {}, - } - - n := &ifinet6{s: s} - contents := n.contents() - if len(contents) != len(want) { - t.Errorf("Got len(n.contents()) = %d, want = %d", len(contents), len(want)) - } - got := map[string]struct{}{} - for _, l := range contents { - got[l] = struct{}{} - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Got n.contents() = %v, want = %v", got, want) - } -} diff --git a/pkg/sentry/fs/proc/proc.go b/pkg/sentry/fs/proc/proc.go deleted file mode 100644 index b9629c598..000000000 --- a/pkg/sentry/fs/proc/proc.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2018 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. - -// Package proc implements a partial in-memory file system for procfs. -package proc - -import ( - "fmt" - "sort" - "strconv" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/device" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -// proc is a root proc node. -// -// +stateify savable -type proc struct { - ramfs.Dir - - // k is the Kernel containing this proc node. - k *kernel.Kernel - - // pidns is the PID namespace of the task that mounted the proc filesystem - // that this node represents. - pidns *kernel.PIDNamespace - - // cgroupControllers is a map of controller name to directory in the - // cgroup hierarchy. These controllers are immutable and will be listed - // in /proc/pid/cgroup if not nil. - cgroupControllers map[string]string -} - -// New returns the root node of a partial simple procfs. -func New(ctx context.Context, msrc *fs.MountSource, cgroupControllers map[string]string) (*fs.Inode, error) { - k := kernel.KernelFromContext(ctx) - if k == nil { - return nil, fmt.Errorf("procfs requires a kernel") - } - pidns := kernel.PIDNamespaceFromContext(ctx) - if pidns == nil { - return nil, fmt.Errorf("procfs requires a PID namespace") - } - - // Note that these are just the static members. There are dynamic - // members populated in Readdir and Lookup below. - contents := map[string]*fs.Inode{ - "cpuinfo": newCPUInfo(ctx, msrc), - "filesystems": seqfile.NewSeqFileInode(ctx, &filesystemsData{}, msrc), - "loadavg": seqfile.NewSeqFileInode(ctx, &loadavgData{}, msrc), - "meminfo": seqfile.NewSeqFileInode(ctx, &meminfoData{k}, msrc), - "mounts": newProcInode(ctx, ramfs.NewSymlink(ctx, fs.RootOwner, "self/mounts"), msrc, fs.Symlink, nil), - "net": newProcInode(ctx, ramfs.NewSymlink(ctx, fs.RootOwner, "self/net"), msrc, fs.Symlink, nil), - "self": newSelf(ctx, pidns, msrc), - "stat": seqfile.NewSeqFileInode(ctx, &statData{k}, msrc), - "thread-self": newThreadSelf(ctx, pidns, msrc), - "uptime": newUptime(ctx, msrc), - "version": seqfile.NewSeqFileInode(ctx, &versionData{k}, msrc), - } - - // Construct the proc InodeOperations. - p := &proc{ - Dir: *ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)), - k: k, - pidns: pidns, - cgroupControllers: cgroupControllers, - } - - // Add more contents that need proc to be initialized. - p.AddChild(ctx, "sys", p.newSysDir(ctx, msrc)) - - return newProcInode(ctx, p, msrc, fs.SpecialDirectory, nil), nil -} - -// self is a magical link. -// -// +stateify savable -type self struct { - ramfs.Symlink - - pidns *kernel.PIDNamespace -} - -// newSelf returns a new "self" node. -func newSelf(ctx context.Context, pidns *kernel.PIDNamespace, msrc *fs.MountSource) *fs.Inode { - s := &self{ - Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, ""), - pidns: pidns, - } - return newProcInode(ctx, s, msrc, fs.Symlink, nil) -} - -// newThreadSelf returns a new "threadSelf" node. -func newThreadSelf(ctx context.Context, pidns *kernel.PIDNamespace, msrc *fs.MountSource) *fs.Inode { - s := &threadSelf{ - Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, ""), - pidns: pidns, - } - return newProcInode(ctx, s, msrc, fs.Symlink, nil) -} - -// Readlink implements fs.InodeOperations.Readlink. -func (s *self) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - if t := kernel.TaskFromContext(ctx); t != nil { - tgid := s.pidns.IDOfThreadGroup(t.ThreadGroup()) - if tgid == 0 { - return "", linuxerr.ENOENT - } - return strconv.FormatUint(uint64(tgid), 10), nil - } - - // Who is reading this link? - return "", linuxerr.EINVAL -} - -// threadSelf is more magical than "self" link. -// -// +stateify savable -type threadSelf struct { - ramfs.Symlink - - pidns *kernel.PIDNamespace -} - -// Readlink implements fs.InodeOperations.Readlink. -func (s *threadSelf) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - if t := kernel.TaskFromContext(ctx); t != nil { - tgid := s.pidns.IDOfThreadGroup(t.ThreadGroup()) - tid := s.pidns.IDOfTask(t) - if tid == 0 || tgid == 0 { - return "", linuxerr.ENOENT - } - return fmt.Sprintf("%d/task/%d", tgid, tid), nil - } - - // Who is reading this link? - return "", linuxerr.EINVAL -} - -// Lookup loads an Inode at name into a Dirent. -func (p *proc) Lookup(ctx context.Context, dir *fs.Inode, name string) (*fs.Dirent, error) { - dirent, walkErr := p.Dir.Lookup(ctx, dir, name) - if walkErr == nil { - return dirent, nil - } - - // Try to lookup a corresponding task. - tid, err := strconv.ParseUint(name, 10, 64) - if err != nil { - // Ignore the parse error and return the original. - return nil, walkErr - } - - // Grab the other task. - otherTask := p.pidns.TaskWithID(kernel.ThreadID(tid)) - if otherTask == nil { - // Per above. - return nil, walkErr - } - - // Wrap it in a taskDir. - td := p.newTaskDir(ctx, otherTask, dir.MountSource, true) - return fs.NewDirent(ctx, td, name), nil -} - -// GetFile implements fs.InodeOperations. -func (p *proc) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &rootProcFile{iops: p}), nil -} - -// rootProcFile implements fs.FileOperations for the proc directory. -// -// +stateify savable -type rootProcFile struct { - fsutil.DirFileOperations `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - iops *proc -} - -var _ fs.FileOperations = (*rootProcFile)(nil) - -// Readdir implements fs.FileOperations.Readdir. -func (rpf *rootProcFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) { - offset := file.Offset() - dirCtx := &fs.DirCtx{ - Serializer: ser, - } - - // Get normal directory contents from ramfs dir. - names, m := rpf.iops.Dir.Children() - - // Add dot and dotdot. - root := fs.RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - dot, dotdot := file.Dirent.GetDotAttrs(root) - names = append(names, ".", "..") - m["."] = dot - m[".."] = dotdot - - // Collect tasks. - // Per linux we only include it in directory listings if it's the leader. - // But for whatever crazy reason, you can still walk to the given node. - for _, tg := range rpf.iops.pidns.ThreadGroups() { - if leader := tg.Leader(); leader != nil { - name := strconv.FormatUint(uint64(rpf.iops.pidns.IDOfThreadGroup(tg)), 10) - m[name] = fs.GenericDentAttr(fs.SpecialDirectory, device.ProcDevice) - names = append(names, name) - } - } - - if offset >= int64(len(m)) { - return offset, nil - } - sort.Strings(names) - names = names[offset:] - for _, name := range names { - if err := dirCtx.DirEmit(name, m[name]); err != nil { - return offset, err - } - offset++ - } - return offset, nil -} - -// LINT.ThenChange(../../fsimpl/proc/tasks.go) diff --git a/pkg/sentry/fs/proc/seqfile/BUILD b/pkg/sentry/fs/proc/seqfile/BUILD deleted file mode 100644 index 90bd32345..000000000 --- a/pkg/sentry/fs/proc/seqfile/BUILD +++ /dev/null @@ -1,36 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "seqfile", - srcs = ["seqfile.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/proc/device", - "//pkg/sentry/kernel/time", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - ], -) - -go_test( - name = "seqfile_test", - size = "small", - srcs = ["seqfile_test.go"], - library = ":seqfile", - deps = [ - "//pkg/context", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - "//pkg/sentry/fs/ramfs", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fs/proc/seqfile/seqfile.go b/pkg/sentry/fs/proc/seqfile/seqfile.go deleted file mode 100644 index 69a2ad0e9..000000000 --- a/pkg/sentry/fs/proc/seqfile/seqfile.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2018 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. - -// Package seqfile provides dynamic ordered files. -package seqfile - -import ( - "io" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/device" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// SeqHandle is a helper handle to seek in the file. -type SeqHandle any - -// SeqData holds the data for one unit in the file. -// -// +stateify savable -type SeqData struct { - // The data to be returned to the user. - Buf []byte - - // A seek handle used to find the next valid unit in ReadSeqFiledata. - Handle SeqHandle -} - -// SeqSource is a data source for a SeqFile file. -type SeqSource interface { - // NeedsUpdate returns true if the consumer of SeqData should call - // ReadSeqFileData again. Generation is the generation returned by - // ReadSeqFile or 0. - NeedsUpdate(generation int64) bool - - // Returns a slice of SeqData ordered by unit and the current - // generation. The first entry in the slice is greater than the handle. - // If handle is nil then all known records are returned. Generation - // must always be greater than 0. - ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64) -} - -// SeqGenerationCounter is a counter to keep track if the SeqSource should be -// updated. SeqGenerationCounter is not thread-safe and should be protected -// with a mutex. -type SeqGenerationCounter struct { - // The generation that the SeqData is at. - generation int64 -} - -// SetGeneration sets the generation to the new value, be careful to not set it -// to a value less than current. -func (s *SeqGenerationCounter) SetGeneration(generation int64) { - s.generation = generation -} - -// Update increments the current generation. -func (s *SeqGenerationCounter) Update() { - s.generation++ -} - -// Generation returns the current generation counter. -func (s *SeqGenerationCounter) Generation() int64 { - return s.generation -} - -// IsCurrent returns whether the given generation is current or not. -func (s *SeqGenerationCounter) IsCurrent(generation int64) bool { - return s.Generation() == generation -} - -// SeqFile is used to provide dynamic files that can be ordered by record. -// -// +stateify savable -type SeqFile struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotAllocatable `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeNotTruncatable `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleExtendedAttributes - fsutil.InodeSimpleAttributes - - // mu protects the fields below. - mu sync.Mutex `state:"nosave"` - - SeqSource - - source []SeqData - generation int64 - lastRead int64 -} - -var _ fs.InodeOperations = (*SeqFile)(nil) - -// NewSeqFile returns a seqfile suitable for use by external consumers. -func NewSeqFile(ctx context.Context, source SeqSource) *SeqFile { - return &SeqFile{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - SeqSource: source, - } -} - -// NewSeqFileInode returns an Inode with SeqFile InodeOperations. -func NewSeqFileInode(ctx context.Context, source SeqSource, msrc *fs.MountSource) *fs.Inode { - iops := NewSeqFile(ctx, source) - sattr := fs.StableAttr{ - DeviceID: device.ProcDevice.DeviceID(), - InodeID: device.ProcDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialFile, - } - return fs.NewInode(ctx, iops, msrc, sattr) -} - -// UnstableAttr returns unstable attributes of the SeqFile. -func (s *SeqFile) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - uattr, err := s.InodeSimpleAttributes.UnstableAttr(ctx, inode) - if err != nil { - return fs.UnstableAttr{}, err - } - uattr.ModificationTime = ktime.NowFromContext(ctx) - return uattr, nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (s *SeqFile) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &seqFileOperations{seqFile: s}), nil -} - -// findIndexAndOffset finds the unit that corresponds to a certain offset. -// Returns the unit and the offset within the unit. If there are not enough -// units len(data) and leftover offset is returned. -func findIndexAndOffset(data []SeqData, offset int64) (int, int64) { - for i, buf := range data { - l := int64(len(buf.Buf)) - if offset < l { - return i, offset - } - offset -= l - } - return len(data), offset -} - -// updateSourceLocked requires that s.mu is held. -func (s *SeqFile) updateSourceLocked(ctx context.Context, record int) { - var h SeqHandle - if record == 0 { - h = nil - } else { - h = s.source[record-1].Handle - } - // Save what we have previously read. - s.source = s.source[:record] - var newSource []SeqData - newSource, s.generation = s.SeqSource.ReadSeqFileData(ctx, h) - s.source = append(s.source, newSource...) -} - -// seqFileOperations implements fs.FileOperations. -// -// +stateify savable -type seqFileOperations struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - seqFile *SeqFile -} - -var _ fs.FileOperations = (*seqFileOperations)(nil) - -// Write implements fs.FileOperations.Write. -func (*seqFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EACCES -} - -// Read implements fs.FileOperations.Read. -func (sfo *seqFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - sfo.seqFile.mu.Lock() - defer sfo.seqFile.mu.Unlock() - - sfo.seqFile.NotifyAccess(ctx) - defer func() { sfo.seqFile.lastRead = offset }() - - updated := false - - // Try to find where we should start reading this file. - i, recordOffset := findIndexAndOffset(sfo.seqFile.source, offset) - if i == len(sfo.seqFile.source) { - // Ok, we're at EOF. Let's first check to see if there might be - // more data available to us. If there is more data, add it to - // the end and try reading again. - if !sfo.seqFile.SeqSource.NeedsUpdate(sfo.seqFile.generation) { - return 0, io.EOF - } - oldLen := len(sfo.seqFile.source) - sfo.seqFile.updateSourceLocked(ctx, len(sfo.seqFile.source)) - updated = true - // We know that we had consumed everything up until this point - // so we search in the new slice instead of starting over. - i, recordOffset = findIndexAndOffset(sfo.seqFile.source[oldLen:], recordOffset) - i += oldLen - // i is at most the length of the slice which is - // len(sfo.seqFile.source) - oldLen. So at most i will be equal to - // len(sfo.seqFile.source). - if i == len(sfo.seqFile.source) { - return 0, io.EOF - } - } - - var done int64 - // We're reading parts of a record, finish reading the current object - // before continuing on to the next. We don't refresh our data source - // before this record is completed. - if recordOffset != 0 { - n, err := dst.CopyOut(ctx, sfo.seqFile.source[i].Buf[recordOffset:]) - done += int64(n) - dst = dst.DropFirst(n) - if dst.NumBytes() == 0 || err != nil { - return done, err - } - i++ - } - - // Next/New unit, update the source file if necessary. Make an extra - // check to see if we've seeked backwards and if so always update our - // data source. - if !updated && (sfo.seqFile.SeqSource.NeedsUpdate(sfo.seqFile.generation) || sfo.seqFile.lastRead > offset) { - sfo.seqFile.updateSourceLocked(ctx, i) - // recordOffset is 0 here and we won't update records behind the - // current one so recordOffset is still 0 even though source - // just got updated. Just read the next record. - } - - // Finish by reading all the available data. - for _, buf := range sfo.seqFile.source[i:] { - n, err := dst.CopyOut(ctx, buf.Buf) - done += int64(n) - dst = dst.DropFirst(n) - if dst.NumBytes() == 0 || err != nil { - return done, err - } - } - - // If the file shrank (entries not yet read were removed above) - // while we tried to read we can end up with nothing read. - if done == 0 && dst.NumBytes() != 0 { - return 0, io.EOF - } - return done, nil -} diff --git a/pkg/sentry/fs/proc/seqfile/seqfile_test.go b/pkg/sentry/fs/proc/seqfile/seqfile_test.go deleted file mode 100644 index 98e394569..000000000 --- a/pkg/sentry/fs/proc/seqfile/seqfile_test.go +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2018 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. - -package seqfile - -import ( - "bytes" - "fmt" - "io" - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/usermem" -) - -type seqTest struct { - actual []SeqData - update bool -} - -func (s *seqTest) Init() { - var sq []SeqData - // Create some SeqData. - for i := 0; i < 10; i++ { - var b []byte - for j := 0; j < 10; j++ { - b = append(b, byte(i)) - } - sq = append(sq, SeqData{ - Buf: b, - Handle: &testHandle{i: i}, - }) - } - s.actual = sq -} - -// NeedsUpdate reports whether we need to update the data we've previously read. -func (s *seqTest) NeedsUpdate(int64) bool { - return s.update -} - -// ReadSeqFiledata returns a slice of SeqData which contains elements -// greater than the handle. -func (s *seqTest) ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64) { - if handle == nil { - return s.actual, 0 - } - h := *handle.(*testHandle) - var ret []SeqData - for _, b := range s.actual { - // We want the next one. - h2 := *b.Handle.(*testHandle) - if h2.i > h.i { - ret = append(ret, b) - } - } - return ret, 0 -} - -// Flatten a slice of slices into one slice. -func flatten(buf ...[]byte) []byte { - var flat []byte - for _, b := range buf { - flat = append(flat, b...) - } - return flat -} - -type testHandle struct { - i int -} - -type testTable struct { - offset int64 - readBufferSize int - expectedData []byte - expectedError error -} - -func runTableTests(ctx context.Context, table []testTable, dirent *fs.Dirent) error { - for _, tt := range table { - file, err := dirent.Inode.InodeOperations.GetFile(ctx, dirent, fs.FileFlags{Read: true}) - if err != nil { - return fmt.Errorf("GetFile returned error: %v", err) - } - - data := make([]byte, tt.readBufferSize) - resultLen, err := file.Preadv(ctx, usermem.BytesIOSequence(data), tt.offset) - if err != tt.expectedError { - return fmt.Errorf("t.Preadv(len: %v, offset: %v) (error) => %v expected %v", tt.readBufferSize, tt.offset, err, tt.expectedError) - } - expectedLen := int64(len(tt.expectedData)) - if resultLen != expectedLen { - // We make this just an error so we wall through and print the data below. - return fmt.Errorf("t.Preadv(len: %v, offset: %v) (size) => %v expected %v", tt.readBufferSize, tt.offset, resultLen, expectedLen) - } - if !bytes.Equal(data[:expectedLen], tt.expectedData) { - return fmt.Errorf("t.Preadv(len: %v, offset: %v) (data) => %v expected %v", tt.readBufferSize, tt.offset, data[:expectedLen], tt.expectedData) - } - } - return nil -} - -func TestSeqFile(t *testing.T) { - testSource := &seqTest{} - testSource.Init() - - // Create a file that can be R/W. - ctx := contexttest.Context(t) - m := fs.NewPseudoMountSource(ctx) - contents := map[string]*fs.Inode{ - "foo": NewSeqFileInode(ctx, testSource, m), - } - root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777)) - - // How about opening it? - inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory}) - dirent2, err := root.Lookup(ctx, inode, "foo") - if err != nil { - t.Fatalf("failed to walk to foo for n2: %v", err) - } - n2 := dirent2.Inode.InodeOperations - file2, err := n2.GetFile(ctx, dirent2, fs.FileFlags{Read: true, Write: true}) - if err != nil { - t.Fatalf("GetFile returned error: %v", err) - } - - // Writing? - if _, err := file2.Writev(ctx, usermem.BytesIOSequence([]byte("test"))); err == nil { - t.Fatalf("managed to write to n2: %v", err) - } - - // How about reading? - dirent3, err := root.Lookup(ctx, inode, "foo") - if err != nil { - t.Fatalf("failed to walk to foo: %v", err) - } - n3 := dirent3.Inode.InodeOperations - if n2 != n3 { - t.Error("got n2 != n3, want same") - } - - testSource.update = true - - table := []testTable{ - // Read past the end. - {100, 4, []byte{}, io.EOF}, - {110, 4, []byte{}, io.EOF}, - {200, 4, []byte{}, io.EOF}, - // Read a truncated first line. - {0, 4, testSource.actual[0].Buf[:4], nil}, - // Read the whole first line. - {0, 10, testSource.actual[0].Buf, nil}, - // Read the whole first line + 5 bytes of second line. - {0, 15, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:5]), nil}, - // First 4 bytes of the second line. - {10, 4, testSource.actual[1].Buf[:4], nil}, - // Read the two first lines. - {0, 20, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf), nil}, - // Read three lines. - {0, 30, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf), nil}, - // Read everything, but use a bigger buffer than necessary. - {0, 150, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf, testSource.actual[3].Buf, testSource.actual[4].Buf, testSource.actual[5].Buf, testSource.actual[6].Buf, testSource.actual[7].Buf, testSource.actual[8].Buf, testSource.actual[9].Buf), nil}, - // Read the last 3 bytes. - {97, 10, testSource.actual[9].Buf[7:], nil}, - } - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed with testSource.update = %v : %v", testSource.update, err) - } - - // Disable updates and do it again. - testSource.update = false - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed with testSource.update = %v: %v", testSource.update, err) - } -} - -// Test that we behave correctly when the file is updated. -func TestSeqFileFileUpdated(t *testing.T) { - testSource := &seqTest{} - testSource.Init() - testSource.update = true - - // Create a file that can be R/W. - ctx := contexttest.Context(t) - m := fs.NewPseudoMountSource(ctx) - contents := map[string]*fs.Inode{ - "foo": NewSeqFileInode(ctx, testSource, m), - } - root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777)) - - // How about opening it? - inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory}) - dirent2, err := root.Lookup(ctx, inode, "foo") - if err != nil { - t.Fatalf("failed to walk to foo for dirent2: %v", err) - } - - table := []testTable{ - {0, 16, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:6]), nil}, - } - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed: %v", err) - } - // Delete the first entry. - cut := testSource.actual[0].Buf - testSource.actual = testSource.actual[1:] - - table = []testTable{ - // Try reading buffer 0 with an offset. This will not delete the old data. - {1, 5, cut[1:6], nil}, - // Reset our file by reading at offset 0. - {0, 10, testSource.actual[0].Buf, nil}, - {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil}, - // Read the same data a second time. - {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil}, - // Read the following two lines. - {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil}, - } - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed after removing first entry: %v", err) - } - - // Add a new duplicate line in the middle (6666...) - after := testSource.actual[5:] - testSource.actual = testSource.actual[:4] - // Note the list must be sorted. - testSource.actual = append(testSource.actual, after[0]) - testSource.actual = append(testSource.actual, after...) - - table = []testTable{ - {50, 20, flatten(testSource.actual[4].Buf, testSource.actual[5].Buf), nil}, - } - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed after adding middle entry: %v", err) - } - // This will be used in a later test. - oldTestData := testSource.actual - - // Delete everything. - testSource.actual = testSource.actual[:0] - table = []testTable{ - {20, 20, []byte{}, io.EOF}, - } - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed after removing all entries: %v", err) - } - // Restore some of the data. - testSource.actual = oldTestData[:1] - table = []testTable{ - {6, 20, testSource.actual[0].Buf[6:], nil}, - } - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed after adding first entry back: %v", err) - } - - // Re-extend the data - testSource.actual = oldTestData - table = []testTable{ - {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil}, - } - if err := runTableTests(ctx, table, dirent2); err != nil { - t.Errorf("runTableTest failed after extending testSource: %v", err) - } -} diff --git a/pkg/sentry/fs/proc/stat.go b/pkg/sentry/fs/proc/stat.go deleted file mode 100644 index d4fbd76ac..000000000 --- a/pkg/sentry/fs/proc/stat.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -// statData backs /proc/stat. -// -// +stateify savable -type statData struct { - // k is the owning Kernel. - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*statData) NeedsUpdate(generation int64) bool { - return true -} - -// cpuStats contains the breakdown of CPU time for /proc/stat. -type cpuStats struct { - // user is time spent in userspace tasks with non-positive niceness. - user uint64 - - // nice is time spent in userspace tasks with positive niceness. - nice uint64 - - // system is time spent in non-interrupt kernel context. - system uint64 - - // idle is time spent idle. - idle uint64 - - // ioWait is time spent waiting for IO. - ioWait uint64 - - // irq is time spent in interrupt context. - irq uint64 - - // softirq is time spent in software interrupt context. - softirq uint64 - - // steal is involuntary wait time. - steal uint64 - - // guest is time spent in guests with non-positive niceness. - guest uint64 - - // guestNice is time spent in guests with positive niceness. - guestNice uint64 -} - -// String implements fmt.Stringer. -func (c cpuStats) String() string { - return fmt.Sprintf("%d %d %d %d %d %d %d %d %d %d", c.user, c.nice, c.system, c.idle, c.ioWait, c.irq, c.softirq, c.steal, c.guest, c.guestNice) -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (s *statData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - var buf bytes.Buffer - - // TODO(b/37226836): We currently export only zero CPU stats. We could - // at least provide some aggregate stats. - var cpu cpuStats - fmt.Fprintf(&buf, "cpu %s\n", cpu) - - for c, max := uint(0), s.k.ApplicationCores(); c < max; c++ { - fmt.Fprintf(&buf, "cpu%d %s\n", c, cpu) - } - - // The total number of interrupts is dependent on the CPUs and PCI - // devices on the system. See arch_probe_nr_irqs. - // - // Since we don't report real interrupt stats, just choose an arbitrary - // value from a representative VM. - const numInterrupts = 256 - - // The Kernel doesn't handle real interrupts, so report all zeroes. - // TODO(b/37226836): We could count page faults as #PF. - fmt.Fprintf(&buf, "intr 0") // total - for i := 0; i < numInterrupts; i++ { - fmt.Fprintf(&buf, " 0") - } - fmt.Fprintf(&buf, "\n") - - // Total number of context switches. - // TODO(b/37226836): Count this. - fmt.Fprintf(&buf, "ctxt 0\n") - - // CLOCK_REALTIME timestamp from boot, in seconds. - fmt.Fprintf(&buf, "btime %d\n", s.k.Timekeeper().BootTime().Seconds()) - - // Total number of clones. - // TODO(b/37226836): Count this. - fmt.Fprintf(&buf, "processes 0\n") - - // Number of runnable tasks. - // TODO(b/37226836): Count this. - fmt.Fprintf(&buf, "procs_running 0\n") - - // Number of tasks waiting on IO. - // TODO(b/37226836): Count this. - fmt.Fprintf(&buf, "procs_blocked 0\n") - - // Number of each softirq handled. - fmt.Fprintf(&buf, "softirq 0") // total - for i := 0; i < linux.NumSoftIRQ; i++ { - fmt.Fprintf(&buf, " 0") - } - fmt.Fprintf(&buf, "\n") - - return []seqfile.SeqData{ - { - Buf: buf.Bytes(), - Handle: (*statData)(nil), - }, - }, 0 -} - -// LINT.ThenChange(../../fsimpl/proc/task_files.go) diff --git a/pkg/sentry/fs/proc/sys.go b/pkg/sentry/fs/proc/sys.go deleted file mode 100644 index 443b9a94c..000000000 --- a/pkg/sentry/fs/proc/sys.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - "io" - "strconv" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// mmapMinAddrData backs /proc/sys/vm/mmap_min_addr. -// -// +stateify savable -type mmapMinAddrData struct { - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*mmapMinAddrData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (d *mmapMinAddrData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - return []seqfile.SeqData{ - { - Buf: []byte(fmt.Sprintf("%d\n", d.k.Platform.MinUserAddress())), - Handle: (*mmapMinAddrData)(nil), - }, - }, 0 -} - -// +stateify savable -type overcommitMemory struct{} - -func (*overcommitMemory) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource. -func (*overcommitMemory) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - return []seqfile.SeqData{ - { - Buf: []byte("0\n"), - Handle: (*overcommitMemory)(nil), - }, - }, 0 -} - -// +stateify savable -type maxMapCount struct{} - -// NeedsUpdate implements seqfile.SeqSource. -func (*maxMapCount) NeedsUpdate(int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource. -func (*maxMapCount) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - return []seqfile.SeqData{ - { - Buf: []byte("2147483647\n"), - Handle: (*maxMapCount)(nil), - }, - }, 0 -} - -func (p *proc) newKernelDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - h := hostname{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - } - - children := map[string]*fs.Inode{ - "hostname": newProcInode(ctx, &h, msrc, fs.SpecialFile, nil), - "sem": newStaticProcInode(ctx, msrc, []byte(fmt.Sprintf("%d\t%d\t%d\t%d\n", linux.SEMMSL, linux.SEMMNS, linux.SEMOPM, linux.SEMMNI))), - "shmall": newStaticProcInode(ctx, msrc, []byte(strconv.FormatUint(linux.SHMALL, 10))), - "shmmax": newStaticProcInode(ctx, msrc, []byte(strconv.FormatUint(linux.SHMMAX, 10))), - "shmmni": newStaticProcInode(ctx, msrc, []byte(strconv.FormatUint(linux.SHMMNI, 10))), - "msgmni": newStaticProcInode(ctx, msrc, []byte(strconv.FormatUint(linux.MSGMNI, 10))), - "msgmax": newStaticProcInode(ctx, msrc, []byte(strconv.FormatUint(linux.MSGMAX, 10))), - "msgmnb": newStaticProcInode(ctx, msrc, []byte(strconv.FormatUint(linux.MSGMNB, 10))), - } - - d := ramfs.NewDir(ctx, children, fs.RootOwner, fs.FilePermsFromMode(0555)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil) -} - -func (p *proc) newVMDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - children := map[string]*fs.Inode{ - "max_map_count": seqfile.NewSeqFileInode(ctx, &maxMapCount{}, msrc), - "mmap_min_addr": seqfile.NewSeqFileInode(ctx, &mmapMinAddrData{p.k}, msrc), - "overcommit_memory": seqfile.NewSeqFileInode(ctx, &overcommitMemory{}, msrc), - } - d := ramfs.NewDir(ctx, children, fs.RootOwner, fs.FilePermsFromMode(0555)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil) -} - -func (p *proc) newSysDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - children := map[string]*fs.Inode{ - "kernel": p.newKernelDir(ctx, msrc), - "net": p.newSysNetDir(ctx, msrc), - "vm": p.newVMDir(ctx, msrc), - } - - d := ramfs.NewDir(ctx, children, fs.RootOwner, fs.FilePermsFromMode(0555)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil) -} - -// hostname is the inode for a file containing the system hostname. -// -// +stateify savable -type hostname struct { - fsutil.SimpleFileInode -} - -// GetFile implements fs.InodeOperations.GetFile. -func (h *hostname) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, d, flags, &hostnameFile{}), nil -} - -var _ fs.InodeOperations = (*hostname)(nil) - -// +stateify savable -type hostnameFile struct { - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSeek `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` -} - -// Read implements fs.FileOperations.Read. -func (hf *hostnameFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - utsns := kernel.UTSNamespaceFromContext(ctx) - contents := []byte(utsns.HostName() + "\n") - if offset >= int64(len(contents)) { - return 0, io.EOF - } - n, err := dst.CopyOut(ctx, contents[offset:]) - return int64(n), err - -} - -var _ fs.FileOperations = (*hostnameFile)(nil) - -// LINT.ThenChange(../../fsimpl/proc/tasks_sys.go) diff --git a/pkg/sentry/fs/proc/sys_net.go b/pkg/sentry/fs/proc/sys_net.go deleted file mode 100644 index 71f37d582..000000000 --- a/pkg/sentry/fs/proc/sys_net.go +++ /dev/null @@ -1,697 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - "io" - "math" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/device" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/inet" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -type tcpMemDir int - -const ( - tcpRMem tcpMemDir = iota - tcpWMem -) - -// tcpMemInode is used to read/write the size of netstack tcp buffers. -// -// TODO(b/121381035): If we have multiple proc mounts, concurrent writes can -// leave netstack and the proc files in an inconsistent state. Since we set the -// buffer size from these proc files on restore, we may also race and end up in -// an inconsistent state on restore. -// -// +stateify savable -type tcpMemInode struct { - fsutil.SimpleFileInode - dir tcpMemDir - s inet.Stack `state:"wait"` - - // size stores the tcp buffer size during save, and sets the buffer - // size in netstack in restore. We must save/restore this here, since - // a netstack instance is created on restore. - size inet.TCPBufferSize - - // mu protects against concurrent reads/writes to files based on this - // inode. - mu sync.Mutex `state:"nosave"` -} - -var _ fs.InodeOperations = (*tcpMemInode)(nil) - -func newTCPMemInode(ctx context.Context, msrc *fs.MountSource, s inet.Stack, dir tcpMemDir) *fs.Inode { - tm := &tcpMemInode{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0644), linux.PROC_SUPER_MAGIC), - s: s, - dir: dir, - } - sattr := fs.StableAttr{ - DeviceID: device.ProcDevice.DeviceID(), - InodeID: device.ProcDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialFile, - } - return fs.NewInode(ctx, tm, msrc, sattr) -} - -// Truncate implements fs.InodeOperations.Truncate. -func (*tcpMemInode) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (t *tcpMemInode) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - return fs.NewFile(ctx, dirent, flags, &tcpMemFile{tcpMemInode: t}), nil -} - -// +stateify savable -type tcpMemFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - tcpMemInode *tcpMemInode -} - -var _ fs.FileOperations = (*tcpMemFile)(nil) - -// Read implements fs.FileOperations.Read. -func (f *tcpMemFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset != 0 { - return 0, io.EOF - } - f.tcpMemInode.mu.Lock() - defer f.tcpMemInode.mu.Unlock() - - size, err := readSize(f.tcpMemInode.dir, f.tcpMemInode.s) - if err != nil { - return 0, err - } - s := fmt.Sprintf("%d\t%d\t%d\n", size.Min, size.Default, size.Max) - n, err := dst.CopyOut(ctx, []byte(s)) - return int64(n), err -} - -// Write implements fs.FileOperations.Write. -func (f *tcpMemFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, nil - } - f.tcpMemInode.mu.Lock() - defer f.tcpMemInode.mu.Unlock() - - src = src.TakeFirst(hostarch.PageSize - 1) - size, err := readSize(f.tcpMemInode.dir, f.tcpMemInode.s) - if err != nil { - return 0, err - } - buf := []int32{int32(size.Min), int32(size.Default), int32(size.Max)} - n, cperr := usermem.CopyInt32StringsInVec(ctx, src.IO, src.Addrs, buf, src.Opts) - newSize := inet.TCPBufferSize{ - Min: int(buf[0]), - Default: int(buf[1]), - Max: int(buf[2]), - } - if err := writeSize(f.tcpMemInode.dir, f.tcpMemInode.s, newSize); err != nil { - return n, err - } - return n, cperr -} - -func readSize(dirType tcpMemDir, s inet.Stack) (inet.TCPBufferSize, error) { - switch dirType { - case tcpRMem: - return s.TCPReceiveBufferSize() - case tcpWMem: - return s.TCPSendBufferSize() - default: - panic(fmt.Sprintf("unknown tcpMemFile type: %v", dirType)) - } -} - -func writeSize(dirType tcpMemDir, s inet.Stack, size inet.TCPBufferSize) error { - switch dirType { - case tcpRMem: - return s.SetTCPReceiveBufferSize(size) - case tcpWMem: - return s.SetTCPSendBufferSize(size) - default: - panic(fmt.Sprintf("unknown tcpMemFile type: %v", dirType)) - } -} - -// +stateify savable -type tcpSack struct { - fsutil.SimpleFileInode - - stack inet.Stack `state:"wait"` - enabled *bool -} - -func newTCPSackInode(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode { - ts := &tcpSack{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0644), linux.PROC_SUPER_MAGIC), - stack: s, - } - sattr := fs.StableAttr{ - DeviceID: device.ProcDevice.DeviceID(), - InodeID: device.ProcDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialFile, - } - return fs.NewInode(ctx, ts, msrc, sattr) -} - -// Truncate implements fs.InodeOperations.Truncate. -func (*tcpSack) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (s *tcpSack) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - flags.Pwrite = true - return fs.NewFile(ctx, dirent, flags, &tcpSackFile{ - tcpSack: s, - stack: s.stack, - }), nil -} - -// +stateify savable -type tcpSackFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - tcpSack *tcpSack - - stack inet.Stack `state:"wait"` -} - -// Read implements fs.FileOperations.Read. -func (f *tcpSackFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset != 0 { - return 0, io.EOF - } - - if f.tcpSack.enabled == nil { - sack, err := f.stack.TCPSACKEnabled() - if err != nil { - return 0, err - } - f.tcpSack.enabled = &sack - } - - val := "0\n" - if *f.tcpSack.enabled { - // Technically, this is not quite compatible with Linux. Linux - // stores these as an integer, so if you write "2" into - // tcp_sack, you should get 2 back. Tough luck. - val = "1\n" - } - n, err := dst.CopyOut(ctx, []byte(val)) - return int64(n), err -} - -// Write implements fs.FileOperations.Write. -func (f *tcpSackFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, nil - } - - // Only consider size of one memory page for input for performance reasons. - // We are only reading if it's zero or not anyway. - src = src.TakeFirst(hostarch.PageSize - 1) - - var v int32 - n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts) - if err != nil { - return n, err - } - if f.tcpSack.enabled == nil { - f.tcpSack.enabled = new(bool) - } - *f.tcpSack.enabled = v != 0 - return n, f.tcpSack.stack.SetTCPSACKEnabled(*f.tcpSack.enabled) -} - -// +stateify savable -type tcpRecovery struct { - fsutil.SimpleFileInode - - stack inet.Stack `state:"wait"` - recovery inet.TCPLossRecovery -} - -func newTCPRecoveryInode(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode { - ts := &tcpRecovery{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0644), linux.PROC_SUPER_MAGIC), - stack: s, - } - sattr := fs.StableAttr{ - DeviceID: device.ProcDevice.DeviceID(), - InodeID: device.ProcDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialFile, - } - return fs.NewInode(ctx, ts, msrc, sattr) -} - -// Truncate implements fs.InodeOperations.Truncate. -func (*tcpRecovery) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (r *tcpRecovery) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - flags.Pwrite = true - return fs.NewFile(ctx, dirent, flags, &tcpRecoveryFile{ - tcpRecovery: r, - stack: r.stack, - }), nil -} - -// +stateify savable -type tcpRecoveryFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - tcpRecovery *tcpRecovery - - stack inet.Stack `state:"wait"` -} - -// Read implements fs.FileOperations.Read. -func (f *tcpRecoveryFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset != 0 { - return 0, io.EOF - } - - recovery, err := f.stack.TCPRecovery() - if err != nil { - return 0, err - } - f.tcpRecovery.recovery = recovery - s := fmt.Sprintf("%d\n", f.tcpRecovery.recovery) - n, err := dst.CopyOut(ctx, []byte(s)) - return int64(n), err -} - -// Write implements fs.FileOperations.Write. -func (f *tcpRecoveryFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, nil - } - src = src.TakeFirst(hostarch.PageSize - 1) - - var v int32 - n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts) - if err != nil { - return 0, err - } - f.tcpRecovery.recovery = inet.TCPLossRecovery(v) - if err := f.tcpRecovery.stack.SetTCPRecovery(f.tcpRecovery.recovery); err != nil { - return 0, err - } - return n, nil -} - -func (p *proc) newSysNetCore(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode { - // The following files are simple stubs until they are implemented in - // netstack, most of these files are configuration related. We use the - // value closest to the actual netstack behavior or any empty file, - // all of these files will have mode 0444 (read-only for all users). - contents := map[string]*fs.Inode{ - "default_qdisc": newStaticProcInode(ctx, msrc, []byte("pfifo_fast")), - "message_burst": newStaticProcInode(ctx, msrc, []byte("10")), - "message_cost": newStaticProcInode(ctx, msrc, []byte("5")), - "optmem_max": newStaticProcInode(ctx, msrc, []byte("0")), - "rmem_default": newStaticProcInode(ctx, msrc, []byte("212992")), - "rmem_max": newStaticProcInode(ctx, msrc, []byte("212992")), - "somaxconn": newStaticProcInode(ctx, msrc, []byte("128")), - "wmem_default": newStaticProcInode(ctx, msrc, []byte("212992")), - "wmem_max": newStaticProcInode(ctx, msrc, []byte("212992")), - } - - d := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil) -} - -// ipForwarding implements fs.InodeOperations. -// -// ipForwarding is used to enable/disable packet forwarding of netstack. -// -// +stateify savable -type ipForwarding struct { - fsutil.SimpleFileInode - - stack inet.Stack `state:"wait"` - - // enabled stores the IPv4 forwarding state on save. - // We must save/restore this here, since a netstack instance - // is created on restore. - enabled bool -} - -func newIPForwardingInode(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode { - ipf := &ipForwarding{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - stack: s, - } - sattr := fs.StableAttr{ - DeviceID: device.ProcDevice.DeviceID(), - InodeID: device.ProcDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialFile, - } - return fs.NewInode(ctx, ipf, msrc, sattr) -} - -// Truncate implements fs.InodeOperations.Truncate. Truncate is called when -// O_TRUNC is specified for any kind of existing Dirent but is not called via -// (f)truncate for proc files. -func (*ipForwarding) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// +stateify savable -type ipForwardingFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - ipf *ipForwarding - - stack inet.Stack `state:"wait"` -} - -// GetFile implements fs.InodeOperations.GetFile. -func (ipf *ipForwarding) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - flags.Pwrite = true - return fs.NewFile(ctx, dirent, flags, &ipForwardingFile{ - stack: ipf.stack, - ipf: ipf, - }), nil -} - -// Read implements fs.FileOperations.Read. -func (f *ipForwardingFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset != 0 { - return 0, io.EOF - } - - val := "0\n" - if f.ipf.enabled { - // Technically, this is not quite compatible with Linux. Linux - // stores these as an integer, so if you write "2" into - // ip_forward, you should get 2 back. - val = "1\n" - } - n, err := dst.CopyOut(ctx, []byte(val)) - return int64(n), err -} - -// Write implements fs.FileOperations.Write. -// -// Offset is ignored, multiple writes are not supported. -func (f *ipForwardingFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, nil - } - - // Only consider size of one memory page for input for performance reasons. - // We are only reading if it's zero or not anyway. - src = src.TakeFirst(hostarch.PageSize - 1) - - var v int32 - n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts) - if err != nil { - return n, err - } - f.ipf.enabled = v != 0 - return n, f.stack.SetForwarding(ipv4.ProtocolNumber, f.ipf.enabled) -} - -// portRangeInode implements fs.InodeOperations. It provides and allows -// modification of the range of ephemeral ports that IPv4 and IPv6 sockets -// choose from. -// -// +stateify savable -type portRangeInode struct { - fsutil.SimpleFileInode - - stack inet.Stack `state:"wait"` - - // start and end store the port range. We must save/restore this here, - // since a netstack instance is created on restore. - start *uint16 - end *uint16 -} - -func newPortRangeInode(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode { - ipf := &portRangeInode{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0644), linux.PROC_SUPER_MAGIC), - stack: s, - } - sattr := fs.StableAttr{ - DeviceID: device.ProcDevice.DeviceID(), - InodeID: device.ProcDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialFile, - } - return fs.NewInode(ctx, ipf, msrc, sattr) -} - -// Truncate implements fs.InodeOperations.Truncate. Truncate is called when -// O_TRUNC is specified for any kind of existing Dirent but is not called via -// (f)truncate for proc files. -func (*portRangeInode) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// +stateify savable -type portRangeFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - inode *portRangeInode -} - -// GetFile implements fs.InodeOperations.GetFile. -func (in *portRangeInode) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - flags.Pwrite = true - return fs.NewFile(ctx, dirent, flags, &portRangeFile{ - inode: in, - }), nil -} - -// Read implements fs.FileOperations.Read. -func (pf *portRangeFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset != 0 { - return 0, io.EOF - } - - if pf.inode.start == nil { - start, end := pf.inode.stack.PortRange() - pf.inode.start = &start - pf.inode.end = &end - } - - contents := fmt.Sprintf("%d %d\n", *pf.inode.start, *pf.inode.end) - n, err := dst.CopyOut(ctx, []byte(contents)) - return int64(n), err -} - -// Write implements fs.FileOperations.Write. -// -// Offset is ignored, multiple writes are not supported. -func (pf *portRangeFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, nil - } - - // Only consider size of one memory page for input for performance - // reasons. - src = src.TakeFirst(hostarch.PageSize - 1) - - ports := make([]int32, 2) - n, err := usermem.CopyInt32StringsInVec(ctx, src.IO, src.Addrs, ports, src.Opts) - if err != nil { - return 0, err - } - - // Port numbers must be uint16s. - if ports[0] < 0 || ports[1] < 0 || ports[0] > math.MaxUint16 || ports[1] > math.MaxUint16 { - return 0, linuxerr.EINVAL - } - - if err := pf.inode.stack.SetPortRange(uint16(ports[0]), uint16(ports[1])); err != nil { - return 0, err - } - if pf.inode.start == nil { - pf.inode.start = new(uint16) - pf.inode.end = new(uint16) - } - *pf.inode.start = uint16(ports[0]) - *pf.inode.end = uint16(ports[1]) - return n, nil -} - -func (p *proc) newSysNetIPv4Dir(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode { - contents := map[string]*fs.Inode{ - // Add tcp_sack. - "tcp_sack": newTCPSackInode(ctx, msrc, s), - - // Add ip_forward. - "ip_forward": newIPForwardingInode(ctx, msrc, s), - - // Allow for configurable ephemeral port ranges. Note that this - // controls ports for both IPv4 and IPv6 sockets. - "ip_local_port_range": newPortRangeInode(ctx, msrc, s), - - // The following files are simple stubs until they are - // implemented in netstack, most of these files are - // configuration related. We use the value closest to the - // actual netstack behavior or any empty file, all of these - // files will have mode 0444 (read-only for all users). - "ip_local_reserved_ports": newStaticProcInode(ctx, msrc, []byte("")), - "ipfrag_time": newStaticProcInode(ctx, msrc, []byte("30")), - "ip_nonlocal_bind": newStaticProcInode(ctx, msrc, []byte("0")), - "ip_no_pmtu_disc": newStaticProcInode(ctx, msrc, []byte("1")), - - // tcp_allowed_congestion_control tell the user what they are - // able to do as an unprivledged process so we leave it empty. - "tcp_allowed_congestion_control": newStaticProcInode(ctx, msrc, []byte("")), - "tcp_available_congestion_control": newStaticProcInode(ctx, msrc, []byte("reno")), - "tcp_congestion_control": newStaticProcInode(ctx, msrc, []byte("reno")), - - // Many of the following stub files are features netstack - // doesn't support. The unsupported features return "0" to - // indicate they are disabled. - "tcp_base_mss": newStaticProcInode(ctx, msrc, []byte("1280")), - "tcp_dsack": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_early_retrans": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_fack": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_fastopen": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_fastopen_key": newStaticProcInode(ctx, msrc, []byte("")), - "tcp_invalid_ratelimit": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_keepalive_intvl": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_keepalive_probes": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_keepalive_time": newStaticProcInode(ctx, msrc, []byte("7200")), - "tcp_mtu_probing": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_no_metrics_save": newStaticProcInode(ctx, msrc, []byte("1")), - "tcp_probe_interval": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_probe_threshold": newStaticProcInode(ctx, msrc, []byte("0")), - "tcp_retries1": newStaticProcInode(ctx, msrc, []byte("3")), - "tcp_retries2": newStaticProcInode(ctx, msrc, []byte("15")), - "tcp_rfc1337": newStaticProcInode(ctx, msrc, []byte("1")), - "tcp_slow_start_after_idle": newStaticProcInode(ctx, msrc, []byte("1")), - "tcp_synack_retries": newStaticProcInode(ctx, msrc, []byte("5")), - "tcp_syn_retries": newStaticProcInode(ctx, msrc, []byte("3")), - "tcp_timestamps": newStaticProcInode(ctx, msrc, []byte("1")), - } - - // Add tcp_rmem. - if _, err := s.TCPReceiveBufferSize(); err == nil { - contents["tcp_rmem"] = newTCPMemInode(ctx, msrc, s, tcpRMem) - } - - // Add tcp_wmem. - if _, err := s.TCPSendBufferSize(); err == nil { - contents["tcp_wmem"] = newTCPMemInode(ctx, msrc, s, tcpWMem) - } - - // Add tcp_recovery. - if _, err := s.TCPRecovery(); err == nil { - contents["tcp_recovery"] = newTCPRecoveryInode(ctx, msrc, s) - } - - d := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil) -} - -func (p *proc) newSysNetDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - var contents map[string]*fs.Inode - // TODO(gvisor.dev/issue/1833): Support for using the network stack in the - // network namespace of the calling process. - if s := p.k.RootNetworkNamespace().Stack(); s != nil { - contents = map[string]*fs.Inode{ - "ipv4": p.newSysNetIPv4Dir(ctx, msrc, s), - "core": p.newSysNetCore(ctx, msrc, s), - } - } - d := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil) -} - -// LINT.ThenChange(../../fsimpl/proc/tasks_sys.go) diff --git a/pkg/sentry/fs/proc/sys_net_state.go b/pkg/sentry/fs/proc/sys_net_state.go deleted file mode 100644 index 51d2be647..000000000 --- a/pkg/sentry/fs/proc/sys_net_state.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" -) - -// beforeSave is invoked by stateify. -func (t *tcpMemInode) beforeSave() { - size, err := readSize(t.dir, t.s) - if err != nil { - panic(fmt.Sprintf("failed to read TCP send / receive buffer sizes: %v", err)) - } - t.size = size -} - -// afterLoad is invoked by stateify. -func (t *tcpMemInode) afterLoad() { - if err := writeSize(t.dir, t.s, t.size); err != nil { - panic(fmt.Sprintf("failed to write previous TCP send / receive buffer sizes [%v]: %v", t.size, err)) - } -} - -// afterLoad is invoked by stateify. -func (s *tcpSack) afterLoad() { - if s.enabled != nil { - if err := s.stack.SetTCPSACKEnabled(*s.enabled); err != nil { - panic(fmt.Sprintf("failed to set previous TCP sack configuration [%v]: %v", *s.enabled, err)) - } - } -} - -// afterLoad is invoked by stateify. -func (ipf *ipForwarding) afterLoad() { - if err := ipf.stack.SetForwarding(ipv4.ProtocolNumber, ipf.enabled); err != nil { - panic(fmt.Sprintf("ipf.stack.SetForwarding(%d, %t): %s", ipv4.ProtocolNumber, ipf.enabled, err)) - } -} diff --git a/pkg/sentry/fs/proc/sys_net_test.go b/pkg/sentry/fs/proc/sys_net_test.go deleted file mode 100644 index 6ef5738e7..000000000 --- a/pkg/sentry/fs/proc/sys_net_test.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/inet" - "gvisor.dev/gvisor/pkg/usermem" -) - -func TestQuerySendBufferSize(t *testing.T) { - ctx := context.Background() - s := inet.NewTestStack() - s.TCPSendBufSize = inet.TCPBufferSize{100, 200, 300} - tmi := &tcpMemInode{s: s, dir: tcpWMem} - tmf := &tcpMemFile{tcpMemInode: tmi} - - buf := make([]byte, 100) - dst := usermem.BytesIOSequence(buf) - n, err := tmf.Read(ctx, nil, dst, 0) - if err != nil { - t.Fatalf("Read failed: %v", err) - } - - if got, want := string(buf[:n]), "100\t200\t300\n"; got != want { - t.Fatalf("Bad string: got %v, want %v", got, want) - } -} - -func TestQueryRecvBufferSize(t *testing.T) { - ctx := context.Background() - s := inet.NewTestStack() - s.TCPRecvBufSize = inet.TCPBufferSize{100, 200, 300} - tmi := &tcpMemInode{s: s, dir: tcpRMem} - tmf := &tcpMemFile{tcpMemInode: tmi} - - buf := make([]byte, 100) - dst := usermem.BytesIOSequence(buf) - n, err := tmf.Read(ctx, nil, dst, 0) - if err != nil { - t.Fatalf("Read failed: %v", err) - } - - if got, want := string(buf[:n]), "100\t200\t300\n"; got != want { - t.Fatalf("Bad string: got %v, want %v", got, want) - } -} - -var cases = []struct { - str string - initial inet.TCPBufferSize - final inet.TCPBufferSize -}{ - { - str: "", - initial: inet.TCPBufferSize{1, 2, 3}, - final: inet.TCPBufferSize{1, 2, 3}, - }, - { - str: "100\n", - initial: inet.TCPBufferSize{1, 100, 200}, - final: inet.TCPBufferSize{100, 100, 200}, - }, - { - str: "100 200 300\n", - initial: inet.TCPBufferSize{1, 2, 3}, - final: inet.TCPBufferSize{100, 200, 300}, - }, -} - -func TestConfigureSendBufferSize(t *testing.T) { - ctx := context.Background() - s := inet.NewTestStack() - for _, c := range cases { - s.TCPSendBufSize = c.initial - tmi := &tcpMemInode{s: s, dir: tcpWMem} - tmf := &tcpMemFile{tcpMemInode: tmi} - - // Write the values. - src := usermem.BytesIOSequence([]byte(c.str)) - if n, err := tmf.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil { - t.Errorf("Write, case = %q: got (%d, %v), wanted (%d, nil)", c.str, n, err, len(c.str)) - } - - // Read the values from the stack and check them. - if s.TCPSendBufSize != c.final { - t.Errorf("TCPSendBufferSize, case = %q: got %v, wanted %v", c.str, s.TCPSendBufSize, c.final) - } - } -} - -func TestConfigureRecvBufferSize(t *testing.T) { - ctx := context.Background() - s := inet.NewTestStack() - for _, c := range cases { - s.TCPRecvBufSize = c.initial - tmi := &tcpMemInode{s: s, dir: tcpRMem} - tmf := &tcpMemFile{tcpMemInode: tmi} - - // Write the values. - src := usermem.BytesIOSequence([]byte(c.str)) - if n, err := tmf.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil { - t.Errorf("Write, case = %q: got (%d, %v), wanted (%d, nil)", c.str, n, err, len(c.str)) - } - - // Read the values from the stack and check them. - if s.TCPRecvBufSize != c.final { - t.Errorf("TCPRecvBufferSize, case = %q: got %v, wanted %v", c.str, s.TCPRecvBufSize, c.final) - } - } -} - -// TestIPForwarding tests the implementation of -// /proc/sys/net/ipv4/ip_forwarding -func TestIPForwarding(t *testing.T) { - ctx := context.Background() - s := inet.NewTestStack() - - var cases = []struct { - comment string - initial bool - str string - final bool - }{ - { - comment: `Forwarding is disabled; write 1 and enable forwarding`, - initial: false, - str: "1", - final: true, - }, - { - comment: `Forwarding is disabled; write 0 and disable forwarding`, - initial: false, - str: "0", - final: false, - }, - { - comment: `Forwarding is enabled; write 1 and enable forwarding`, - initial: true, - str: "1", - final: true, - }, - { - comment: `Forwarding is enabled; write 0 and disable forwarding`, - initial: true, - str: "0", - final: false, - }, - { - comment: `Forwarding is disabled; write 2404 and enable forwarding`, - initial: false, - str: "2404", - final: true, - }, - { - comment: `Forwarding is enabled; write 2404 and enable forwarding`, - initial: true, - str: "2404", - final: true, - }, - } - for _, c := range cases { - t.Run(c.comment, func(t *testing.T) { - s.IPForwarding = c.initial - ipf := &ipForwarding{stack: s} - file := &ipForwardingFile{ - stack: s, - ipf: ipf, - } - - // Write the values. - src := usermem.BytesIOSequence([]byte(c.str)) - if n, err := file.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil { - t.Errorf("file.Write(ctx, nil, %q, 0) = (%d, %v); want (%d, nil)", c.str, n, err, len(c.str)) - } - - // Read the values from the stack and check them. - if got, want := s.IPForwarding, c.final; got != want { - t.Errorf("s.IPForwarding incorrect; got: %v, want: %v", got, want) - } - - }) - } -} diff --git a/pkg/sentry/fs/proc/task.go b/pkg/sentry/fs/proc/task.go deleted file mode 100644 index f61184087..000000000 --- a/pkg/sentry/fs/proc/task.go +++ /dev/null @@ -1,1068 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - "io" - "sort" - "strconv" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/device" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/fsbridge" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/limits" - "gvisor.dev/gvisor/pkg/sentry/mm" - "gvisor.dev/gvisor/pkg/sentry/usage" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// getTaskMM gets the kernel task's MemoryManager. No additional reference is -// taken on mm here. This is safe because MemoryManager.destroy is required to -// leave the MemoryManager in a state where it's still usable as a -// DynamicBytesSource. -func getTaskMM(t *kernel.Task) *mm.MemoryManager { - var tmm *mm.MemoryManager - t.WithMuLocked(func(t *kernel.Task) { - if mm := t.MemoryManager(); mm != nil { - tmm = mm - } - }) - return tmm -} - -// getTaskMMIncRef returns t's MemoryManager. If getTaskMMIncRef succeeds, the -// MemoryManager's users count is incremented, and must be decremented by the -// caller when it is no longer in use. -func getTaskMMIncRef(t *kernel.Task) (*mm.MemoryManager, error) { - if t.ExitState() == kernel.TaskExitDead { - return nil, linuxerr.ESRCH - } - var m *mm.MemoryManager - t.WithMuLocked(func(t *kernel.Task) { - m = t.MemoryManager() - }) - if m == nil || !m.IncUsers() { - return nil, io.EOF - } - return m, nil -} - -func checkTaskState(t *kernel.Task) error { - switch t.ExitState() { - case kernel.TaskExitZombie: - return linuxerr.EACCES - case kernel.TaskExitDead: - return linuxerr.ESRCH - } - return nil -} - -// taskDir represents a task-level directory. -// -// +stateify savable -type taskDir struct { - ramfs.Dir - - t *kernel.Task -} - -var _ fs.InodeOperations = (*taskDir)(nil) - -// newTaskDir creates a new proc task entry. -func (p *proc) newTaskDir(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, isThreadGroup bool) *fs.Inode { - contents := map[string]*fs.Inode{ - "auxv": newAuxvec(ctx, t, msrc), - "cmdline": newExecArgInode(ctx, t, msrc, cmdlineExecArg), - "comm": newComm(ctx, t, msrc), - "cwd": newCwd(ctx, t, msrc), - "environ": newExecArgInode(ctx, t, msrc, environExecArg), - "exe": newExe(ctx, t, msrc), - "fd": newFdDir(ctx, t, msrc), - "fdinfo": newFdInfoDir(ctx, t, msrc), - "gid_map": newGIDMap(ctx, t, msrc), - "io": newIO(ctx, t, msrc, isThreadGroup), - "maps": newMaps(ctx, t, msrc), - "mem": newMem(ctx, t, msrc), - "mountinfo": seqfile.NewSeqFileInode(ctx, &mountInfoFile{t: t}, msrc), - "mounts": seqfile.NewSeqFileInode(ctx, &mountsFile{t: t}, msrc), - "net": newNetDir(ctx, t, msrc), - "ns": newNamespaceDir(ctx, t, msrc), - "oom_score": newOOMScore(ctx, msrc), - "oom_score_adj": newOOMScoreAdj(ctx, t, msrc), - "root": newRoot(ctx, t, msrc), - "smaps": newSmaps(ctx, t, msrc), - "stat": newTaskStat(ctx, t, msrc, isThreadGroup, p.pidns), - "statm": newStatm(ctx, t, msrc), - "status": newStatus(ctx, t, msrc, p.pidns), - "uid_map": newUIDMap(ctx, t, msrc), - } - if isThreadGroup { - contents["task"] = p.newSubtasks(ctx, t, msrc) - } - if len(p.cgroupControllers) > 0 { - contents["cgroup"] = newCGroupInode(ctx, msrc, p.cgroupControllers) - } - - // N.B. taskOwnedInodeOps enforces dumpability-based ownership. - d := &taskDir{ - Dir: *ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)), - t: t, - } - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, t) -} - -// subtasks represents a /proc/TID/task directory. -// -// +stateify savable -type subtasks struct { - ramfs.Dir - - t *kernel.Task - p *proc -} - -var _ fs.InodeOperations = (*subtasks)(nil) - -func (p *proc) newSubtasks(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - s := &subtasks{ - Dir: *ramfs.NewDir(ctx, nil, fs.RootOwner, fs.FilePermsFromMode(0555)), - t: t, - p: p, - } - return newProcInode(ctx, s, msrc, fs.SpecialDirectory, t) -} - -// UnstableAttr returns unstable attributes of the subtasks. -func (s *subtasks) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - uattr, err := s.Dir.UnstableAttr(ctx, inode) - if err != nil { - return fs.UnstableAttr{}, err - } - // We can't rely on ramfs' implementation because the task directories are - // generated dynamically. - uattr.Links = uint64(2 + s.t.ThreadGroup().Count()) - return uattr, nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (s *subtasks) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &subtasksFile{t: s.t, pidns: s.p.pidns}), nil -} - -// +stateify savable -type subtasksFile struct { - fsutil.DirFileOperations `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - t *kernel.Task - pidns *kernel.PIDNamespace -} - -// Readdir implements fs.FileOperations.Readdir. -func (f *subtasksFile) Readdir(ctx context.Context, file *fs.File, ser fs.DentrySerializer) (int64, error) { - dirCtx := fs.DirCtx{ - Serializer: ser, - } - - // Note that unlike most Readdir implementations, the offset here is - // not an index into the subtasks, but rather the TID of the next - // subtask to emit. - offset := file.Offset() - - tasks := f.t.ThreadGroup().MemberIDs(f.pidns) - if len(tasks) == 0 { - return offset, linuxerr.ENOENT - } - - if offset == 0 { - // Serialize "." and "..". - root := fs.RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - dot, dotdot := file.Dirent.GetDotAttrs(root) - if err := dirCtx.DirEmit(".", dot); err != nil { - return offset, err - } - if err := dirCtx.DirEmit("..", dotdot); err != nil { - return offset, err - } - } - - // Serialize tasks. - taskInts := make([]int, 0, len(tasks)) - for _, tid := range tasks { - taskInts = append(taskInts, int(tid)) - } - - sort.Sort(sort.IntSlice(taskInts)) - // Find the task to start at. - idx := sort.SearchInts(taskInts, int(offset)) - if idx == len(taskInts) { - return offset, nil - } - taskInts = taskInts[idx:] - - var tid int - for _, tid = range taskInts { - name := strconv.FormatUint(uint64(tid), 10) - attr := fs.GenericDentAttr(fs.SpecialDirectory, device.ProcDevice) - if err := dirCtx.DirEmit(name, attr); err != nil { - // Returned offset is next tid to serialize. - return int64(tid), err - } - } - // We serialized them all. Next offset should be higher than last - // serialized tid. - return int64(tid) + 1, nil -} - -var _ fs.FileOperations = (*subtasksFile)(nil) - -// Lookup loads an Inode in a task's subtask directory into a Dirent. -func (s *subtasks) Lookup(ctx context.Context, dir *fs.Inode, p string) (*fs.Dirent, error) { - tid, err := strconv.ParseUint(p, 10, 32) - if err != nil { - return nil, linuxerr.ENOENT - } - - task := s.p.pidns.TaskWithID(kernel.ThreadID(tid)) - if task == nil { - return nil, linuxerr.ENOENT - } - if task.ThreadGroup() != s.t.ThreadGroup() { - return nil, linuxerr.ENOENT - } - - td := s.p.newTaskDir(ctx, task, dir.MountSource, false) - return fs.NewDirent(ctx, td, p), nil -} - -// exe is an fs.InodeOperations symlink for the /proc/PID/exe file. -// -// +stateify savable -type exe struct { - ramfs.Symlink - - t *kernel.Task -} - -func newExe(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - exeSymlink := &exe{ - Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, ""), - t: t, - } - return newProcInode(ctx, exeSymlink, msrc, fs.Symlink, t) -} - -func (e *exe) executable() (file fsbridge.File, err error) { - if err := checkTaskState(e.t); err != nil { - return nil, err - } - mm := getTaskMM(e.t) - if mm == nil { - return nil, linuxerr.EACCES - } - - // The MemoryManager may be destroyed, in which case - // MemoryManager.destroy will simply set the executable to nil - // (with locks held). - file = mm.Executable() - if file == nil { - err = linuxerr.ESRCH - } - return -} - -// Readlink implements fs.InodeOperations. -func (e *exe) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - if !kernel.ContextCanTrace(ctx, e.t, false) { - return "", linuxerr.EACCES - } - - // Pull out the executable for /proc/TID/exe. - exec, err := e.executable() - if err != nil { - return "", err - } - defer exec.DecRef(ctx) - - return exec.PathnameWithDeleted(ctx), nil -} - -// cwd is an fs.InodeOperations symlink for the /proc/PID/cwd file. -// -// +stateify savable -type cwd struct { - ramfs.Symlink - - t *kernel.Task -} - -func newCwd(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - cwdSymlink := &cwd{ - Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, ""), - t: t, - } - return newProcInode(ctx, cwdSymlink, msrc, fs.Symlink, t) -} - -// Readlink implements fs.InodeOperations. -func (e *cwd) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - if !kernel.ContextCanTrace(ctx, e.t, false) { - return "", linuxerr.EACCES - } - if err := checkTaskState(e.t); err != nil { - return "", err - } - cwd := e.t.FSContext().WorkingDirectory() - if cwd == nil { - // It could have raced with process deletion. - return "", linuxerr.ESRCH - } - defer cwd.DecRef(ctx) - - root := fs.RootFromContext(ctx) - if root == nil { - // It could have raced with process deletion. - return "", linuxerr.ESRCH - } - defer root.DecRef(ctx) - - name, _ := cwd.FullName(root) - return name, nil -} - -// root is an fs.InodeOperations symlink for the /proc/PID/root file. -// -// +stateify savable -type root struct { - ramfs.Symlink - - t *kernel.Task -} - -func newRoot(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - rootSymlink := &root{ - Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, ""), - t: t, - } - return newProcInode(ctx, rootSymlink, msrc, fs.Symlink, t) -} - -// Readlink implements fs.InodeOperations. -func (e *root) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - if !kernel.ContextCanTrace(ctx, e.t, false) { - return "", linuxerr.EACCES - } - if err := checkTaskState(e.t); err != nil { - return "", err - } - root := e.t.FSContext().RootDirectory() - if root == nil { - // It could have raced with process deletion. - return "", linuxerr.ESRCH - } - defer root.DecRef(ctx) - - fsRoot := fs.RootFromContext(ctx) - if fsRoot == nil { - // It could have raced with process deletion. - return "", linuxerr.ESRCH - } - defer fsRoot.DecRef(ctx) - - name, _ := root.FullName(fsRoot) - return name, nil -} - -// namespaceSymlink represents a symlink in the namespacefs, such as the files -// in /proc//ns. -// -// +stateify savable -type namespaceSymlink struct { - ramfs.Symlink - - t *kernel.Task -} - -func newNamespaceSymlink(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, name string) *fs.Inode { - // TODO(rahat): Namespace symlinks should contain the namespace name and the - // inode number for the namespace instance, so for example user:[123456]. We - // currently fake the inode number by sticking the symlink inode in its - // place. - target := fmt.Sprintf("%s:[%d]", name, device.ProcDevice.NextIno()) - n := &namespaceSymlink{ - Symlink: *ramfs.NewSymlink(ctx, fs.RootOwner, target), - t: t, - } - return newProcInode(ctx, n, msrc, fs.Symlink, t) -} - -// Readlink reads the symlink value. -func (n *namespaceSymlink) Readlink(ctx context.Context, inode *fs.Inode) (string, error) { - if err := checkTaskState(n.t); err != nil { - return "", err - } - return n.Symlink.Readlink(ctx, inode) -} - -// Getlink implements fs.InodeOperations.Getlink. -func (n *namespaceSymlink) Getlink(ctx context.Context, inode *fs.Inode) (*fs.Dirent, error) { - if !kernel.ContextCanTrace(ctx, n.t, false) { - return nil, linuxerr.EACCES - } - if err := checkTaskState(n.t); err != nil { - return nil, err - } - - // Create a new regular file to fake the namespace file. - iops := fsutil.NewNoReadWriteFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0777), linux.PROC_SUPER_MAGIC) - return fs.NewDirent(ctx, newProcInode(ctx, iops, inode.MountSource, fs.RegularFile, nil), n.Symlink.Target), nil -} - -func newNamespaceDir(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - contents := map[string]*fs.Inode{ - "net": newNamespaceSymlink(ctx, t, msrc, "net"), - "pid": newNamespaceSymlink(ctx, t, msrc, "pid"), - "user": newNamespaceSymlink(ctx, t, msrc, "user"), - } - d := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0511)) - return newProcInode(ctx, d, msrc, fs.SpecialDirectory, t) -} - -// memData implements fs.Inode for /proc/[pid]/mem. -// -// +stateify savable -type memData struct { - fsutil.SimpleFileInode - - t *kernel.Task -} - -// memDataFile implements fs.FileOperations for /proc/[pid]/mem. -// -// +stateify savable -type memDataFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - t *kernel.Task -} - -func newMem(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - inode := &memData{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0400), linux.PROC_SUPER_MAGIC), - t: t, - } - return newProcInode(ctx, inode, msrc, fs.SpecialFile, t) -} - -// Truncate implements fs.InodeOperations.Truncate. -func (m *memData) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (m *memData) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - // TODO(gvisor.dev/issue/260): Add check for PTRACE_MODE_ATTACH_FSCREDS - // Permission to read this file is governed by PTRACE_MODE_ATTACH_FSCREDS - // Since we dont implement setfsuid/setfsgid we can just use PTRACE_MODE_ATTACH - if !kernel.ContextCanTrace(ctx, m.t, true) { - return nil, linuxerr.EACCES - } - if err := checkTaskState(m.t); err != nil { - return nil, err - } - // Enable random access reads - flags.Pread = true - return fs.NewFile(ctx, dirent, flags, &memDataFile{t: m.t}), nil -} - -// Read implements fs.FileOperations.Read. -func (m *memDataFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if dst.NumBytes() == 0 { - return 0, nil - } - mm, err := getTaskMMIncRef(m.t) - if err != nil { - return 0, nil - } - defer mm.DecUsers(ctx) - // Buffer the read data because of MM locks - buf := make([]byte, dst.NumBytes()) - n, readErr := mm.CopyIn(ctx, hostarch.Addr(offset), buf, usermem.IOOpts{IgnorePermissions: true}) - if n > 0 { - if _, err := dst.CopyOut(ctx, buf[:n]); err != nil { - return 0, linuxerr.EFAULT - } - return int64(n), nil - } - if readErr != nil { - return 0, linuxerr.EIO - } - return 0, nil -} - -// mapsData implements seqfile.SeqSource for /proc/[pid]/maps. -// -// +stateify savable -type mapsData struct { - t *kernel.Task -} - -func newMaps(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - return newProcInode(ctx, seqfile.NewSeqFile(ctx, &mapsData{t}), msrc, fs.SpecialFile, t) -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (md *mapsData) NeedsUpdate(generation int64) bool { - if mm := getTaskMM(md.t); mm != nil { - return mm.NeedsUpdate(generation) - } - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (md *mapsData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if mm := getTaskMM(md.t); mm != nil { - return mm.ReadMapsSeqFileData(ctx, h) - } - return []seqfile.SeqData{}, 0 -} - -// smapsData implements seqfile.SeqSource for /proc/[pid]/smaps. -// -// +stateify savable -type smapsData struct { - t *kernel.Task -} - -func newSmaps(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - return newProcInode(ctx, seqfile.NewSeqFile(ctx, &smapsData{t}), msrc, fs.SpecialFile, t) -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (sd *smapsData) NeedsUpdate(generation int64) bool { - if mm := getTaskMM(sd.t); mm != nil { - return mm.NeedsUpdate(generation) - } - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (sd *smapsData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if mm := getTaskMM(sd.t); mm != nil { - return mm.ReadSmapsSeqFileData(ctx, h) - } - return []seqfile.SeqData{}, 0 -} - -// +stateify savable -type taskStatData struct { - t *kernel.Task - - // If tgstats is true, accumulate fault stats (not implemented) and CPU - // time across all tasks in t's thread group. - tgstats bool - - // pidns is the PID namespace associated with the proc filesystem that - // includes the file using this statData. - pidns *kernel.PIDNamespace -} - -func newTaskStat(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, showSubtasks bool, pidns *kernel.PIDNamespace) *fs.Inode { - return newProcInode(ctx, seqfile.NewSeqFile(ctx, &taskStatData{t, showSubtasks /* tgstats */, pidns}), msrc, fs.SpecialFile, t) -} - -// NeedsUpdate returns whether the generation is old or not. -func (s *taskStatData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData returns data for the SeqFile reader. -// SeqData, the current generation and where in the file the handle corresponds to. -func (s *taskStatData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - var buf bytes.Buffer - - fmt.Fprintf(&buf, "%d ", s.pidns.IDOfTask(s.t)) - fmt.Fprintf(&buf, "(%s) ", s.t.Name()) - fmt.Fprintf(&buf, "%c ", s.t.StateStatus()[0]) - ppid := kernel.ThreadID(0) - if parent := s.t.Parent(); parent != nil { - ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup()) - } - fmt.Fprintf(&buf, "%d ", ppid) - fmt.Fprintf(&buf, "%d ", s.pidns.IDOfProcessGroup(s.t.ThreadGroup().ProcessGroup())) - fmt.Fprintf(&buf, "%d ", s.pidns.IDOfSession(s.t.ThreadGroup().Session())) - fmt.Fprintf(&buf, "0 0 " /* tty_nr tpgid */) - fmt.Fprintf(&buf, "0 " /* flags */) - fmt.Fprintf(&buf, "0 0 0 0 " /* minflt cminflt majflt cmajflt */) - var cputime usage.CPUStats - if s.tgstats { - cputime = s.t.ThreadGroup().CPUStats() - } else { - cputime = s.t.CPUStats() - } - fmt.Fprintf(&buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime)) - cputime = s.t.ThreadGroup().JoinedChildCPUStats() - fmt.Fprintf(&buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime)) - fmt.Fprintf(&buf, "%d %d ", s.t.Priority(), s.t.Niceness()) - fmt.Fprintf(&buf, "%d ", s.t.ThreadGroup().Count()) - - // itrealvalue. Since kernel 2.6.17, this field is no longer - // maintained, and is hard coded as 0. - fmt.Fprintf(&buf, "0 ") - - // Start time is relative to boot time, expressed in clock ticks. - fmt.Fprintf(&buf, "%d ", linux.ClockTFromDuration(s.t.StartTime().Sub(s.t.Kernel().Timekeeper().BootTime()))) - - var vss, rss uint64 - if mm := getTaskMM(s.t); mm != nil { - vss = mm.VirtualMemorySize() - rss = mm.ResidentSetSize() - } - fmt.Fprintf(&buf, "%d %d ", vss, rss/hostarch.PageSize) - - // rsslim. - fmt.Fprintf(&buf, "%d ", s.t.ThreadGroup().Limits().Get(limits.Rss).Cur) - - fmt.Fprintf(&buf, "0 0 0 0 0 " /* startcode endcode startstack kstkesp kstkeip */) - fmt.Fprintf(&buf, "0 0 0 0 0 " /* signal blocked sigignore sigcatch wchan */) - fmt.Fprintf(&buf, "0 0 " /* nswap cnswap */) - terminationSignal := linux.Signal(0) - if s.t == s.t.ThreadGroup().Leader() { - terminationSignal = s.t.ThreadGroup().TerminationSignal() - } - fmt.Fprintf(&buf, "%d ", terminationSignal) - fmt.Fprintf(&buf, "0 0 0 " /* processor rt_priority policy */) - fmt.Fprintf(&buf, "0 0 0 " /* delayacct_blkio_ticks guest_time cguest_time */) - fmt.Fprintf(&buf, "0 0 0 0 0 0 0 " /* start_data end_data start_brk arg_start arg_end env_start env_end */) - fmt.Fprintf(&buf, "0\n" /* exit_code */) - - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*taskStatData)(nil)}}, 0 -} - -// statmData implements seqfile.SeqSource for /proc/[pid]/statm. -// -// +stateify savable -type statmData struct { - t *kernel.Task -} - -func newStatm(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - return newProcInode(ctx, seqfile.NewSeqFile(ctx, &statmData{t}), msrc, fs.SpecialFile, t) -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (s *statmData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (s *statmData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - var vss, rss uint64 - if mm := getTaskMM(s.t); mm != nil { - vss = mm.VirtualMemorySize() - rss = mm.ResidentSetSize() - } - - var buf bytes.Buffer - fmt.Fprintf(&buf, "%d %d 0 0 0 0 0\n", vss/hostarch.PageSize, rss/hostarch.PageSize) - - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*statmData)(nil)}}, 0 -} - -// statusData implements seqfile.SeqSource for /proc/[pid]/status. -// -// +stateify savable -type statusData struct { - t *kernel.Task - pidns *kernel.PIDNamespace -} - -func newStatus(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, pidns *kernel.PIDNamespace) *fs.Inode { - return newProcInode(ctx, seqfile.NewSeqFile(ctx, &statusData{t, pidns}), msrc, fs.SpecialFile, t) -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (s *statusData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (s *statusData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - var buf bytes.Buffer - fmt.Fprintf(&buf, "Name:\t%s\n", s.t.Name()) - fmt.Fprintf(&buf, "State:\t%s\n", s.t.StateStatus()) - fmt.Fprintf(&buf, "Tgid:\t%d\n", s.pidns.IDOfThreadGroup(s.t.ThreadGroup())) - fmt.Fprintf(&buf, "Pid:\t%d\n", s.pidns.IDOfTask(s.t)) - ppid := kernel.ThreadID(0) - if parent := s.t.Parent(); parent != nil { - ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup()) - } - fmt.Fprintf(&buf, "PPid:\t%d\n", ppid) - tpid := kernel.ThreadID(0) - if tracer := s.t.Tracer(); tracer != nil { - tpid = s.pidns.IDOfTask(tracer) - } - fmt.Fprintf(&buf, "TracerPid:\t%d\n", tpid) - var fds int - var vss, rss, data uint64 - s.t.WithMuLocked(func(t *kernel.Task) { - if fdTable := t.FDTable(); fdTable != nil { - fds = fdTable.CurrentMaxFDs() - } - }) - - if mm := getTaskMM(s.t); mm != nil { - vss = mm.VirtualMemorySize() - rss = mm.ResidentSetSize() - data = mm.VirtualDataSize() - } - fmt.Fprintf(&buf, "FDSize:\t%d\n", fds) - fmt.Fprintf(&buf, "VmSize:\t%d kB\n", vss>>10) - fmt.Fprintf(&buf, "VmRSS:\t%d kB\n", rss>>10) - fmt.Fprintf(&buf, "VmData:\t%d kB\n", data>>10) - fmt.Fprintf(&buf, "Threads:\t%d\n", s.t.ThreadGroup().Count()) - creds := s.t.Credentials() - fmt.Fprintf(&buf, "CapInh:\t%016x\n", creds.InheritableCaps) - fmt.Fprintf(&buf, "CapPrm:\t%016x\n", creds.PermittedCaps) - fmt.Fprintf(&buf, "CapEff:\t%016x\n", creds.EffectiveCaps) - fmt.Fprintf(&buf, "CapBnd:\t%016x\n", creds.BoundingCaps) - fmt.Fprintf(&buf, "Seccomp:\t%d\n", s.t.SeccompMode()) - // We unconditionally report a single NUMA node. See - // pkg/sentry/syscalls/linux/sys_mempolicy.go. - fmt.Fprintf(&buf, "Mems_allowed:\t1\n") - fmt.Fprintf(&buf, "Mems_allowed_list:\t0\n") - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*statusData)(nil)}}, 0 -} - -// ioUsage is the /proc//io and /proc//task//io data provider. -type ioUsage interface { - // IOUsage returns the io usage data. - IOUsage() *usage.IO -} - -// +stateify savable -type ioData struct { - ioUsage -} - -func newIO(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, isThreadGroup bool) *fs.Inode { - if isThreadGroup { - return newProcInode(ctx, seqfile.NewSeqFile(ctx, &ioData{t.ThreadGroup()}), msrc, fs.SpecialFile, t) - } - return newProcInode(ctx, seqfile.NewSeqFile(ctx, &ioData{t}), msrc, fs.SpecialFile, t) -} - -// NeedsUpdate returns whether the generation is old or not. -func (i *ioData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData returns data for the SeqFile reader. -// SeqData, the current generation and where in the file the handle corresponds to. -func (i *ioData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - io := usage.IO{} - io.Accumulate(i.IOUsage()) - - var buf bytes.Buffer - fmt.Fprintf(&buf, "rchar: %d\n", io.CharsRead.Load()) - fmt.Fprintf(&buf, "wchar: %d\n", io.CharsWritten.Load()) - fmt.Fprintf(&buf, "syscr: %d\n", io.ReadSyscalls.Load()) - fmt.Fprintf(&buf, "syscw: %d\n", io.WriteSyscalls.Load()) - fmt.Fprintf(&buf, "read_bytes: %d\n", io.BytesRead.Load()) - fmt.Fprintf(&buf, "write_bytes: %d\n", io.BytesWritten.Load()) - fmt.Fprintf(&buf, "cancelled_write_bytes: %d\n", io.BytesWriteCancelled.Load()) - - return []seqfile.SeqData{{Buf: buf.Bytes(), Handle: (*ioData)(nil)}}, 0 -} - -// comm is a file containing the command name for a task. -// -// On Linux, /proc/[pid]/comm is writable, and writing to the comm file changes -// the thread name. We don't implement this yet as there are no known users of -// this feature. -// -// +stateify savable -type comm struct { - fsutil.SimpleFileInode - - t *kernel.Task -} - -// newComm returns a new comm file. -func newComm(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - c := &comm{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - t: t, - } - return newProcInode(ctx, c, msrc, fs.SpecialFile, t) -} - -// Check implements fs.InodeOperations.Check. -func (c *comm) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { - // This file can always be read or written by members of the same - // thread group. See fs/proc/base.c:proc_tid_comm_permission. - // - // N.B. This check is currently a no-op as we don't yet support writing - // and this file is world-readable anyways. - t := kernel.TaskFromContext(ctx) - if t != nil && t.ThreadGroup() == c.t.ThreadGroup() && !p.Execute { - return true - } - - return fs.ContextCanAccessFile(ctx, inode, p) -} - -// GetFile implements fs.InodeOperations.GetFile. -func (c *comm) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &commFile{t: c.t}), nil -} - -// +stateify savable -type commFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - t *kernel.Task -} - -var _ fs.FileOperations = (*commFile)(nil) - -// Read implements fs.FileOperations.Read. -func (f *commFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset < 0 { - return 0, linuxerr.EINVAL - } - - buf := []byte(f.t.Name() + "\n") - if offset >= int64(len(buf)) { - return 0, io.EOF - } - - n, err := dst.CopyOut(ctx, buf[offset:]) - return int64(n), err -} - -// auxvec is a file containing the auxiliary vector for a task. -// -// +stateify savable -type auxvec struct { - fsutil.SimpleFileInode - - t *kernel.Task -} - -// newAuxvec returns a new auxvec file. -func newAuxvec(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - a := &auxvec{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - t: t, - } - return newProcInode(ctx, a, msrc, fs.SpecialFile, t) -} - -// GetFile implements fs.InodeOperations.GetFile. -func (a *auxvec) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &auxvecFile{t: a.t}), nil -} - -// +stateify savable -type auxvecFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - t *kernel.Task -} - -// Read implements fs.FileOperations.Read. -func (f *auxvecFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset < 0 { - return 0, linuxerr.EINVAL - } - - m, err := getTaskMMIncRef(f.t) - if err != nil { - return 0, err - } - defer m.DecUsers(ctx) - auxv := m.Auxv() - - // Space for buffer with AT_NULL (0) terminator at the end. - size := (len(auxv) + 1) * 16 - if offset >= int64(size) { - return 0, io.EOF - } - - buf := make([]byte, size) - for i, e := range auxv { - hostarch.ByteOrder.PutUint64(buf[16*i:], e.Key) - hostarch.ByteOrder.PutUint64(buf[16*i+8:], uint64(e.Value)) - } - - n, err := dst.CopyOut(ctx, buf[offset:]) - return int64(n), err -} - -// newOOMScore returns a oom_score file. It is a stub that always returns 0. -// TODO(gvisor.dev/issue/1967) -func newOOMScore(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - return newStaticProcInode(ctx, msrc, []byte("0\n")) -} - -// oomScoreAdj is a file containing the oom_score adjustment for a task. -// -// +stateify savable -type oomScoreAdj struct { - fsutil.SimpleFileInode - - t *kernel.Task -} - -// +stateify savable -type oomScoreAdjFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - t *kernel.Task -} - -// newOOMScoreAdj returns a oom_score_adj file. -func newOOMScoreAdj(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - i := &oomScoreAdj{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0644), linux.PROC_SUPER_MAGIC), - t: t, - } - return newProcInode(ctx, i, msrc, fs.SpecialFile, t) -} - -// Truncate implements fs.InodeOperations.Truncate. Truncate is called when -// O_TRUNC is specified for any kind of existing Dirent but is not called via -// (f)truncate for proc files. -func (*oomScoreAdj) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -func (o *oomScoreAdj) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &oomScoreAdjFile{t: o.t}), nil -} - -// Read implements fs.FileOperations.Read. -func (f *oomScoreAdjFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if f.t.ExitState() == kernel.TaskExitDead { - return 0, linuxerr.ESRCH - } - var buf bytes.Buffer - fmt.Fprintf(&buf, "%d\n", f.t.OOMScoreAdj()) - if offset >= int64(buf.Len()) { - return 0, io.EOF - } - n, err := dst.CopyOut(ctx, buf.Bytes()[offset:]) - return int64(n), err -} - -// Write implements fs.FileOperations.Write. -func (f *oomScoreAdjFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - if src.NumBytes() == 0 { - return 0, nil - } - - // Limit input size so as not to impact performance if input size is large. - src = src.TakeFirst(hostarch.PageSize - 1) - - var v int32 - n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts) - if err != nil { - return 0, err - } - - if f.t.ExitState() == kernel.TaskExitDead { - return 0, linuxerr.ESRCH - } - if err := f.t.SetOOMScoreAdj(v); err != nil { - return 0, err - } - - return n, nil -} - -// LINT.ThenChange(../../fsimpl/proc/task.go|../../fsimpl/proc/task_files.go) diff --git a/pkg/sentry/fs/proc/uid_gid_map.go b/pkg/sentry/fs/proc/uid_gid_map.go deleted file mode 100644 index fcdc1e7bd..000000000 --- a/pkg/sentry/fs/proc/uid_gid_map.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "bytes" - "fmt" - "io" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// idMapInodeOperations implements fs.InodeOperations for -// /proc/[pid]/{uid,gid}_map. -// -// +stateify savable -type idMapInodeOperations struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotAllocatable `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeNotTruncatable `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - fsutil.InodeSimpleExtendedAttributes - - t *kernel.Task - gids bool -} - -var _ fs.InodeOperations = (*idMapInodeOperations)(nil) - -// newUIDMap returns a new uid_map file. -func newUIDMap(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - return newIDMap(ctx, t, msrc, false /* gids */) -} - -// newGIDMap returns a new gid_map file. -func newGIDMap(ctx context.Context, t *kernel.Task, msrc *fs.MountSource) *fs.Inode { - return newIDMap(ctx, t, msrc, true /* gids */) -} - -func newIDMap(ctx context.Context, t *kernel.Task, msrc *fs.MountSource, gids bool) *fs.Inode { - return newProcInode(ctx, &idMapInodeOperations{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermsFromMode(0644), linux.PROC_SUPER_MAGIC), - t: t, - gids: gids, - }, msrc, fs.SpecialFile, t) -} - -// GetFile implements fs.InodeOperations.GetFile. -func (imio *idMapInodeOperations) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &idMapFileOperations{ - iops: imio, - }), nil -} - -// +stateify savable -type idMapFileOperations struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - iops *idMapInodeOperations -} - -var _ fs.FileOperations = (*idMapFileOperations)(nil) - -// "There is an (arbitrary) limit on the number of lines in the file. As at -// Linux 3.18, the limit is five lines." - user_namespaces(7) -const maxIDMapLines = 5 - -// Read implements fs.FileOperations.Read. -func (imfo *idMapFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset < 0 { - return 0, linuxerr.EINVAL - } - var entries []auth.IDMapEntry - if imfo.iops.gids { - entries = imfo.iops.t.UserNamespace().GIDMap() - } else { - entries = imfo.iops.t.UserNamespace().UIDMap() - } - var buf bytes.Buffer - for _, e := range entries { - fmt.Fprintf(&buf, "%10d %10d %10d\n", e.FirstID, e.FirstParentID, e.Length) - } - if offset >= int64(buf.Len()) { - return 0, io.EOF - } - n, err := dst.CopyOut(ctx, buf.Bytes()[offset:]) - return int64(n), err -} - -// Write implements fs.FileOperations.Write. -func (imfo *idMapFileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - // "In addition, the number of bytes written to the file must be less than - // the system page size, and the write must be performed at the start of - // the file ..." - user_namespaces(7) - srclen := src.NumBytes() - if srclen >= hostarch.PageSize || offset != 0 { - return 0, linuxerr.EINVAL - } - b := make([]byte, srclen) - if _, err := src.CopyIn(ctx, b); err != nil { - return 0, err - } - - // Truncate from the first NULL byte. - var nul int64 - nul = int64(bytes.IndexByte(b, 0)) - if nul == -1 { - nul = srclen - } - b = b[:nul] - // Remove the last \n. - if nul >= 1 && b[nul-1] == '\n' { - b = b[:nul-1] - } - lines := bytes.SplitN(b, []byte("\n"), maxIDMapLines+1) - if len(lines) > maxIDMapLines { - return 0, linuxerr.EINVAL - } - - entries := make([]auth.IDMapEntry, len(lines)) - for i, l := range lines { - var e auth.IDMapEntry - _, err := fmt.Sscan(string(l), &e.FirstID, &e.FirstParentID, &e.Length) - if err != nil { - return 0, linuxerr.EINVAL - } - entries[i] = e - } - var err error - if imfo.iops.gids { - err = imfo.iops.t.UserNamespace().SetGIDMap(ctx, entries) - } else { - err = imfo.iops.t.UserNamespace().SetUIDMap(ctx, entries) - } - if err != nil { - return 0, err - } - - // On success, Linux's kernel/user_namespace.c:map_write() always returns - // count, even if fewer bytes were used. - return int64(srclen), nil -} - -// LINT.ThenChange(../../fsimpl/proc/task_files.go) diff --git a/pkg/sentry/fs/proc/uptime.go b/pkg/sentry/fs/proc/uptime.go deleted file mode 100644 index ac896f963..000000000 --- a/pkg/sentry/fs/proc/uptime.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - "io" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// uptime is a file containing the system uptime. -// -// +stateify savable -type uptime struct { - fsutil.SimpleFileInode - - // The "start time" of the sandbox. - startTime ktime.Time -} - -// newUptime returns a new uptime file. -func newUptime(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - u := &uptime{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.PROC_SUPER_MAGIC), - startTime: ktime.NowFromContext(ctx), - } - return newProcInode(ctx, u, msrc, fs.SpecialFile, nil) -} - -// GetFile implements fs.InodeOperations.GetFile. -func (u *uptime) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &uptimeFile{startTime: u.startTime}), nil -} - -// +stateify savable -type uptimeFile struct { - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - startTime ktime.Time -} - -// Read implements fs.FileOperations.Read. -func (f *uptimeFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset < 0 { - return 0, linuxerr.EINVAL - } - - now := ktime.NowFromContext(ctx) - // Pretend that we've spent zero time sleeping (second number). - s := []byte(fmt.Sprintf("%.2f 0.00\n", now.Sub(f.startTime).Seconds())) - if offset >= int64(len(s)) { - return 0, io.EOF - } - - n, err := dst.CopyOut(ctx, s[offset:]) - return int64(n), err -} - -// LINT.ThenChange(../../fsimpl/proc/tasks_files.go) diff --git a/pkg/sentry/fs/proc/version.go b/pkg/sentry/fs/proc/version.go deleted file mode 100644 index cc22f1f9c..000000000 --- a/pkg/sentry/fs/proc/version.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2018 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. - -package proc - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -// versionData backs /proc/version. -// -// +stateify savable -type versionData struct { - // k is the owning Kernel. - k *kernel.Kernel -} - -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (*versionData) NeedsUpdate(generation int64) bool { - return true -} - -// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData. -func (v *versionData) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - if h != nil { - return nil, 0 - } - - init := v.k.GlobalInit() - if init == nil { - // Attempted to read before the init Task is created. This can - // only occur during startup, which should never need to read - // this file. - panic("Attempted to read version before initial Task is available") - } - - // /proc/version takes the form: - // - // "SYSNAME version RELEASE (COMPILE_USER@COMPILE_HOST) - // (COMPILER_VERSION) VERSION" - // - // where: - // - SYSNAME, RELEASE, and VERSION are the same as returned by - // sys_utsname - // - COMPILE_USER is the user that build the kernel - // - COMPILE_HOST is the hostname of the machine on which the kernel - // was built - // - COMPILER_VERSION is the version reported by the building compiler - // - // Since we don't really want to expose build information to - // applications, those fields are omitted. - // - // FIXME(mpratt): Using Version from the init task SyscallTable - // disregards the different version a task may have (e.g., in a uts - // namespace). - ver := init.Leader().SyscallTable().Version - return []seqfile.SeqData{ - { - Buf: []byte(fmt.Sprintf("%s version %s %s\n", ver.Sysname, ver.Release, ver.Version)), - Handle: (*versionData)(nil), - }, - }, 0 -} - -// LINT.ThenChange(../../fsimpl/proc/task_files.go) diff --git a/pkg/sentry/fs/ramfs/BUILD b/pkg/sentry/fs/ramfs/BUILD deleted file mode 100644 index bfff010c5..000000000 --- a/pkg/sentry/fs/ramfs/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "ramfs", - srcs = [ - "dir.go", - "socket.go", - "symlink.go", - "tree.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/anon", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/socket/unix/transport", - "//pkg/sync", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "ramfs_test", - size = "small", - srcs = ["tree_test.go"], - library = ":ramfs", - deps = [ - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - ], -) diff --git a/pkg/sentry/fs/ramfs/dir.go b/pkg/sentry/fs/ramfs/dir.go deleted file mode 100644 index b1fadee7a..000000000 --- a/pkg/sentry/fs/ramfs/dir.go +++ /dev/null @@ -1,548 +0,0 @@ -// Copyright 2018 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. - -// Package ramfs provides the fundamentals for a simple in-memory filesystem. -package ramfs - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sync" -) - -// CreateOps represents operations to create different file types. -type CreateOps struct { - // NewDir creates a new directory. - NewDir func(ctx context.Context, dir *fs.Inode, perms fs.FilePermissions) (*fs.Inode, error) - - // NewFile creates a new file. - NewFile func(ctx context.Context, dir *fs.Inode, perms fs.FilePermissions) (*fs.Inode, error) - - // NewSymlink creates a new symlink with permissions 0777. - NewSymlink func(ctx context.Context, dir *fs.Inode, target string) (*fs.Inode, error) - - // NewBoundEndpoint creates a new socket. - NewBoundEndpoint func(ctx context.Context, dir *fs.Inode, ep transport.BoundEndpoint, perms fs.FilePermissions) (*fs.Inode, error) - - // NewFifo creates a new fifo. - NewFifo func(ctx context.Context, dir *fs.Inode, perm fs.FilePermissions) (*fs.Inode, error) -} - -// Dir represents a single directory in the filesystem. -// -// +stateify savable -type Dir struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeIsDirAllocate `state:"nosave"` - fsutil.InodeIsDirTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - fsutil.InodeSimpleExtendedAttributes - - // CreateOps may be provided. - // - // These may only be modified during initialization (while the application - // is not running). No sychronization is performed when accessing these - // operations during syscalls. - *CreateOps `state:"nosave"` - - // mu protects the fields below. - mu sync.Mutex `state:"nosave"` - - // children are inodes that are in this directory. A reference is held - // on each inode while it is in the map. - children map[string]*fs.Inode - - // dentryMap is a sortedDentryMap containing entries for all children. - // Its entries are kept up-to-date with d.children. - dentryMap *fs.SortedDentryMap -} - -var _ fs.InodeOperations = (*Dir)(nil) - -// NewDir returns a new Dir with the given contents and attributes. A reference -// on each fs.Inode in the `contents` map will be donated to this Dir. -func NewDir(ctx context.Context, contents map[string]*fs.Inode, owner fs.FileOwner, perms fs.FilePermissions) *Dir { - d := &Dir{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, perms, linux.RAMFS_MAGIC), - } - - if contents == nil { - contents = make(map[string]*fs.Inode) - } - d.children = contents - - // Build the entries map ourselves, rather than calling addChildLocked, - // because it will be faster. - entries := make(map[string]fs.DentAttr, len(contents)) - for name, inode := range contents { - entries[name] = fs.DentAttr{ - Type: inode.StableAttr.Type, - InodeID: inode.StableAttr.InodeID, - } - } - d.dentryMap = fs.NewSortedDentryMap(entries) - - // Directories have an extra link, corresponding to '.'. - d.AddLink() - - return d -} - -// addChildLocked add the child inode, inheriting its reference. -func (d *Dir) addChildLocked(ctx context.Context, name string, inode *fs.Inode) { - d.children[name] = inode - d.dentryMap.Add(name, fs.DentAttr{ - Type: inode.StableAttr.Type, - InodeID: inode.StableAttr.InodeID, - }) - - // If the child is a directory, increment this dir's link count, - // corresponding to '..' from the subdirectory. - if fs.IsDir(inode.StableAttr) { - d.AddLink() - // ctime updated below. - } - - // Given we're now adding this inode to the directory we must also - // increase its link count. Similarly we decrement it in removeChildLocked. - // - // Changing link count updates ctime. - inode.AddLink() - inode.InodeOperations.NotifyStatusChange(ctx) - - // We've change the directory. This always updates our mtime and ctime. - d.NotifyModificationAndStatusChange(ctx) -} - -// AddChild adds a child to this dir, inheriting its reference. -func (d *Dir) AddChild(ctx context.Context, name string, inode *fs.Inode) { - d.mu.Lock() - defer d.mu.Unlock() - d.addChildLocked(ctx, name, inode) -} - -// FindChild returns (child, true) if the directory contains name. -func (d *Dir) FindChild(name string) (*fs.Inode, bool) { - d.mu.Lock() - defer d.mu.Unlock() - child, ok := d.children[name] - return child, ok -} - -// Children returns the names and DentAttrs of all children. It can be used to -// implement Readdir for types that embed ramfs.Dir. -func (d *Dir) Children() ([]string, map[string]fs.DentAttr) { - d.mu.Lock() - defer d.mu.Unlock() - - // Return a copy to prevent callers from modifying our children. - names, entries := d.dentryMap.GetAll() - namesCopy := make([]string, len(names)) - copy(namesCopy, names) - - entriesCopy := make(map[string]fs.DentAttr) - for k, v := range entries { - entriesCopy[k] = v - } - - return namesCopy, entriesCopy -} - -// removeChildLocked attempts to remove an entry from this directory. It -// returns the removed fs.Inode along with its reference, which callers are -// responsible for decrementing. -func (d *Dir) removeChildLocked(ctx context.Context, name string) (*fs.Inode, error) { - inode, ok := d.children[name] - if !ok { - return nil, linuxerr.EACCES - } - - delete(d.children, name) - d.dentryMap.Remove(name) - d.NotifyModification(ctx) - - // If the child was a subdirectory, then we must decrement this dir's - // link count which was the child's ".." directory entry. - if fs.IsDir(inode.StableAttr) { - d.DropLink() - // ctime changed below. - } - - // Given we're now removing this inode to the directory we must also - // decrease its link count. Similarly it is increased in addChildLocked. - // - // Changing link count updates ctime. - inode.DropLink() - inode.InodeOperations.NotifyStatusChange(ctx) - - // We've change the directory. This always updates our mtime and ctime. - d.NotifyModificationAndStatusChange(ctx) - - return inode, nil -} - -// Remove removes the named non-directory. -func (d *Dir) Remove(ctx context.Context, _ *fs.Inode, name string) error { - if len(name) > linux.NAME_MAX { - return linuxerr.ENAMETOOLONG - } - - d.mu.Lock() - defer d.mu.Unlock() - inode, err := d.removeChildLocked(ctx, name) - if err != nil { - return err - } - - // Remove our reference on the inode. - inode.DecRef(ctx) - return nil -} - -// RemoveDirectory removes the named directory. -func (d *Dir) RemoveDirectory(ctx context.Context, _ *fs.Inode, name string) error { - if len(name) > linux.NAME_MAX { - return linuxerr.ENAMETOOLONG - } - - d.mu.Lock() - defer d.mu.Unlock() - - // Get the child and make sure it is not empty. - childInode, err := d.walkLocked(ctx, name) - if err != nil { - return err - } - if ok, err := hasChildren(ctx, childInode); err != nil { - return err - } else if ok { - return linuxerr.ENOTEMPTY - } - - // Child was empty. Proceed with removal. - inode, err := d.removeChildLocked(ctx, name) - if err != nil { - return err - } - - // Remove our reference on the inode. - inode.DecRef(ctx) - - return nil -} - -// Lookup loads an inode at p into a Dirent. It returns the fs.Dirent along -// with a reference. -func (d *Dir) Lookup(ctx context.Context, _ *fs.Inode, p string) (*fs.Dirent, error) { - if len(p) > linux.NAME_MAX { - return nil, linuxerr.ENAMETOOLONG - } - - d.mu.Lock() - defer d.mu.Unlock() - - inode, err := d.walkLocked(ctx, p) - if err != nil { - return nil, err - } - - // Take a reference on the inode before returning it. This reference - // is owned by the dirent we are about to create. - inode.IncRef() - return fs.NewDirent(ctx, inode, p), nil -} - -// walkLocked must be called with d.mu held. -func (d *Dir) walkLocked(ctx context.Context, p string) (*fs.Inode, error) { - // Lookup a child node. - if inode, ok := d.children[p]; ok { - return inode, nil - } - - // fs.InodeOperations.Lookup returns linuxerr.ENOENT if p - // does not exist. - return nil, linuxerr.ENOENT -} - -// createInodeOperationsCommon creates a new child node at this dir by calling -// makeInodeOperations. It is the common logic for creating a new child. -func (d *Dir) createInodeOperationsCommon(ctx context.Context, name string, makeInodeOperations func() (*fs.Inode, error)) (*fs.Inode, error) { - if len(name) > linux.NAME_MAX { - return nil, linuxerr.ENAMETOOLONG - } - - d.mu.Lock() - defer d.mu.Unlock() - - inode, err := makeInodeOperations() - if err != nil { - return nil, err - } - - d.addChildLocked(ctx, name, inode) - - return inode, nil -} - -// Create creates a new Inode with the given name and returns its File. -func (d *Dir) Create(ctx context.Context, dir *fs.Inode, name string, flags fs.FileFlags, perms fs.FilePermissions) (*fs.File, error) { - if d.CreateOps == nil || d.CreateOps.NewFile == nil { - return nil, linuxerr.EACCES - } - - inode, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) { - return d.NewFile(ctx, dir, perms) - }) - if err != nil { - return nil, err - } - - // Take an extra ref on inode, which will be owned by the dirent. - inode.IncRef() - - // Create the Dirent and corresponding file. - created := fs.NewDirent(ctx, inode, name) - defer created.DecRef(ctx) - return created.Inode.GetFile(ctx, created, flags) -} - -// CreateLink returns a new link. -func (d *Dir) CreateLink(ctx context.Context, dir *fs.Inode, oldname, newname string) error { - if d.CreateOps == nil || d.CreateOps.NewSymlink == nil { - return linuxerr.EACCES - } - _, err := d.createInodeOperationsCommon(ctx, newname, func() (*fs.Inode, error) { - return d.NewSymlink(ctx, dir, oldname) - }) - return err -} - -// CreateHardLink creates a new hard link. -func (d *Dir) CreateHardLink(ctx context.Context, dir *fs.Inode, target *fs.Inode, name string) error { - if len(name) > linux.NAME_MAX { - return linuxerr.ENAMETOOLONG - } - - d.mu.Lock() - defer d.mu.Unlock() - - // Take an extra reference on the inode and add it to our children. - target.IncRef() - - // The link count will be incremented in addChildLocked. - d.addChildLocked(ctx, name, target) - - return nil -} - -// CreateDirectory returns a new subdirectory. -func (d *Dir) CreateDirectory(ctx context.Context, dir *fs.Inode, name string, perms fs.FilePermissions) error { - if d.CreateOps == nil || d.CreateOps.NewDir == nil { - return linuxerr.EACCES - } - _, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) { - return d.NewDir(ctx, dir, perms) - }) - return err -} - -// Bind implements fs.InodeOperations.Bind. -func (d *Dir) Bind(ctx context.Context, dir *fs.Inode, name string, ep transport.BoundEndpoint, perms fs.FilePermissions) (*fs.Dirent, error) { - if d.CreateOps == nil || d.CreateOps.NewBoundEndpoint == nil { - return nil, linuxerr.EACCES - } - inode, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) { - return d.NewBoundEndpoint(ctx, dir, ep, perms) - }) - if err == unix.EEXIST { - return nil, unix.EADDRINUSE - } - if err != nil { - return nil, err - } - // Take another ref on inode which will be donated to the new dirent. - inode.IncRef() - return fs.NewDirent(ctx, inode, name), nil -} - -// CreateFifo implements fs.InodeOperations.CreateFifo. -func (d *Dir) CreateFifo(ctx context.Context, dir *fs.Inode, name string, perms fs.FilePermissions) error { - if d.CreateOps == nil || d.CreateOps.NewFifo == nil { - return linuxerr.EACCES - } - _, err := d.createInodeOperationsCommon(ctx, name, func() (*fs.Inode, error) { - return d.NewFifo(ctx, dir, perms) - }) - return err -} - -// GetFile implements fs.InodeOperations.GetFile. -func (d *Dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - flags.Pread = true - return fs.NewFile(ctx, dirent, flags, &dirFileOperations{dir: d}), nil -} - -// Rename implements fs.InodeOperations.Rename. -func (*Dir) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return Rename(ctx, oldParent.InodeOperations, oldName, newParent.InodeOperations, newName, replacement) -} - -// Release implements fs.InodeOperation.Release. -func (d *Dir) Release(ctx context.Context) { - // Drop references on all children. - d.mu.Lock() - for _, i := range d.children { - i.DecRef(ctx) - } - d.mu.Unlock() -} - -// dirFileOperations implements fs.FileOperations for a ramfs directory. -// -// +stateify savable -type dirFileOperations struct { - fsutil.DirFileOperations `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - // dirCursor contains the name of the last directory entry that was - // serialized. - dirCursor string - - // dir is the ramfs dir that this file corresponds to. - dir *Dir -} - -var _ fs.FileOperations = (*dirFileOperations)(nil) - -// Seek implements fs.FileOperations.Seek. -func (dfo *dirFileOperations) Seek(ctx context.Context, file *fs.File, whence fs.SeekWhence, offset int64) (int64, error) { - return fsutil.SeekWithDirCursor(ctx, file, whence, offset, &dfo.dirCursor) -} - -// IterateDir implements DirIterator.IterateDir. -func (dfo *dirFileOperations) IterateDir(ctx context.Context, d *fs.Dirent, dirCtx *fs.DirCtx, offset int) (int, error) { - dfo.dir.mu.Lock() - defer dfo.dir.mu.Unlock() - - n, err := fs.GenericReaddir(dirCtx, dfo.dir.dentryMap) - return offset + n, err -} - -// Readdir implements FileOperations.Readdir. -func (dfo *dirFileOperations) Readdir(ctx context.Context, file *fs.File, serializer fs.DentrySerializer) (int64, error) { - root := fs.RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - dirCtx := &fs.DirCtx{ - Serializer: serializer, - DirCursor: &dfo.dirCursor, - } - dfo.dir.InodeSimpleAttributes.NotifyAccess(ctx) - return fs.DirentReaddir(ctx, file.Dirent, dfo, root, dirCtx, file.Offset()) -} - -// hasChildren is a helper method that determines whether an arbitrary inode -// (not necessarily ramfs) has any children. -func hasChildren(ctx context.Context, inode *fs.Inode) (bool, error) { - // Take an extra ref on inode which will be given to the dirent and - // dropped when that dirent is destroyed. - inode.IncRef() - d := fs.NewTransientDirent(inode) - defer d.DecRef(ctx) - - file, err := inode.GetFile(ctx, d, fs.FileFlags{Read: true}) - if err != nil { - return false, err - } - defer file.DecRef(ctx) - - ser := &fs.CollectEntriesSerializer{} - if err := file.Readdir(ctx, ser); err != nil { - return false, err - } - // We will always write "." and "..", so ignore those two. - if ser.Written() > 2 { - return true, nil - } - return false, nil -} - -// Rename renames from a *ramfs.Dir to another *ramfs.Dir. -func Rename(ctx context.Context, oldParent fs.InodeOperations, oldName string, newParent fs.InodeOperations, newName string, replacement bool) error { - op, ok := oldParent.(*Dir) - if !ok { - return linuxerr.EXDEV - } - np, ok := newParent.(*Dir) - if !ok { - return linuxerr.EXDEV - } - if len(newName) > linux.NAME_MAX { - return linuxerr.ENAMETOOLONG - } - - np.mu.Lock() - defer np.mu.Unlock() - - // Is this is an overwriting rename? - if replacement { - replaced, ok := np.children[newName] - if !ok { - panic(fmt.Sprintf("Dirent claims rename is replacement, but %q is missing from %+v", newName, np)) - } - - // Non-empty directories cannot be replaced. - if fs.IsDir(replaced.StableAttr) { - if ok, err := hasChildren(ctx, replaced); err != nil { - return err - } else if ok { - return linuxerr.ENOTEMPTY - } - } - - // Remove the replaced child and drop our reference on it. - inode, err := np.removeChildLocked(ctx, newName) - if err != nil { - return err - } - inode.DecRef(ctx) - } - - // Be careful, we may have already grabbed this mutex above. - if op != np { - op.mu.Lock() - defer op.mu.Unlock() - } - - // Do the swap. - n := op.children[oldName] - op.removeChildLocked(ctx, oldName) - np.addChildLocked(ctx, newName, n) - - return nil -} diff --git a/pkg/sentry/fs/ramfs/socket.go b/pkg/sentry/fs/ramfs/socket.go deleted file mode 100644 index dc9d27bb3..000000000 --- a/pkg/sentry/fs/ramfs/socket.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2018 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. - -package ramfs - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/waiter" -) - -// Socket represents a socket. -// -// +stateify savable -type Socket struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotAllocatable `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeNotTruncatable `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - fsutil.InodeSimpleExtendedAttributes - - // ep is the bound endpoint. - ep transport.BoundEndpoint -} - -var _ fs.InodeOperations = (*Socket)(nil) - -// NewSocket returns a new Socket. -func NewSocket(ctx context.Context, ep transport.BoundEndpoint, owner fs.FileOwner, perms fs.FilePermissions) *Socket { - return &Socket{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, perms, linux.SOCKFS_MAGIC), - ep: ep, - } -} - -// BoundEndpoint returns the socket data. -func (s *Socket) BoundEndpoint(*fs.Inode, string) transport.BoundEndpoint { - // ramfs only supports stored sentry internal sockets. Only gofer sockets - // care about the path argument. - return s.ep -} - -// GetFile implements fs.FileOperations.GetFile. -func (s *Socket) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return nil, linuxerr.ENXIO -} - -// +stateify savable -type socketFileOperations struct { - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoRead `state:"nosave"` - fsutil.FileNoSeek `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` -} - -var _ fs.FileOperations = (*socketFileOperations)(nil) diff --git a/pkg/sentry/fs/ramfs/symlink.go b/pkg/sentry/fs/ramfs/symlink.go deleted file mode 100644 index d988349aa..000000000 --- a/pkg/sentry/fs/ramfs/symlink.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 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. - -package ramfs - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/waiter" -) - -// Symlink represents a symlink. -// -// +stateify savable -type Symlink struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotAllocatable `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotTruncatable `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - fsutil.InodeSimpleExtendedAttributes - - // Target is the symlink target. - Target string -} - -var _ fs.InodeOperations = (*Symlink)(nil) - -// NewSymlink returns a new Symlink. -func NewSymlink(ctx context.Context, owner fs.FileOwner, target string) *Symlink { - // A symlink is assumed to always have permissions 0777. - return &Symlink{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, owner, fs.FilePermsFromMode(0777), linux.RAMFS_MAGIC), - Target: target, - } -} - -// UnstableAttr returns all attributes of this ramfs symlink. -func (s *Symlink) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - uattr, err := s.InodeSimpleAttributes.UnstableAttr(ctx, inode) - if err != nil { - return fs.UnstableAttr{}, err - } - uattr.Size = int64(len(s.Target)) - uattr.Usage = uattr.Size - return uattr, nil -} - -// SetPermissions on a symlink is always rejected. -func (s *Symlink) SetPermissions(context.Context, *fs.Inode, fs.FilePermissions) bool { - return false -} - -// Readlink reads the symlink value. -func (s *Symlink) Readlink(ctx context.Context, _ *fs.Inode) (string, error) { - s.NotifyAccess(ctx) - return s.Target, nil -} - -// Getlink returns ErrResolveViaReadlink, falling back to walking to the result -// of Readlink(). -func (*Symlink) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) { - return nil, fs.ErrResolveViaReadlink -} - -// GetFile implements fs.FileOperations.GetFile. -func (s *Symlink) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &symlinkFileOperations{}), nil -} - -// +stateify savable -type symlinkFileOperations struct { - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoRead `state:"nosave"` - fsutil.FileNoSeek `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` -} - -var _ fs.FileOperations = (*symlinkFileOperations)(nil) diff --git a/pkg/sentry/fs/ramfs/tree.go b/pkg/sentry/fs/ramfs/tree.go deleted file mode 100644 index 0ace636c9..000000000 --- a/pkg/sentry/fs/ramfs/tree.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 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. - -package ramfs - -import ( - "fmt" - "path" - "strings" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/anon" -) - -// MakeDirectoryTree constructs a ramfs tree of all directories containing -// subdirs. Each element of subdir must be a clean path, and cannot be empty or -// "/". -// -// All directories in the created tree will have full (read-write-execute) -// permissions, but note that file creation inside the directories is not -// actually supported because ramfs.Dir.CreateOpts == nil. However, these -// directory trees are normally "underlayed" under another filesystem (possibly -// the root), and file creation inside these directories in the overlay will be -// possible if the upper is writeable. -func MakeDirectoryTree(ctx context.Context, msrc *fs.MountSource, subdirs []string) (*fs.Inode, error) { - root := emptyDir(ctx, msrc) - for _, subdir := range subdirs { - if path.Clean(subdir) != subdir { - return nil, fmt.Errorf("cannot add subdir at an unclean path: %q", subdir) - } - if subdir == "" || subdir == "/" { - return nil, fmt.Errorf("cannot add subdir at %q", subdir) - } - makeSubdir(ctx, msrc, root.InodeOperations.(*Dir), subdir) - } - return root, nil -} - -// makeSubdir installs into root each component of subdir. The final component is -// a *ramfs.Dir. -func makeSubdir(ctx context.Context, msrc *fs.MountSource, root *Dir, subdir string) { - for _, c := range strings.Split(subdir, "/") { - if len(c) == 0 { - continue - } - child, ok := root.FindChild(c) - if !ok { - child = emptyDir(ctx, msrc) - root.AddChild(ctx, c, child) - } - root = child.InodeOperations.(*Dir) - } -} - -// emptyDir returns an empty *ramfs.Dir with all permissions granted. -func emptyDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - dir := NewDir(ctx, make(map[string]*fs.Inode), fs.RootOwner, fs.FilePermsFromMode(0777)) - return fs.NewInode(ctx, dir, msrc, fs.StableAttr{ - DeviceID: anon.PseudoDevice.DeviceID(), - InodeID: anon.PseudoDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Directory, - }) -} diff --git a/pkg/sentry/fs/ramfs/tree_test.go b/pkg/sentry/fs/ramfs/tree_test.go deleted file mode 100644 index 3e0d1e07e..000000000 --- a/pkg/sentry/fs/ramfs/tree_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2018 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. - -package ramfs - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -func TestMakeDirectoryTree(t *testing.T) { - - for _, test := range []struct { - name string - subdirs []string - }{ - { - name: "abs paths", - subdirs: []string{ - "/tmp", - "/tmp/a/b", - "/tmp/a/c/d", - "/tmp/c", - "/proc", - "/dev/a/b", - "/tmp", - }, - }, - { - name: "rel paths", - subdirs: []string{ - "tmp", - "tmp/a/b", - "tmp/a/c/d", - "tmp/c", - "proc", - "dev/a/b", - "tmp", - }, - }, - } { - ctx := contexttest.Context(t) - mount := fs.NewPseudoMountSource(ctx) - tree, err := MakeDirectoryTree(ctx, mount, test.subdirs) - if err != nil { - t.Errorf("%s: failed to make ramfs tree, got error %v, want nil", test.name, err) - continue - } - - // Expect to be able to find each of the paths. - mm, err := fs.NewMountNamespace(ctx, tree) - if err != nil { - t.Errorf("%s: failed to create mount manager: %v", test.name, err) - continue - } - root := mm.Root() - defer mm.DecRef(ctx) - - for _, p := range test.subdirs { - maxTraversals := uint(0) - if _, err := mm.FindInode(ctx, root, nil, p, &maxTraversals); err != nil { - t.Errorf("%s: failed to find node %s: %v", test.name, p, err) - break - } - } - } -} diff --git a/pkg/sentry/fs/sys/BUILD b/pkg/sentry/fs/sys/BUILD deleted file mode 100644 index fdbc5f912..000000000 --- a/pkg/sentry/fs/sys/BUILD +++ /dev/null @@ -1,24 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "sys", - srcs = [ - "device.go", - "devices.go", - "fs.go", - "sys.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/hostarch", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/ramfs", - "//pkg/sentry/kernel", - ], -) diff --git a/pkg/sentry/fs/sys/device.go b/pkg/sentry/fs/sys/device.go deleted file mode 100644 index 4e79dbb71..000000000 --- a/pkg/sentry/fs/sys/device.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018 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. - -package sys - -import "gvisor.dev/gvisor/pkg/sentry/device" - -// sysfsDevice is the sysfs virtual device. -var sysfsDevice = device.NewAnonDevice() diff --git a/pkg/sentry/fs/sys/devices.go b/pkg/sentry/fs/sys/devices.go deleted file mode 100644 index b67065956..000000000 --- a/pkg/sentry/fs/sys/devices.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018 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. - -package sys - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// +stateify savable -type cpunum struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopRelease `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotAllocatable `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeNotTruncatable `state:"nosave"` - fsutil.InodeNotVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - fsutil.InodeStaticFileGetter -} - -var _ fs.InodeOperations = (*cpunum)(nil) - -func newPossible(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - var maxCore uint - k := kernel.KernelFromContext(ctx) - if k != nil { - maxCore = k.ApplicationCores() - 1 - } - contents := []byte(fmt.Sprintf("0-%d\n", maxCore)) - - c := &cpunum{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), linux.SYSFS_MAGIC), - InodeStaticFileGetter: fsutil.InodeStaticFileGetter{ - Contents: contents, - }, - } - return newFile(ctx, c, msrc) -} - -func newCPU(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - m := map[string]*fs.Inode{ - "online": newPossible(ctx, msrc), - "possible": newPossible(ctx, msrc), - "present": newPossible(ctx, msrc), - } - - // Add directories for each of the cpus. - if k := kernel.KernelFromContext(ctx); k != nil { - for i := 0; uint(i) < k.ApplicationCores(); i++ { - m[fmt.Sprintf("cpu%d", i)] = newDir(ctx, msrc, nil) - } - } - - return newDir(ctx, msrc, m) -} - -func newSystemDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - return newDir(ctx, msrc, map[string]*fs.Inode{ - "cpu": newCPU(ctx, msrc), - }) -} - -func newDevicesDir(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - return newDir(ctx, msrc, map[string]*fs.Inode{ - "system": newSystemDir(ctx, msrc), - }) -} diff --git a/pkg/sentry/fs/sys/fs.go b/pkg/sentry/fs/sys/fs.go deleted file mode 100644 index 01c4f4944..000000000 --- a/pkg/sentry/fs/sys/fs.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 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. - -package sys - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// filesystem is a sysfs. -// -// +stateify savable -type filesystem struct{} - -var _ fs.Filesystem = (*filesystem)(nil) - -func init() { - fs.RegisterFilesystem(&filesystem{}) -} - -// FilesystemName is the name under which the filesystem is registered. -// Name matches fs/sysfs/mount.c:sysfs_fs_type.name. -const FilesystemName = "sysfs" - -// Name is the name of the file system. -func (*filesystem) Name() string { - return FilesystemName -} - -// AllowUserMount allows users to mount(2) this file system. -func (*filesystem) AllowUserMount() bool { - return true -} - -// AllowUserList allows this filesystem to be listed in /proc/filesystems. -func (*filesystem) AllowUserList() bool { - return true -} - -// Flags returns that there is nothing special about this file system. -// -// In Linux, sysfs returns FS_USERNS_VISIBLE | FS_USERNS_MOUNT, see fs/sysfs/mount.c. -func (*filesystem) Flags() fs.FilesystemFlags { - return 0 -} - -// Mount returns a sysfs root which can be positioned in the vfs. -func (f *filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, _ any) (*fs.Inode, error) { - // device is always ignored. - // sysfs ignores data, see fs/sysfs/mount.c:sysfs_mount. - - return New(ctx, fs.NewNonCachingMountSource(ctx, f, flags)), nil -} diff --git a/pkg/sentry/fs/sys/sys.go b/pkg/sentry/fs/sys/sys.go deleted file mode 100644 index 101779a7a..000000000 --- a/pkg/sentry/fs/sys/sys.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 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. - -// Package sys implements a sysfs filesystem. -package sys - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" -) - -func newFile(ctx context.Context, node fs.InodeOperations, msrc *fs.MountSource) *fs.Inode { - sattr := fs.StableAttr{ - DeviceID: sysfsDevice.DeviceID(), - InodeID: sysfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialFile, - } - return fs.NewInode(ctx, node, msrc, sattr) -} - -func newDir(ctx context.Context, msrc *fs.MountSource, contents map[string]*fs.Inode) *fs.Inode { - d := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555)) - return fs.NewInode(ctx, d, msrc, fs.StableAttr{ - DeviceID: sysfsDevice.DeviceID(), - InodeID: sysfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.SpecialDirectory, - }) -} - -// New returns the root node of a partial simple sysfs. -func New(ctx context.Context, msrc *fs.MountSource) *fs.Inode { - return newDir(ctx, msrc, map[string]*fs.Inode{ - // Add a basic set of top-level directories. In Linux, these - // are dynamically added depending on the KConfig. Here we just - // add the most common ones. - "block": newDir(ctx, msrc, nil), - "bus": newDir(ctx, msrc, nil), - "class": newDir(ctx, msrc, map[string]*fs.Inode{ - "power_supply": newDir(ctx, msrc, nil), - }), - "dev": newDir(ctx, msrc, nil), - "devices": newDevicesDir(ctx, msrc), - "firmware": newDir(ctx, msrc, nil), - "fs": newDir(ctx, msrc, nil), - "kernel": newDir(ctx, msrc, nil), - "module": newDir(ctx, msrc, nil), - "power": newDir(ctx, msrc, nil), - }) -} diff --git a/pkg/sentry/fs/timerfd/BUILD b/pkg/sentry/fs/timerfd/BUILD deleted file mode 100644 index 13d2c4106..000000000 --- a/pkg/sentry/fs/timerfd/BUILD +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "timerfd", - srcs = ["timerfd.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/atomicbitops", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/anon", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/kernel/time", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/fs/timerfd/timerfd.go b/pkg/sentry/fs/timerfd/timerfd.go deleted file mode 100644 index 5b561828f..000000000 --- a/pkg/sentry/fs/timerfd/timerfd.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2018 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. - -// Package timerfd implements the semantics of Linux timerfd objects as -// described by timerfd_create(2). -package timerfd - -import ( - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/anon" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// TimerOperations implements fs.FileOperations for timerfds. -// -// +stateify savable -type TimerOperations struct { - fsutil.FileZeroSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - events waiter.Queue - timer *ktime.Timer - - // val is the number of timer expirations since the last successful call to - // Readv, Preadv, or SetTime. val is accessed using atomic memory - // operations. - val atomicbitops.Uint64 -} - -// NewFile returns a timerfd File that receives time from c. -func NewFile(ctx context.Context, c ktime.Clock) *fs.File { - dirent := fs.NewDirent(ctx, anon.NewInode(ctx), "anon_inode:[timerfd]") - // Release the initial dirent reference after NewFile takes a reference. - defer dirent.DecRef(ctx) - tops := &TimerOperations{} - tops.timer = ktime.NewTimer(c, tops) - // Timerfds reject writes, but the Write flag must be set in order to - // ensure that our Writev/Pwritev methods actually get called to return - // the correct errors. - return fs.NewFile(ctx, dirent, fs.FileFlags{Read: true, Write: true}, tops) -} - -// Release implements fs.FileOperations.Release. -func (t *TimerOperations) Release(context.Context) { - t.timer.Destroy() -} - -// PauseTimer pauses the associated Timer. -func (t *TimerOperations) PauseTimer() { - t.timer.Pause() -} - -// ResumeTimer resumes the associated Timer. -func (t *TimerOperations) ResumeTimer() { - t.timer.Resume() -} - -// Clock returns the associated Timer's Clock. -func (t *TimerOperations) Clock() ktime.Clock { - return t.timer.Clock() -} - -// GetTime returns the associated Timer's setting and the time at which it was -// observed. -func (t *TimerOperations) GetTime() (ktime.Time, ktime.Setting) { - return t.timer.Get() -} - -// SetTime atomically changes the associated Timer's setting, resets the number -// of expirations to 0, and returns the previous setting and the time at which -// it was observed. -func (t *TimerOperations) SetTime(s ktime.Setting) (ktime.Time, ktime.Setting) { - return t.timer.SwapAnd(s, func() { t.val.Store(0) }) -} - -// Readiness implements waiter.Waitable.Readiness. -func (t *TimerOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - var ready waiter.EventMask - if t.val.Load() != 0 { - ready |= waiter.ReadableEvents - } - return ready -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (t *TimerOperations) EventRegister(e *waiter.Entry) error { - t.events.EventRegister(e) - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (t *TimerOperations) EventUnregister(e *waiter.Entry) { - t.events.EventUnregister(e) -} - -// Read implements fs.FileOperations.Read. -func (t *TimerOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - const sizeofUint64 = 8 - if dst.NumBytes() < sizeofUint64 { - return 0, linuxerr.EINVAL - } - if val := t.val.Swap(0); val != 0 { - var buf [sizeofUint64]byte - hostarch.ByteOrder.PutUint64(buf[:], val) - if _, err := dst.CopyOut(ctx, buf[:]); err != nil { - // Linux does not undo consuming the number of expirations even if - // writing to userspace fails. - return 0, err - } - return sizeofUint64, nil - } - return 0, linuxerr.ErrWouldBlock -} - -// Write implements fs.FileOperations.Write. -func (t *TimerOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EINVAL -} - -// NotifyTimer implements ktime.TimerListener.NotifyTimer. -func (t *TimerOperations) NotifyTimer(exp uint64, setting ktime.Setting) (ktime.Setting, bool) { - t.val.Add(exp) - t.events.Notify(waiter.ReadableEvents) - return ktime.Setting{}, false -} diff --git a/pkg/sentry/fs/tmpfs/BUILD b/pkg/sentry/fs/tmpfs/BUILD deleted file mode 100644 index 459df854a..000000000 --- a/pkg/sentry/fs/tmpfs/BUILD +++ /dev/null @@ -1,53 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "tmpfs", - srcs = [ - "device.go", - "file_regular.go", - "fs.go", - "inode_file.go", - "tmpfs.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/safemem", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/ramfs", - "//pkg/sentry/fsmetric", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/pipe", - "//pkg/sentry/kernel/time", - "//pkg/sentry/memmap", - "//pkg/sentry/pgalloc", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/usage", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - ], -) - -go_test( - name = "tmpfs_test", - size = "small", - srcs = ["file_test.go"], - library = ":tmpfs", - deps = [ - "//pkg/context", - "//pkg/hostarch", - "//pkg/sentry/fs", - "//pkg/sentry/kernel/contexttest", - "//pkg/sentry/usage", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fs/tmpfs/device.go b/pkg/sentry/fs/tmpfs/device.go deleted file mode 100644 index ae7c55ee1..000000000 --- a/pkg/sentry/fs/tmpfs/device.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018 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. - -package tmpfs - -import "gvisor.dev/gvisor/pkg/sentry/device" - -// tmpfsDevice is the kernel tmpfs device. -var tmpfsDevice = device.NewAnonDevice() diff --git a/pkg/sentry/fs/tmpfs/file_regular.go b/pkg/sentry/fs/tmpfs/file_regular.go deleted file mode 100644 index 614f8f8a1..000000000 --- a/pkg/sentry/fs/tmpfs/file_regular.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 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. - -package tmpfs - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// regularFileOperations implements fs.FileOperations for a regular -// tmpfs file. -// -// +stateify savable -type regularFileOperations struct { - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoopFsync `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - // iops is the InodeOperations of a regular tmpfs file. It is - // guaranteed to be the same as file.Dirent.Inode.InodeOperations, - // see operations that take fs.File below. - iops *fileInodeOperations -} - -// Read implements fs.FileOperations.Read. -func (r *regularFileOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - return r.iops.read(ctx, file, dst, offset) -} - -// Write implements fs.FileOperations.Write. -func (r *regularFileOperations) Write(ctx context.Context, file *fs.File, src usermem.IOSequence, offset int64) (int64, error) { - return r.iops.write(ctx, src, offset) -} - -// ConfigureMMap implements fs.FileOperations.ConfigureMMap. -func (r *regularFileOperations) ConfigureMMap(ctx context.Context, file *fs.File, opts *memmap.MMapOpts) error { - return fsutil.GenericConfigureMMap(file, r.iops, opts) -} diff --git a/pkg/sentry/fs/tmpfs/file_test.go b/pkg/sentry/fs/tmpfs/file_test.go deleted file mode 100644 index 1718f9372..000000000 --- a/pkg/sentry/fs/tmpfs/file_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2018 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. - -package tmpfs - -import ( - "bytes" - "testing" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel/contexttest" - "gvisor.dev/gvisor/pkg/sentry/usage" - "gvisor.dev/gvisor/pkg/usermem" -) - -func newFileInode(ctx context.Context) *fs.Inode { - m := fs.NewCachingMountSource(ctx, &Filesystem{}, fs.MountSourceFlags{}) - iops := NewInMemoryFile(ctx, usage.Tmpfs, fs.WithCurrentTime(ctx, fs.UnstableAttr{})) - return fs.NewInode(ctx, iops, m, fs.StableAttr{ - DeviceID: tmpfsDevice.DeviceID(), - InodeID: tmpfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.RegularFile, - }) -} - -func newFile(ctx context.Context) *fs.File { - inode := newFileInode(ctx) - f, _ := inode.GetFile(ctx, fs.NewDirent(ctx, inode, "stub"), fs.FileFlags{Read: true, Write: true}) - return f -} - -// Allocate once, write twice. -func TestGrow(t *testing.T) { - ctx := contexttest.Context(t) - f := newFile(ctx) - defer f.DecRef(ctx) - - abuf := bytes.Repeat([]byte{'a'}, 68) - n, err := f.Pwritev(ctx, usermem.BytesIOSequence(abuf), 0) - if n != int64(len(abuf)) || err != nil { - t.Fatalf("Pwritev got (%d, %v) want (%d, nil)", n, err, len(abuf)) - } - - bbuf := bytes.Repeat([]byte{'b'}, 856) - n, err = f.Pwritev(ctx, usermem.BytesIOSequence(bbuf), 68) - if n != int64(len(bbuf)) || err != nil { - t.Fatalf("Pwritev got (%d, %v) want (%d, nil)", n, err, len(bbuf)) - } - - rbuf := make([]byte, len(abuf)+len(bbuf)) - n, err = f.Preadv(ctx, usermem.BytesIOSequence(rbuf), 0) - if n != int64(len(rbuf)) || err != nil { - t.Fatalf("Preadv got (%d, %v) want (%d, nil)", n, err, len(rbuf)) - } - - if want := append(abuf, bbuf...); !bytes.Equal(rbuf, want) { - t.Fatalf("Read %v, want %v", rbuf, want) - } -} diff --git a/pkg/sentry/fs/tmpfs/fs.go b/pkg/sentry/fs/tmpfs/fs.go deleted file mode 100644 index e8091318e..000000000 --- a/pkg/sentry/fs/tmpfs/fs.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2018 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. - -package tmpfs - -import ( - "fmt" - "strconv" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" -) - -const ( - // Set initial permissions for the root directory. - modeKey = "mode" - - // UID for the root directory. - rootUIDKey = "uid" - - // GID for the root directory. - rootGIDKey = "gid" - - // cacheKey sets the caching policy for the mount. - cacheKey = "cache" - - // cacheAll uses the virtual file system cache for everything (default). - cacheAll = "cache" - - // cacheRevalidate allows dirents to be cached, but revalidates them on each - // lookup. - cacheRevalidate = "revalidate" - - // Permissions that exceed modeMask will be rejected. - modeMask = 01777 - - // Default permissions are read/write/execute. - defaultMode = 0777 -) - -// Filesystem is a tmpfs. -// -// +stateify savable -type Filesystem struct{} - -var _ fs.Filesystem = (*Filesystem)(nil) - -func init() { - fs.RegisterFilesystem(&Filesystem{}) -} - -// FilesystemName is the name under which the filesystem is registered. -// Name matches mm/shmem.c:shmem_fs_type.name. -const FilesystemName = "tmpfs" - -// Name is the name of the file system. -func (*Filesystem) Name() string { - return FilesystemName -} - -// AllowUserMount allows users to mount(2) this file system. -func (*Filesystem) AllowUserMount() bool { - return true -} - -// AllowUserList allows this filesystem to be listed in /proc/filesystems. -func (*Filesystem) AllowUserList() bool { - return true -} - -// Flags returns that there is nothing special about this file system. -// -// In Linux, tmpfs returns FS_USERNS_MOUNT, see mm/shmem.c. -func (*Filesystem) Flags() fs.FilesystemFlags { - return 0 -} - -// Mount returns a tmpfs root that can be positioned in the vfs. -func (f *Filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, _ any) (*fs.Inode, error) { - // device is always ignored. - - // Parse generic comma-separated key=value options, this file system expects them. - options := fs.GenericMountSourceOptions(data) - - // Parse the root directory permissions. - perms := fs.FilePermsFromMode(defaultMode) - if m, ok := options[modeKey]; ok { - i, err := strconv.ParseUint(m, 8, 32) - if err != nil { - return nil, fmt.Errorf("mode value not parsable 'mode=%s': %v", m, err) - } - if i&^modeMask != 0 { - return nil, fmt.Errorf("invalid mode %q: must be less than %o", m, modeMask) - } - perms = fs.FilePermsFromMode(linux.FileMode(i)) - delete(options, modeKey) - } - - creds := auth.CredentialsFromContext(ctx) - owner := fs.FileOwnerFromContext(ctx) - if uidstr, ok := options[rootUIDKey]; ok { - uid, err := strconv.ParseInt(uidstr, 10, 32) - if err != nil { - return nil, fmt.Errorf("uid value not parsable 'uid=%d': %v", uid, err) - } - owner.UID = creds.UserNamespace.MapToKUID(auth.UID(uid)) - delete(options, rootUIDKey) - } - - if gidstr, ok := options[rootGIDKey]; ok { - gid, err := strconv.ParseInt(gidstr, 10, 32) - if err != nil { - return nil, fmt.Errorf("gid value not parsable 'gid=%d': %v", gid, err) - } - owner.GID = creds.UserNamespace.MapToKGID(auth.GID(gid)) - delete(options, rootGIDKey) - } - - // Construct a mount which will follow the cache options provided. - // - // TODO(gvisor.dev/issue/179): There should be no reason to disable - // caching once bind mounts are properly supported. - var msrc *fs.MountSource - switch options[cacheKey] { - case "", cacheAll: - msrc = fs.NewCachingMountSource(ctx, f, flags) - case cacheRevalidate: - msrc = fs.NewRevalidatingMountSource(ctx, f, flags) - default: - return nil, fmt.Errorf("invalid cache policy option %q", options[cacheKey]) - } - delete(options, cacheKey) - - // Fail if the caller passed us more options than we can parse. They may be - // expecting us to set something we can't set. - if len(options) > 0 { - return nil, fmt.Errorf("unsupported mount options: %v", options) - } - - // Construct the tmpfs root. - return NewDir(ctx, nil, owner, perms, msrc, nil /* parent */) -} diff --git a/pkg/sentry/fs/tmpfs/inode_file.go b/pkg/sentry/fs/tmpfs/inode_file.go deleted file mode 100644 index 4c3493754..000000000 --- a/pkg/sentry/fs/tmpfs/inode_file.go +++ /dev/null @@ -1,688 +0,0 @@ -// Copyright 2018 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. - -package tmpfs - -import ( - "fmt" - "io" - "math" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fsmetric" - "gvisor.dev/gvisor/pkg/sentry/kernel" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/pgalloc" - "gvisor.dev/gvisor/pkg/sentry/usage" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -// fileInodeOperations implements fs.InodeOperations for a regular tmpfs file. -// These files are backed by pages allocated from a platform.Memory, and may be -// directly mapped. -// -// Lock order: attrMu -> mapsMu -> dataMu. -// -// +stateify savable -type fileInodeOperations struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotDirectory `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - - fsutil.InodeSimpleExtendedAttributes - - // kernel is used to allocate memory that stores the file's contents. - kernel *kernel.Kernel - - // memUsage is the default memory usage that will be reported by this file. - memUsage usage.MemoryKind - - attrMu sync.Mutex `state:"nosave"` - - // attr contains the unstable metadata for the file. - // - // attr is protected by attrMu. attr.Size is protected by both attrMu - // and dataMu; reading it requires locking either mutex, while mutating - // it requires locking both. - attr fs.UnstableAttr - - mapsMu sync.Mutex `state:"nosave"` - - // mappings tracks mappings of the file into memmap.MappingSpaces. - // - // mappings is protected by mapsMu. - mappings memmap.MappingSet - - // writableMappingPages tracks how many pages of virtual memory are mapped - // as potentially writable from this file. If a page has multiple mappings, - // each mapping is counted separately. - // - // This counter is susceptible to overflow as we can potentially count - // mappings from many VMAs. We count pages rather than bytes to slightly - // mitigate this. - // - // Protected by mapsMu. - writableMappingPages uint64 - - dataMu sync.RWMutex `state:"nosave"` - - // data maps offsets into the file to offsets into platform.Memory() that - // store the file's data. - // - // data is protected by dataMu. - data fsutil.FileRangeSet - - // seals represents file seals on this inode. - // - // Protected by dataMu. - seals uint32 -} - -var _ fs.InodeOperations = (*fileInodeOperations)(nil) - -// NewInMemoryFile returns a new file backed by Kernel.MemoryFile(). -func NewInMemoryFile(ctx context.Context, usage usage.MemoryKind, uattr fs.UnstableAttr) fs.InodeOperations { - return &fileInodeOperations{ - attr: uattr, - kernel: kernel.KernelFromContext(ctx), - memUsage: usage, - seals: linux.F_SEAL_SEAL, - } -} - -// NewMemfdInode creates a new inode backing a memfd. Memory used by the memfd -// is backed by platform memory. -func NewMemfdInode(ctx context.Context, allowSeals bool) *fs.Inode { - // Per Linux, mm/shmem.c:__shmem_file_setup(), memfd inodes are set up with - // S_IRWXUGO. - perms := fs.PermMask{Read: true, Write: true, Execute: true} - iops := NewInMemoryFile(ctx, usage.Tmpfs, fs.UnstableAttr{ - Owner: fs.FileOwnerFromContext(ctx), - Perms: fs.FilePermissions{User: perms, Group: perms, Other: perms}}).(*fileInodeOperations) - if allowSeals { - iops.seals = 0 - } - return fs.NewInode(ctx, iops, fs.NewNonCachingMountSource(ctx, nil, fs.MountSourceFlags{}), fs.StableAttr{ - Type: fs.RegularFile, - DeviceID: tmpfsDevice.DeviceID(), - InodeID: tmpfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - }) -} - -// Release implements fs.InodeOperations.Release. -func (f *fileInodeOperations) Release(context.Context) { - f.dataMu.Lock() - defer f.dataMu.Unlock() - f.data.DropAll(f.kernel.MemoryFile()) -} - -// Mappable implements fs.InodeOperations.Mappable. -func (f *fileInodeOperations) Mappable(*fs.Inode) memmap.Mappable { - return f -} - -// Rename implements fs.InodeOperations.Rename. -func (*fileInodeOperations) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return rename(ctx, oldParent, oldName, newParent, newName, replacement) -} - -// GetFile implements fs.InodeOperations.GetFile. -func (f *fileInodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - if fs.IsSocket(d.Inode.StableAttr) { - return nil, linuxerr.ENXIO - } - - if flags.Write { - fsmetric.TmpfsOpensW.Increment() - } else if flags.Read { - fsmetric.TmpfsOpensRO.Increment() - } - flags.Pread = true - flags.Pwrite = true - return fs.NewFile(ctx, d, flags, ®ularFileOperations{iops: f}), nil -} - -// UnstableAttr returns unstable attributes of this tmpfs file. -func (f *fileInodeOperations) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - f.attrMu.Lock() - f.dataMu.RLock() - attr := f.attr - attr.Usage = int64(f.data.Span()) - f.dataMu.RUnlock() - f.attrMu.Unlock() - return attr, nil -} - -// Check implements fs.InodeOperations.Check. -func (f *fileInodeOperations) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { - return fs.ContextCanAccessFile(ctx, inode, p) -} - -// SetPermissions implements fs.InodeOperations.SetPermissions. -func (f *fileInodeOperations) SetPermissions(ctx context.Context, _ *fs.Inode, p fs.FilePermissions) bool { - f.attrMu.Lock() - f.attr.SetPermissions(ctx, p) - f.attrMu.Unlock() - return true -} - -// SetTimestamps implements fs.InodeOperations.SetTimestamps. -func (f *fileInodeOperations) SetTimestamps(ctx context.Context, _ *fs.Inode, ts fs.TimeSpec) error { - f.attrMu.Lock() - f.attr.SetTimestamps(ctx, ts) - f.attrMu.Unlock() - return nil -} - -// SetOwner implements fs.InodeOperations.SetOwner. -func (f *fileInodeOperations) SetOwner(ctx context.Context, _ *fs.Inode, owner fs.FileOwner) error { - f.attrMu.Lock() - f.attr.SetOwner(ctx, owner) - f.attrMu.Unlock() - return nil -} - -// Truncate implements fs.InodeOperations.Truncate. -func (f *fileInodeOperations) Truncate(ctx context.Context, _ *fs.Inode, size int64) error { - f.attrMu.Lock() - defer f.attrMu.Unlock() - - f.dataMu.Lock() - oldSize := f.attr.Size - - // Check if current seals allow truncation. - switch { - case size > oldSize && f.seals&linux.F_SEAL_GROW != 0: // Grow sealed - fallthrough - case oldSize > size && f.seals&linux.F_SEAL_SHRINK != 0: // Shrink sealed - f.dataMu.Unlock() - return linuxerr.EPERM - } - - if oldSize != size { - f.attr.Size = size - // Update mtime and ctime. - now := ktime.NowFromContext(ctx) - f.attr.ModificationTime = now - f.attr.StatusChangeTime = now - - // Truncating clears privilege bits. - f.attr.Perms.SetUID = false - if f.attr.Perms.Group.Execute { - f.attr.Perms.SetGID = false - } - } - f.dataMu.Unlock() - - // Nothing left to do unless shrinking the file. - if oldSize <= size { - return nil - } - - oldpgend := fs.OffsetPageEnd(oldSize) - newpgend := fs.OffsetPageEnd(size) - - // Invalidate past translations of truncated pages. - if newpgend != oldpgend { - f.mapsMu.Lock() - f.mappings.Invalidate(memmap.MappableRange{newpgend, oldpgend}, memmap.InvalidateOpts{ - // Compare Linux's mm/shmem.c:shmem_setattr() => - // mm/memory.c:unmap_mapping_range(evencows=1). - InvalidatePrivate: true, - }) - f.mapsMu.Unlock() - } - - // We are now guaranteed that there are no translations of truncated pages, - // and can remove them. - f.dataMu.Lock() - defer f.dataMu.Unlock() - f.data.Truncate(uint64(size), f.kernel.MemoryFile()) - - return nil -} - -// Allocate implements fs.InodeOperations.Allocate. -func (f *fileInodeOperations) Allocate(ctx context.Context, _ *fs.Inode, offset, length int64) error { - newSize := offset + length - - f.attrMu.Lock() - defer f.attrMu.Unlock() - f.dataMu.Lock() - defer f.dataMu.Unlock() - - if newSize <= f.attr.Size { - return nil - } - - // Check if current seals allow growth. - if f.seals&linux.F_SEAL_GROW != 0 { - return linuxerr.EPERM - } - - f.attr.Size = newSize - - now := ktime.NowFromContext(ctx) - f.attr.ModificationTime = now - f.attr.StatusChangeTime = now - - return nil -} - -// AddLink implements fs.InodeOperations.AddLink. -func (f *fileInodeOperations) AddLink() { - f.attrMu.Lock() - f.attr.Links++ - f.attrMu.Unlock() -} - -// DropLink implements fs.InodeOperations.DropLink. -func (f *fileInodeOperations) DropLink() { - f.attrMu.Lock() - f.attr.Links-- - f.attrMu.Unlock() -} - -// NotifyStatusChange implements fs.InodeOperations.NotifyStatusChange. -func (f *fileInodeOperations) NotifyStatusChange(ctx context.Context) { - f.attrMu.Lock() - f.attr.StatusChangeTime = ktime.NowFromContext(ctx) - f.attrMu.Unlock() -} - -// IsVirtual implements fs.InodeOperations.IsVirtual. -func (*fileInodeOperations) IsVirtual() bool { - return true -} - -// StatFS implements fs.InodeOperations.StatFS. -func (*fileInodeOperations) StatFS(context.Context) (fs.Info, error) { - return fsInfo, nil -} - -func (f *fileInodeOperations) read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - start := fsmetric.StartReadWait() - defer fsmetric.FinishReadWait(fsmetric.TmpfsReadWait, start) - fsmetric.TmpfsReads.Increment() - - // Zero length reads for tmpfs are no-ops. - if dst.NumBytes() == 0 { - return 0, nil - } - - // Have we reached EOF? We check for this again in - // fileReadWriter.ReadToBlocks to avoid holding f.attrMu (which would - // serialize reads) or f.dataMu (which would violate lock ordering), but - // check here first (before calling into MM) since reading at EOF is - // common: getting a return value of 0 from a read syscall is the only way - // to detect EOF. - // - // TODO(jamieliu): Separate out f.attr.Size and use atomics instead of - // f.dataMu. - f.dataMu.RLock() - size := f.attr.Size - f.dataMu.RUnlock() - if offset >= size { - return 0, io.EOF - } - - n, err := dst.CopyOutFrom(ctx, &fileReadWriter{f, offset}) - if !file.Dirent.Inode.MountSource.Flags.NoAtime { - // Compare Linux's mm/filemap.c:do_generic_file_read() => file_accessed(). - f.attrMu.Lock() - f.attr.AccessTime = ktime.NowFromContext(ctx) - f.attrMu.Unlock() - } - return n, err -} - -func (f *fileInodeOperations) write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { - // Zero length writes for tmpfs are no-ops. - if src.NumBytes() == 0 { - return 0, nil - } - - f.attrMu.Lock() - defer f.attrMu.Unlock() - // Compare Linux's mm/filemap.c:__generic_file_write_iter() => file_update_time(). - now := ktime.NowFromContext(ctx) - f.attr.ModificationTime = now - f.attr.StatusChangeTime = now - nwritten, err := src.CopyInTo(ctx, &fileReadWriter{f, offset}) - - // Writing clears privilege bits. - if nwritten > 0 { - f.attr.Perms.DropSetUIDAndMaybeGID() - } - - return nwritten, err -} - -type fileReadWriter struct { - f *fileInodeOperations - offset int64 -} - -// ReadToBlocks implements safemem.Reader.ReadToBlocks. -func (rw *fileReadWriter) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) { - rw.f.dataMu.RLock() - defer rw.f.dataMu.RUnlock() - - // Compute the range to read. - if rw.offset >= rw.f.attr.Size { - return 0, io.EOF - } - end := fs.ReadEndOffset(rw.offset, int64(dsts.NumBytes()), rw.f.attr.Size) - if end == rw.offset { // dsts.NumBytes() == 0? - return 0, nil - } - - mf := rw.f.kernel.MemoryFile() - var done uint64 - seg, gap := rw.f.data.Find(uint64(rw.offset)) - for rw.offset < end { - mr := memmap.MappableRange{uint64(rw.offset), uint64(end)} - switch { - case seg.Ok(): - // Get internal mappings. - ims, err := mf.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), hostarch.Read) - if err != nil { - return done, err - } - - // Copy from internal mappings. - n, err := safemem.CopySeq(dsts, ims) - done += n - rw.offset += int64(n) - dsts = dsts.DropFirst64(n) - if err != nil { - return done, err - } - - // Continue. - seg, gap = seg.NextNonEmpty() - - case gap.Ok(): - // Tmpfs holes are zero-filled. - gapmr := gap.Range().Intersect(mr) - dst := dsts.TakeFirst64(gapmr.Length()) - n, err := safemem.ZeroSeq(dst) - done += n - rw.offset += int64(n) - dsts = dsts.DropFirst64(n) - if err != nil { - return done, err - } - - // Continue. - seg, gap = gap.NextSegment(), fsutil.FileRangeGapIterator{} - } - } - return done, nil -} - -// WriteFromBlocks implements safemem.Writer.WriteFromBlocks. -func (rw *fileReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) { - rw.f.dataMu.Lock() - defer rw.f.dataMu.Unlock() - - // Compute the range to write. - if srcs.NumBytes() == 0 { - // Nothing to do. - return 0, nil - } - end := fs.WriteEndOffset(rw.offset, int64(srcs.NumBytes())) - if end == math.MaxInt64 { - // Overflow. - return 0, linuxerr.EINVAL - } - - // Check if seals prevent either file growth or all writes. - switch { - case rw.f.seals&linux.F_SEAL_WRITE != 0: // Write sealed - return 0, linuxerr.EPERM - case end > rw.f.attr.Size && rw.f.seals&linux.F_SEAL_GROW != 0: // Grow sealed - // When growth is sealed, Linux effectively allows writes which would - // normally grow the file to partially succeed up to the current EOF, - // rounded down to the page boundary before the EOF. - // - // This happens because writes (and thus the growth check) for tmpfs - // files proceed page-by-page on Linux, and the final write to the page - // containing EOF fails, resulting in a partial write up to the start of - // that page. - // - // To emulate this behaviour, artifically truncate the write to the - // start of the page containing the current EOF. - // - // See Linux, mm/filemap.c:generic_perform_write() and - // mm/shmem.c:shmem_write_begin(). - if pgstart := int64(hostarch.Addr(rw.f.attr.Size).RoundDown()); end > pgstart { - end = pgstart - } - if end <= rw.offset { - // Truncation would result in no data being written. - return 0, linuxerr.EPERM - } - } - - defer func() { - // If the write ends beyond the file's previous size, it causes the - // file to grow. - if rw.offset > rw.f.attr.Size { - rw.f.attr.Size = rw.offset - } - }() - - mf := rw.f.kernel.MemoryFile() - // Page-aligned mr for when we need to allocate memory. RoundUp can't - // overflow since end is an int64. - pgstartaddr := hostarch.Addr(rw.offset).RoundDown() - pgendaddr, _ := hostarch.Addr(end).RoundUp() - pgMR := memmap.MappableRange{uint64(pgstartaddr), uint64(pgendaddr)} - - var done uint64 - seg, gap := rw.f.data.Find(uint64(rw.offset)) - for rw.offset < end { - mr := memmap.MappableRange{uint64(rw.offset), uint64(end)} - switch { - case seg.Ok(): - // Get internal mappings. - ims, err := mf.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), hostarch.Write) - if err != nil { - return done, err - } - - // Copy to internal mappings. - n, err := safemem.CopySeq(ims, srcs) - done += n - rw.offset += int64(n) - srcs = srcs.DropFirst64(n) - if err != nil { - return done, err - } - - // Continue. - seg, gap = seg.NextNonEmpty() - - case gap.Ok(): - // Allocate memory for the write. - gapMR := gap.Range().Intersect(pgMR) - fr, err := mf.Allocate(gapMR.Length(), pgalloc.AllocOpts{Kind: rw.f.memUsage}) - if err != nil { - return done, err - } - - // Write to that memory as usual. - seg, gap = rw.f.data.Insert(gap, gapMR, fr.Start), fsutil.FileRangeGapIterator{} - } - } - return done, nil -} - -// AddMapping implements memmap.Mappable.AddMapping. -func (f *fileInodeOperations) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error { - f.mapsMu.Lock() - defer f.mapsMu.Unlock() - - f.dataMu.RLock() - defer f.dataMu.RUnlock() - - // Reject writable mapping if F_SEAL_WRITE is set. - if f.seals&linux.F_SEAL_WRITE != 0 && writable { - return linuxerr.EPERM - } - - f.mappings.AddMapping(ms, ar, offset, writable) - if writable { - pagesBefore := f.writableMappingPages - - // ar is guaranteed to be page aligned per memmap.Mappable. - f.writableMappingPages += uint64(ar.Length() / hostarch.PageSize) - - if f.writableMappingPages < pagesBefore { - panic(fmt.Sprintf("Overflow while mapping potentially writable pages pointing to a tmpfs file. Before %v, after %v", pagesBefore, f.writableMappingPages)) - } - } - - return nil -} - -// RemoveMapping implements memmap.Mappable.RemoveMapping. -func (f *fileInodeOperations) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) { - f.mapsMu.Lock() - defer f.mapsMu.Unlock() - - f.mappings.RemoveMapping(ms, ar, offset, writable) - - if writable { - pagesBefore := f.writableMappingPages - - // ar is guaranteed to be page aligned per memmap.Mappable. - f.writableMappingPages -= uint64(ar.Length() / hostarch.PageSize) - - if f.writableMappingPages > pagesBefore { - panic(fmt.Sprintf("Underflow while unmapping potentially writable pages pointing to a tmpfs file. Before %v, after %v", pagesBefore, f.writableMappingPages)) - } - } -} - -// CopyMapping implements memmap.Mappable.CopyMapping. -func (f *fileInodeOperations) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error { - return f.AddMapping(ctx, ms, dstAR, offset, writable) -} - -// Translate implements memmap.Mappable.Translate. -func (f *fileInodeOperations) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) { - f.dataMu.Lock() - defer f.dataMu.Unlock() - - // Constrain translations to f.attr.Size (rounded up) to prevent - // translation to pages that may be concurrently truncated. - pgend := fs.OffsetPageEnd(f.attr.Size) - var beyondEOF bool - if required.End > pgend { - if required.Start >= pgend { - return nil, &memmap.BusError{io.EOF} - } - beyondEOF = true - required.End = pgend - } - if optional.End > pgend { - optional.End = pgend - } - - mf := f.kernel.MemoryFile() - _, cerr := f.data.Fill(ctx, required, optional, uint64(f.attr.Size), mf, f.memUsage, false /* populate */, func(_ context.Context, dsts safemem.BlockSeq, _ uint64) (uint64, error) { - // Newly-allocated pages are zeroed, so we don't need to do anything. - return dsts.NumBytes(), nil - }) - - var ts []memmap.Translation - var translatedEnd uint64 - for seg := f.data.FindSegment(required.Start); seg.Ok() && seg.Start() < required.End; seg, _ = seg.NextNonEmpty() { - segMR := seg.Range().Intersect(optional) - ts = append(ts, memmap.Translation{ - Source: segMR, - File: mf, - Offset: seg.FileRangeOf(segMR).Start, - Perms: hostarch.AnyAccess, - }) - translatedEnd = segMR.End - } - - // Don't return the error returned by f.data.Fill if it occurred outside of - // required. - if translatedEnd < required.End && cerr != nil { - return ts, &memmap.BusError{cerr} - } - if beyondEOF { - return ts, &memmap.BusError{io.EOF} - } - return ts, nil -} - -// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable. -func (f *fileInodeOperations) InvalidateUnsavable(ctx context.Context) error { - return nil -} - -// GetSeals returns the current set of seals on a memfd inode. -func GetSeals(inode *fs.Inode) (uint32, error) { - if f, ok := inode.InodeOperations.(*fileInodeOperations); ok { - f.dataMu.RLock() - defer f.dataMu.RUnlock() - return f.seals, nil - } - // Not a memfd inode. - return 0, linuxerr.EINVAL -} - -// AddSeals adds new file seals to a memfd inode. -func AddSeals(inode *fs.Inode, val uint32) error { - if f, ok := inode.InodeOperations.(*fileInodeOperations); ok { - f.mapsMu.Lock() - defer f.mapsMu.Unlock() - f.dataMu.Lock() - defer f.dataMu.Unlock() - - if f.seals&linux.F_SEAL_SEAL != 0 { - // Seal applied which prevents addition of any new seals. - return linuxerr.EPERM - } - - // F_SEAL_WRITE can only be added if there are no active writable maps. - if f.seals&linux.F_SEAL_WRITE == 0 && val&linux.F_SEAL_WRITE != 0 { - if f.writableMappingPages > 0 { - return linuxerr.EBUSY - } - } - - // Seals can only be added, never removed. - f.seals |= val - return nil - } - // Not a memfd inode. - return linuxerr.EINVAL -} diff --git a/pkg/sentry/fs/tmpfs/tmpfs.go b/pkg/sentry/fs/tmpfs/tmpfs.go deleted file mode 100644 index e06f39730..000000000 --- a/pkg/sentry/fs/tmpfs/tmpfs.go +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright 2018 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. - -// Package tmpfs is a filesystem implementation backed by memory. -package tmpfs - -import ( - "math" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sentry/usage" -) - -var fsInfo = fs.Info{ - Type: linux.TMPFS_MAGIC, - - // tmpfs currently does not support configurable size limits. In Linux, - // such a tmpfs mount will return f_blocks == f_bfree == f_bavail == 0 from - // statfs(2). However, many applications treat this as having a size limit - // of 0. To work around this, claim to have a very large but non-zero size, - // chosen to ensure that BlockSize * Blocks does not overflow int64 (which - // applications may also handle incorrectly). - // NOTE(b/29637826): Support for configurable tmpfs size was added to VFS2. - TotalBlocks: math.MaxInt64 / hostarch.PageSize, - FreeBlocks: math.MaxInt64 / hostarch.PageSize, -} - -// rename implements fs.InodeOperations.Rename for tmpfs nodes. -func rename(ctx context.Context, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - // Don't allow renames across different mounts. - if newParent.MountSource != oldParent.MountSource { - return linuxerr.EXDEV - } - - op := oldParent.InodeOperations.(*Dir) - np := newParent.InodeOperations.(*Dir) - return ramfs.Rename(ctx, op.ramfsDir, oldName, np.ramfsDir, newName, replacement) -} - -// Dir is a directory. -// -// +stateify savable -type Dir struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeIsDirTruncate `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - // Ideally this would be embedded, so that we "inherit" all of the - // InodeOperations implemented by ramfs.Dir for free. - // - // However, ramfs.dirFileOperations stores a pointer to a ramfs.Dir, - // and our save/restore package does not allow saving a pointer to an - // embedded field elsewhere. - // - // Thus, we must make the ramfs.Dir is a field, and we delegate all the - // InodeOperation methods to it. - ramfsDir *ramfs.Dir - - // kernel is used to allocate memory as storage for tmpfs Files. - kernel *kernel.Kernel -} - -var _ fs.InodeOperations = (*Dir)(nil) - -// NewDir returns a new directory. -func NewDir(ctx context.Context, contents map[string]*fs.Inode, owner fs.FileOwner, perms fs.FilePermissions, msrc *fs.MountSource, parent *fs.Inode) (*fs.Inode, error) { - // If the parent has setgid enabled, the new directory enables it and changes - // its GID. - if parent != nil { - parentUattr, err := parent.UnstableAttr(ctx) - if err != nil { - return nil, err - } - if parentUattr.Perms.SetGID { - owner.GID = parentUattr.Owner.GID - perms.SetGID = true - } - } - - d := &Dir{ - ramfsDir: ramfs.NewDir(ctx, contents, owner, perms), - kernel: kernel.KernelFromContext(ctx), - } - - // Manually set the CreateOps. - d.ramfsDir.CreateOps = d.newCreateOps() - - return fs.NewInode(ctx, d, msrc, fs.StableAttr{ - DeviceID: tmpfsDevice.DeviceID(), - InodeID: tmpfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Directory, - }), nil -} - -// afterLoad is invoked by stateify. -func (d *Dir) afterLoad() { - // Per NewDir, manually set the CreateOps. - d.ramfsDir.CreateOps = d.newCreateOps() -} - -// GetFile implements fs.InodeOperations.GetFile. -func (d *Dir) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return d.ramfsDir.GetFile(ctx, dirent, flags) -} - -// AddLink implements fs.InodeOperations.AddLink. -func (d *Dir) AddLink() { - d.ramfsDir.AddLink() -} - -// DropLink implements fs.InodeOperations.DropLink. -func (d *Dir) DropLink() { - d.ramfsDir.DropLink() -} - -// Bind implements fs.InodeOperations.Bind. -func (d *Dir) Bind(ctx context.Context, dir *fs.Inode, name string, ep transport.BoundEndpoint, perms fs.FilePermissions) (*fs.Dirent, error) { - return d.ramfsDir.Bind(ctx, dir, name, ep, perms) -} - -// Create implements fs.InodeOperations.Create. -func (d *Dir) Create(ctx context.Context, dir *fs.Inode, name string, flags fs.FileFlags, perms fs.FilePermissions) (*fs.File, error) { - return d.ramfsDir.Create(ctx, dir, name, flags, perms) -} - -// CreateLink implements fs.InodeOperations.CreateLink. -func (d *Dir) CreateLink(ctx context.Context, dir *fs.Inode, oldname, newname string) error { - return d.ramfsDir.CreateLink(ctx, dir, oldname, newname) -} - -// CreateHardLink implements fs.InodeOperations.CreateHardLink. -func (d *Dir) CreateHardLink(ctx context.Context, dir *fs.Inode, target *fs.Inode, name string) error { - return d.ramfsDir.CreateHardLink(ctx, dir, target, name) -} - -// CreateDirectory implements fs.InodeOperations.CreateDirectory. -func (d *Dir) CreateDirectory(ctx context.Context, dir *fs.Inode, name string, perms fs.FilePermissions) error { - return d.ramfsDir.CreateDirectory(ctx, dir, name, perms) -} - -// CreateFifo implements fs.InodeOperations.CreateFifo. -func (d *Dir) CreateFifo(ctx context.Context, dir *fs.Inode, name string, perms fs.FilePermissions) error { - return d.ramfsDir.CreateFifo(ctx, dir, name, perms) -} - -// GetXattr implements fs.InodeOperations.GetXattr. -func (d *Dir) GetXattr(ctx context.Context, i *fs.Inode, name string, size uint64) (string, error) { - return d.ramfsDir.GetXattr(ctx, i, name, size) -} - -// SetXattr implements fs.InodeOperations.SetXattr. -func (d *Dir) SetXattr(ctx context.Context, i *fs.Inode, name, value string, flags uint32) error { - return d.ramfsDir.SetXattr(ctx, i, name, value, flags) -} - -// ListXattr implements fs.InodeOperations.ListXattr. -func (d *Dir) ListXattr(ctx context.Context, i *fs.Inode, size uint64) (map[string]struct{}, error) { - return d.ramfsDir.ListXattr(ctx, i, size) -} - -// RemoveXattr implements fs.InodeOperations.RemoveXattr. -func (d *Dir) RemoveXattr(ctx context.Context, i *fs.Inode, name string) error { - return d.ramfsDir.RemoveXattr(ctx, i, name) -} - -// Lookup implements fs.InodeOperations.Lookup. -func (d *Dir) Lookup(ctx context.Context, i *fs.Inode, p string) (*fs.Dirent, error) { - return d.ramfsDir.Lookup(ctx, i, p) -} - -// NotifyStatusChange implements fs.InodeOperations.NotifyStatusChange. -func (d *Dir) NotifyStatusChange(ctx context.Context) { - d.ramfsDir.NotifyStatusChange(ctx) -} - -// Remove implements fs.InodeOperations.Remove. -func (d *Dir) Remove(ctx context.Context, i *fs.Inode, name string) error { - return d.ramfsDir.Remove(ctx, i, name) -} - -// RemoveDirectory implements fs.InodeOperations.RemoveDirectory. -func (d *Dir) RemoveDirectory(ctx context.Context, i *fs.Inode, name string) error { - return d.ramfsDir.RemoveDirectory(ctx, i, name) -} - -// UnstableAttr implements fs.InodeOperations.UnstableAttr. -func (d *Dir) UnstableAttr(ctx context.Context, i *fs.Inode) (fs.UnstableAttr, error) { - return d.ramfsDir.UnstableAttr(ctx, i) -} - -// SetPermissions implements fs.InodeOperations.SetPermissions. -func (d *Dir) SetPermissions(ctx context.Context, i *fs.Inode, p fs.FilePermissions) bool { - return d.ramfsDir.SetPermissions(ctx, i, p) -} - -// SetOwner implements fs.InodeOperations.SetOwner. -func (d *Dir) SetOwner(ctx context.Context, i *fs.Inode, owner fs.FileOwner) error { - return d.ramfsDir.SetOwner(ctx, i, owner) -} - -// SetTimestamps implements fs.InodeOperations.SetTimestamps. -func (d *Dir) SetTimestamps(ctx context.Context, i *fs.Inode, ts fs.TimeSpec) error { - return d.ramfsDir.SetTimestamps(ctx, i, ts) -} - -// newCreateOps builds the custom CreateOps for this Dir. -func (d *Dir) newCreateOps() *ramfs.CreateOps { - return &ramfs.CreateOps{ - NewDir: func(ctx context.Context, dir *fs.Inode, perms fs.FilePermissions) (*fs.Inode, error) { - return NewDir(ctx, nil, fs.FileOwnerFromContext(ctx), perms, dir.MountSource, dir) - }, - NewFile: func(ctx context.Context, dir *fs.Inode, perms fs.FilePermissions) (*fs.Inode, error) { - // If the parent has setgid enabled, change the GID of the new file. - owner := fs.FileOwnerFromContext(ctx) - parentUattr, err := dir.UnstableAttr(ctx) - if err != nil { - return nil, err - } - if parentUattr.Perms.SetGID { - owner.GID = parentUattr.Owner.GID - } - - uattr := fs.WithCurrentTime(ctx, fs.UnstableAttr{ - Owner: owner, - Perms: perms, - // Always start unlinked. - Links: 0, - }) - iops := NewInMemoryFile(ctx, usage.Tmpfs, uattr) - return fs.NewInode(ctx, iops, dir.MountSource, fs.StableAttr{ - DeviceID: tmpfsDevice.DeviceID(), - InodeID: tmpfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.RegularFile, - }), nil - }, - NewSymlink: func(ctx context.Context, dir *fs.Inode, target string) (*fs.Inode, error) { - return NewSymlink(ctx, target, fs.FileOwnerFromContext(ctx), dir.MountSource), nil - }, - NewBoundEndpoint: func(ctx context.Context, dir *fs.Inode, socket transport.BoundEndpoint, perms fs.FilePermissions) (*fs.Inode, error) { - return NewSocket(ctx, socket, fs.FileOwnerFromContext(ctx), perms, dir.MountSource), nil - }, - NewFifo: func(ctx context.Context, dir *fs.Inode, perms fs.FilePermissions) (*fs.Inode, error) { - return NewFifo(ctx, fs.FileOwnerFromContext(ctx), perms, dir.MountSource), nil - }, - } -} - -// Rename implements fs.InodeOperations.Rename. -func (d *Dir) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return rename(ctx, oldParent, oldName, newParent, newName, replacement) -} - -// StatFS implements fs.InodeOperations.StatFS. -func (*Dir) StatFS(context.Context) (fs.Info, error) { - return fsInfo, nil -} - -// Allocate implements fs.InodeOperations.Allocate. -func (d *Dir) Allocate(ctx context.Context, node *fs.Inode, offset, length int64) error { - return d.ramfsDir.Allocate(ctx, node, offset, length) -} - -// Release implements fs.InodeOperations.Release. -func (d *Dir) Release(ctx context.Context) { - d.ramfsDir.Release(ctx) -} - -// Symlink is a symlink. -// -// +stateify savable -type Symlink struct { - ramfs.Symlink -} - -// NewSymlink returns a new symlink with the provided permissions. -func NewSymlink(ctx context.Context, target string, owner fs.FileOwner, msrc *fs.MountSource) *fs.Inode { - s := &Symlink{Symlink: *ramfs.NewSymlink(ctx, owner, target)} - return fs.NewInode(ctx, s, msrc, fs.StableAttr{ - DeviceID: tmpfsDevice.DeviceID(), - InodeID: tmpfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Symlink, - }) -} - -// Rename implements fs.InodeOperations.Rename. -func (s *Symlink) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return rename(ctx, oldParent, oldName, newParent, newName, replacement) -} - -// StatFS returns the tmpfs info. -func (s *Symlink) StatFS(context.Context) (fs.Info, error) { - return fsInfo, nil -} - -// Socket is a socket. -// -// +stateify savable -type Socket struct { - ramfs.Socket - fsutil.InodeNotTruncatable `state:"nosave"` - fsutil.InodeNotAllocatable `state:"nosave"` -} - -// NewSocket returns a new socket with the provided permissions. -func NewSocket(ctx context.Context, socket transport.BoundEndpoint, owner fs.FileOwner, perms fs.FilePermissions, msrc *fs.MountSource) *fs.Inode { - s := &Socket{Socket: *ramfs.NewSocket(ctx, socket, owner, perms)} - return fs.NewInode(ctx, s, msrc, fs.StableAttr{ - DeviceID: tmpfsDevice.DeviceID(), - InodeID: tmpfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Socket, - }) -} - -// Rename implements fs.InodeOperations.Rename. -func (s *Socket) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return rename(ctx, oldParent, oldName, newParent, newName, replacement) -} - -// StatFS returns the tmpfs info. -func (s *Socket) StatFS(context.Context) (fs.Info, error) { - return fsInfo, nil -} - -// Fifo is a tmpfs named pipe. -// -// +stateify savable -type Fifo struct { - fs.InodeOperations -} - -// NewFifo creates a new named pipe. -func NewFifo(ctx context.Context, owner fs.FileOwner, perms fs.FilePermissions, msrc *fs.MountSource) *fs.Inode { - // First create a pipe. - p := pipe.NewPipe(true /* isNamed */, pipe.DefaultPipeSize) - - // Build pipe InodeOperations. - iops := pipe.NewInodeOperations(ctx, perms, p) - - // Wrap the iops with our Fifo. - fifoIops := &Fifo{iops} - - // Build a new Inode. - return fs.NewInode(ctx, fifoIops, msrc, fs.StableAttr{ - DeviceID: tmpfsDevice.DeviceID(), - InodeID: tmpfsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Pipe, - }) -} - -// Rename implements fs.InodeOperations.Rename. -func (f *Fifo) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error { - return rename(ctx, oldParent, oldName, newParent, newName, replacement) -} - -// StatFS returns the tmpfs info. -func (*Fifo) StatFS(context.Context) (fs.Info, error) { - return fsInfo, nil -} diff --git a/pkg/sentry/fs/tty/BUILD b/pkg/sentry/fs/tty/BUILD deleted file mode 100644 index 9e9dc06f3..000000000 --- a/pkg/sentry/fs/tty/BUILD +++ /dev/null @@ -1,49 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "tty", - srcs = [ - "dir.go", - "fs.go", - "line_discipline.go", - "master.go", - "queue.go", - "replica.go", - "terminal.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/marshal/primitive", - "//pkg/refs", - "//pkg/safemem", - "//pkg/sentry/arch", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/unimpl", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - ], -) - -go_test( - name = "tty_test", - size = "small", - srcs = ["tty_test.go"], - library = ":tty", - deps = [ - "//pkg/abi/linux", - "//pkg/sentry/contexttest", - "//pkg/usermem", - ], -) diff --git a/pkg/sentry/fs/tty/dir.go b/pkg/sentry/fs/tty/dir.go deleted file mode 100644 index 5716e2ee9..000000000 --- a/pkg/sentry/fs/tty/dir.go +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright 2018 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. - -// Package tty provide pseudoterminals via a devpts filesystem. -package tty - -import ( - "fmt" - "math" - "strconv" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// dirInodeOperations is the root of a devpts mount. -// -// This indirectly manages all terminals within the mount. -// -// New Terminals are created by masterInodeOperations.GetFile, which registers -// the replica Inode in the this directory for discovery via Lookup/Readdir. The -// replica inode is unregistered when the master file is Released, as the replica -// is no longer discoverable at that point. -// -// References on the underlying Terminal are held by masterFileOperations and -// replicaInodeOperations. -// -// masterInodeOperations and replicaInodeOperations hold a pointer to -// dirInodeOperations, which is reference counted by the refcount their -// corresponding Dirents hold on their parent (this directory). -// -// dirInodeOperations implements fs.InodeOperations. -// -// +stateify savable -type dirInodeOperations struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeIsDirAllocate `state:"nosave"` - fsutil.InodeIsDirTruncate `state:"nosave"` - fsutil.InodeNoExtendedAttributes `state:"nosave"` - fsutil.InodeNoopWriteOut `state:"nosave"` - fsutil.InodeNotMappable `state:"nosave"` - fsutil.InodeNotRenameable `state:"nosave"` - fsutil.InodeNotSocket `state:"nosave"` - fsutil.InodeNotSymlink `state:"nosave"` - fsutil.InodeVirtual `state:"nosave"` - - fsutil.InodeSimpleAttributes - - // msrc is the super block this directory is on. - // - // TODO(chrisko): Plumb this through instead of storing it here. - msrc *fs.MountSource - - // mu protects the fields below. - mu sync.Mutex `state:"nosave"` - - // master is the master PTY inode. - master *fs.Inode - - // replicas contains the replica inodes reachable from the directory. - // - // A new replica is added by allocateTerminal and is removed by - // masterFileOperations.Release. - // - // A reference is held on every replica in the map. - replicas map[uint32]*fs.Inode - - // dentryMap is a SortedDentryMap used to implement Readdir containing - // the master and all entries in replicas. - dentryMap *fs.SortedDentryMap - - // next is the next pty index to use. - // - // TODO(b/29356795): reuse indices when ptys are closed. - next uint32 -} - -var _ fs.InodeOperations = (*dirInodeOperations)(nil) - -// newDir creates a new dir with a ptmx file and no terminals. -func newDir(ctx context.Context, m *fs.MountSource) *fs.Inode { - d := &dirInodeOperations{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.RootOwner, fs.FilePermsFromMode(0555), linux.DEVPTS_SUPER_MAGIC), - msrc: m, - replicas: make(map[uint32]*fs.Inode), - dentryMap: fs.NewSortedDentryMap(nil), - } - // Linux devpts uses a default mode of 0000 for ptmx which can be - // changed with the ptmxmode mount option. However, that default is not - // useful here (since we'd *always* need the mount option, so it is - // accessible by default). - d.master = newMasterInode(ctx, d, fs.RootOwner, fs.FilePermsFromMode(0666)) - d.dentryMap.Add("ptmx", fs.DentAttr{ - Type: d.master.StableAttr.Type, - InodeID: d.master.StableAttr.InodeID, - }) - - return fs.NewInode(ctx, d, m, fs.StableAttr{ - DeviceID: ptsDevice.DeviceID(), - // N.B. Linux always uses inode id 1 for the directory. See - // fs/devpts/inode.c:devpts_fill_super. - // - // TODO(b/75267214): Since ptsDevice must be shared between - // different mounts, we must not assign fixed numbers. - InodeID: ptsDevice.NextIno(), - BlockSize: hostarch.PageSize, - Type: fs.Directory, - }) -} - -// Release implements fs.InodeOperations.Release. -func (d *dirInodeOperations) Release(ctx context.Context) { - d.mu.Lock() - defer d.mu.Unlock() - - d.master.DecRef(ctx) - if len(d.replicas) != 0 { - panic(fmt.Sprintf("devpts directory still contains active terminals: %+v", d)) - } -} - -// Lookup implements fs.InodeOperations.Lookup. -func (d *dirInodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string) (*fs.Dirent, error) { - d.mu.Lock() - defer d.mu.Unlock() - - // Master? - if name == "ptmx" { - d.master.IncRef() - return fs.NewDirent(ctx, d.master, name), nil - } - - // Replica number? - n, err := strconv.ParseUint(name, 10, 32) - if err != nil { - // Not found. - return nil, linuxerr.ENOENT - } - - s, ok := d.replicas[uint32(n)] - if !ok { - return nil, linuxerr.ENOENT - } - - s.IncRef() - return fs.NewDirent(ctx, s, name), nil -} - -// Create implements fs.InodeOperations.Create. -// -// Creation is never allowed. -func (d *dirInodeOperations) Create(ctx context.Context, dir *fs.Inode, name string, flags fs.FileFlags, perm fs.FilePermissions) (*fs.File, error) { - return nil, linuxerr.EACCES -} - -// CreateDirectory implements fs.InodeOperations.CreateDirectory. -// -// Creation is never allowed. -func (d *dirInodeOperations) CreateDirectory(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions) error { - return linuxerr.EACCES -} - -// CreateLink implements fs.InodeOperations.CreateLink. -// -// Creation is never allowed. -func (d *dirInodeOperations) CreateLink(ctx context.Context, dir *fs.Inode, oldname, newname string) error { - return linuxerr.EACCES -} - -// CreateHardLink implements fs.InodeOperations.CreateHardLink. -// -// Creation is never allowed. -func (d *dirInodeOperations) CreateHardLink(ctx context.Context, dir *fs.Inode, target *fs.Inode, name string) error { - return linuxerr.EACCES -} - -// CreateFifo implements fs.InodeOperations.CreateFifo. -// -// Creation is never allowed. -func (d *dirInodeOperations) CreateFifo(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions) error { - return linuxerr.EACCES -} - -// Remove implements fs.InodeOperations.Remove. -// -// Removal is never allowed. -func (d *dirInodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string) error { - return linuxerr.EPERM -} - -// RemoveDirectory implements fs.InodeOperations.RemoveDirectory. -// -// Removal is never allowed. -func (d *dirInodeOperations) RemoveDirectory(ctx context.Context, dir *fs.Inode, name string) error { - return linuxerr.EPERM -} - -// Bind implements fs.InodeOperations.Bind. -func (d *dirInodeOperations) Bind(ctx context.Context, dir *fs.Inode, name string, data transport.BoundEndpoint, perm fs.FilePermissions) (*fs.Dirent, error) { - return nil, linuxerr.EPERM -} - -// GetFile implements fs.InodeOperations.GetFile. -func (d *dirInodeOperations) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &dirFileOperations{di: d}), nil -} - -// allocateTerminal creates a new Terminal and installs a pts node for it. -// -// The caller must call DecRef when done with the returned Terminal. -func (d *dirInodeOperations) allocateTerminal(ctx context.Context) (*Terminal, error) { - d.mu.Lock() - defer d.mu.Unlock() - - n := d.next - if n == math.MaxUint32 { - return nil, linuxerr.ENOMEM - } - - if _, ok := d.replicas[n]; ok { - panic(fmt.Sprintf("pty index collision; index %d already exists", n)) - } - - t := newTerminal(ctx, d, n) - d.next++ - - // The reference returned by newTerminal is returned to the caller. - // Take another for the replica inode. - t.IncRef() - - // Create a pts node. The owner is based on the context that opens - // ptmx. - creds := auth.CredentialsFromContext(ctx) - uid, gid := creds.EffectiveKUID, creds.EffectiveKGID - replica := newReplicaInode(ctx, d, t, fs.FileOwner{uid, gid}, fs.FilePermsFromMode(0666)) - - d.replicas[n] = replica - d.dentryMap.Add(strconv.FormatUint(uint64(n), 10), fs.DentAttr{ - Type: replica.StableAttr.Type, - InodeID: replica.StableAttr.InodeID, - }) - - return t, nil -} - -// masterClose is called when the master end of t is closed. -func (d *dirInodeOperations) masterClose(ctx context.Context, t *Terminal) { - d.mu.Lock() - defer d.mu.Unlock() - - // The replica end disappears from the directory when the master end is - // closed, even if the replica end is open elsewhere. - // - // N.B. since we're using a backdoor method to remove a directory entry - // we won't properly fire inotify events like Linux would. - s, ok := d.replicas[t.n] - if !ok { - panic(fmt.Sprintf("Terminal %+v doesn't exist in %+v?", t, d)) - } - - s.DecRef(ctx) - delete(d.replicas, t.n) - d.dentryMap.Remove(strconv.FormatUint(uint64(t.n), 10)) -} - -// dirFileOperations are the fs.FileOperations for the directory. -// -// This is nearly identical to fsutil.DirFileOperations, except that it takes -// df.di.mu in IterateDir. -// -// +stateify savable -type dirFileOperations struct { - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FileGenericSeek `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - // di is the inode operations. - di *dirInodeOperations - - // dirCursor contains the name of the last directory entry that was - // serialized. - dirCursor string -} - -var _ fs.FileOperations = (*dirFileOperations)(nil) - -// IterateDir implements DirIterator.IterateDir. -func (df *dirFileOperations) IterateDir(ctx context.Context, d *fs.Dirent, dirCtx *fs.DirCtx, offset int) (int, error) { - df.di.mu.Lock() - defer df.di.mu.Unlock() - - n, err := fs.GenericReaddir(dirCtx, df.di.dentryMap) - return offset + n, err -} - -// Readdir implements FileOperations.Readdir. -func (df *dirFileOperations) Readdir(ctx context.Context, file *fs.File, serializer fs.DentrySerializer) (int64, error) { - root := fs.RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - dirCtx := &fs.DirCtx{ - Serializer: serializer, - DirCursor: &df.dirCursor, - } - return fs.DirentReaddir(ctx, file.Dirent, df, root, dirCtx, file.Offset()) -} - -// Read implements FileOperations.Read -func (df *dirFileOperations) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EISDIR -} - -// Write implements FileOperations.Write. -func (df *dirFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EISDIR -} diff --git a/pkg/sentry/fs/tty/fs.go b/pkg/sentry/fs/tty/fs.go deleted file mode 100644 index 498e08db4..000000000 --- a/pkg/sentry/fs/tty/fs.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2018 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. - -package tty - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -// ptsDevice is the pseudo-filesystem device. -var ptsDevice = device.NewAnonDevice() - -// filesystem is a devpts filesystem. -// -// This devpts is always in the new "multi-instance" mode. i.e., it contains a -// ptmx device tied to this mount. -// -// +stateify savable -type filesystem struct{} - -func init() { - fs.RegisterFilesystem(&filesystem{}) -} - -// Name matches drivers/devpts/indoe.c:devpts_fs_type.name. -func (*filesystem) Name() string { - return "devpts" -} - -// AllowUserMount allows users to mount(2) this file system. -func (*filesystem) AllowUserMount() bool { - // TODO(b/29356795): Users may mount this once the terminals are in a - // usable state. - return false -} - -// AllowUserList allows this filesystem to be listed in /proc/filesystems. -func (*filesystem) AllowUserList() bool { - return true -} - -// Flags returns that there is nothing special about this file system. -func (*filesystem) Flags() fs.FilesystemFlags { - return 0 -} - -// MountSource returns a devpts root that can be positioned in the vfs. -func (f *filesystem) Mount(ctx context.Context, device string, flags fs.MountSourceFlags, data string, _ any) (*fs.Inode, error) { - // device is always ignored. - - // No options are supported. - if data != "" { - return nil, linuxerr.EINVAL - } - - return newDir(ctx, fs.NewMountSource(ctx, &superOperations{}, f, flags)), nil -} - -// superOperations implements fs.MountSourceOperations, preventing caching. -// -// +stateify savable -type superOperations struct{} - -// Revalidate implements fs.DirentOperations.Revalidate. -// -// It always returns true, forcing a Lookup for all entries. -// -// Replica entries are dropped from dir when their master is closed, so an -// existing replica Dirent in the tree is not sufficient to guarantee that it -// still exists on the filesystem. -func (superOperations) Revalidate(context.Context, string, *fs.Inode, *fs.Inode) bool { - return true -} - -// Keep implements fs.DirentOperations.Keep. -// -// Keep returns false because Revalidate would force a lookup on cached entries -// anyways. -func (superOperations) Keep(*fs.Dirent) bool { - return false -} - -// CacheReaddir implements fs.DirentOperations.CacheReaddir. -// -// CacheReaddir returns false because entries change on master operations. -func (superOperations) CacheReaddir() bool { - return false -} - -// ResetInodeMappings implements MountSourceOperations.ResetInodeMappings. -func (superOperations) ResetInodeMappings() {} - -// SaveInodeMapping implements MountSourceOperations.SaveInodeMapping. -func (superOperations) SaveInodeMapping(*fs.Inode, string) {} - -// Destroy implements MountSourceOperations.Destroy. -func (superOperations) Destroy(context.Context) {} diff --git a/pkg/sentry/fs/tty/line_discipline.go b/pkg/sentry/fs/tty/line_discipline.go deleted file mode 100644 index 65f972530..000000000 --- a/pkg/sentry/fs/tty/line_discipline.go +++ /dev/null @@ -1,445 +0,0 @@ -// Copyright 2018 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. - -package tty - -import ( - "bytes" - "unicode/utf8" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -const ( - // canonMaxBytes is the number of bytes that fit into a single line of - // terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE - // in include/linux/tty.h. - canonMaxBytes = 4096 - - // nonCanonMaxBytes is the maximum number of bytes that can be read at - // a time in noncanonical mode. - nonCanonMaxBytes = canonMaxBytes - 1 - - spacesPerTab = 8 -) - -// lineDiscipline dictates how input and output are handled between the -// pseudoterminal (pty) master and replica. It can be configured to alter I/O, -// modify control characters (e.g. Ctrl-C for SIGINT), etc. The following man -// pages are good resources for how to affect the line discipline: -// -// - termios(3) -// - tty_ioctl(4) -// -// This file corresponds most closely to drivers/tty/n_tty.c. -// -// lineDiscipline has a simple structure but supports a multitude of options -// (see the above man pages). It consists of two queues of bytes: one from the -// terminal master to replica (the input queue) and one from replica to master -// (the output queue). When bytes are written to one end of the pty, the line -// discipline reads the bytes, modifies them or takes special action if -// required, and enqueues them to be read by the other end of the pty: -// -// input from terminal +-------------+ input to process (e.g. bash) -// +------------------------>| input queue |---------------------------+ -// | (inputQueueWrite) +-------------+ (inputQueueRead) | -// | | -// | v -// -// masterFD replicaFD -// -// ^ | -// | | -// | output to terminal +--------------+ output from process | -// +------------------------| output queue |<--------------------------+ -// (outputQueueRead) +--------------+ (outputQueueWrite) -// -// Lock order: -// -// termiosMu -// inQueue.mu -// outQueue.mu -// -// +stateify savable -type lineDiscipline struct { - // sizeMu protects size. - sizeMu sync.Mutex `state:"nosave"` - - // size is the terminal size (width and height). - size linux.WindowSize - - // inQueue is the input queue of the terminal. - inQueue queue - - // outQueue is the output queue of the terminal. - outQueue queue - - // termiosMu protects termios. - termiosMu sync.RWMutex `state:"nosave"` - - // termios is the terminal configuration used by the lineDiscipline. - termios linux.KernelTermios - - // column is the location in a row of the cursor. This is important for - // handling certain special characters like backspace. - column int - - // masterWaiter is used to wait on the master end of the TTY. - masterWaiter waiter.Queue - - // replicaWaiter is used to wait on the replica end of the TTY. - replicaWaiter waiter.Queue -} - -func newLineDiscipline(termios linux.KernelTermios) *lineDiscipline { - ld := lineDiscipline{termios: termios} - ld.inQueue.transformer = &inputQueueTransformer{} - ld.outQueue.transformer = &outputQueueTransformer{} - return &ld -} - -// getTermios gets the linux.Termios for the tty. -func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { - l.termiosMu.RLock() - defer l.termiosMu.RUnlock() - // We must copy a Termios struct, not KernelTermios. - t := l.termios.ToTermios() - _, err := t.CopyOut(task, args[2].Pointer()) - return 0, err -} - -// setTermios sets a linux.Termios for the tty. -func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { - l.termiosMu.Lock() - defer l.termiosMu.Unlock() - oldCanonEnabled := l.termios.LEnabled(linux.ICANON) - // We must copy a Termios struct, not KernelTermios. - var t linux.Termios - _, err := t.CopyIn(task, args[2].Pointer()) - l.termios.FromTermios(t) - - // If canonical mode is turned off, move bytes from inQueue's wait - // buffer to its read buffer. Anything already in the read buffer is - // now readable. - if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) { - l.inQueue.mu.Lock() - l.inQueue.pushWaitBufLocked(l) - l.inQueue.readable = true - l.inQueue.mu.Unlock() - l.replicaWaiter.Notify(waiter.ReadableEvents) - } - - return 0, err -} - -func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error { - l.sizeMu.Lock() - defer l.sizeMu.Unlock() - _, err := l.size.CopyOut(t, args[2].Pointer()) - return err -} - -func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error { - l.sizeMu.Lock() - defer l.sizeMu.Unlock() - _, err := l.size.CopyIn(t, args[2].Pointer()) - return err -} - -func (l *lineDiscipline) masterReadiness() waiter.EventMask { - // We don't have to lock a termios because the default master termios - // is immutable. - return l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios) -} - -func (l *lineDiscipline) replicaReadiness() waiter.EventMask { - l.termiosMu.RLock() - defer l.termiosMu.RUnlock() - return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios) -} - -func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, args arch.SyscallArguments) error { - return l.inQueue.readableSize(t, args) -} - -func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { - l.termiosMu.RLock() - defer l.termiosMu.RUnlock() - n, pushed, err := l.inQueue.read(ctx, dst, l) - if err != nil { - return 0, err - } - if n > 0 { - l.masterWaiter.Notify(waiter.WritableEvents) - if pushed { - l.replicaWaiter.Notify(waiter.ReadableEvents) - } - return n, nil - } - return 0, linuxerr.ErrWouldBlock -} - -func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { - l.termiosMu.RLock() - defer l.termiosMu.RUnlock() - n, err := l.inQueue.write(ctx, src, l) - if err != nil { - return 0, err - } - if n > 0 { - l.replicaWaiter.Notify(waiter.ReadableEvents) - return n, nil - } - return 0, linuxerr.ErrWouldBlock -} - -func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, args arch.SyscallArguments) error { - return l.outQueue.readableSize(t, args) -} - -func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { - l.termiosMu.RLock() - defer l.termiosMu.RUnlock() - n, pushed, err := l.outQueue.read(ctx, dst, l) - if err != nil { - return 0, err - } - if n > 0 { - l.replicaWaiter.Notify(waiter.WritableEvents) - if pushed { - l.masterWaiter.Notify(waiter.ReadableEvents) - } - return n, nil - } - return 0, linuxerr.ErrWouldBlock -} - -func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { - l.termiosMu.RLock() - defer l.termiosMu.RUnlock() - n, err := l.outQueue.write(ctx, src, l) - if err != nil { - return 0, err - } - if n > 0 { - l.masterWaiter.Notify(waiter.ReadableEvents) - return n, nil - } - return 0, linuxerr.ErrWouldBlock -} - -// transformer is a helper interface to make it easier to stateify queue. -type transformer interface { - // transform functions require queue's mutex to be held. - transform(*lineDiscipline, *queue, []byte) int -} - -// outputQueueTransformer implements transformer. It performs line discipline -// transformations on the output queue. -// -// +stateify savable -type outputQueueTransformer struct{} - -// transform does output processing for one end of the pty. See -// drivers/tty/n_tty.c:do_output_char for an analogous kernel function. -// -// Preconditions: -// - l.termiosMu must be held for reading. -// - q.mu must be held. -func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int { - // transformOutput is effectively always in noncanonical mode, as the - // master termios never has ICANON set. - - if !l.termios.OEnabled(linux.OPOST) { - q.readBuf = append(q.readBuf, buf...) - if len(q.readBuf) > 0 { - q.readable = true - } - return len(buf) - } - - var ret int - for len(buf) > 0 { - size := l.peek(buf) - cBytes := append([]byte{}, buf[:size]...) - ret += size - buf = buf[size:] - // We're guaranteed that cBytes has at least one element. - switch cBytes[0] { - case '\n': - if l.termios.OEnabled(linux.ONLRET) { - l.column = 0 - } - if l.termios.OEnabled(linux.ONLCR) { - q.readBuf = append(q.readBuf, '\r', '\n') - continue - } - case '\r': - if l.termios.OEnabled(linux.ONOCR) && l.column == 0 { - continue - } - if l.termios.OEnabled(linux.OCRNL) { - cBytes[0] = '\n' - if l.termios.OEnabled(linux.ONLRET) { - l.column = 0 - } - break - } - l.column = 0 - case '\t': - spaces := spacesPerTab - l.column%spacesPerTab - if l.termios.OutputFlags&linux.TABDLY == linux.XTABS { - l.column += spaces - q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...) - continue - } - l.column += spaces - case '\b': - if l.column > 0 { - l.column-- - } - default: - l.column++ - } - q.readBuf = append(q.readBuf, cBytes...) - } - if len(q.readBuf) > 0 { - q.readable = true - } - return ret -} - -// inputQueueTransformer implements transformer. It performs line discipline -// transformations on the input queue. -// -// +stateify savable -type inputQueueTransformer struct{} - -// transform does input processing for one end of the pty. Characters read are -// transformed according to flags set in the termios struct. See -// drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel -// function. -// -// Preconditions: -// - l.termiosMu must be held for reading. -// - q.mu must be held. -func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int { - // If there's a line waiting to be read in canonical mode, don't write - // anything else to the read buffer. - if l.termios.LEnabled(linux.ICANON) && q.readable { - return 0 - } - - maxBytes := nonCanonMaxBytes - if l.termios.LEnabled(linux.ICANON) { - maxBytes = canonMaxBytes - } - - var ret int - for len(buf) > 0 && len(q.readBuf) < canonMaxBytes { - size := l.peek(buf) - cBytes := append([]byte{}, buf[:size]...) - // We're guaranteed that cBytes has at least one element. - switch cBytes[0] { - case '\r': - if l.termios.IEnabled(linux.IGNCR) { - buf = buf[size:] - ret += size - continue - } - if l.termios.IEnabled(linux.ICRNL) { - cBytes[0] = '\n' - } - case '\n': - if l.termios.IEnabled(linux.INLCR) { - cBytes[0] = '\r' - } - } - - // In canonical mode, we discard non-terminating characters - // after the first 4095. - if l.shouldDiscard(q, cBytes) { - buf = buf[size:] - ret += size - continue - } - - // Stop if the buffer would be overfilled. - if len(q.readBuf)+size > maxBytes { - break - } - buf = buf[size:] - ret += size - - // If we get EOF, make the buffer available for reading. - if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) { - q.readable = true - break - } - - q.readBuf = append(q.readBuf, cBytes...) - - // Anything written to the readBuf will have to be echoed. - if l.termios.LEnabled(linux.ECHO) { - l.outQueue.writeBytes(cBytes, l) - l.masterWaiter.Notify(waiter.ReadableEvents) - } - - // If we finish a line, make it available for reading. - if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) { - q.readable = true - break - } - } - - // In noncanonical mode, everything is readable. - if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 { - q.readable = true - } - - return ret -} - -// shouldDiscard returns whether c should be discarded. In canonical mode, if -// too many bytes are enqueued, we keep reading input and discarding it until -// we find a terminating character. Signal/echo processing still occurs. -// -// Precondition: -// - l.termiosMu must be held for reading. -// - q.mu must be held. -func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool { - return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes) -} - -// peek returns the size in bytes of the next character to process. As long as -// b isn't empty, peek returns a value of at least 1. -func (l *lineDiscipline) peek(b []byte) int { - size := 1 - // If UTF-8 support is enabled, runes might be multiple bytes. - if l.termios.IEnabled(linux.IUTF8) { - _, size = utf8.DecodeRune(b) - } - return size -} - -// LINT.ThenChange(../../fsimpl/devpts/line_discipline.go) diff --git a/pkg/sentry/fs/tty/master.go b/pkg/sentry/fs/tty/master.go deleted file mode 100644 index 7702f6c98..000000000 --- a/pkg/sentry/fs/tty/master.go +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright 2018 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. - -package tty - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/unimpl" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// masterInodeOperations are the fs.InodeOperations for the master end of the -// Terminal (ptmx file). -// -// +stateify savable -type masterInodeOperations struct { - fsutil.SimpleFileInode - - // d is the containing dir. - d *dirInodeOperations -} - -var _ fs.InodeOperations = (*masterInodeOperations)(nil) - -// newMasterInode creates an Inode for the master end of a terminal. -func newMasterInode(ctx context.Context, d *dirInodeOperations, owner fs.FileOwner, p fs.FilePermissions) *fs.Inode { - iops := &masterInodeOperations{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, owner, p, linux.DEVPTS_SUPER_MAGIC), - d: d, - } - - return fs.NewInode(ctx, iops, d.msrc, fs.StableAttr{ - DeviceID: ptsDevice.DeviceID(), - // N.B. Linux always uses inode id 2 for ptmx. See - // fs/devpts/inode.c:mknod_ptmx. - // - // TODO(b/75267214): Since ptsDevice must be shared between - // different mounts, we must not assign fixed numbers. - InodeID: ptsDevice.NextIno(), - Type: fs.CharacterDevice, - // See fs/devpts/inode.c:devpts_fill_super. - BlockSize: 1024, - // The PTY master effectively has two different major/minor - // device numbers. - // - // This one is returned by stat for both opened and unopened - // instances of this inode. - // - // When the inode is opened (GetFile), a new device number is - // allocated based on major UNIX98_PTY_MASTER_MAJOR and the tty - // index as minor number. However, this device number is only - // accessible via ioctl(TIOCGDEV) and /proc/TID/stat. - DeviceFileMajor: linux.TTYAUX_MAJOR, - DeviceFileMinor: linux.PTMX_MINOR, - }) -} - -// Release implements fs.InodeOperations.Release. -func (mi *masterInodeOperations) Release(context.Context) { -} - -// Truncate implements fs.InodeOperations.Truncate. -func (*masterInodeOperations) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -// -// It allocates a new terminal. -func (mi *masterInodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - t, err := mi.d.allocateTerminal(ctx) - if err != nil { - return nil, err - } - - return fs.NewFile(ctx, d, flags, &masterFileOperations{ - d: mi.d, - t: t, - }), nil -} - -// masterFileOperations are the fs.FileOperations for the master end of a terminal. -// -// +stateify savable -type masterFileOperations struct { - fsutil.FilePipeSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - // d is the containing dir. - d *dirInodeOperations - - // t is the connected Terminal. - t *Terminal -} - -var _ fs.FileOperations = (*masterFileOperations)(nil) - -// Release implements fs.FileOperations.Release. -func (mf *masterFileOperations) Release(ctx context.Context) { - mf.d.masterClose(ctx, mf.t) - mf.t.DecRef(ctx) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (mf *masterFileOperations) EventRegister(e *waiter.Entry) error { - mf.t.ld.masterWaiter.EventRegister(e) - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (mf *masterFileOperations) EventUnregister(e *waiter.Entry) { - mf.t.ld.masterWaiter.EventUnregister(e) -} - -// Readiness implements waiter.Waitable.Readiness. -func (mf *masterFileOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - return mf.t.ld.masterReadiness() -} - -// Read implements fs.FileOperations.Read. -func (mf *masterFileOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - return mf.t.ld.outputQueueRead(ctx, dst) -} - -// Write implements fs.FileOperations.Write. -func (mf *masterFileOperations) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - return mf.t.ld.inputQueueWrite(ctx, src) -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (mf *masterFileOperations) Ioctl(ctx context.Context, file *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - t := kernel.TaskFromContext(ctx) - if t == nil { - // ioctl(2) may only be called from a task goroutine. - return 0, linuxerr.ENOTTY - } - - switch cmd := args[1].Uint(); cmd { - case linux.FIONREAD: // linux.FIONREAD == linux.TIOCINQ - // Get the number of bytes in the output queue read buffer. - return 0, mf.t.ld.outputQueueReadSize(t, args) - case linux.TCGETS: - // N.B. TCGETS on the master actually returns the configuration - // of the replica end. - return mf.t.ld.getTermios(t, args) - case linux.TCSETS: - // N.B. TCSETS on the master actually affects the configuration - // of the replica end. - return mf.t.ld.setTermios(t, args) - case linux.TCSETSW: - // TODO(b/29356795): This should drain the output queue first. - return mf.t.ld.setTermios(t, args) - case linux.TIOCGPTN: - nP := primitive.Uint32(mf.t.n) - _, err := nP.CopyOut(t, args[2].Pointer()) - return 0, err - case linux.TIOCSPTLCK: - // TODO(b/29356795): Implement pty locking. For now just pretend we do. - return 0, nil - case linux.TIOCGWINSZ: - return 0, mf.t.ld.windowSize(t, args) - case linux.TIOCSWINSZ: - return 0, mf.t.ld.setWindowSize(t, args) - case linux.TIOCSCTTY: - // Make the given terminal the controlling terminal of the - // calling process. - return 0, mf.t.setControllingTTY(ctx, args, true /* isMaster */, file.Flags().Read) - case linux.TIOCNOTTY: - // Release this process's controlling terminal. - return 0, mf.t.releaseControllingTTY(ctx, args, true /* isMaster */) - case linux.TIOCGPGRP: - // Get the foreground process group. - return mf.t.foregroundProcessGroup(ctx, args, true /* isMaster */) - case linux.TIOCSPGRP: - // Set the foreground process group. - return mf.t.setForegroundProcessGroup(ctx, args, true /* isMaster */) - default: - maybeEmitUnimplementedEvent(ctx, cmd) - return 0, linuxerr.ENOTTY - } -} - -// maybeEmitUnimplementedEvent emits unimplemented event if cmd is valid. -func maybeEmitUnimplementedEvent(ctx context.Context, cmd uint32) { - switch cmd { - case linux.TCGETS, - linux.TCSETS, - linux.TCSETSW, - linux.TCSETSF, - linux.TIOCGWINSZ, - linux.TIOCSWINSZ, - linux.TIOCSETD, - linux.TIOCSBRK, - linux.TIOCCBRK, - linux.TCSBRK, - linux.TCSBRKP, - linux.TIOCSTI, - linux.TIOCCONS, - linux.FIONBIO, - linux.TIOCEXCL, - linux.TIOCNXCL, - linux.TIOCGEXCL, - linux.TIOCGSID, - linux.TIOCGETD, - linux.TIOCVHANGUP, - linux.TIOCGDEV, - linux.TIOCMGET, - linux.TIOCMSET, - linux.TIOCMBIC, - linux.TIOCMBIS, - linux.TIOCGICOUNT, - linux.TCFLSH, - linux.TIOCSSERIAL, - linux.TIOCGPTPEER: - - unimpl.EmitUnimplementedEvent(ctx) - } -} - -// LINT.ThenChange(../../fsimpl/devpts/master.go) diff --git a/pkg/sentry/fs/tty/queue.go b/pkg/sentry/fs/tty/queue.go deleted file mode 100644 index 73d40a982..000000000 --- a/pkg/sentry/fs/tty/queue.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2018 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. - -package tty - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// waitBufMaxBytes is the maximum size of a wait buffer. It is based on -// TTYB_DEFAULT_MEM_LIMIT. -const waitBufMaxBytes = 131072 - -// queue represents one of the input or output queues between a pty master and -// replica. Bytes written to a queue are added to the read buffer until it is -// full, at which point they are written to the wait buffer. Bytes are -// processed (i.e. undergo termios transformations) as they are added to the -// read buffer. The read buffer is readable when its length is nonzero and -// readable is true. -// -// +stateify savable -type queue struct { - // mu protects everything in queue. - mu sync.Mutex `state:"nosave"` - - // readBuf is buffer of data ready to be read when readable is true. - // This data has been processed. - readBuf []byte - - // waitBuf contains data that can't fit into readBuf. It is put here - // until it can be loaded into the read buffer. waitBuf contains data - // that hasn't been processed. - waitBuf [][]byte - waitBufLen uint64 - - // readable indicates whether the read buffer can be read from. In - // canonical mode, there can be an unterminated line in the read buffer, - // so readable must be checked. - readable bool - - // transform is the the queue's function for transforming bytes - // entering the queue. For example, transform might convert all '\r's - // entering the queue to '\n's. - transformer -} - -// readReadiness returns whether q is ready to be read from. -func (q *queue) readReadiness(t *linux.KernelTermios) waiter.EventMask { - q.mu.Lock() - defer q.mu.Unlock() - if len(q.readBuf) > 0 && q.readable { - return waiter.ReadableEvents - } - return waiter.EventMask(0) -} - -// writeReadiness returns whether q is ready to be written to. -func (q *queue) writeReadiness(t *linux.KernelTermios) waiter.EventMask { - q.mu.Lock() - defer q.mu.Unlock() - if q.waitBufLen < waitBufMaxBytes { - return waiter.WritableEvents - } - return waiter.EventMask(0) -} - -// readableSize writes the number of readable bytes to userspace. -func (q *queue) readableSize(t *kernel.Task, args arch.SyscallArguments) error { - q.mu.Lock() - defer q.mu.Unlock() - size := primitive.Int32(0) - if q.readable { - size = primitive.Int32(len(q.readBuf)) - } - - _, err := size.CopyOut(t, args[2].Pointer()) - return err - -} - -// read reads from q to userspace. It returns the number of bytes read as well -// as whether the read caused more readable data to become available (whether -// data was pushed from the wait buffer to the read buffer). -// -// Preconditions: l.termiosMu must be held for reading. -func (q *queue) read(ctx context.Context, dst usermem.IOSequence, l *lineDiscipline) (int64, bool, error) { - q.mu.Lock() - defer q.mu.Unlock() - - if !q.readable { - return 0, false, linuxerr.ErrWouldBlock - } - - if dst.NumBytes() > canonMaxBytes { - dst = dst.TakeFirst(canonMaxBytes) - } - - n, err := dst.CopyOutFrom(ctx, safemem.ReaderFunc(func(dst safemem.BlockSeq) (uint64, error) { - src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(q.readBuf)) - n, err := safemem.CopySeq(dst, src) - if err != nil { - return 0, err - } - q.readBuf = q.readBuf[n:] - - // If we read everything, this queue is no longer readable. - if len(q.readBuf) == 0 { - q.readable = false - } - - return n, nil - })) - if err != nil { - return 0, false, err - } - - // Move data from the queue's wait buffer to its read buffer. - nPushed := q.pushWaitBufLocked(l) - - return int64(n), nPushed > 0, nil -} - -// write writes to q from userspace. -// -// Preconditions: l.termiosMu must be held for reading. -func (q *queue) write(ctx context.Context, src usermem.IOSequence, l *lineDiscipline) (int64, error) { - q.mu.Lock() - defer q.mu.Unlock() - - // Copy data into the wait buffer. - n, err := src.CopyInTo(ctx, safemem.WriterFunc(func(src safemem.BlockSeq) (uint64, error) { - copyLen := src.NumBytes() - room := waitBufMaxBytes - q.waitBufLen - // If out of room, return EAGAIN. - if room == 0 && copyLen > 0 { - return 0, linuxerr.ErrWouldBlock - } - // Cap the size of the wait buffer. - if copyLen > room { - copyLen = room - src = src.TakeFirst64(room) - } - buf := make([]byte, copyLen) - - // Copy the data into the wait buffer. - dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)) - n, err := safemem.CopySeq(dst, src) - if err != nil { - return 0, err - } - q.waitBufAppend(buf) - - return n, nil - })) - if err != nil { - return 0, err - } - - // Push data from the wait to the read buffer. - q.pushWaitBufLocked(l) - - return n, nil -} - -// writeBytes writes to q from b. -// -// Preconditions: l.termiosMu must be held for reading. -func (q *queue) writeBytes(b []byte, l *lineDiscipline) { - q.mu.Lock() - defer q.mu.Unlock() - - // Write to the wait buffer. - q.waitBufAppend(b) - q.pushWaitBufLocked(l) -} - -// pushWaitBufLocked fills the queue's read buffer with data from the wait -// buffer. -// -// Preconditions: -// - l.termiosMu must be held for reading. -// - q.mu must be locked. -func (q *queue) pushWaitBufLocked(l *lineDiscipline) int { - if q.waitBufLen == 0 { - return 0 - } - - // Move data from the wait to the read buffer. - var total int - var i int - for i = 0; i < len(q.waitBuf); i++ { - n := q.transform(l, q, q.waitBuf[i]) - total += n - if n != len(q.waitBuf[i]) { - // The read buffer filled up without consuming the - // entire buffer. - q.waitBuf[i] = q.waitBuf[i][n:] - break - } - } - - // Update wait buffer based on consumed data. - q.waitBuf = q.waitBuf[i:] - q.waitBufLen -= uint64(total) - - return total -} - -// Precondition: q.mu must be locked. -func (q *queue) waitBufAppend(b []byte) { - q.waitBuf = append(q.waitBuf, b) - q.waitBufLen += uint64(len(b)) -} - -// LINT.ThenChange(../../fsimpl/devpts/queue.go) diff --git a/pkg/sentry/fs/tty/replica.go b/pkg/sentry/fs/tty/replica.go deleted file mode 100644 index e58e610a1..000000000 --- a/pkg/sentry/fs/tty/replica.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2018 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. - -package tty - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// LINT.IfChange - -// replicaInodeOperations are the fs.InodeOperations for the replica end of the -// Terminal (pts file). -// -// +stateify savable -type replicaInodeOperations struct { - fsutil.SimpleFileInode - - // d is the containing dir. - d *dirInodeOperations - - // t is the connected Terminal. - t *Terminal -} - -var _ fs.InodeOperations = (*replicaInodeOperations)(nil) - -// newReplicaInode creates an fs.Inode for the replica end of a terminal. -// -// newReplicaInode takes ownership of t. -func newReplicaInode(ctx context.Context, d *dirInodeOperations, t *Terminal, owner fs.FileOwner, p fs.FilePermissions) *fs.Inode { - iops := &replicaInodeOperations{ - SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, owner, p, linux.DEVPTS_SUPER_MAGIC), - d: d, - t: t, - } - - return fs.NewInode(ctx, iops, d.msrc, fs.StableAttr{ - DeviceID: ptsDevice.DeviceID(), - // N.B. Linux always uses inode id = tty index + 3. See - // fs/devpts/inode.c:devpts_pty_new. - // - // TODO(b/75267214): Since ptsDevice must be shared between - // different mounts, we must not assign fixed numbers. - InodeID: ptsDevice.NextIno(), - Type: fs.CharacterDevice, - // See fs/devpts/inode.c:devpts_fill_super. - BlockSize: 1024, - DeviceFileMajor: linux.UNIX98_PTY_REPLICA_MAJOR, - DeviceFileMinor: t.n, - }) -} - -// Release implements fs.InodeOperations.Release. -func (si *replicaInodeOperations) Release(ctx context.Context) { - si.t.DecRef(ctx) -} - -// Truncate implements fs.InodeOperations.Truncate. -func (*replicaInodeOperations) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// GetFile implements fs.InodeOperations.GetFile. -// -// This may race with destruction of the terminal. If the terminal is gone, it -// returns ENOENT. -func (si *replicaInodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, d, flags, &replicaFileOperations{si: si}), nil -} - -// replicaFileOperations are the fs.FileOperations for the replica end of a -// terminal. -// -// +stateify savable -type replicaFileOperations struct { - fsutil.FilePipeSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - // si is the inode operations. - si *replicaInodeOperations -} - -var _ fs.FileOperations = (*replicaFileOperations)(nil) - -// Release implements fs.FileOperations.Release. -func (sf *replicaFileOperations) Release(context.Context) { -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (sf *replicaFileOperations) EventRegister(e *waiter.Entry) error { - sf.si.t.ld.replicaWaiter.EventRegister(e) - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (sf *replicaFileOperations) EventUnregister(e *waiter.Entry) { - sf.si.t.ld.replicaWaiter.EventUnregister(e) -} - -// Readiness implements waiter.Waitable.Readiness. -func (sf *replicaFileOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - return sf.si.t.ld.replicaReadiness() -} - -// Read implements fs.FileOperations.Read. -func (sf *replicaFileOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - return sf.si.t.ld.inputQueueRead(ctx, dst) -} - -// Write implements fs.FileOperations.Write. -func (sf *replicaFileOperations) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - return sf.si.t.ld.outputQueueWrite(ctx, src) -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (sf *replicaFileOperations) Ioctl(ctx context.Context, file *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - t := kernel.TaskFromContext(ctx) - if t == nil { - // ioctl(2) may only be called from a task goroutine. - return 0, linuxerr.ENOTTY - } - - switch cmd := args[1].Uint(); cmd { - case linux.FIONREAD: // linux.FIONREAD == linux.TIOCINQ - // Get the number of bytes in the input queue read buffer. - return 0, sf.si.t.ld.inputQueueReadSize(t, args) - case linux.TCGETS: - return sf.si.t.ld.getTermios(t, args) - case linux.TCSETS: - return sf.si.t.ld.setTermios(t, args) - case linux.TCSETSW: - // TODO(b/29356795): This should drain the output queue first. - return sf.si.t.ld.setTermios(t, args) - case linux.TIOCGPTN: - nP := primitive.Uint32(sf.si.t.n) - _, err := nP.CopyOut(t, args[2].Pointer()) - return 0, err - case linux.TIOCGWINSZ: - return 0, sf.si.t.ld.windowSize(t, args) - case linux.TIOCSWINSZ: - return 0, sf.si.t.ld.setWindowSize(t, args) - case linux.TIOCSCTTY: - // Make the given terminal the controlling terminal of the - // calling process. - return 0, sf.si.t.setControllingTTY(ctx, args, false /* isMaster */, file.Flags().Read) - case linux.TIOCNOTTY: - // Release this process's controlling terminal. - return 0, sf.si.t.releaseControllingTTY(ctx, args, false /* isMaster */) - case linux.TIOCGPGRP: - // Get the foreground process group. - return sf.si.t.foregroundProcessGroup(ctx, args, false /* isMaster */) - case linux.TIOCSPGRP: - // Set the foreground process group. - return sf.si.t.setForegroundProcessGroup(ctx, args, false /* isMaster */) - default: - maybeEmitUnimplementedEvent(ctx, cmd) - return 0, linuxerr.ENOTTY - } -} - -// LINT.ThenChange(../../fsimpl/devpts/replica.go) diff --git a/pkg/sentry/fs/tty/terminal.go b/pkg/sentry/fs/tty/terminal.go deleted file mode 100644 index d551267af..000000000 --- a/pkg/sentry/fs/tty/terminal.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2018 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. - -package tty - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/kernel" -) - -// LINT.IfChange - -// Terminal is a pseudoterminal. -// -// +stateify savable -type Terminal struct { - refs.AtomicRefCount - - // n is the terminal index. It is immutable. - n uint32 - - // d is the containing directory. It is immutable. - d *dirInodeOperations - - // ld is the line discipline of the terminal. It is immutable. - ld *lineDiscipline - - // masterKTTY contains the controlling process of the master end of - // this terminal. This field is immutable. - masterKTTY *kernel.TTY - - // replicaKTTY contains the controlling process of the replica end of this - // terminal. This field is immutable. - replicaKTTY *kernel.TTY -} - -func newTerminal(ctx context.Context, d *dirInodeOperations, n uint32) *Terminal { - termios := linux.DefaultReplicaTermios - t := Terminal{ - d: d, - n: n, - ld: newLineDiscipline(termios), - masterKTTY: &kernel.TTY{Index: n}, - replicaKTTY: &kernel.TTY{Index: n}, - } - t.EnableLeakCheck("tty.Terminal") - return &t -} - -// setControllingTTY makes tm the controlling terminal of the calling thread -// group. -func (tm *Terminal) setControllingTTY(ctx context.Context, args arch.SyscallArguments, isMaster bool, readable bool) error { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("setControllingTTY must be called from a task context") - } - - steal := args[2].Int() == 1 - return task.ThreadGroup().SetControllingTTY(tm.tty(isMaster), steal, readable) -} - -// releaseControllingTTY removes tm as the controlling terminal of the calling -// thread group. -func (tm *Terminal) releaseControllingTTY(ctx context.Context, args arch.SyscallArguments, isMaster bool) error { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("releaseControllingTTY must be called from a task context") - } - - return task.ThreadGroup().ReleaseControllingTTY(tm.tty(isMaster)) -} - -// foregroundProcessGroup gets the process group ID of tm's foreground process. -func (tm *Terminal) foregroundProcessGroup(ctx context.Context, args arch.SyscallArguments, isMaster bool) (uintptr, error) { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("foregroundProcessGroup must be called from a task context") - } - - ret, err := task.ThreadGroup().ForegroundProcessGroup(tm.tty(isMaster)) - if err != nil { - return 0, err - } - - // Write it out to *arg. - retP := primitive.Int32(ret) - _, err = retP.CopyOut(task, args[2].Pointer()) - return 0, err -} - -// foregroundProcessGroup sets tm's foreground process. -func (tm *Terminal) setForegroundProcessGroup(ctx context.Context, args arch.SyscallArguments, isMaster bool) (uintptr, error) { - task := kernel.TaskFromContext(ctx) - if task == nil { - panic("setForegroundProcessGroup must be called from a task context") - } - - // Read in the process group ID. - var pgid primitive.Int32 - if _, err := pgid.CopyIn(task, args[2].Pointer()); err != nil { - return 0, err - } - - ret, err := task.ThreadGroup().SetForegroundProcessGroup(tm.tty(isMaster), kernel.ProcessGroupID(pgid)) - return uintptr(ret), err -} - -func (tm *Terminal) tty(isMaster bool) *kernel.TTY { - if isMaster { - return tm.masterKTTY - } - return tm.replicaKTTY -} - -// LINT.ThenChange(../../fsimpl/devpts/terminal.go) diff --git a/pkg/sentry/fs/tty/tty_test.go b/pkg/sentry/fs/tty/tty_test.go deleted file mode 100644 index 49edee83d..000000000 --- a/pkg/sentry/fs/tty/tty_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2018 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. - -package tty - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/usermem" -) - -func TestSimpleMasterToReplica(t *testing.T) { - ld := newLineDiscipline(linux.DefaultReplicaTermios) - ctx := contexttest.Context(t) - inBytes := []byte("hello, tty\n") - src := usermem.BytesIOSequence(inBytes) - outBytes := make([]byte, 32) - dst := usermem.BytesIOSequence(outBytes) - - // Write to the input queue. - nw, err := ld.inputQueueWrite(ctx, src) - if err != nil { - t.Fatalf("error writing to input queue: %v", err) - } - if nw != int64(len(inBytes)) { - t.Fatalf("wrote wrong length: got %d, want %d", nw, len(inBytes)) - } - - // Read from the input queue. - nr, err := ld.inputQueueRead(ctx, dst) - if err != nil { - t.Fatalf("error reading from input queue: %v", err) - } - if nr != int64(len(inBytes)) { - t.Fatalf("read wrong length: got %d, want %d", nr, len(inBytes)) - } - - outStr := string(outBytes[:nr]) - inStr := string(inBytes) - if outStr != inStr { - t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr) - } -} diff --git a/pkg/sentry/fsimpl/devpts/BUILD b/pkg/sentry/fsimpl/devpts/BUILD index 9ceaf7584..c0c58839c 100644 --- a/pkg/sentry/fsimpl/devpts/BUILD +++ b/pkg/sentry/fsimpl/devpts/BUILD @@ -39,8 +39,8 @@ go_library( "//pkg/safemem", "//pkg/sentry/arch", "//pkg/sentry/fs", - "//pkg/sentry/fs/lock", "//pkg/sentry/fsimpl/kernfs", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", "//pkg/sentry/unimpl", diff --git a/pkg/sentry/fsimpl/gofer/BUILD b/pkg/sentry/fsimpl/gofer/BUILD index d2134db5a..748abf918 100644 --- a/pkg/sentry/fsimpl/gofer/BUILD +++ b/pkg/sentry/fsimpl/gofer/BUILD @@ -78,8 +78,8 @@ go_library( "//pkg/refsvfs2", "//pkg/safemem", "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", "//pkg/sentry/fsimpl/host", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsmetric", "//pkg/sentry/hostfd", "//pkg/sentry/kernel", diff --git a/pkg/sentry/fsimpl/gofer/gofer.go b/pkg/sentry/fsimpl/gofer/gofer.go index 1c3407aea..0c565dfb6 100644 --- a/pkg/sentry/fsimpl/gofer/gofer.go +++ b/pkg/sentry/fsimpl/gofer/gofer.go @@ -56,7 +56,7 @@ import ( refs_vfs1 "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/refsvfs2" "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - fslock "gvisor.dev/gvisor/pkg/sentry/fs/lock" + fslock "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/pipe" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" diff --git a/pkg/sentry/fsimpl/host/BUILD b/pkg/sentry/fsimpl/host/BUILD index 6a636514e..3387b1a2a 100644 --- a/pkg/sentry/fsimpl/host/BUILD +++ b/pkg/sentry/fsimpl/host/BUILD @@ -42,8 +42,8 @@ go_library( "//pkg/safemem", "//pkg/sentry/arch", "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", "//pkg/sentry/fsimpl/kernfs", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/hostfd", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/fsimpl/kernfs/BUILD b/pkg/sentry/fsimpl/kernfs/BUILD index c6f4cac8e..3f051a600 100644 --- a/pkg/sentry/fsimpl/kernfs/BUILD +++ b/pkg/sentry/fsimpl/kernfs/BUILD @@ -130,7 +130,7 @@ go_library( "//pkg/refsvfs2", "//pkg/safemem", "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/time", "//pkg/sentry/memmap", diff --git a/pkg/sentry/fs/lock/BUILD b/pkg/sentry/fsimpl/lock/BUILD similarity index 100% rename from pkg/sentry/fs/lock/BUILD rename to pkg/sentry/fsimpl/lock/BUILD diff --git a/pkg/sentry/fs/lock/lock.go b/pkg/sentry/fsimpl/lock/lock.go similarity index 100% rename from pkg/sentry/fs/lock/lock.go rename to pkg/sentry/fsimpl/lock/lock.go diff --git a/pkg/sentry/fs/lock/lock_range_test.go b/pkg/sentry/fsimpl/lock/lock_range_test.go similarity index 100% rename from pkg/sentry/fs/lock/lock_range_test.go rename to pkg/sentry/fsimpl/lock/lock_range_test.go diff --git a/pkg/sentry/fs/lock/lock_set_functions.go b/pkg/sentry/fsimpl/lock/lock_set_functions.go similarity index 100% rename from pkg/sentry/fs/lock/lock_set_functions.go rename to pkg/sentry/fsimpl/lock/lock_set_functions.go diff --git a/pkg/sentry/fs/lock/lock_test.go b/pkg/sentry/fsimpl/lock/lock_test.go similarity index 100% rename from pkg/sentry/fs/lock/lock_test.go rename to pkg/sentry/fsimpl/lock/lock_test.go diff --git a/pkg/sentry/fsimpl/overlay/BUILD b/pkg/sentry/fsimpl/overlay/BUILD index 98b60a483..11a279276 100644 --- a/pkg/sentry/fsimpl/overlay/BUILD +++ b/pkg/sentry/fsimpl/overlay/BUILD @@ -102,7 +102,7 @@ go_library( "//pkg/refs", "//pkg/refsvfs2", "//pkg/sentry/arch", - "//pkg/sentry/fs/lock", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/kernel/auth", "//pkg/sentry/memmap", "//pkg/sentry/socket/unix/transport", diff --git a/pkg/sentry/fsimpl/proc/BUILD b/pkg/sentry/fsimpl/proc/BUILD index 4e2ebe234..8d9615d2b 100644 --- a/pkg/sentry/fsimpl/proc/BUILD +++ b/pkg/sentry/fsimpl/proc/BUILD @@ -88,9 +88,9 @@ go_library( "//pkg/refs", "//pkg/refsvfs2", "//pkg/safemem", - "//pkg/sentry/fs/lock", "//pkg/sentry/fsbridge", "//pkg/sentry/fsimpl/kernfs", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/inet", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/fsimpl/tmpfs/BUILD b/pkg/sentry/fsimpl/tmpfs/BUILD index 213be2303..8fd0192c7 100644 --- a/pkg/sentry/fsimpl/tmpfs/BUILD +++ b/pkg/sentry/fsimpl/tmpfs/BUILD @@ -101,7 +101,7 @@ go_library( "//pkg/sentry/arch", "//pkg/sentry/fs", "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsmetric", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/pipe", @@ -133,7 +133,6 @@ go_test( "//pkg/refs", "//pkg/sentry/contexttest", "//pkg/sentry/fs", - "//pkg/sentry/fs/tmpfs", "//pkg/sentry/kernel/auth", "//pkg/sentry/vfs", ], @@ -156,7 +155,7 @@ go_test( "//pkg/errors/linuxerr", "//pkg/fspath", "//pkg/sentry/contexttest", - "//pkg/sentry/fs/lock", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/kernel/auth", "//pkg/sentry/vfs", "//pkg/usermem", diff --git a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go index 0f68fc812..00a2ef513 100644 --- a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go +++ b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go @@ -27,7 +27,6 @@ import ( "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/sentry/contexttest" "gvisor.dev/gvisor/pkg/sentry/fs" - _ "gvisor.dev/gvisor/pkg/sentry/fs/tmpfs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" @@ -88,87 +87,6 @@ func fileOpOn(ctx context.Context, mntns *fs.MountNamespace, root, wd *fs.Dirent return err } -func BenchmarkVFS1TmpfsStat(b *testing.B) { - for _, depth := range depths { - b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { - ctx := contexttest.Context(b) - - // Create VFS. - tmpfsFS, ok := fs.FindFilesystem("tmpfs") - if !ok { - b.Fatalf("failed to find tmpfs filesystem type") - } - rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil) - if err != nil { - b.Fatalf("failed to create tmpfs root mount: %v", err) - } - mntns, err := fs.NewMountNamespace(ctx, rootInode) - if err != nil { - b.Fatalf("failed to create mount namespace: %v", err) - } - defer mntns.DecRef(ctx) - - var filePathBuilder strings.Builder - filePathBuilder.WriteByte('/') - - // Create nested directories with given depth. - root := mntns.Root() - defer root.DecRef(ctx) - d := root - d.IncRef() - defer d.DecRef(ctx) - for i := depth; i > 0; i-- { - name := fmt.Sprintf("%d", i) - if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil { - b.Fatalf("failed to create directory %q: %v", name, err) - } - next, err := d.Walk(ctx, root, name) - if err != nil { - b.Fatalf("failed to walk to directory %q: %v", name, err) - } - d.DecRef(ctx) - d = next - filePathBuilder.WriteString(name) - filePathBuilder.WriteByte('/') - } - - // Create the file that will be stat'd. - file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644)) - if err != nil { - b.Fatalf("failed to create file %q: %v", filename, err) - } - file.DecRef(ctx) - filePathBuilder.WriteString(filename) - filePath := filePathBuilder.String() - - dirPath := false - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - uattr, err := d.Inode.UnstableAttr(ctx) - if err != nil { - return err - } - // Sanity check. - if uattr.Perms.User.Execute { - b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode()) - } - return nil - }) - if err != nil { - b.Fatalf("stat(%q) failed: %v", filePath, err) - } - } - // Don't include deferred cleanup in benchmark time. - b.StopTimer() - }) - } -} - func BenchmarkVFS2TmpfsStat(b *testing.B) { for _, depth := range depths { b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { @@ -261,108 +179,6 @@ func BenchmarkVFS2TmpfsStat(b *testing.B) { } } -func BenchmarkVFS1TmpfsMountStat(b *testing.B) { - for _, depth := range depths { - b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { - ctx := contexttest.Context(b) - - // Create VFS. - tmpfsFS, ok := fs.FindFilesystem("tmpfs") - if !ok { - b.Fatalf("failed to find tmpfs filesystem type") - } - rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil) - if err != nil { - b.Fatalf("failed to create tmpfs root mount: %v", err) - } - mntns, err := fs.NewMountNamespace(ctx, rootInode) - if err != nil { - b.Fatalf("failed to create mount namespace: %v", err) - } - defer mntns.DecRef(ctx) - - var filePathBuilder strings.Builder - filePathBuilder.WriteByte('/') - - // Create and mount the submount. - root := mntns.Root() - defer root.DecRef(ctx) - if err := root.Inode.CreateDirectory(ctx, root, mountPointName, fs.FilePermsFromMode(0755)); err != nil { - b.Fatalf("failed to create mount point: %v", err) - } - mountPoint, err := root.Walk(ctx, root, mountPointName) - if err != nil { - b.Fatalf("failed to walk to mount point: %v", err) - } - defer mountPoint.DecRef(ctx) - submountInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil) - if err != nil { - b.Fatalf("failed to create tmpfs submount: %v", err) - } - if err := mntns.Mount(ctx, mountPoint, submountInode); err != nil { - b.Fatalf("failed to mount tmpfs submount: %v", err) - } - filePathBuilder.WriteString(mountPointName) - filePathBuilder.WriteByte('/') - - // Create nested directories with given depth. - d, err := root.Walk(ctx, root, mountPointName) - if err != nil { - b.Fatalf("failed to walk to mount root: %v", err) - } - defer d.DecRef(ctx) - for i := depth; i > 0; i-- { - name := fmt.Sprintf("%d", i) - if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil { - b.Fatalf("failed to create directory %q: %v", name, err) - } - next, err := d.Walk(ctx, root, name) - if err != nil { - b.Fatalf("failed to walk to directory %q: %v", name, err) - } - d.DecRef(ctx) - d = next - filePathBuilder.WriteString(name) - filePathBuilder.WriteByte('/') - } - - // Create the file that will be stat'd. - file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644)) - if err != nil { - b.Fatalf("failed to create file %q: %v", filename, err) - } - file.DecRef(ctx) - filePathBuilder.WriteString(filename) - filePath := filePathBuilder.String() - - dirPath := false - runtime.GC() - b.ResetTimer() - for i := 0; i < b.N; i++ { - err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error { - if dirPath && !fs.IsDir(d.Inode.StableAttr) { - return linuxerr.ENOTDIR - } - uattr, err := d.Inode.UnstableAttr(ctx) - if err != nil { - return err - } - // Sanity check. - if uattr.Perms.User.Execute { - b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode()) - } - return nil - }) - if err != nil { - b.Fatalf("stat(%q) failed: %v", filePath, err) - } - } - // Don't include deferred cleanup in benchmark time. - b.StopTimer() - }) - } -} - func BenchmarkVFS2TmpfsMountStat(b *testing.B) { for _, depth := range depths { b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { diff --git a/pkg/sentry/fsimpl/tmpfs/regular_file_test.go b/pkg/sentry/fsimpl/tmpfs/regular_file_test.go index d32bedc67..6f8b83566 100644 --- a/pkg/sentry/fsimpl/tmpfs/regular_file_test.go +++ b/pkg/sentry/fsimpl/tmpfs/regular_file_test.go @@ -23,7 +23,7 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/usermem" ) diff --git a/pkg/sentry/fs/user/BUILD b/pkg/sentry/fsimpl/user/BUILD similarity index 100% rename from pkg/sentry/fs/user/BUILD rename to pkg/sentry/fsimpl/user/BUILD diff --git a/pkg/sentry/fs/user/path.go b/pkg/sentry/fsimpl/user/path.go similarity index 100% rename from pkg/sentry/fs/user/path.go rename to pkg/sentry/fsimpl/user/path.go diff --git a/pkg/sentry/fs/user/user.go b/pkg/sentry/fsimpl/user/user.go similarity index 100% rename from pkg/sentry/fs/user/user.go rename to pkg/sentry/fsimpl/user/user.go diff --git a/pkg/sentry/fs/user/user_test.go b/pkg/sentry/fsimpl/user/user_test.go similarity index 100% rename from pkg/sentry/fs/user/user_test.go rename to pkg/sentry/fsimpl/user/user_test.go diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index cd74c613b..05c42e2ac 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -319,10 +319,9 @@ go_library( "//pkg/sentry/arch", "//pkg/sentry/device", "//pkg/sentry/fs", - "//pkg/sentry/fs/lock", - "//pkg/sentry/fs/timerfd", "//pkg/sentry/fsbridge", "//pkg/sentry/fsimpl/kernfs", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsimpl/mqfs", "//pkg/sentry/fsimpl/pipefs", "//pkg/sentry/fsimpl/sockfs", diff --git a/pkg/sentry/kernel/epoll/BUILD b/pkg/sentry/kernel/epoll/BUILD deleted file mode 100644 index 7e08f22a5..000000000 --- a/pkg/sentry/kernel/epoll/BUILD +++ /dev/null @@ -1,77 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") -load("//tools/go_generics:defs.bzl", "go_template_instance") - -package(licenses = ["notice"]) - -go_template_instance( - name = "epoll_mutex", - out = "epoll_mutex.go", - package = "epoll", - prefix = "epoll", - substrs = { - "genericMark": "epoll", - }, - template = "//pkg/sync/locking:generic_mutex", -) - -go_template_instance( - name = "epoll_list_mutex", - out = "epoll_list_mutex.go", - package = "epoll", - prefix = "epollList", - substrs = { - "genericMark": "epollList", - }, - template = "//pkg/sync/locking:generic_mutex", -) - -go_template_instance( - name = "epoll_list", - out = "epoll_list.go", - package = "epoll", - prefix = "pollEntry", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*pollEntry", - "Linker": "*pollEntry", - }, -) - -go_library( - name = "epoll", - srcs = [ - "epoll.go", - "epoll_list.go", - "epoll_list_mutex.go", - "epoll_mutex.go", - "epoll_state.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/refs", - "//pkg/sentry/fs", - "//pkg/sentry/fs/anon", - "//pkg/sentry/fs/fsutil", - "//pkg/sync", - "//pkg/sync/locking", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "epoll_test", - size = "small", - srcs = [ - "epoll_test.go", - ], - library = ":epoll", - deps = [ - "//pkg/sentry/contexttest", - "//pkg/sentry/fs/filetest", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/kernel/epoll/epoll.go b/pkg/sentry/kernel/epoll/epoll.go deleted file mode 100644 index 7ab08a73e..000000000 --- a/pkg/sentry/kernel/epoll/epoll.go +++ /dev/null @@ -1,551 +0,0 @@ -// Copyright 2018 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. - -// Package epoll provides an implementation of Linux's IO event notification -// facility. See epoll(7) for more details. -// -// Lock order: -// -// EventPoll.mu -// fdnotifier.notifier.mu -// EventPoll.listsMu -// unix.baseEndpoint.Mutex -package epoll - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/anon" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// EntryFlags is a bitmask that holds an entry's flags. -type EntryFlags int - -// Valid entry flags. -const ( - OneShot EntryFlags = 1 << iota - EdgeTriggered -) - -// FileIdentifier identifies a file. We cannot use just the FD because it could -// potentially be reassigned. We also cannot use just the file pointer because -// it is possible to have multiple entries for the same file object as long as -// they are created with different FDs (i.e., the FDs point to the same file). -// -// +stateify savable -type FileIdentifier struct { - File *fs.File `state:"wait"` - Fd int32 -} - -// pollEntry holds all the state associated with an event poll entry, that is, -// a file being observed by an event poll object. -// -// +stateify savable -type pollEntry struct { - pollEntryEntry - file *refs.WeakRef `state:"manual"` - id FileIdentifier `state:"wait"` - userData [2]int32 - waiter waiter.Entry - mask waiter.EventMask - flags EntryFlags - - epoll *EventPoll - - // We cannot save the current list pointer as it points into EventPoll - // struct, while state framework currently does not support such - // in-struct pointers. Instead, EventPoll will properly set this field - // in its loading logic. - curList *pollEntryList `state:"nosave"` - - readySeq uint32 -} - -// WeakRefGone implements refs.WeakRefUser.WeakRefGone. -// weakReferenceGone is called when the file in the weak reference is destroyed. -// The poll entry is removed in response to this. -func (p *pollEntry) WeakRefGone(ctx context.Context) { - p.epoll.RemoveEntry(ctx, p.id) -} - -// EventPoll holds all the state associated with an event poll object, that is, -// collection of files to observe and their current state. -// -// +stateify savable -type EventPoll struct { - fsutil.FilePipeSeek `state:"zerovalue"` - fsutil.FileNotDirReaddir `state:"zerovalue"` - fsutil.FileNoFsync `state:"zerovalue"` - fsutil.FileNoopFlush `state:"zerovalue"` - fsutil.FileNoIoctl `state:"zerovalue"` - fsutil.FileNoMMap `state:"zerovalue"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - // Wait queue is used to notify interested parties when the event poll - // object itself becomes readable or writable. - waiter.Queue - - // files is the map of all the files currently being observed, it is - // protected by mu. - mu epollMutex `state:"nosave"` - files map[FileIdentifier]*pollEntry - - // listsMu protects manipulation of the lists below. It needs to be a - // different lock to avoid circular lock acquisition order involving - // the wait queue mutexes and mu. The full order is mu, observed file - // wait queue mutex, then listsMu; this allows listsMu to be acquired - // when (*pollEntry).NotifyEvent is called. - // - // An entry is always in one of the following lists: - // readyList -- when there's a chance that it's ready to have - // events delivered to epoll waiters. Given that being - // ready is a transient state, the Readiness() and - // readEvents() functions always call the entry's file - // Readiness() function to confirm it's ready. - // waitingList -- when there's no chance that the entry is ready, - // so it's waiting for the (*pollEntry).NotifyEvent to be - // called on it before it gets moved to the readyList. - // disabledList -- when the entry is disabled. This happens when - // a one-shot entry gets delivered via readEvents(). - listsMu epollListMutex `state:"nosave"` - readyList pollEntryList - waitingList pollEntryList - disabledList pollEntryList - - // readySeq is used to detect calls to pollEntry.NotifyEvent() while - // eventsAvailable() or ReadEvents() are running with listsMu unlocked. - // readySeq is protected by both mu and listsMu; reading requires either - // mutex to be locked, but mutation requires both mutexes to be locked. - readySeq uint32 -} - -// cycleMu is used to serialize all the cycle checks. This is only used when -// an event poll file is added as an entry to another event poll. Such checks -// are serialized to avoid lock acquisition order inversion: if a thread is -// adding A to B, and another thread is adding B to A, each would acquire A's -// and B's mutexes in reverse order, and could cause deadlocks. Having this -// lock prevents this by allowing only one check at a time to happen. -// -// We do the cycle check to prevent callers from introducing potentially -// infinite recursions. If a caller were to add A to B and then B to A, for -// event poll A to know if it's readable, it would need to check event poll B, -// which in turn would need event poll A and so on indefinitely. -var cycleMu sync.Mutex - -// NewEventPoll allocates and initializes a new event poll object. -func NewEventPoll(ctx context.Context) *fs.File { - // name matches fs/eventpoll.c:epoll_create1. - dirent := fs.NewDirent(ctx, anon.NewInode(ctx), fmt.Sprintf("anon_inode:[eventpoll]")) - // Release the initial dirent reference after NewFile takes a reference. - defer dirent.DecRef(ctx) - return fs.NewFile(ctx, dirent, fs.FileFlags{}, &EventPoll{ - files: make(map[FileIdentifier]*pollEntry), - }) -} - -// Release implements fs.FileOperations.Release. -func (e *EventPoll) Release(ctx context.Context) { - // We need to take the lock now because files may be attempting to - // remove entries in parallel if they get destroyed. - e.mu.Lock() - defer e.mu.Unlock() - - // Go through all entries and clean up. - for _, entry := range e.files { - entry.id.File.EventUnregister(&entry.waiter) - entry.file.Drop(ctx) - } - e.files = nil -} - -// Read implements fs.FileOperations.Read. -func (*EventPoll) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, unix.ENOSYS -} - -// Write implements fs.FileOperations.Write. -func (*EventPoll) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, unix.ENOSYS -} - -// eventsAvailable determines if 'e' has events available for delivery. -func (e *EventPoll) eventsAvailable() bool { - e.mu.Lock() - defer e.mu.Unlock() - - // We can't call fs.File.Readiness() while holding e.listsMu due to lock - // ordering requirements. Instead, hold e.mu to prevent changes to the set - // of pollEntries, then temporarily move all pollEntries already on - // e.readyList to a local list that we can iterate without holding - // e.listsMu. pollEntry.curList is left set to &e.readyList so that - // pollEntry.NotifyEvent() doesn't touch pollEntryEntry. - var ( - readyList pollEntryList - waitingList pollEntryList - ) - e.listsMu.Lock() - readyList.PushBackList(&e.readyList) - e.readySeq++ - e.listsMu.Unlock() - if readyList.Empty() { - return false - } - defer func() { - notify := true - e.listsMu.Lock() - e.readyList.PushFrontList(&readyList) - var next *pollEntry - for entry := waitingList.Front(); entry != nil; entry = next { - next = entry.Next() - if entry.readySeq == e.readySeq { - // entry.NotifyEvent() was called while we were running. - waitingList.Remove(entry) - e.readyList.PushBack(entry) - notify = true - } else { - entry.curList = &e.waitingList - } - } - e.waitingList.PushBackList(&waitingList) - e.listsMu.Unlock() - if notify { - e.Notify(waiter.ReadableEvents) - } - }() - - for it := readyList.Front(); it != nil; { - entry := it - it = it.Next() - - // If the entry is ready, we know 'e' has at least one entry - // ready for delivery. - ready := entry.id.File.Readiness(entry.mask) - if ready != 0 { - return true - } - - // Entry is not ready, so move it to waiting list. entry.curList will - // be updated with e.listsMu locked in the deferred function above. - readyList.Remove(entry) - waitingList.PushBack(entry) - } - - return false -} - -// Readiness determines if the event poll object is currently readable (i.e., -// if there are pending events for delivery). -func (e *EventPoll) Readiness(mask waiter.EventMask) waiter.EventMask { - ready := waiter.EventMask(0) - - if (mask&waiter.ReadableEvents) != 0 && e.eventsAvailable() { - ready |= waiter.ReadableEvents - } - - return ready -} - -// ReadEvents returns up to max available events. -func (e *EventPoll) ReadEvents(max int) []linux.EpollEvent { - e.mu.Lock() - defer e.mu.Unlock() - - // We can't call fs.File.Readiness() while holding e.listsMu due to lock - // ordering requirements. Instead, hold e.mu to prevent changes to the set - // of pollEntries, then temporarily move all pollEntries already on - // e.readyList to a local list that we can iterate without holding - // e.listsMu. pollEntry.curList is left set to &e.readyList so that - // pollEntry.NotifyEvent() doesn't touch pollEntryEntry. - var ( - readyList pollEntryList - waitingList pollEntryList - requeueList pollEntryList - disabledList pollEntryList - ret []linux.EpollEvent - ) - e.listsMu.Lock() - readyList.PushBackList(&e.readyList) - e.readySeq++ - e.listsMu.Unlock() - if readyList.Empty() { - return nil - } - defer func() { - notify := false - e.listsMu.Lock() - e.readyList.PushFrontList(&readyList) - var next *pollEntry - for entry := waitingList.Front(); entry != nil; entry = next { - next = entry.Next() - if entry.readySeq == e.readySeq { - // entry.NotifyEvent() was called while we were running. - waitingList.Remove(entry) - e.readyList.PushBack(entry) - notify = true - } else { - entry.curList = &e.waitingList - } - } - e.readyList.PushBackList(&requeueList) - e.waitingList.PushBackList(&waitingList) - for entry := disabledList.Front(); entry != nil; entry = entry.Next() { - entry.curList = &e.disabledList - } - e.disabledList.PushBackList(&disabledList) - e.listsMu.Unlock() - if notify { - e.Notify(waiter.ReadableEvents) - } - }() - - // Go through all entries we believe may be ready. - for it := readyList.Front(); it != nil && len(ret) < max; { - entry := it - it = it.Next() - - // Check the entry's readiness. It it's not really ready, we - // just put it back in the waiting list and move on to the next - // entry. - ready := entry.id.File.Readiness(entry.mask) & entry.mask - if ready == 0 { - readyList.Remove(entry) - waitingList.PushBack(entry) - continue - } - - // Add event to the array that will be returned to caller. - ret = append(ret, linux.EpollEvent{ - Events: uint32(ready), - Data: entry.userData, - }) - - // The entry is consumed, so we must move it to the disabled - // list in case it's one-shot, or back to the wait list if it's - // edge-triggered. If it's neither, we leave it in the ready - // list so that its readiness can be checked the next time - // around; however, we must move it to the end of the list so - // that other events can be delivered as well. - readyList.Remove(entry) - if entry.flags&OneShot != 0 { - disabledList.PushBack(entry) - } else if entry.flags&EdgeTriggered != 0 { - waitingList.PushBack(entry) - } else { - requeueList.PushBack(entry) - } - } - - return ret -} - -// NotifyEvent implements waiter.EventListener.NotifyEvent. -// -// NotifyEvent is called when one of the files we're polling becomes ready. It -// moves said file to the readyList if it's currently in the waiting list. -func (p *pollEntry) NotifyEvent(waiter.EventMask) { - e := p.epoll - - e.listsMu.Lock() - - p.readySeq = e.readySeq - - if p.curList == &e.waitingList { - e.waitingList.Remove(p) - e.readyList.PushBack(p) - p.curList = &e.readyList - e.listsMu.Unlock() - - e.Notify(waiter.ReadableEvents) - return - } - - e.listsMu.Unlock() -} - -// initEntryReadiness initializes the entry's state with regards to its -// readiness by placing it in the appropriate list and registering for -// notifications. -func (e *EventPoll) initEntryReadiness(entry *pollEntry) { - // A new entry starts off in the waiting list. - e.listsMu.Lock() - e.waitingList.PushBack(entry) - entry.curList = &e.waitingList - e.listsMu.Unlock() - - // Register for event notifications. - f := entry.id.File - entry.waiter.Init(entry, entry.mask) - f.EventRegister(&entry.waiter) - - // Check if the file happens to already be in a ready state. - if ready := f.Readiness(entry.mask) & entry.mask; ready != 0 { - entry.NotifyEvent(ready) - } -} - -// observes checks if event poll object e is directly or indirectly observing -// event poll object ep. It uses a bounded recursive depth-first search. -func (e *EventPoll) observes(ep *EventPoll, depthLeft int) bool { - // If we reached the maximum depth, we'll consider that we found it - // because we don't want to allow chains that are too long. - if depthLeft <= 0 { - return true - } - - e.mu.Lock() - defer e.mu.Unlock() - - // Go through each observed file and check if it is or observes ep. - for id := range e.files { - f, ok := id.File.FileOperations.(*EventPoll) - if !ok { - continue - } - - if f == ep || f.observes(ep, depthLeft-1) { - return true - } - } - - return false -} - -// AddEntry adds a new file to the collection of files observed by e. -func (e *EventPoll) AddEntry(id FileIdentifier, flags EntryFlags, mask waiter.EventMask, data [2]int32) error { - // Acquire cycle check lock if another event poll is being added. - ep, ok := id.File.FileOperations.(*EventPoll) - if ok { - cycleMu.Lock() - defer cycleMu.Unlock() - } - - e.mu.Lock() - defer e.mu.Unlock() - - // Fail if the file already has an entry. - if _, ok := e.files[id]; ok { - return unix.EEXIST - } - - // Check if a cycle would be created. We use 4 as the limit because - // that's the value used by linux and we want to emulate it. - if ep != nil { - if e == ep { - return unix.EINVAL - } - - if ep.observes(e, 4) { - return unix.ELOOP - } - } - - // Create new entry and add it to map. - // - // N.B. Even though we are creating a weak reference here, we know it - // won't trigger a callback because we hold a reference to the file - // throughout the execution of this function. - entry := &pollEntry{ - id: id, - userData: data, - epoll: e, - flags: flags, - mask: mask, - } - entry.waiter.Init(entry, mask) - e.files[id] = entry - entry.file = refs.NewWeakRef(id.File, entry) - - // Initialize the readiness state of the new entry. - e.initEntryReadiness(entry) - - return nil -} - -// UpdateEntry updates the flags, mask and user data associated with a file that -// is already part of the collection of observed files. -func (e *EventPoll) UpdateEntry(id FileIdentifier, flags EntryFlags, mask waiter.EventMask, data [2]int32) error { - e.mu.Lock() - defer e.mu.Unlock() - - // Fail if the file doesn't have an entry. - entry, ok := e.files[id] - if !ok { - return unix.ENOENT - } - - // Unregister the old mask and remove entry from the list it's in, so - // (*pollEntry).NotifyEvent is guaranteed to not be called on this - // entry anymore. - entry.id.File.EventUnregister(&entry.waiter) - - // Remove entry from whatever list it's in. This ensure that no other - // threads have access to this entry as the only way left to find it - // is via e.files, but we hold e.mu, which prevents that. - e.listsMu.Lock() - entry.curList.Remove(entry) - e.listsMu.Unlock() - - // Initialize new readiness state. - entry.flags = flags - entry.mask = mask - entry.userData = data - e.initEntryReadiness(entry) - - return nil -} - -// RemoveEntry a files from the collection of observed files. -func (e *EventPoll) RemoveEntry(ctx context.Context, id FileIdentifier) error { - e.mu.Lock() - defer e.mu.Unlock() - - // Fail if the file doesn't have an entry. - entry, ok := e.files[id] - if !ok { - return unix.ENOENT - } - - // Unregister from file first so that no concurrent attempts will be - // made to manipulate the file. - entry.id.File.EventUnregister(&entry.waiter) - - // Remove from the current list. - e.listsMu.Lock() - entry.curList.Remove(entry) - entry.curList = nil - e.listsMu.Unlock() - - // Remove file from map, and drop weak reference. - delete(e.files, id) - entry.file.Drop(ctx) - - return nil -} - -// EventRegister implements waiter.Waitable. -func (e *EventPoll) EventRegister(entry *waiter.Entry) error { - e.Queue.EventRegister(entry) - return nil -} diff --git a/pkg/sentry/kernel/epoll/epoll_state.go b/pkg/sentry/kernel/epoll/epoll_state.go deleted file mode 100644 index 135a6d72c..000000000 --- a/pkg/sentry/kernel/epoll/epoll_state.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 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. - -package epoll - -import ( - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/waiter" -) - -// afterLoad is invoked by stateify. -func (p *pollEntry) afterLoad() { - p.file = refs.NewWeakRef(p.id.File, p) -} - -// afterLoad is invoked by stateify. -func (e *EventPoll) afterLoad() { - e.listsMu.Lock() - defer e.listsMu.Unlock() - - for _, ls := range []*pollEntryList{&e.waitingList, &e.readyList, &e.disabledList} { - for it := ls.Front(); it != nil; it = it.Next() { - it.curList = ls - } - } - - for it := e.waitingList.Front(); it != nil; { - entry := it - it = it.Next() - - if entry.id.File.Readiness(entry.mask) != 0 { - e.waitingList.Remove(entry) - e.readyList.PushBack(entry) - entry.curList = &e.readyList - e.Notify(waiter.ReadableEvents) - } - } -} diff --git a/pkg/sentry/kernel/epoll/epoll_test.go b/pkg/sentry/kernel/epoll/epoll_test.go deleted file mode 100644 index 8ef6cb3e7..000000000 --- a/pkg/sentry/kernel/epoll/epoll_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 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. - -package epoll - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs/filetest" - "gvisor.dev/gvisor/pkg/waiter" -) - -func TestFileDestroyed(t *testing.T) { - f := filetest.NewTestFile(t) - id := FileIdentifier{f, 12} - - ctx := contexttest.Context(t) - efile := NewEventPoll(ctx) - e := efile.FileOperations.(*EventPoll) - if err := e.AddEntry(id, 0, waiter.ReadableEvents, [2]int32{}); err != nil { - t.Fatalf("addEntry failed: %v", err) - } - - // Check that we get an event reported twice in a row. - evt := e.ReadEvents(1) - if len(evt) != 1 { - t.Fatalf("Unexpected number of ready events: want %v, got %v", 1, len(evt)) - } - - evt = e.ReadEvents(1) - if len(evt) != 1 { - t.Fatalf("Unexpected number of ready events: want %v, got %v", 1, len(evt)) - } - - // Destroy the file. Check that we get no more events. - f.DecRef(ctx) - - evt = e.ReadEvents(1) - if len(evt) != 0 { - t.Fatalf("Unexpected number of ready events: want %v, got %v", 0, len(evt)) - } - -} diff --git a/pkg/sentry/kernel/eventfd/BUILD b/pkg/sentry/kernel/eventfd/BUILD deleted file mode 100644 index f240a68aa..000000000 --- a/pkg/sentry/kernel/eventfd/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "eventfd", - srcs = ["eventfd.go"], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/fdnotifier", - "//pkg/hostarch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/anon", - "//pkg/sentry/fs/fsutil", - "//pkg/sync", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "eventfd_test", - size = "small", - srcs = ["eventfd_test.go"], - library = ":eventfd", - deps = [ - "//pkg/sentry/contexttest", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/kernel/eventfd/eventfd.go b/pkg/sentry/kernel/eventfd/eventfd.go deleted file mode 100644 index 20e654c00..000000000 --- a/pkg/sentry/kernel/eventfd/eventfd.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2018 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. - -// Package eventfd provides an implementation of Linux's file-based event -// notification. -package eventfd - -import ( - "fmt" - "math" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fdnotifier" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/anon" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// EventOperations represents an event with the semantics of Linux's file-based event -// notification (eventfd). Eventfds are usually internal to the Sentry but in certain -// situations they may be converted into a host-backed eventfd. -// -// +stateify savable -type EventOperations struct { - fsutil.FileNoopRelease `state:"nosave"` - fsutil.FilePipeSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - // Mutex that protects accesses to the fields of this event. - mu sync.Mutex `state:"nosave"` - - // Queue is used to notify interested parties when the event object - // becomes readable or writable. - wq waiter.Queue - - // val is the current value of the event counter. - val uint64 - - // semMode specifies whether the event is in "semaphore" mode. - semMode bool - - // hostfd indicates whether this eventfd is passed through to the host. - hostfd int -} - -// New creates a new event object with the supplied initial value and mode. -func New(ctx context.Context, initVal uint64, semMode bool) *fs.File { - // name matches fs/eventfd.c:eventfd_file_create. - dirent := fs.NewDirent(ctx, anon.NewInode(ctx), "anon_inode:[eventfd]") - // Release the initial dirent reference after NewFile takes a reference. - defer dirent.DecRef(ctx) - return fs.NewFile(ctx, dirent, fs.FileFlags{Read: true, Write: true}, &EventOperations{ - val: initVal, - semMode: semMode, - hostfd: -1, - }) -} - -// HostFD returns the host eventfd associated with this event. -func (e *EventOperations) HostFD() (int, error) { - e.mu.Lock() - defer e.mu.Unlock() - if e.hostfd >= 0 { - return e.hostfd, nil - } - - flags := linux.EFD_NONBLOCK - if e.semMode { - flags |= linux.EFD_SEMAPHORE - } - - fd, _, err := unix.Syscall(unix.SYS_EVENTFD2, uintptr(e.val), uintptr(flags), 0) - if err != 0 { - return -1, err - } - - if err := fdnotifier.AddFD(int32(fd), &e.wq); err != nil { - unix.Close(int(fd)) - return -1, err - } - - e.hostfd = int(fd) - return e.hostfd, nil -} - -// Release implements fs.FileOperations.Release. -func (e *EventOperations) Release(context.Context) { - e.mu.Lock() - defer e.mu.Unlock() - if e.hostfd >= 0 { - fdnotifier.RemoveFD(int32(e.hostfd)) - unix.Close(e.hostfd) - e.hostfd = -1 - } -} - -// Read implements fs.FileOperations.Read. -func (e *EventOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - if dst.NumBytes() < 8 { - return 0, unix.EINVAL - } - if err := e.read(ctx, dst); err != nil { - return 0, err - } - return 8, nil -} - -// Write implements fs.FileOperations.Write. -func (e *EventOperations) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - if src.NumBytes() < 8 { - return 0, unix.EINVAL - } - if err := e.write(ctx, src); err != nil { - return 0, err - } - return 8, nil -} - -// Must be called with e.mu locked. -func (e *EventOperations) hostRead(ctx context.Context, dst usermem.IOSequence) error { - var buf [8]byte - - if _, err := unix.Read(e.hostfd, buf[:]); err != nil { - if err == unix.EWOULDBLOCK { - return linuxerr.ErrWouldBlock - } - return err - } - - _, err := dst.CopyOut(ctx, buf[:]) - return err -} - -func (e *EventOperations) read(ctx context.Context, dst usermem.IOSequence) error { - e.mu.Lock() - - if e.hostfd >= 0 { - defer e.mu.Unlock() - return e.hostRead(ctx, dst) - } - - // We can't complete the read if the value is currently zero. - if e.val == 0 { - e.mu.Unlock() - return linuxerr.ErrWouldBlock - } - - // Update the value based on the mode the event is operating in. - var val uint64 - if e.semMode { - val = 1 - // Consistent with Linux, this is done even if writing to memory fails. - e.val-- - } else { - val = e.val - e.val = 0 - } - - e.mu.Unlock() - - // Notify writers. We do this even if we were already writable because - // it is possible that a writer is waiting to write the maximum value - // to the event. - e.wq.Notify(waiter.WritableEvents) - - var buf [8]byte - hostarch.ByteOrder.PutUint64(buf[:], val) - _, err := dst.CopyOut(ctx, buf[:]) - return err -} - -// Must be called with e.mu locked. -func (e *EventOperations) hostWrite(val uint64) error { - var buf [8]byte - hostarch.ByteOrder.PutUint64(buf[:], val) - _, err := unix.Write(e.hostfd, buf[:]) - if err == unix.EWOULDBLOCK { - return linuxerr.ErrWouldBlock - } - return err -} - -func (e *EventOperations) write(ctx context.Context, src usermem.IOSequence) error { - var buf [8]byte - if _, err := src.CopyIn(ctx, buf[:]); err != nil { - return err - } - val := hostarch.ByteOrder.Uint64(buf[:]) - - return e.Signal(val) -} - -// Signal is an internal function to signal the event fd. -func (e *EventOperations) Signal(val uint64) error { - if val == math.MaxUint64 { - return unix.EINVAL - } - - e.mu.Lock() - - if e.hostfd >= 0 { - defer e.mu.Unlock() - return e.hostWrite(val) - } - - // We only allow writes that won't cause the value to go over the max - // uint64 minus 1. - if val > math.MaxUint64-1-e.val { - e.mu.Unlock() - return linuxerr.ErrWouldBlock - } - - e.val += val - e.mu.Unlock() - - // Always trigger a notification. - e.wq.Notify(waiter.ReadableEvents) - - return nil -} - -// Readiness returns the ready events for the event fd. -func (e *EventOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - e.mu.Lock() - if e.hostfd >= 0 { - defer e.mu.Unlock() - return fdnotifier.NonBlockingPoll(int32(e.hostfd), mask) - } - - ready := waiter.EventMask(0) - if e.val > 0 { - ready |= waiter.ReadableEvents - } - - if e.val < math.MaxUint64-1 { - ready |= waiter.WritableEvents - } - e.mu.Unlock() - - return mask & ready -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (e *EventOperations) EventRegister(entry *waiter.Entry) error { - e.wq.EventRegister(entry) - - e.mu.Lock() - defer e.mu.Unlock() - if e.hostfd >= 0 { - if err := fdnotifier.UpdateFD(int32(e.hostfd)); err != nil { - return err - } - } - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (e *EventOperations) EventUnregister(entry *waiter.Entry) { - e.wq.EventUnregister(entry) - - e.mu.Lock() - defer e.mu.Unlock() - if e.hostfd >= 0 { - if err := fdnotifier.UpdateFD(int32(e.hostfd)); err != nil { - panic(fmt.Sprint("UpdateFD:", err)) - } - } -} diff --git a/pkg/sentry/kernel/eventfd/eventfd_test.go b/pkg/sentry/kernel/eventfd/eventfd_test.go deleted file mode 100644 index cf3e47461..000000000 --- a/pkg/sentry/kernel/eventfd/eventfd_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 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. - -package eventfd - -import ( - "testing" - - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -func TestEventfd(t *testing.T) { - initVals := []uint64{ - 0, - // Using a non-zero initial value verifies that writing to an - // eventfd signals when the eventfd's counter was already - // non-zero. - 343, - } - - for _, initVal := range initVals { - ctx := contexttest.Context(t) - - // Make a new event that is writable. - event := New(ctx, initVal, false) - - // Register a callback for a write event. - w, ch := waiter.NewChannelEntry(waiter.ReadableEvents) - event.EventRegister(&w) - defer event.EventUnregister(&w) - - data := []byte("00000124") - // Create and submit a write request. - n, err := event.Writev(ctx, usermem.BytesIOSequence(data)) - if err != nil { - t.Fatal(err) - } - if n != 8 { - t.Errorf("eventfd.write wrote %d bytes, not full int64", n) - } - - // Check if the callback fired due to the write event. - select { - case <-ch: - default: - t.Errorf("Didn't get notified of EventIn after write") - } - } -} - -func TestEventfdStat(t *testing.T) { - ctx := contexttest.Context(t) - - // Make a new event that is writable. - event := New(ctx, 0, false) - - // Create and submit an stat request. - uattr, err := event.Dirent.Inode.UnstableAttr(ctx) - if err != nil { - t.Fatalf("eventfd stat request failed: %v", err) - } - if uattr.Size != 0 { - t.Fatal("EventFD size should be 0") - } -} diff --git a/pkg/sentry/kernel/fd_table.go b/pkg/sentry/kernel/fd_table.go index 611ed83c0..c724f8ecf 100644 --- a/pkg/sentry/kernel/fd_table.go +++ b/pkg/sentry/kernel/fd_table.go @@ -25,7 +25,7 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/limits" "gvisor.dev/gvisor/pkg/sentry/vfs" ) diff --git a/pkg/sentry/kernel/signalfd/BUILD b/pkg/sentry/kernel/signalfd/BUILD deleted file mode 100644 index 13c762d89..000000000 --- a/pkg/sentry/kernel/signalfd/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//pkg/sync/locking:locking.bzl", "declare_mutex") - -licenses(["notice"]) - -declare_mutex( - name = "signal_operations_mutex", - out = "signal_operations_mutex.go", - package = "signalfd", - prefix = "operations", -) - -go_library( - name = "signalfd", - srcs = [ - "signal_operations_mutex.go", - "signalfd.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/sentry/fs", - "//pkg/sentry/fs/anon", - "//pkg/sentry/fs/fsutil", - "//pkg/sentry/kernel", - "//pkg/sync", - "//pkg/sync/locking", - "//pkg/usermem", - "//pkg/waiter", - ], -) diff --git a/pkg/sentry/kernel/signalfd/signalfd.go b/pkg/sentry/kernel/signalfd/signalfd.go deleted file mode 100644 index ed23f3312..000000000 --- a/pkg/sentry/kernel/signalfd/signalfd.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2019 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. - -// Package signalfd provides an implementation of signal file descriptors. -package signalfd - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/anon" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// SignalOperations represent a file with signalfd semantics. -// -// +stateify savable -type SignalOperations struct { - fsutil.FilePipeSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoIoctl `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileNoSplice `state:"nosave"` - fsutil.FileNoWrite `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - // target is the original task target. - // - // The semantics here are a bit broken. Linux will always use current - // for all reads, regardless of where the signalfd originated. We can't - // do exactly that because we need to plumb the context through - // EventRegister in order to support proper blocking behavior. This - // will undoubtedly become very complicated quickly. - target *kernel.Task - - // queue is the set of listeners. - queue waiter.Queue - - // mu protects below. - mu operationsMutex `state:"nosave"` - - // entry is the entry reigstered with the target. - entry waiter.Entry -} - -// New creates a new signalfd object with the supplied mask. -func New(ctx context.Context, mask linux.SignalSet) (*fs.File, error) { - t := kernel.TaskFromContext(ctx) - if t == nil { - // No task context? Not valid. - return nil, linuxerr.EINVAL - } - // name matches fs/signalfd.c:signalfd4. - dirent := fs.NewDirent(ctx, anon.NewInode(ctx), "anon_inode:[signalfd]") - s := &SignalOperations{target: t} - s.entry.Init(s, waiter.EventMask(mask)) - s.target.SignalRegister(&s.entry) - return fs.NewFile(ctx, dirent, fs.FileFlags{Read: true, Write: true}, s), nil -} - -// Release implements fs.FileOperations.Release. -func (s *SignalOperations) Release(context.Context) { - s.target.SignalUnregister(&s.entry) -} - -// Mask returns the signal mask. -func (s *SignalOperations) Mask() linux.SignalSet { - s.mu.Lock() - defer s.mu.Unlock() - return linux.SignalSet(s.entry.Mask()) -} - -// SetMask sets the signal mask. -func (s *SignalOperations) SetMask(mask linux.SignalSet) { - s.mu.Lock() - defer s.mu.Unlock() - s.target.SignalUnregister(&s.entry) - s.entry.Init(s, waiter.EventMask(mask)) - s.target.SignalRegister(&s.entry) -} - -// Read implements fs.FileOperations.Read. -func (s *SignalOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - // Attempt to dequeue relevant signals. - info, err := s.target.Sigtimedwait(s.Mask(), 0) - if err != nil { - // There must be no signal available. - return 0, linuxerr.ErrWouldBlock - } - - // Copy out the signal info using the specified format. - infoNative := linux.SignalfdSiginfo{ - Signo: uint32(info.Signo), - Errno: info.Errno, - Code: info.Code, - PID: uint32(info.PID()), - UID: uint32(info.UID()), - Status: info.Status(), - Overrun: uint32(info.Overrun()), - Addr: info.Addr(), - } - n, err := infoNative.WriteTo(dst.Writer(ctx)) - if err == usermem.ErrEndOfIOSequence { - // Partial copy-out ok. - err = nil - } - return n, err -} - -// Readiness implements waiter.Waitable.Readiness. -func (s *SignalOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - if mask&waiter.ReadableEvents != 0 && s.target.PendingSignals()&s.Mask() != 0 { - return waiter.ReadableEvents // Pending signals. - } - return 0 -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (s *SignalOperations) EventRegister(e *waiter.Entry) error { - s.queue.EventRegister(e) - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (s *SignalOperations) EventUnregister(e *waiter.Entry) { - s.queue.EventUnregister(e) -} - -// NotifyEvent implements waiter.EventListener.NotifyEvent. -func (s *SignalOperations) NotifyEvent(mask waiter.EventMask) { - s.queue.Notify(waiter.EventIn) -} diff --git a/pkg/sentry/mm/BUILD b/pkg/sentry/mm/BUILD index 55561fb42..ad89ac4c9 100644 --- a/pkg/sentry/mm/BUILD +++ b/pkg/sentry/mm/BUILD @@ -182,7 +182,6 @@ go_library( "//pkg/safecopy", "//pkg/safemem", "//pkg/sentry/arch", - "//pkg/sentry/fs/proc/seqfile", "//pkg/sentry/fsbridge", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/futex", diff --git a/pkg/sentry/mm/procfs.go b/pkg/sentry/mm/procfs.go index c5ff51809..f22e089a7 100644 --- a/pkg/sentry/mm/procfs.go +++ b/pkg/sentry/mm/procfs.go @@ -21,7 +21,6 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/fs/proc/seqfile" "gvisor.dev/gvisor/pkg/sentry/memmap" ) @@ -53,11 +52,6 @@ const ( "VmFlags: rd ex \n" ) -// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate. -func (mm *MemoryManager) NeedsUpdate(generation int64) bool { - return true -} - // MapsCallbackFuncForBuffer creates a /proc/[pid]/maps entry including the trailing newline. func (mm *MemoryManager) MapsCallbackFuncForBuffer(buf *bytes.Buffer) MapsCallbackFunc { return func(start, end hostarch.Addr, permissions hostarch.AccessType, private string, offset uint64, devMajor, devMinor uint32, inode uint64, path string) { @@ -108,45 +102,6 @@ func (mm *MemoryManager) ReadMapsDataInto(ctx context.Context, fn MapsCallbackFu } } -// ReadMapsSeqFileData is called by fs/proc.mapsData.ReadSeqFileData to -// implement /proc/[pid]/maps. -func (mm *MemoryManager) ReadMapsSeqFileData(ctx context.Context, handle seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - // FIXME(b/235153601): Need to replace RLockBypass with RLockBypass - // after fixing b/235153601. - mm.mappingMu.RLockBypass() - defer mm.mappingMu.RUnlockBypass() - var data []seqfile.SeqData - var start hostarch.Addr - if handle != nil { - start = *handle.(*hostarch.Addr) - } - for vseg := mm.vmas.LowerBoundSegment(start); vseg.Ok(); vseg = vseg.NextSegment() { - vmaAddr := vseg.End() - data = append(data, seqfile.SeqData{ - Buf: mm.vmaMapsEntryLocked(ctx, vseg), - Handle: &vmaAddr, - }) - } - - // We always emulate vsyscall, so advertise it here. Everything about a - // vsyscall region is static, so just hard code the maps entry since we - // don't have a real vma backing it. The vsyscall region is at the end of - // the virtual address space so nothing should be mapped after it (if - // something is really mapped in the tiny ~10 MiB segment afterwards, we'll - // get the sorting on the maps file wrong at worst; but that's not possible - // on any current platform). - // - // Artifically adjust the seqfile handle so we only output vsyscall entry once. - if start != vsyscallEnd { - vmaAddr := vsyscallEnd - data = append(data, seqfile.SeqData{ - Buf: []byte(vsyscallMapsEntry), - Handle: &vmaAddr, - }) - } - return data, 1 -} - // vmaMapsEntryLocked returns a /proc/[pid]/maps entry for the vma iterated by // vseg, including the trailing newline. // @@ -208,38 +163,6 @@ func (mm *MemoryManager) ReadSmapsDataInto(ctx context.Context, buf *bytes.Buffe } } -// ReadSmapsSeqFileData is called by fs/proc.smapsData.ReadSeqFileData to -// implement /proc/[pid]/smaps. -func (mm *MemoryManager) ReadSmapsSeqFileData(ctx context.Context, handle seqfile.SeqHandle) ([]seqfile.SeqData, int64) { - // FIXME(b/235153601): Need to replace RLockBypass with RLockBypass - // after fixing b/235153601. - mm.mappingMu.RLockBypass() - defer mm.mappingMu.RUnlockBypass() - var data []seqfile.SeqData - var start hostarch.Addr - if handle != nil { - start = *handle.(*hostarch.Addr) - } - for vseg := mm.vmas.LowerBoundSegment(start); vseg.Ok(); vseg = vseg.NextSegment() { - vmaAddr := vseg.End() - data = append(data, seqfile.SeqData{ - Buf: mm.vmaSmapsEntryLocked(ctx, vseg), - Handle: &vmaAddr, - }) - } - - // We always emulate vsyscall, so advertise it here. See - // ReadMapsSeqFileData for additional commentary. - if start != vsyscallEnd { - vmaAddr := vsyscallEnd - data = append(data, seqfile.SeqData{ - Buf: []byte(vsyscallSmapsEntry), - Handle: &vmaAddr, - }) - } - return data, 1 -} - // vmaSmapsEntryLocked returns a /proc/[pid]/smaps entry for the vma iterated // by vseg, including the trailing newline. // diff --git a/pkg/sentry/socket/unix/BUILD b/pkg/sentry/socket/unix/BUILD index f7d155f83..289ca0f6a 100644 --- a/pkg/sentry/socket/unix/BUILD +++ b/pkg/sentry/socket/unix/BUILD @@ -52,7 +52,7 @@ go_library( "//pkg/sentry/device", "//pkg/sentry/fs", "//pkg/sentry/fs/fsutil", - "//pkg/sentry/fs/lock", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsimpl/sockfs", "//pkg/sentry/inet", "//pkg/sentry/kernel", diff --git a/pkg/sentry/syscalls/linux/BUILD b/pkg/sentry/syscalls/linux/BUILD index 4d490ce26..93efe37e1 100644 --- a/pkg/sentry/syscalls/linux/BUILD +++ b/pkg/sentry/syscalls/linux/BUILD @@ -79,11 +79,11 @@ go_library( "//pkg/rand", "//pkg/safemem", "//pkg/sentry/arch", - "//pkg/sentry/fs/lock", "//pkg/sentry/fsbridge", "//pkg/sentry/fsimpl/eventfd", "//pkg/sentry/fsimpl/host", "//pkg/sentry/fsimpl/iouringfs", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsimpl/pipefs", "//pkg/sentry/fsimpl/signalfd", "//pkg/sentry/fsimpl/timerfd", diff --git a/pkg/sentry/syscalls/linux/sys_file.go b/pkg/sentry/syscalls/linux/sys_file.go index 0f4022640..c2c1da34d 100644 --- a/pkg/sentry/syscalls/linux/sys_file.go +++ b/pkg/sentry/syscalls/linux/sys_file.go @@ -24,7 +24,7 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD index 0975abecf..47f262ee3 100644 --- a/pkg/sentry/vfs/BUILD +++ b/pkg/sentry/vfs/BUILD @@ -164,7 +164,7 @@ go_library( "//pkg/safemem", "//pkg/sentry/arch", "//pkg/sentry/fs", - "//pkg/sentry/fs/lock", + "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsmetric", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/time", diff --git a/pkg/sentry/vfs/file_description.go b/pkg/sentry/vfs/file_description.go index 0c1d89056..270d3eafb 100644 --- a/pkg/sentry/vfs/file_description.go +++ b/pkg/sentry/vfs/file_description.go @@ -22,7 +22,7 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs/lock" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/fsmetric" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/memmap" diff --git a/pkg/sentry/vfs/file_description_impl_util.go b/pkg/sentry/vfs/file_description_impl_util.go index 1cd3fd0f6..b47d0c1b9 100644 --- a/pkg/sentry/vfs/file_description_impl_util.go +++ b/pkg/sentry/vfs/file_description_impl_util.go @@ -23,7 +23,7 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/arch" - fslock "gvisor.dev/gvisor/pkg/sentry/fs/lock" + fslock "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/usermem" diff --git a/pkg/sentry/fs/g3doc/fuse.md b/pkg/sentry/vfs/g3doc/fuse.md similarity index 100% rename from pkg/sentry/fs/g3doc/fuse.md rename to pkg/sentry/vfs/g3doc/fuse.md diff --git a/pkg/sentry/vfs/lock.go b/pkg/sentry/vfs/lock.go index c2c5bdcd1..7c2b64755 100644 --- a/pkg/sentry/vfs/lock.go +++ b/pkg/sentry/vfs/lock.go @@ -18,7 +18,7 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" - fslock "gvisor.dev/gvisor/pkg/sentry/fs/lock" + fslock "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" ) // FileLocks supports POSIX and BSD style locks, which correspond to fcntl(2) diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index fa6d83dfe..a0c117ef0 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -51,7 +51,6 @@ go_library( "//pkg/sentry/devices/ttydev", "//pkg/sentry/devices/tundev", "//pkg/sentry/fdimport", - "//pkg/sentry/fs/user", "//pkg/sentry/fsimpl/cgroupfs", "//pkg/sentry/fsimpl/devpts", "//pkg/sentry/fsimpl/devtmpfs", @@ -63,6 +62,7 @@ go_library( "//pkg/sentry/fsimpl/proc", "//pkg/sentry/fsimpl/sys", "//pkg/sentry/fsimpl/tmpfs", + "//pkg/sentry/fsimpl/user", "//pkg/sentry/inet", "//pkg/sentry/kernel", "//pkg/sentry/kernel:uncaught_signal_go_proto", diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index c184e5c3f..38aaae3e1 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -38,8 +38,8 @@ import ( "gvisor.dev/gvisor/pkg/refsvfs2" "gvisor.dev/gvisor/pkg/sentry/control" "gvisor.dev/gvisor/pkg/sentry/fdimport" - "gvisor.dev/gvisor/pkg/sentry/fs/user" "gvisor.dev/gvisor/pkg/sentry/fsimpl/host" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/user" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index 4528ea33d..2897ec729 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -32,7 +32,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/devices/memdev" "gvisor.dev/gvisor/pkg/sentry/devices/ttydev" "gvisor.dev/gvisor/pkg/sentry/devices/tundev" - "gvisor.dev/gvisor/pkg/sentry/fs/user" "gvisor.dev/gvisor/pkg/sentry/fsimpl/cgroupfs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/devpts" "gvisor.dev/gvisor/pkg/sentry/fsimpl/devtmpfs" @@ -43,6 +42,7 @@ import ( "gvisor.dev/gvisor/pkg/sentry/fsimpl/proc" "gvisor.dev/gvisor/pkg/sentry/fsimpl/sys" "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/user" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth"