From 1fa3c06f1e4347a43abe7ad7c195384b2f01af1a Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Mon, 21 Nov 2022 13:54:38 -0800 Subject: [PATCH] Delete VFS1 completely. - Delete pkg/sentry/fs/*. - Move pkg/sentry/fs/fsutil out of VFS1 directory and remove VFS1 components. - Remove remaining unused references to VFS1 from remaining codebase. - Rename/refactor code to avoid even referencing VFS2, unless necessary. - Rewrite VFS1-only tests to VFS2. Updates #1624 PiperOrigin-RevId: 490064269 --- nogo.yaml | 2 +- pkg/sentry/control/fs.go | 2 +- pkg/sentry/control/lifecycle.go | 1 - pkg/sentry/control/proc.go | 4 +- pkg/sentry/fdimport/fdimport.go | 2 +- pkg/sentry/fs/BUILD | 125 -- pkg/sentry/fs/README.md | 229 --- pkg/sentry/fs/attr.go | 507 ------ pkg/sentry/fs/context.go | 138 -- pkg/sentry/fs/copy_up.go | 442 ----- pkg/sentry/fs/dentry.go | 234 --- pkg/sentry/fs/dirent.go | 1567 ----------------- pkg/sentry/fs/dirent_cache.go | 175 -- pkg/sentry/fs/dirent_cache_limiter.go | 56 - pkg/sentry/fs/dirent_state.go | 77 - pkg/sentry/fs/file.go | 538 ------ pkg/sentry/fs/file_operations.go | 177 -- pkg/sentry/fs/file_overlay.go | 555 ------ pkg/sentry/fs/file_state.go | 34 - pkg/sentry/fs/filesystems.go | 160 -- pkg/sentry/fs/flags.go | 138 -- pkg/sentry/fs/fs.go | 174 -- pkg/sentry/fs/fsutil/file.go | 400 ----- pkg/sentry/fs/fsutil/fsutil.go | 24 - pkg/sentry/fs/fsutil/host_mappable.go | 231 --- pkg/sentry/fs/fsutil/inode.go | 532 ------ pkg/sentry/fs/fsutil/inode_cached.go | 1076 ----------- pkg/sentry/fs/fsutil/inode_cached_test.go | 390 ---- pkg/sentry/fs/inode.go | 475 ----- pkg/sentry/fs/inode_inotify.go | 171 -- pkg/sentry/fs/inode_operations.go | 326 ---- pkg/sentry/fs/inode_overlay.go | 749 -------- pkg/sentry/fs/inotify.go | 359 ---- pkg/sentry/fs/inotify_event.go | 140 -- pkg/sentry/fs/inotify_watch.go | 135 -- pkg/sentry/fs/mock.go | 176 -- pkg/sentry/fs/mount.go | 285 --- pkg/sentry/fs/mount_overlay.go | 151 -- pkg/sentry/fs/mounts.go | 622 ------- pkg/sentry/fs/offset.go | 65 - pkg/sentry/fs/overlay.go | 318 ---- pkg/sentry/fs/path.go | 119 -- pkg/sentry/fs/restore.go | 78 - pkg/sentry/fs/save.go | 77 - pkg/sentry/fs/seek.go | 43 - pkg/sentry/fs/splice.go | 169 -- pkg/sentry/fs/sync.go | 43 - pkg/sentry/fsbridge/BUILD | 3 - pkg/sentry/fsbridge/bridge.go | 1 + pkg/sentry/fsbridge/fs.go | 181 -- pkg/sentry/fsbridge/vfs.go | 4 +- pkg/sentry/fsimpl/devpts/BUILD | 1 - pkg/sentry/fsimpl/fuse/dev_test.go | 4 +- pkg/sentry/fsimpl/fuse/fusefs.go | 2 +- pkg/sentry/fsimpl/gofer/BUILD | 2 +- pkg/sentry/fsimpl/gofer/gofer.go | 2 +- pkg/sentry/fsimpl/gofer/regular_file.go | 2 +- pkg/sentry/fsimpl/gofer/special_file.go | 2 +- pkg/sentry/fsimpl/host/BUILD | 2 +- pkg/sentry/fsimpl/iouringfs/iouringfs.go | 2 +- pkg/sentry/fsimpl/kernfs/BUILD | 2 +- pkg/sentry/fsimpl/kernfs/kernfs.go | 2 +- pkg/sentry/fsimpl/kernfs/mmap_util.go | 2 +- pkg/sentry/fsimpl/mqfs/BUILD | 1 - pkg/sentry/fsimpl/mqfs/registry.go | 11 +- pkg/sentry/fsimpl/proc/task_fds.go | 2 +- pkg/sentry/fsimpl/proc/task_files.go | 8 +- pkg/sentry/fsimpl/proc/task_net.go | 28 +- pkg/sentry/fsimpl/proc/tasks_test.go | 12 +- pkg/sentry/fsimpl/sys/sys_test.go | 4 +- pkg/sentry/fsimpl/testutil/kernel.go | 2 +- pkg/sentry/fsimpl/tmpfs/BUILD | 6 +- pkg/sentry/fsimpl/tmpfs/benchmark_test.go | 46 +- pkg/sentry/fsimpl/tmpfs/regular_file.go | 20 +- pkg/sentry/fsmetric/fsmetric.go | 6 +- pkg/sentry/{fs => }/fsutil/BUILD | 13 - pkg/sentry/{fs => }/fsutil/README.md | 0 pkg/sentry/{fs => }/fsutil/dirty_set.go | 0 pkg/sentry/{fs => }/fsutil/dirty_set_test.go | 0 pkg/sentry/{fs => }/fsutil/file_range_set.go | 0 pkg/sentry/{fs => }/fsutil/frame_ref_set.go | 0 .../save_restore.go => fsutil/fsutil.go} | 9 +- .../{fs => }/fsutil/host_file_mapper.go | 0 .../{fs => }/fsutil/host_file_mapper_state.go | 0 .../fsutil/host_file_mapper_unsafe.go | 0 pkg/sentry/kernel/BUILD | 14 - pkg/sentry/kernel/fasync/BUILD | 1 - pkg/sentry/kernel/fasync/fasync.go | 13 +- pkg/sentry/kernel/fd_table.go | 368 +--- pkg/sentry/kernel/fd_table_test.go | 60 +- pkg/sentry/kernel/fd_table_unsafe.go | 95 +- pkg/sentry/kernel/fs_context.go | 156 +- pkg/sentry/kernel/ipc/BUILD | 2 +- pkg/sentry/kernel/ipc/object.go | 56 +- pkg/sentry/kernel/ipc/registry.go | 4 +- pkg/sentry/kernel/kernel.go | 188 +- pkg/sentry/kernel/mq/BUILD | 1 - pkg/sentry/kernel/mq/mq.go | 41 +- pkg/sentry/kernel/msgqueue/BUILD | 2 +- pkg/sentry/kernel/msgqueue/msgqueue.go | 30 +- pkg/sentry/kernel/pipe/BUILD | 10 +- pkg/sentry/kernel/pipe/node.go | 121 -- pkg/sentry/kernel/pipe/node_test.go | 295 ---- pkg/sentry/kernel/pipe/pipe.go | 63 - pkg/sentry/kernel/pipe/pipe_test.go | 181 +- pkg/sentry/kernel/pipe/reader.go | 43 - pkg/sentry/kernel/pipe/reader_writer.go | 67 - pkg/sentry/kernel/pipe/writer.go | 43 - pkg/sentry/kernel/seccheck.go | 4 +- pkg/sentry/kernel/semaphore/BUILD | 2 +- pkg/sentry/kernel/semaphore/semaphore.go | 44 +- pkg/sentry/kernel/shm/BUILD | 2 +- pkg/sentry/kernel/shm/shm.go | 33 +- pkg/sentry/kernel/task.go | 64 +- pkg/sentry/kernel/task_clone.go | 5 +- pkg/sentry/kernel/task_context.go | 11 +- pkg/sentry/kernel/task_exec.go | 3 +- pkg/sentry/kernel/thread_group.go | 15 +- pkg/sentry/mm/special_mappable.go | 25 - pkg/sentry/socket/BUILD | 3 - pkg/sentry/socket/control/BUILD | 2 - pkg/sentry/socket/control/control.go | 217 ++- pkg/sentry/socket/control/control_vfs2.go | 128 -- pkg/sentry/socket/hostinet/BUILD | 4 - pkg/sentry/socket/hostinet/socket.go | 243 ++- pkg/sentry/socket/hostinet/socket_unsafe.go | 4 +- pkg/sentry/socket/hostinet/socket_vfs2.go | 199 --- pkg/sentry/socket/netlink/BUILD | 4 - pkg/sentry/socket/netlink/provider.go | 27 +- pkg/sentry/socket/netlink/provider_vfs2.go | 70 - pkg/sentry/socket/netlink/socket.go | 176 +- pkg/sentry/socket/netlink/socket_vfs2.go | 160 -- pkg/sentry/socket/netstack/BUILD | 4 - pkg/sentry/socket/netstack/netstack.go | 499 +++--- pkg/sentry/socket/netstack/netstack_state.go | 4 +- pkg/sentry/socket/netstack/netstack_vfs2.go | 269 --- pkg/sentry/socket/netstack/provider.go | 15 +- pkg/sentry/socket/netstack/provider_vfs2.go | 142 -- pkg/sentry/socket/socket.go | 124 +- pkg/sentry/socket/unix/BUILD | 20 +- pkg/sentry/socket/unix/unix.go | 639 +++---- pkg/sentry/socket/unix/unix_vfs2.go | 421 ----- pkg/sentry/strace/strace.go | 6 +- pkg/sentry/syscalls/linux/path.go | 6 +- pkg/sentry/syscalls/linux/points.go | 4 +- pkg/sentry/syscalls/linux/sys_aio.go | 4 +- pkg/sentry/syscalls/linux/sys_epoll.go | 10 +- pkg/sentry/syscalls/linux/sys_eventfd.go | 2 +- pkg/sentry/syscalls/linux/sys_file.go | 66 +- pkg/sentry/syscalls/linux/sys_getdents.go | 2 +- pkg/sentry/syscalls/linux/sys_inotify.go | 4 +- pkg/sentry/syscalls/linux/sys_iouring.go | 4 +- pkg/sentry/syscalls/linux/sys_mmap.go | 2 +- pkg/sentry/syscalls/linux/sys_mount.go | 4 +- pkg/sentry/syscalls/linux/sys_mq.go | 2 +- pkg/sentry/syscalls/linux/sys_pipe.go | 4 +- pkg/sentry/syscalls/linux/sys_poll.go | 4 +- pkg/sentry/syscalls/linux/sys_prctl.go | 2 +- pkg/sentry/syscalls/linux/sys_read_write.go | 26 +- pkg/sentry/syscalls/linux/sys_signal.go | 4 +- pkg/sentry/syscalls/linux/sys_socket.go | 90 +- pkg/sentry/syscalls/linux/sys_splice.go | 12 +- pkg/sentry/syscalls/linux/sys_stat.go | 14 +- pkg/sentry/syscalls/linux/sys_sync.go | 6 +- pkg/sentry/syscalls/linux/sys_thread.go | 8 +- pkg/sentry/syscalls/linux/sys_timerfd.go | 6 +- pkg/sentry/syscalls/linux/sys_xattr.go | 8 +- pkg/sentry/vfs/BUILD | 1 - pkg/sentry/vfs/README.md | 61 - pkg/sentry/vfs/g3doc/fuse.md | 18 +- pkg/sentry/vfs/g3doc/inotify.md | 10 +- pkg/sentry/vfs/lock.go | 2 +- runsc/boot/loader.go | 4 +- runsc/boot/procfs/dump.go | 8 +- runsc/boot/vfs.go | 2 +- test/fuse/README.md | 2 +- 176 files changed, 1587 insertions(+), 17130 deletions(-) delete mode 100644 pkg/sentry/fs/BUILD delete mode 100644 pkg/sentry/fs/README.md delete mode 100644 pkg/sentry/fs/attr.go delete mode 100644 pkg/sentry/fs/context.go delete mode 100644 pkg/sentry/fs/copy_up.go delete mode 100644 pkg/sentry/fs/dentry.go delete mode 100644 pkg/sentry/fs/dirent.go delete mode 100644 pkg/sentry/fs/dirent_cache.go delete mode 100644 pkg/sentry/fs/dirent_cache_limiter.go delete mode 100644 pkg/sentry/fs/dirent_state.go delete mode 100644 pkg/sentry/fs/file.go delete mode 100644 pkg/sentry/fs/file_operations.go delete mode 100644 pkg/sentry/fs/file_overlay.go delete mode 100644 pkg/sentry/fs/file_state.go delete mode 100644 pkg/sentry/fs/filesystems.go delete mode 100644 pkg/sentry/fs/flags.go delete mode 100644 pkg/sentry/fs/fs.go delete mode 100644 pkg/sentry/fs/fsutil/file.go delete mode 100644 pkg/sentry/fs/fsutil/fsutil.go delete mode 100644 pkg/sentry/fs/fsutil/host_mappable.go delete mode 100644 pkg/sentry/fs/fsutil/inode.go delete mode 100644 pkg/sentry/fs/fsutil/inode_cached.go delete mode 100644 pkg/sentry/fs/fsutil/inode_cached_test.go delete mode 100644 pkg/sentry/fs/inode.go delete mode 100644 pkg/sentry/fs/inode_inotify.go delete mode 100644 pkg/sentry/fs/inode_operations.go delete mode 100644 pkg/sentry/fs/inode_overlay.go delete mode 100644 pkg/sentry/fs/inotify.go delete mode 100644 pkg/sentry/fs/inotify_event.go delete mode 100644 pkg/sentry/fs/inotify_watch.go delete mode 100644 pkg/sentry/fs/mock.go delete mode 100644 pkg/sentry/fs/mount.go delete mode 100644 pkg/sentry/fs/mount_overlay.go delete mode 100644 pkg/sentry/fs/mounts.go delete mode 100644 pkg/sentry/fs/offset.go delete mode 100644 pkg/sentry/fs/overlay.go delete mode 100644 pkg/sentry/fs/path.go delete mode 100644 pkg/sentry/fs/restore.go delete mode 100644 pkg/sentry/fs/save.go delete mode 100644 pkg/sentry/fs/seek.go delete mode 100644 pkg/sentry/fs/splice.go delete mode 100644 pkg/sentry/fs/sync.go delete mode 100644 pkg/sentry/fsbridge/fs.go rename pkg/sentry/{fs => }/fsutil/BUILD (87%) rename pkg/sentry/{fs => }/fsutil/README.md (100%) rename pkg/sentry/{fs => }/fsutil/dirty_set.go (100%) rename pkg/sentry/{fs => }/fsutil/dirty_set_test.go (100%) rename pkg/sentry/{fs => }/fsutil/file_range_set.go (100%) rename pkg/sentry/{fs => }/fsutil/frame_ref_set.go (100%) rename pkg/sentry/{socket/hostinet/save_restore.go => fsutil/fsutil.go} (80%) rename pkg/sentry/{fs => }/fsutil/host_file_mapper.go (100%) rename pkg/sentry/{fs => }/fsutil/host_file_mapper_state.go (100%) rename pkg/sentry/{fs => }/fsutil/host_file_mapper_unsafe.go (100%) delete mode 100644 pkg/sentry/kernel/pipe/node.go delete mode 100644 pkg/sentry/kernel/pipe/node_test.go delete mode 100644 pkg/sentry/kernel/pipe/reader.go delete mode 100644 pkg/sentry/kernel/pipe/reader_writer.go delete mode 100644 pkg/sentry/kernel/pipe/writer.go delete mode 100644 pkg/sentry/socket/control/control_vfs2.go delete mode 100644 pkg/sentry/socket/hostinet/socket_vfs2.go delete mode 100644 pkg/sentry/socket/netlink/provider_vfs2.go delete mode 100644 pkg/sentry/socket/netlink/socket_vfs2.go delete mode 100644 pkg/sentry/socket/netstack/netstack_vfs2.go delete mode 100644 pkg/sentry/socket/netstack/provider_vfs2.go delete mode 100644 pkg/sentry/socket/unix/unix_vfs2.go diff --git a/nogo.yaml b/nogo.yaml index 280e46e4e..911db71c9 100644 --- a/nogo.yaml +++ b/nogo.yaml @@ -164,7 +164,7 @@ analyzers: - "pkg/flipcall/.*_unsafe.go" # Special case. - pkg/gohacks/gohacks_unsafe.go # Special case. - pkg/ring0/pagetables/allocator_unsafe.go # Special case. - - pkg/sentry/fs/fsutil/host_file_mapper_unsafe.go # Special case. + - pkg/sentry/fsutil/host_file_mapper_unsafe.go # Special case. - pkg/sentry/platform/kvm/bluepill_unsafe.go # Special case. - pkg/sentry/platform/kvm/machine_unsafe.go # Special case. - pkg/sentry/platform/safecopy/safecopy_unsafe.go # Special case. diff --git a/pkg/sentry/control/fs.go b/pkg/sentry/control/fs.go index 9121eed3b..0a30606a4 100644 --- a/pkg/sentry/control/fs.go +++ b/pkg/sentry/control/fs.go @@ -75,7 +75,7 @@ func (f *fdReader) Read(p []byte) (int, error) { func cat(k *kernel.Kernel, path string, output *os.File) error { ctx := k.SupervisorContext() creds := auth.NewRootCredentials(k.RootUserNamespace()) - mns := k.GlobalInit().Leader().MountNamespaceVFS2() + mns := k.GlobalInit().Leader().MountNamespace() root := mns.Root() defer root.DecRef(ctx) diff --git a/pkg/sentry/control/lifecycle.go b/pkg/sentry/control/lifecycle.go index 8fc832eec..78c569dd7 100644 --- a/pkg/sentry/control/lifecycle.go +++ b/pkg/sentry/control/lifecycle.go @@ -251,7 +251,6 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error { } initArgs.FDTable = fdTable - // VFS2 is supported in multi-container mode by default. l.mu.RLock() mntns, ok := l.MountNamespacesMap[initArgs.ContainerID] if !ok { diff --git a/pkg/sentry/control/proc.go b/pkg/sentry/control/proc.go index d78a8abc4..89bc60f8f 100644 --- a/pkg/sentry/control/proc.go +++ b/pkg/sentry/control/proc.go @@ -168,7 +168,7 @@ func (proc *Proc) execAsync(args *ExecArgs) (*kernel.ThreadGroup, kernel.ThreadI PIDNamespace: pidns, } if initArgs.MountNamespace != nil { - // initArgs must hold a reference on MountNamespaceVFS2, which will + // initArgs must hold a reference on MountNamespace, which will // be donated to the new process in CreateProcess. initArgs.MountNamespace.IncRef() } @@ -180,7 +180,7 @@ func (proc *Proc) execAsync(args *ExecArgs) (*kernel.ThreadGroup, kernel.ThreadI // Set initArgs so that 'ctx' returns the namespace. // // Add a reference to the namespace, which is transferred to the new process. - initArgs.MountNamespace = proc.Kernel.GlobalInit().Leader().MountNamespaceVFS2() + initArgs.MountNamespace = proc.Kernel.GlobalInit().Leader().MountNamespace() initArgs.MountNamespace.IncRef() } resolved, err := user.ResolveExecutablePath(ctx, &initArgs) diff --git a/pkg/sentry/fdimport/fdimport.go b/pkg/sentry/fdimport/fdimport.go index 49157d094..cbe4d2491 100644 --- a/pkg/sentry/fdimport/fdimport.go +++ b/pkg/sentry/fdimport/fdimport.go @@ -81,7 +81,7 @@ func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth hostFD.Release() // FD is transfered to host FD. } - if err := fdTable.NewFDAtVFS2(ctx, int32(appFD), appFile, kernel.FDFlags{}); err != nil { + if err := fdTable.NewFDAt(ctx, int32(appFD), appFile, kernel.FDFlags{}); err != nil { return nil, err } } diff --git a/pkg/sentry/fs/BUILD b/pkg/sentry/fs/BUILD deleted file mode 100644 index 5a6795556..000000000 --- a/pkg/sentry/fs/BUILD +++ /dev/null @@ -1,125 +0,0 @@ -load("//tools:defs.bzl", "go_library") -load("//tools/go_generics:defs.bzl", "go_template_instance") -load("//pkg/sync/locking:locking.bzl", "declare_mutex") - -package(licenses = ["notice"]) - -go_library( - name = "fs", - srcs = [ - "attr.go", - "context.go", - "copy_up.go", - "dentry.go", - "dirent.go", - "dirent_cache.go", - "dirent_cache_limiter.go", - "dirent_list.go", - "dirent_state.go", - "event_list.go", - "file.go", - "file_operations.go", - "file_overlay.go", - "file_state.go", - "filesystems.go", - "flags.go", - "fs.go", - "inode.go", - "inode_inotify.go", - "inode_operations.go", - "inode_overlay.go", - "inotify.go", - "inotify_event.go", - "inotify_event_mutex.go", - "inotify_mutex.go", - "inotify_watch.go", - "mock.go", - "mount.go", - "mount_overlay.go", - "mounts.go", - "namespace_mutex.go", - "offset.go", - "overlay.go", - "path.go", - "restore.go", - "save.go", - "seek.go", - "splice.go", - "sync.go", - ], - visibility = ["//pkg/sentry:internal"], - deps = [ - "//pkg/abi/linux", - "//pkg/atomicbitops", - "//pkg/context", - "//pkg/errors/linuxerr", - "//pkg/hostarch", - "//pkg/log", - "//pkg/p9", - "//pkg/refs", - "//pkg/secio", - "//pkg/sentry/arch", - "//pkg/sentry/device", - "//pkg/sentry/fsimpl/lock", - "//pkg/sentry/fsmetric", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/time", - "//pkg/sentry/limits", - "//pkg/sentry/memmap", - "//pkg/sentry/platform", - "//pkg/sentry/socket/unix/transport", - "//pkg/sentry/uniqueid", - "//pkg/sentry/usage", - "//pkg/state", - "//pkg/sync", - "//pkg/sync/locking", - "//pkg/usermem", - "//pkg/waiter", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_template_instance( - name = "dirent_list", - out = "dirent_list.go", - package = "fs", - prefix = "dirent", - template = "//pkg/ilist:generic_list", - types = { - "Linker": "*Dirent", - "Element": "*Dirent", - }, -) - -go_template_instance( - name = "event_list", - out = "event_list.go", - package = "fs", - prefix = "event", - template = "//pkg/ilist:generic_list", - types = { - "Linker": "*Event", - "Element": "*Event", - }, -) - -declare_mutex( - name = "namespace_mutex", - out = "namespace_mutex.go", - package = "fs", - prefix = "namespace", -) - -declare_mutex( - name = "inotify_event_mutex", - out = "inotify_event_mutex.go", - package = "fs", - prefix = "inotifyEvent", -) - -declare_mutex( - name = "inotify_mutex", - out = "inotify_mutex.go", - package = "fs", - prefix = "inotify", -) diff --git a/pkg/sentry/fs/README.md b/pkg/sentry/fs/README.md deleted file mode 100644 index db4a1b730..000000000 --- a/pkg/sentry/fs/README.md +++ /dev/null @@ -1,229 +0,0 @@ -This package provides an implementation of the Linux virtual filesystem. - -[TOC] - -## Overview - -- An `fs.Dirent` caches an `fs.Inode` in memory at a path in the VFS, giving - the `fs.Inode` a relative position with respect to other `fs.Inode`s. - -- If an `fs.Dirent` is referenced by two file descriptors, then those file - descriptors are coherent with each other: they depend on the same - `fs.Inode`. - -- A mount point is an `fs.Dirent` for which `fs.Dirent.mounted` is true. It - exposes the root of a mounted filesystem. - -- The `fs.Inode` produced by a registered filesystem on mount(2) owns an - `fs.MountedFilesystem` from which other `fs.Inode`s will be looked up. For a - remote filesystem, the `fs.MountedFilesystem` owns the connection to that - remote filesystem. - -- In general: - -``` -fs.Inode <------------------------------ -| | -| | -produced by | -exactly one | -| responsible for the -| virtual identity of -v | -fs.MountedFilesystem ------------------- -``` - -Glossary: - -- VFS: virtual filesystem. - -- inode: a virtual file object holding a cached view of a file on a backing - filesystem (includes metadata and page caches). - -- superblock: the virtual state of a mounted filesystem (e.g. the virtual - inode number set). - -- mount namespace: a view of the mounts under a root (during path traversal, - the VFS makes visible/follows the mount point that is in the current task's - mount namespace). - -## Save and restore - -An application's hard dependencies on filesystem state can be broken down into -two categories: - -- The state necessary to execute a traversal on or view the *virtual* - filesystem hierarchy, regardless of what files an application has open. - -- The state necessary to represent open files. - -The first is always necessary to save and restore. An application may never have -any open file descriptors, but across save and restore it should see a coherent -view of any mount namespace. NOTE(b/63601033): Currently only one "initial" -mount namespace is supported. - -The second is so that system calls across save and restore are coherent with -each other (e.g. so that unintended re-reads or overwrites do not occur). - -Specifically this state is: - -- An `fs.MountManager` containing mount points. - -- A `kernel.FDTable` containing pointers to open files. - -Anything else managed by the VFS that can be easily loaded into memory from a -filesystem is synced back to those filesystems and is not saved. Examples are -pages in page caches used for optimizations (i.e. readahead and writeback), and -directory entries used to accelerate path lookups. - -### Mount points - -Saving and restoring a mount point means saving and restoring: - -- The root of the mounted filesystem. - -- Mount flags, which control how the VFS interacts with the mounted - filesystem. - -- Any relevant metadata about the mounted filesystem. - -- All `fs.Inode`s referenced by the application that reside under the mount - point. - -`fs.MountedFilesystem` is metadata about a filesystem that is mounted. It is -referenced by every `fs.Inode` loaded into memory under the mount point -including the `fs.Inode` of the mount point itself. The `fs.MountedFilesystem` -maps file objects on the filesystem to a virtualized `fs.Inode` number and vice -versa. - -To restore all `fs.Inode`s under a given mount point, each `fs.Inode` leverages -its dependency on an `fs.MountedFilesystem`. Since the `fs.MountedFilesystem` -knows how an `fs.Inode` maps to a file object on a backing filesystem, this -mapping can be trivially consulted by each `fs.Inode` when the `fs.Inode` is -restored. - -In detail, a mount point is saved in two steps: - -- First, after the kernel is paused but before state.Save, we walk all mount - namespaces and install a mapping from `fs.Inode` numbers to file paths - relative to the root of the mounted filesystem in each - `fs.MountedFilesystem`. This is subsequently called the set of `fs.Inode` - mappings. - -- Second, during state.Save, each `fs.MountedFilesystem` decides whether to - save the set of `fs.Inode` mappings. In-memory filesystems, like tmpfs, have - no need to save a set of `fs.Inode` mappings, since the `fs.Inode`s can be - entirely encoded in state file. Each `fs.MountedFilesystem` also optionally - saves the device name from when the filesystem was originally mounted. Each - `fs.Inode` saves its virtual identifier and a reference to a - `fs.MountedFilesystem`. - -A mount point is restored in two steps: - -- First, before state.Load, all mount configurations are stored in a global - `fs.RestoreEnvironment`. This tells us what mount points the user wants to - restore and how to re-establish pointers to backing filesystems. - -- Second, during state.Load, each `fs.MountedFilesystem` optionally searches - for a mount in the `fs.RestoreEnvironment` that matches its saved device - name. The `fs.MountedFilesystem` then reestablishes a pointer to the root of - the mounted filesystem. For example, the mount specification provides the - network connection for a mounted remote filesystem client to communicate - with its remote file server. The `fs.MountedFilesystem` also trivially loads - its set of `fs.Inode` mappings. When an `fs.Inode` is encountered, the - `fs.Inode` loads its virtual identifier and its reference a - `fs.MountedFilesystem`. It uses the `fs.MountedFilesystem` to obtain the - root of the mounted filesystem and the `fs.Inode` mappings to obtain the - relative file path to its data. With these, the `fs.Inode` re-establishes a - pointer to its file object. - -A mount point can trivially restore its `fs.Inode`s in parallel since -`fs.Inode`s have a restore dependency on their `fs.MountedFilesystem` and not on -each other. - -### Open files - -An `fs.File` references the following filesystem objects: - -```go -fs.File -> fs.Dirent -> fs.Inode -> fs.MountedFilesystem -``` - -The `fs.Inode` is restored using its `fs.MountedFilesystem`. The -[Mount points](#mount-points) section above describes how this happens in -detail. The `fs.Dirent` restores its pointer to an `fs.Inode`, pointers to -parent and children `fs.Dirents`, and the basename of the file. - -Otherwise an `fs.File` restores flags, an offset, and a unique identifier (only -used internally). - -It may use the `fs.Inode`, which it indirectly holds a reference on through the -`fs.Dirent`, to reestablish an open file handle on the backing filesystem (e.g. -to continue reading and writing). - -## Overlay - -The overlay implementation in the fs package takes Linux overlayfs as a frame of -reference but corrects for several POSIX consistency errors. - -In Linux overlayfs, the `struct inode` used for reading and writing to the same -file may be different. This is because the `struct inode` is dissociated with -the process of copying up the file from the upper to the lower directory. Since -flock(2) and fcntl(2) locks, inotify(7) watches, page caches, and a file's -identity are all stored directly or indirectly off the `struct inode`, these -properties of the `struct inode` may be stale after the first modification. This -can lead to file locking bugs, missed inotify events, and inconsistent data in -shared memory mappings of files, to name a few problems. - -The fs package maintains a single `fs.Inode` to represent a directory entry in -an overlay and defines operations on this `fs.Inode` which synchronize with the -copy up process. This achieves several things: - -+ File locks, inotify watches, and the identity of the file need not be copied - at all. - -+ Memory mappings of files coordinate with the copy up process so that if a - file in the lower directory is memory mapped, all references to it are - invalidated, forcing the application to re-fault on memory mappings of the - file under the upper directory. - -The `fs.Inode` holds metadata about files in the upper and/or lower directories -via an `fs.overlayEntry`. The `fs.overlayEntry` implements the `fs.Mappable` -interface. It multiplexes between upper and lower directory memory mappings and -stores a copy of memory references so they can be transferred to the upper -directory `fs.Mappable` when the file is copied up. - -The lower filesystem in an overlay may contain another (nested) overlay, but the -upper filesystem may not contain another overlay. In other words, nested -overlays form a tree structure that only allows branching in the lower -filesystem. - -Caching decisions in the overlay are delegated to the upper filesystem, meaning -that the Keep and Revalidate methods on the overlay return the same values as -the upper filesystem. A small wrinkle is that the lower filesystem is not -allowed to return `true` from Revalidate, as the overlay can not reload inodes -from the lower filesystem. A lower filesystem that does return `true` from -Revalidate will trigger a panic. - -The `fs.Inode` also holds a reference to a `fs.MountedFilesystem` that -normalizes across the mounted filesystem state of the upper and lower -directories. - -When a file is copied from the lower to the upper directory, attempts to -interact with the file block until the copy completes. All copying synchronizes -with rename(2). - -## Future Work - -### Overlay - -When a file is copied from a lower directory to an upper directory, several -locks are taken: the global renamuMu and the copyMu of the `fs.Inode` being -copied. This blocks operations on the file, including fault handling of memory -mappings. Performance could be improved by copying files into a temporary -directory that resides on the same filesystem as the upper directory and doing -an atomic rename, holding locks only during the rename operation. - -Additionally files are copied up synchronously. For large files, this causes a -noticeable latency. Performance could be improved by pipelining copies at -non-overlapping file offsets. diff --git a/pkg/sentry/fs/attr.go b/pkg/sentry/fs/attr.go deleted file mode 100644 index 4c99944e7..000000000 --- a/pkg/sentry/fs/attr.go +++ /dev/null @@ -1,507 +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" - "os" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" -) - -// InodeType enumerates types of Inodes. -type InodeType int - -const ( - // RegularFile is a regular file. - RegularFile InodeType = iota - - // SpecialFile is a file that doesn't support SeekEnd. It is used for - // things like proc files. - SpecialFile - - // Directory is a directory. - Directory - - // SpecialDirectory is a directory that *does* support SeekEnd. It's - // the opposite of the SpecialFile scenario above. It similarly - // supports proc files. - SpecialDirectory - - // Symlink is a symbolic link. - Symlink - - // Pipe is a pipe (named or regular). - Pipe - - // Socket is a socket. - Socket - - // CharacterDevice is a character device. - CharacterDevice - - // BlockDevice is a block device. - BlockDevice - - // Anonymous is an anonymous type when none of the above apply. - // Epoll fds and event-driven fds fit this category. - Anonymous -) - -// String returns a human-readable representation of the InodeType. -func (n InodeType) String() string { - switch n { - case RegularFile, SpecialFile: - return "file" - case Directory, SpecialDirectory: - return "directory" - case Symlink: - return "symlink" - case Pipe: - return "pipe" - case Socket: - return "socket" - case CharacterDevice: - return "character-device" - case BlockDevice: - return "block-device" - case Anonymous: - return "anonymous" - default: - return "unknown" - } -} - -// LinuxType returns the linux file type for this inode type. -func (n InodeType) LinuxType() uint32 { - switch n { - case RegularFile, SpecialFile: - return linux.ModeRegular - case Directory, SpecialDirectory: - return linux.ModeDirectory - case Symlink: - return linux.ModeSymlink - case Pipe: - return linux.ModeNamedPipe - case CharacterDevice: - return linux.ModeCharacterDevice - case BlockDevice: - return linux.ModeBlockDevice - case Socket: - return linux.ModeSocket - default: - return 0 - } -} - -// ToDirentType converts an InodeType to a linux dirent type field. -func ToDirentType(nodeType InodeType) uint8 { - switch nodeType { - case RegularFile, SpecialFile: - return linux.DT_REG - case Symlink: - return linux.DT_LNK - case Directory, SpecialDirectory: - return linux.DT_DIR - case Pipe: - return linux.DT_FIFO - case CharacterDevice: - return linux.DT_CHR - case BlockDevice: - return linux.DT_BLK - case Socket: - return linux.DT_SOCK - default: - return linux.DT_UNKNOWN - } -} - -// ToInodeType coverts a linux file type to InodeType. -func ToInodeType(linuxFileType linux.FileMode) InodeType { - switch linuxFileType { - case linux.ModeRegular: - return RegularFile - case linux.ModeDirectory: - return Directory - case linux.ModeSymlink: - return Symlink - case linux.ModeNamedPipe: - return Pipe - case linux.ModeCharacterDevice: - return CharacterDevice - case linux.ModeBlockDevice: - return BlockDevice - case linux.ModeSocket: - return Socket - default: - panic(fmt.Sprintf("unknown file mode: %d", linuxFileType)) - } -} - -// StableAttr contains Inode attributes that will be stable throughout the -// lifetime of the Inode. -// -// +stateify savable -type StableAttr struct { - // Type is the InodeType of a InodeOperations. - Type InodeType - - // DeviceID is the device on which a InodeOperations resides. - DeviceID uint64 - - // InodeID uniquely identifies InodeOperations on its device. - InodeID uint64 - - // BlockSize is the block size of data backing this InodeOperations. - BlockSize int64 - - // DeviceFileMajor is the major device number of this Node, if it is a - // device file. - DeviceFileMajor uint16 - - // DeviceFileMinor is the minor device number of this Node, if it is a - // device file. - DeviceFileMinor uint32 -} - -// IsRegular returns true if StableAttr.Type matches a regular file. -func IsRegular(s StableAttr) bool { - return s.Type == RegularFile -} - -// IsFile returns true if StableAttr.Type matches any type of file. -func IsFile(s StableAttr) bool { - return s.Type == RegularFile || s.Type == SpecialFile -} - -// IsDir returns true if StableAttr.Type matches any type of directory. -func IsDir(s StableAttr) bool { - return s.Type == Directory || s.Type == SpecialDirectory -} - -// IsSymlink returns true if StableAttr.Type matches a symlink. -func IsSymlink(s StableAttr) bool { - return s.Type == Symlink -} - -// IsPipe returns true if StableAttr.Type matches any type of pipe. -func IsPipe(s StableAttr) bool { - return s.Type == Pipe -} - -// IsAnonymous returns true if StableAttr.Type matches any type of anonymous. -func IsAnonymous(s StableAttr) bool { - return s.Type == Anonymous -} - -// IsSocket returns true if StableAttr.Type matches any type of socket. -func IsSocket(s StableAttr) bool { - return s.Type == Socket -} - -// IsCharDevice returns true if StableAttr.Type matches a character device. -func IsCharDevice(s StableAttr) bool { - return s.Type == CharacterDevice -} - -// UnstableAttr contains Inode attributes that may change over the lifetime -// of the Inode. -// -// +stateify savable -type UnstableAttr struct { - // Size is the file size in bytes. - Size int64 - - // Usage is the actual data usage in bytes. - Usage int64 - - // Perms is the protection (read/write/execute for user/group/other). - Perms FilePermissions - - // Owner describes the ownership of this file. - Owner FileOwner - - // AccessTime is the time of last access - AccessTime ktime.Time - - // ModificationTime is the time of last modification. - ModificationTime ktime.Time - - // StatusChangeTime is the time of last attribute modification. - StatusChangeTime ktime.Time - - // Links is the number of hard links. - Links uint64 -} - -// SetOwner sets the owner and group if they are valid. -// -// This method is NOT thread-safe. Callers must prevent concurrent calls. -func (ua *UnstableAttr) SetOwner(ctx context.Context, owner FileOwner) { - if owner.UID.Ok() { - ua.Owner.UID = owner.UID - } - if owner.GID.Ok() { - ua.Owner.GID = owner.GID - } - ua.StatusChangeTime = ktime.NowFromContext(ctx) -} - -// SetPermissions sets the permissions. -// -// This method is NOT thread-safe. Callers must prevent concurrent calls. -func (ua *UnstableAttr) SetPermissions(ctx context.Context, p FilePermissions) { - ua.Perms = p - ua.StatusChangeTime = ktime.NowFromContext(ctx) -} - -// SetTimestamps sets the timestamps according to the TimeSpec. -// -// This method is NOT thread-safe. Callers must prevent concurrent calls. -func (ua *UnstableAttr) SetTimestamps(ctx context.Context, ts TimeSpec) { - if ts.ATimeOmit && ts.MTimeOmit { - return - } - - now := ktime.NowFromContext(ctx) - if !ts.ATimeOmit { - if ts.ATimeSetSystemTime { - ua.AccessTime = now - } else { - ua.AccessTime = ts.ATime - } - } - if !ts.MTimeOmit { - if ts.MTimeSetSystemTime { - ua.ModificationTime = now - } else { - ua.ModificationTime = ts.MTime - } - } - ua.StatusChangeTime = now -} - -// WithCurrentTime returns u with AccessTime == ModificationTime == current time. -func WithCurrentTime(ctx context.Context, u UnstableAttr) UnstableAttr { - t := ktime.NowFromContext(ctx) - u.AccessTime = t - u.ModificationTime = t - u.StatusChangeTime = t - return u -} - -// AttrMask contains fields to mask StableAttr and UnstableAttr. -// -// +stateify savable -type AttrMask struct { - Type bool - DeviceID bool - InodeID bool - BlockSize bool - Size bool - Usage bool - Perms bool - UID bool - GID bool - AccessTime bool - ModificationTime bool - StatusChangeTime bool - Links bool -} - -// Empty returns true if all fields in AttrMask are false. -func (a AttrMask) Empty() bool { - return a == AttrMask{} -} - -// PermMask are file access permissions. -// -// +stateify savable -type PermMask struct { - // Read indicates reading is permitted. - Read bool - - // Write indicates writing is permitted. - Write bool - - // Execute indicates execution is permitted. - Execute bool -} - -// OnlyRead returns true when only the read bit is set. -func (p PermMask) OnlyRead() bool { - return p.Read && !p.Write && !p.Execute -} - -// String implements the fmt.Stringer interface for PermMask. -func (p PermMask) String() string { - return fmt.Sprintf("PermMask{Read: %v, Write: %v, Execute: %v}", p.Read, p.Write, p.Execute) -} - -// Mode returns the system mode (unix.S_IXOTH, etc.) for these permissions -// in the "other" bits. -func (p PermMask) Mode() (mode os.FileMode) { - if p.Read { - mode |= unix.S_IROTH - } - if p.Write { - mode |= unix.S_IWOTH - } - if p.Execute { - mode |= unix.S_IXOTH - } - return -} - -// SupersetOf returns true iff the permissions in p are a superset of the -// permissions in other. -func (p PermMask) SupersetOf(other PermMask) bool { - if !p.Read && other.Read { - return false - } - if !p.Write && other.Write { - return false - } - if !p.Execute && other.Execute { - return false - } - return true -} - -// FilePermissions represents the permissions of a file, with -// Read/Write/Execute bits for user, group, and other. -// -// +stateify savable -type FilePermissions struct { - User PermMask - Group PermMask - Other PermMask - - // Sticky, if set on directories, restricts renaming and deletion of - // files in those directories to the directory owner, file owner, or - // CAP_FOWNER. The sticky bit is ignored when set on other files. - Sticky bool - - // SetUID executables can call UID-setting syscalls without CAP_SETUID. - SetUID bool - - // SetGID executables can call GID-setting syscalls without CAP_SETGID. - SetGID bool -} - -// PermsFromMode takes the Other permissions (last 3 bits) of a FileMode and -// returns a set of PermMask. -func PermsFromMode(mode linux.FileMode) (perms PermMask) { - perms.Read = mode&linux.ModeOtherRead != 0 - perms.Write = mode&linux.ModeOtherWrite != 0 - perms.Execute = mode&linux.ModeOtherExec != 0 - return -} - -// FilePermsFromP9 converts a p9.FileMode to a FilePermissions struct. -func FilePermsFromP9(mode p9.FileMode) FilePermissions { - return FilePermsFromMode(linux.FileMode(mode)) -} - -// FilePermsFromMode converts a system file mode to a FilePermissions struct. -func FilePermsFromMode(mode linux.FileMode) (fp FilePermissions) { - perm := mode.Permissions() - fp.Other = PermsFromMode(perm) - fp.Group = PermsFromMode(perm >> 3) - fp.User = PermsFromMode(perm >> 6) - fp.Sticky = mode&linux.ModeSticky == linux.ModeSticky - fp.SetUID = mode&linux.ModeSetUID == linux.ModeSetUID - fp.SetGID = mode&linux.ModeSetGID == linux.ModeSetGID - return -} - -// LinuxMode returns the linux mode_t representation of these permissions. -func (f FilePermissions) LinuxMode() linux.FileMode { - m := linux.FileMode(f.User.Mode()<<6 | f.Group.Mode()<<3 | f.Other.Mode()) - if f.SetUID { - m |= linux.ModeSetUID - } - if f.SetGID { - m |= linux.ModeSetGID - } - if f.Sticky { - m |= linux.ModeSticky - } - return m -} - -// OSMode returns the Go runtime's OS independent os.FileMode representation of -// these permissions. -func (f FilePermissions) OSMode() os.FileMode { - m := os.FileMode(f.User.Mode()<<6 | f.Group.Mode()<<3 | f.Other.Mode()) - if f.SetUID { - m |= os.ModeSetuid - } - if f.SetGID { - m |= os.ModeSetgid - } - if f.Sticky { - m |= os.ModeSticky - } - return m -} - -// AnyExecute returns true if any of U/G/O have the execute bit set. -func (f FilePermissions) AnyExecute() bool { - return f.User.Execute || f.Group.Execute || f.Other.Execute -} - -// AnyWrite returns true if any of U/G/O have the write bit set. -func (f FilePermissions) AnyWrite() bool { - return f.User.Write || f.Group.Write || f.Other.Write -} - -// AnyRead returns true if any of U/G/O have the read bit set. -func (f FilePermissions) AnyRead() bool { - return f.User.Read || f.Group.Read || f.Other.Read -} - -// HasSetUIDOrGID returns true if either the setuid or setgid bit is set. -func (f FilePermissions) HasSetUIDOrGID() bool { - return f.SetUID || f.SetGID -} - -// DropSetUIDAndMaybeGID turns off setuid, and turns off setgid if f allows -// group execution. -func (f *FilePermissions) DropSetUIDAndMaybeGID() { - f.SetUID = false - if f.Group.Execute { - f.SetGID = false - } -} - -// FileOwner represents ownership of a file. -// -// +stateify savable -type FileOwner struct { - UID auth.KUID - GID auth.KGID -} - -// RootOwner corresponds to KUID/KGID 0/0. -var RootOwner = FileOwner{ - UID: auth.RootKUID, - GID: auth.RootKGID, -} diff --git a/pkg/sentry/fs/context.go b/pkg/sentry/fs/context.go deleted file mode 100644 index d03e07e6e..000000000 --- a/pkg/sentry/fs/context.go +++ /dev/null @@ -1,138 +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 ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" -) - -// contextID is the fs package's type for context.Context.Value keys. -type contextID int - -const ( - // CtxRoot is a Context.Value key for a Dirent. - CtxRoot contextID = iota - - // CtxDirentCacheLimiter is a Context.Value key for DirentCacheLimiter. - CtxDirentCacheLimiter -) - -// ContextCanAccessFile determines whether `file` can be accessed in the requested way -// (for reading, writing, or execution) using the caller's credentials and user -// namespace, as does Linux's fs/namei.c:generic_permission. -func ContextCanAccessFile(ctx context.Context, inode *Inode, reqPerms PermMask) bool { - creds := auth.CredentialsFromContext(ctx) - uattr, err := inode.UnstableAttr(ctx) - if err != nil { - return false - } - - p := uattr.Perms.Other - // Are we owner or in group? - if uattr.Owner.UID == creds.EffectiveKUID { - p = uattr.Perms.User - } else if creds.InGroup(uattr.Owner.GID) { - p = uattr.Perms.Group - } - - // Do not allow programs to be executed if MS_NOEXEC is set. - if IsFile(inode.StableAttr) && reqPerms.Execute && inode.MountSource.Flags.NoExec { - return false - } - - // Are permissions satisfied without capability checks? - if p.SupersetOf(reqPerms) { - return true - } - - if IsDir(inode.StableAttr) { - // CAP_DAC_OVERRIDE can override any perms on directories. - if inode.CheckCapability(ctx, linux.CAP_DAC_OVERRIDE) { - return true - } - - // CAP_DAC_READ_SEARCH can normally only override Read perms, - // but for directories it can also override execution. - if !reqPerms.Write && inode.CheckCapability(ctx, linux.CAP_DAC_READ_SEARCH) { - return true - } - } - - // CAP_DAC_OVERRIDE can always override Read/Write. - // Can override executable only when at least one execute bit is set. - if !reqPerms.Execute || uattr.Perms.AnyExecute() { - if inode.CheckCapability(ctx, linux.CAP_DAC_OVERRIDE) { - return true - } - } - - // Read perms can be overridden by CAP_DAC_READ_SEARCH. - if reqPerms.OnlyRead() && inode.CheckCapability(ctx, linux.CAP_DAC_READ_SEARCH) { - return true - } - return false -} - -// FileOwnerFromContext returns a FileOwner using the effective user and group -// IDs used by ctx. -func FileOwnerFromContext(ctx context.Context) FileOwner { - creds := auth.CredentialsFromContext(ctx) - return FileOwner{creds.EffectiveKUID, creds.EffectiveKGID} -} - -// RootFromContext returns the root of the virtual filesystem observed by ctx, -// or nil if ctx is not associated with a virtual filesystem. If -// RootFromContext returns a non-nil fs.Dirent, a reference is taken on it. -func RootFromContext(ctx context.Context) *Dirent { - if v := ctx.Value(CtxRoot); v != nil { - return v.(*Dirent) - } - return nil -} - -// DirentCacheLimiterFromContext returns the DirentCacheLimiter used by ctx, or -// nil if ctx does not have a dirent cache limiter. -func DirentCacheLimiterFromContext(ctx context.Context) *DirentCacheLimiter { - if v := ctx.Value(CtxDirentCacheLimiter); v != nil { - return v.(*DirentCacheLimiter) - } - return nil -} - -type rootContext struct { - context.Context - root *Dirent -} - -// WithRoot returns a copy of ctx with the given root. -func WithRoot(ctx context.Context, root *Dirent) context.Context { - return &rootContext{ - Context: ctx, - root: root, - } -} - -// Value implements Context.Value. -func (rc rootContext) Value(key any) any { - switch key { - case CtxRoot: - rc.root.IncRef() - return rc.root - default: - return rc.Context.Value(key) - } -} diff --git a/pkg/sentry/fs/copy_up.go b/pkg/sentry/fs/copy_up.go deleted file mode 100644 index fb65be705..000000000 --- a/pkg/sentry/fs/copy_up.go +++ /dev/null @@ -1,442 +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" - "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/log" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -// copyUp copies a file in an overlay from a lower filesystem to an -// upper filesytem so that the file can be modified in the upper -// filesystem. Copying a file involves several steps: -// -// - All parent directories of the file are created in the upper -// filesystem if they don't exist there. For instance: -// -// upper /dir0 -// lower /dir0/dir1/file -// -// copyUp of /dir0/dir1/file creates /dir0/dir1 in order to create -// /dir0/dir1/file. -// -// - The file content is copied from the lower file to the upper -// file. For symlinks this is the symlink target. For directories, -// upper directory entries are merged with lower directory entries -// so there is no need to copy any entries. -// -// - A subset of file attributes of the lower file are set on the -// upper file. These are the file owner, the file timestamps, -// and all non-overlay extended attributes. copyUp will fail if -// the upper filesystem does not support the setting of these -// attributes. -// -// The file's permissions are set when the file is created and its -// size will be brought up to date when its contents are copied. -// Notably no attempt is made to bring link count up to date because -// hard links are currently not preserved across overlay filesystems. -// -// - Memory mappings of the lower file are invalidated and memory -// references are transferred to the upper file. From this point on, -// memory mappings of the file will be backed by content in the upper -// filesystem. -// -// Synchronization: -// -// copyUp synchronizes with rename(2) using renameMu to ensure that -// parentage does not change while a file is being copied. In the context -// of rename(2), copyUpLockedForRename should be used to avoid deadlock on -// renameMu. -// -// The following operations synchronize with copyUp using copyMu: -// -// - InodeOperations, i.e. to ensure that looking up a directory takes -// into account new upper filesystem directories created by copy up, -// which subsequently can be modified. -// -// - FileOperations, i.e. to ensure that reading from a file does not -// continue using a stale, lower filesystem handle when the file is -// written to. -// -// Lock ordering: Dirent.mu -> Inode.overlay.copyMu -> Inode.mu. -// -// Caveats: -// -// If any step in copying up a file fails, copyUp cleans the upper -// filesystem of any partially up-to-date file. If this cleanup fails, -// the overlay may be in an unacceptable, inconsistent state, so copyUp -// panics. If copyUp fails because any step (above) fails, a generic -// error is returned. -// -// copyUp currently makes no attempt to optimize copying up file content. -// For large files, this means that copyUp blocks until the entire file -// is copied synchronously. -func copyUp(ctx context.Context, d *Dirent) error { - renameMu.RLock() - defer renameMu.RUnlock() - return copyUpLockedForRename(ctx, d) -} - -// copyUpLockedForRename is the same as copyUp except that it does not lock -// renameMu. -// -// It copies each component of d that does not yet exist in the upper -// filesystem. If d already exists in the upper filesystem, it is a no-op. -// -// Any error returned indicates a failure to copy all of d. This may -// leave the upper filesystem filled with any number of parent directories -// but the upper filesystem will never be in an inconsistent state. -// -// Preconditions: d.Inode.overlay is non-nil. -func copyUpLockedForRename(ctx context.Context, d *Dirent) error { - for { - // Did we race with another copy up or does there - // already exist something in the upper filesystem - // for d? - d.Inode.overlay.copyMu.RLock() - if d.Inode.overlay.upper != nil { - d.Inode.overlay.copyMu.RUnlock() - // Done, d is in the upper filesystem. - return nil - } - d.Inode.overlay.copyMu.RUnlock() - - // Find the next component to copy up. We will work our way - // down to the last component of d and finally copy it. - next := findNextCopyUp(ctx, d) - - // Attempt to copy. - if err := doCopyUp(ctx, next); err != nil { - return err - } - } -} - -// findNextCopyUp finds the next component of d from root that does not -// yet exist in the upper filesystem. The parent of this component is -// also returned, which is the root of the overlay in the worst case. -func findNextCopyUp(ctx context.Context, d *Dirent) *Dirent { - next := d - for parent := next.parent; ; /* checked in-loop */ /* updated in-loop */ { - // Does this parent have a non-nil upper Inode? - parent.Inode.overlay.copyMu.RLock() - if parent.Inode.overlay.upper != nil { - parent.Inode.overlay.copyMu.RUnlock() - // Note that since we found an upper, it is stable. - return next - } - parent.Inode.overlay.copyMu.RUnlock() - - // Continue searching for a parent with a non-nil - // upper Inode. - next = parent - parent = next.parent - } -} - -func doCopyUp(ctx context.Context, d *Dirent) error { - // Fail fast on Inode types we won't be able to copy up anyways. These - // Inodes may block in GetFile while holding copyMu for reading. If we - // then try to take copyMu for writing here, we'd deadlock. - t := d.Inode.overlay.lower.StableAttr.Type - if t != RegularFile && t != Directory && t != Symlink { - return linuxerr.EINVAL - } - - // Wait to get exclusive access to the upper Inode. - d.Inode.overlay.copyMu.Lock() - defer d.Inode.overlay.copyMu.Unlock() - if d.Inode.overlay.upper != nil { - // We raced with another doCopyUp, no problem. - return nil - } - - // Perform the copy. - return copyUpLocked(ctx, d.parent, d) -} - -// copyUpLocked creates a copy of next in the upper filesystem of parent. -// -// copyUpLocked must be called with d.Inode.overlay.copyMu locked. -// -// Returns a generic error on failure. -// -// Preconditions: -// - parent.Inode.overlay.upper must be non-nil. -// - next.Inode.overlay.copyMu must be locked writable. -// - next.Inode.overlay.lower must be non-nil. -// - next.Inode.overlay.lower.StableAttr.Type must be RegularFile, Directory, -// or Symlink. -// - upper filesystem must support setting file ownership and timestamps. -func copyUpLocked(ctx context.Context, parent *Dirent, next *Dirent) error { - // Extract the attributes of the file we wish to copy. - attrs, err := next.Inode.overlay.lower.UnstableAttr(ctx) - if err != nil { - log.Warningf("copy up failed to get lower attributes: %v", err) - return linuxerr.EIO - } - - var childUpperInode *Inode - parentUpper := parent.Inode.overlay.upper - root := RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - - // Create the file in the upper filesystem and get an Inode for it. - switch next.Inode.StableAttr.Type { - case RegularFile: - childFile, err := parentUpper.Create(ctx, root, next.name, FileFlags{Read: true, Write: true}, attrs.Perms) - if err != nil { - log.Warningf("copy up failed to create file: %v", err) - return linuxerr.EIO - } - defer childFile.DecRef(ctx) - childUpperInode = childFile.Dirent.Inode - - case Directory: - if err := parentUpper.CreateDirectory(ctx, root, next.name, attrs.Perms); err != nil { - log.Warningf("copy up failed to create directory: %v", err) - return linuxerr.EIO - } - childUpper, err := parentUpper.Lookup(ctx, next.name) - if err != nil { - werr := fmt.Errorf("copy up failed to lookup directory: %v", err) - cleanupUpper(ctx, parentUpper, next.name, werr) - return linuxerr.EIO - } - defer childUpper.DecRef(ctx) - childUpperInode = childUpper.Inode - - case Symlink: - childLower := next.Inode.overlay.lower - link, err := childLower.Readlink(ctx) - if err != nil { - log.Warningf("copy up failed to read symlink value: %v", err) - return linuxerr.EIO - } - if err := parentUpper.CreateLink(ctx, root, link, next.name); err != nil { - log.Warningf("copy up failed to create symlink: %v", err) - return linuxerr.EIO - } - childUpper, err := parentUpper.Lookup(ctx, next.name) - if err != nil { - werr := fmt.Errorf("copy up failed to lookup symlink: %v", err) - cleanupUpper(ctx, parentUpper, next.name, werr) - return linuxerr.EIO - } - defer childUpper.DecRef(ctx) - childUpperInode = childUpper.Inode - - default: - panic(fmt.Sprintf("copy up of invalid type %v on %+v", next.Inode.StableAttr.Type, next)) - } - - // Bring file attributes up to date. This does not include size, which will be - // brought up to date with copyContentsLocked. - if err := copyAttributesLocked(ctx, childUpperInode, next.Inode.overlay.lower); err != nil { - werr := fmt.Errorf("copy up failed to copy up attributes: %v", err) - cleanupUpper(ctx, parentUpper, next.name, werr) - return linuxerr.EIO - } - - // Copy the entire file. - if err := copyContentsLocked(ctx, childUpperInode, next.Inode.overlay.lower, attrs.Size); err != nil { - werr := fmt.Errorf("copy up failed to copy up contents: %v", err) - cleanupUpper(ctx, parentUpper, next.name, werr) - return linuxerr.EIO - } - - lowerMappable := next.Inode.overlay.lower.Mappable() - upperMappable := childUpperInode.Mappable() - if lowerMappable != nil && upperMappable == nil { - werr := fmt.Errorf("copy up failed: cannot ensure memory mapping coherence") - cleanupUpper(ctx, parentUpper, next.name, werr) - return linuxerr.EIO - } - - // Propagate memory mappings to the upper Inode. - next.Inode.overlay.mapsMu.Lock() - defer next.Inode.overlay.mapsMu.Unlock() - if upperMappable != nil { - // Remember which mappings we added so we can remove them on failure. - allAdded := make(map[memmap.MappableRange]memmap.MappingsOfRange) - for seg := next.Inode.overlay.mappings.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { - added := make(memmap.MappingsOfRange) - for m := range seg.Value() { - if err := upperMappable.AddMapping(ctx, m.MappingSpace, m.AddrRange, seg.Start(), m.Writable); err != nil { - for m := range added { - upperMappable.RemoveMapping(ctx, m.MappingSpace, m.AddrRange, seg.Start(), m.Writable) - } - for mr, mappings := range allAdded { - for m := range mappings { - upperMappable.RemoveMapping(ctx, m.MappingSpace, m.AddrRange, mr.Start, m.Writable) - } - } - return err - } - added[m] = struct{}{} - } - allAdded[seg.Range()] = added - } - } - - // Take a reference on the upper Inode (transferred to - // next.Inode.overlay.upper) and make new translations use it. - overlay := next.Inode.overlay - overlay.dataMu.Lock() - childUpperInode.IncRef() - overlay.upper = childUpperInode - overlay.dataMu.Unlock() - - // Invalidate existing translations through the lower Inode. - overlay.mappings.InvalidateAll(memmap.InvalidateOpts{}) - - // Remove existing memory mappings from the lower Inode. - if lowerMappable != nil { - for seg := overlay.mappings.FirstSegment(); seg.Ok(); seg = seg.NextSegment() { - for m := range seg.Value() { - lowerMappable.RemoveMapping(ctx, m.MappingSpace, m.AddrRange, seg.Start(), m.Writable) - } - } - } - - return nil -} - -// cleanupUpper is called when copy-up fails. It logs the copy-up error and -// attempts to remove name from parent. If that fails, then it panics. -func cleanupUpper(ctx context.Context, parent *Inode, name string, copyUpErr error) { - log.Warningf(copyUpErr.Error()) - if err := parent.InodeOperations.Remove(ctx, parent, name); err != nil { - // Unfortunately we don't have much choice. We shouldn't - // willingly give the caller access to a nonsense filesystem. - panic(fmt.Sprintf("overlay filesystem is in an inconsistent state: copyUp got error: %v; then cleanup failed to remove %q from upper filesystem: %v.", copyUpErr, name, err)) - } -} - -// copyUpBuffers is a buffer pool for copying file content. The buffer -// size is the same used by io.Copy. -var copyUpBuffers = sync.Pool{ - New: func() any { - b := make([]byte, 8*hostarch.PageSize) - return &b - }, -} - -// copyContentsLocked copies the contents of lower to upper. It panics if -// less than size bytes can be copied. -func copyContentsLocked(ctx context.Context, upper *Inode, lower *Inode, size int64) error { - // We don't support copying up for anything other than regular files. - if lower.StableAttr.Type != RegularFile { - return nil - } - - // Get a handle to the upper filesystem, which we will write to. - upperFile, err := overlayFile(ctx, upper, FileFlags{Write: true}) - if err != nil { - return err - } - defer upperFile.DecRef(ctx) - - // Get a handle to the lower filesystem, which we will read from. - lowerFile, err := overlayFile(ctx, lower, FileFlags{Read: true}) - if err != nil { - return err - } - defer lowerFile.DecRef(ctx) - - // Use a buffer pool to minimize allocations. - buf := copyUpBuffers.Get().(*[]byte) - defer copyUpBuffers.Put(buf) - - // Transfer the contents. - // - // One might be able to optimize this by doing parallel reads, parallel writes and reads, larger - // buffers, etc. But we really don't know anything about the underlying implementation, so these - // optimizations could be self-defeating. So we leave this as simple as possible. - var offset int64 - for { - nr, err := lowerFile.FileOperations.Read(ctx, lowerFile, usermem.BytesIOSequence(*buf), offset) - if err != nil && err != io.EOF { - return err - } - if nr == 0 { - if offset != size { - // Same as in cleanupUpper, we cannot live - // with ourselves if we do anything less. - panic(fmt.Sprintf("filesystem is in an inconsistent state: wrote only %d bytes of %d sized file", offset, size)) - } - return nil - } - nw, err := upperFile.FileOperations.Write(ctx, upperFile, usermem.BytesIOSequence((*buf)[:nr]), offset) - if err != nil { - return err - } - offset += nw - } -} - -// copyAttributesLocked copies a subset of lower's attributes to upper, -// specifically owner, timestamps (except of status change time), and -// extended attributes. Notably no attempt is made to copy link count. -// Size and permissions are set on upper when the file content is copied -// and when the file is created respectively. -func copyAttributesLocked(ctx context.Context, upper *Inode, lower *Inode) error { - // Extract attributes from the lower filesystem. - lowerAttr, err := lower.UnstableAttr(ctx) - if err != nil { - return err - } - lowerXattr, err := lower.ListXattr(ctx, linux.XATTR_SIZE_MAX) - if err != nil && !linuxerr.Equals(linuxerr.EOPNOTSUPP, err) { - return err - } - - // Set the attributes on the upper filesystem. - if err := upper.InodeOperations.SetOwner(ctx, upper, lowerAttr.Owner); err != nil { - return err - } - if err := upper.InodeOperations.SetTimestamps(ctx, upper, TimeSpec{ - ATime: lowerAttr.AccessTime, - MTime: lowerAttr.ModificationTime, - }); err != nil { - return err - } - for name := range lowerXattr { - // Don't copy-up attributes that configure an overlay in the - // lower. - if isXattrOverlay(name) { - continue - } - value, err := lower.GetXattr(ctx, name, linux.XATTR_SIZE_MAX) - if err != nil { - return err - } - if err := upper.InodeOperations.SetXattr(ctx, upper, name, value, 0 /* flags */); err != nil { - return err - } - } - return nil -} diff --git a/pkg/sentry/fs/dentry.go b/pkg/sentry/fs/dentry.go deleted file mode 100644 index 6b2699f15..000000000 --- a/pkg/sentry/fs/dentry.go +++ /dev/null @@ -1,234 +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 ( - "sort" - - "gvisor.dev/gvisor/pkg/sentry/device" -) - -// DentAttr is the metadata of a directory entry. It is a subset of StableAttr. -// -// +stateify savable -type DentAttr struct { - // Type is the InodeType of an Inode. - Type InodeType - - // InodeID uniquely identifies an Inode on a device. - InodeID uint64 -} - -// GenericDentAttr returns a generic DentAttr where: -// -// Type == nt -// InodeID == the inode id of a new inode on device. -func GenericDentAttr(nt InodeType, device *device.Device) DentAttr { - return DentAttr{ - Type: nt, - InodeID: device.NextIno(), - } -} - -// DentrySerializer serializes a directory entry. -type DentrySerializer interface { - // CopyOut serializes a directory entry based on its name and attributes. - CopyOut(name string, attributes DentAttr) error - - // Written returns the number of bytes written. - Written() int -} - -// CollectEntriesSerializer copies DentAttrs to Entries. The order in -// which entries are encountered is preserved in Order. -type CollectEntriesSerializer struct { - Entries map[string]DentAttr - Order []string -} - -// CopyOut implements DentrySerializer.CopyOut. -func (c *CollectEntriesSerializer) CopyOut(name string, attr DentAttr) error { - if c.Entries == nil { - c.Entries = make(map[string]DentAttr) - } - c.Entries[name] = attr - c.Order = append(c.Order, name) - return nil -} - -// Written implements DentrySerializer.Written. -func (c *CollectEntriesSerializer) Written() int { - return len(c.Entries) -} - -// DirCtx is used in FileOperations.IterateDir to emit directory entries. It is -// not thread-safe. -type DirCtx struct { - // Serializer is used to serialize the node attributes. - Serializer DentrySerializer - - // attrs are DentAttrs - attrs map[string]DentAttr - - // DirCursor is the directory cursor. - DirCursor *string -} - -// DirEmit is called for each directory entry. -func (c *DirCtx) DirEmit(name string, attr DentAttr) error { - if c.Serializer != nil { - if err := c.Serializer.CopyOut(name, attr); err != nil { - return err - } - } - if c.attrs == nil { - c.attrs = make(map[string]DentAttr) - } - c.attrs[name] = attr - return nil -} - -// DentAttrs returns a map of DentAttrs corresponding to the emitted directory -// entries. -func (c *DirCtx) DentAttrs() map[string]DentAttr { - if c.attrs == nil { - c.attrs = make(map[string]DentAttr) - } - return c.attrs -} - -// GenericReaddir serializes DentAttrs based on a SortedDentryMap that must -// contain _all_ up-to-date DentAttrs under a directory. If ctx.DirCursor is -// not nil, it is updated to the name of the last DentAttr that was -// successfully serialized. -// -// Returns the number of entries serialized. -func GenericReaddir(ctx *DirCtx, s *SortedDentryMap) (int, error) { - // Retrieve the next directory entries. - var names []string - var entries map[string]DentAttr - if ctx.DirCursor != nil { - names, entries = s.GetNext(*ctx.DirCursor) - } else { - names, entries = s.GetAll() - } - - // Try to serialize each entry. - var serialized int - for _, name := range names { - // Skip "" per POSIX. Skip "." and ".." which will be added by Dirent.Readdir. - if name == "" || name == "." || name == ".." { - continue - } - - // Emit the directory entry. - if err := ctx.DirEmit(name, entries[name]); err != nil { - // Return potentially a partial serialized count. - return serialized, err - } - - // We successfully serialized this entry. - serialized++ - - // Update the cursor with the name of the entry last serialized. - if ctx.DirCursor != nil { - *ctx.DirCursor = name - } - } - - // Everything was serialized. - return serialized, nil -} - -// SortedDentryMap is a sorted map of names and fs.DentAttr entries. -// -// +stateify savable -type SortedDentryMap struct { - // names is always kept in sorted-order. - names []string - - // entries maps names to fs.DentAttrs. - entries map[string]DentAttr -} - -// NewSortedDentryMap maintains entries in name sorted order. -func NewSortedDentryMap(entries map[string]DentAttr) *SortedDentryMap { - s := &SortedDentryMap{ - names: make([]string, 0, len(entries)), - entries: entries, - } - // Don't allow s.entries to be nil, because nil maps arn't Saveable. - if s.entries == nil { - s.entries = make(map[string]DentAttr) - } - - // Collect names from entries and sort them. - for name := range s.entries { - s.names = append(s.names, name) - } - sort.Strings(s.names) - return s -} - -// GetAll returns all names and entries in s. Callers should not modify the -// returned values. -func (s *SortedDentryMap) GetAll() ([]string, map[string]DentAttr) { - return s.names, s.entries -} - -// GetNext returns names after cursor in s and all entries. -func (s *SortedDentryMap) GetNext(cursor string) ([]string, map[string]DentAttr) { - i := sort.SearchStrings(s.names, cursor) - if i == len(s.names) { - return nil, s.entries - } - - // Return everything strictly after the cursor. - if s.names[i] == cursor { - i++ - } - return s.names[i:], s.entries -} - -// Add adds an entry with the given name to the map, preserving sort order. If -// name already exists in the map, its entry will be overwritten. -func (s *SortedDentryMap) Add(name string, entry DentAttr) { - if _, ok := s.entries[name]; !ok { - // Map does not yet contain an entry with this name. We must - // insert it in s.names at the appropriate spot. - i := sort.SearchStrings(s.names, name) - s.names = append(s.names, "") - copy(s.names[i+1:], s.names[i:]) - s.names[i] = name - } - s.entries[name] = entry -} - -// Remove removes an entry with the given name from the map, preserving sort order. -func (s *SortedDentryMap) Remove(name string) { - if _, ok := s.entries[name]; !ok { - return - } - i := sort.SearchStrings(s.names, name) - copy(s.names[i:], s.names[i+1:]) - s.names = s.names[:len(s.names)-1] - delete(s.entries, name) -} - -// Contains reports whether the map contains an entry with the given name. -func (s *SortedDentryMap) Contains(name string) bool { - _, ok := s.entries[name] - return ok -} diff --git a/pkg/sentry/fs/dirent.go b/pkg/sentry/fs/dirent.go deleted file mode 100644 index 8f8fc18c0..000000000 --- a/pkg/sentry/fs/dirent.go +++ /dev/null @@ -1,1567 +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" - "path" - - "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/refs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sentry/uniqueid" - "gvisor.dev/gvisor/pkg/sync" -) - -type globalDirentMap struct { - mu sync.Mutex - dirents map[*Dirent]struct{} -} - -func (g *globalDirentMap) add(d *Dirent) { - g.mu.Lock() - g.dirents[d] = struct{}{} - g.mu.Unlock() -} - -func (g *globalDirentMap) remove(d *Dirent) { - g.mu.Lock() - delete(g.dirents, d) - g.mu.Unlock() -} - -// allDirents keeps track of all Dirents that need to be considered in -// Save/Restore for inode mappings. -// -// Because inodes do not hold paths, but inodes for external file systems map -// to an external path, every user-visible Dirent is stored in this map and -// iterated through upon save to keep inode ID -> restore path mappings. -var allDirents = globalDirentMap{ - dirents: map[*Dirent]struct{}{}, -} - -// renameMu protects the parent of *all* Dirents. (See explanation in -// lockForRename.) -// -// See fs.go for lock ordering. -var renameMu sync.RWMutex - -// Dirent holds an Inode in memory. -// -// A Dirent may be negative or positive: -// -// A negative Dirent contains a nil Inode and indicates that a path does not exist. This -// is a convention taken from the Linux dcache, see fs/dcache.c. A negative Dirent remains -// cached until a create operation replaces it with a positive Dirent. A negative Dirent -// always has one reference owned by its parent and takes _no_ reference on its parent. This -// ensures that its parent can be unhashed regardless of negative children. -// -// A positive Dirent contains a non-nil Inode. It remains cached for as long as there remain -// references to it. A positive Dirent always takes a reference on its parent. -// -// A Dirent may be a root Dirent (parent is nil) or be parented (non-nil parent). -// -// Dirents currently do not attempt to free entries that lack application references under -// memory pressure. -// -// +stateify savable -type Dirent struct { - // AtomicRefCount is our reference count. - refs.AtomicRefCount - - // userVisible indicates whether the Dirent is visible to the user or - // not. Only user-visible Dirents should save inode mappings in - // save/restore, as only they hold the real path to the underlying - // inode. - // - // See newDirent and Dirent.afterLoad. - userVisible bool - - // Inode is the underlying file object. - // - // Inode is exported currently to assist in implementing overlay Inodes (where a - // Inode.InodeOperations.Lookup may need to merge the Inode contained in a positive Dirent with - // another Inode). This is normally done before the Dirent is parented (there are - // no external references to it). - // - // Other objects in the VFS may take a reference to this Inode but only while holding - // a reference to this Dirent. - Inode *Inode - - // name is the name (i.e. basename) of this entry. - // - // N.B. name is protected by parent.mu, not this node's mu! - name string - - // parent is the parent directory. - // - // We hold a hard reference to the parent. - // - // parent is protected by renameMu. - parent *Dirent - - // deleted may be set atomically when removed. - deleted atomicbitops.Int32 - - // mounted is true if Dirent is a mount point, similar to include/linux/dcache.h:DCACHE_MOUNTED. - mounted bool - - // direntEntry identifies this Dirent as an element in a DirentCache. DirentCaches - // and their contents are not saved. - direntEntry `state:"nosave"` - - // dirMu is a read-write mutex that protects caching decisions made by directory operations. - // Lock ordering: dirMu must be taken before mu (see below). Details: - // - // dirMu does not participate in Rename; instead mu and renameMu are used, see lockForRename. - // - // Creation and Removal operations must be synchronized with Walk to prevent stale negative - // caching. Note that this requirement is not specific to a _Dirent_ doing negative caching. - // The following race exists at any level of the VFS: - // - // For an object D that represents a directory, containing a cache of non-existent paths, - // protected by D.cacheMu: - // - // T1: T2: - // D.lookup(name) - // --> ENOENT - // D.create(name) - // --> success - // D.cacheMu.Lock - // delete(D.cache, name) - // D.cacheMu.Unlock - // D.cacheMu.Lock - // D.cache[name] = true - // D.cacheMu.Unlock - // - // D.lookup(name) - // D.cacheMu.Lock - // if D.cache[name] { - // --> ENOENT (wrong) - // } - // D.cacheMu.Lock - // - // Correct: - // - // T1: T2: - // D.cacheMu.Lock - // D.lookup(name) - // --> ENOENT - // D.cache[name] = true - // D.cacheMu.Unlock - // D.cacheMu.Lock - // D.create(name) - // --> success - // delete(D.cache, name) - // D.cacheMu.Unlock - // - // D.cacheMu.Lock - // D.lookup(name) - // --> EXISTS (right) - // D.cacheMu.Unlock - // - // Note that the above "correct" solution causes too much lock contention: all lookups are - // synchronized with each other. This is a problem because lookups are involved in any VFS - // path operation. - // - // A Dirent diverges from the single D.cacheMu and instead uses two locks: dirMu to protect - // concurrent creation/removal/lookup caching, and mu to protect the Dirent's children map - // in general. - // - // This allows for concurrent Walks to be executed in order to pipeline lookups. For instance - // for a hot directory /a/b, threads T1, T2, T3 will only block on each other update the - // children map of /a/b when their individual lookups complete. - // - // T1: T2: T3: - // stat(/a/b/c) stat(/a/b/d) stat(/a/b/e) - dirMu sync.RWMutex `state:"nosave"` - - // mu protects the below fields. Lock ordering: mu must be taken after dirMu. - mu sync.Mutex `state:"nosave"` - - // children are cached via weak references. - children map[string]*refs.WeakRef `state:".(map[string]*Dirent)"` -} - -// NewDirent returns a new root Dirent, taking the caller's reference on inode. The caller -// holds the only reference to the Dirent. Parents may call hashChild to parent this Dirent. -func NewDirent(ctx context.Context, inode *Inode, name string) *Dirent { - d := newDirent(inode, name) - allDirents.add(d) - d.userVisible = true - return d -} - -// NewTransientDirent creates a transient Dirent that shouldn't actually be -// visible to users. -// -// An Inode is required. -func NewTransientDirent(inode *Inode) *Dirent { - if inode == nil { - panic("an inode is required") - } - return newDirent(inode, "transient") -} - -func newDirent(inode *Inode, name string) *Dirent { - // The Dirent needs to maintain one reference to MountSource. - if inode != nil { - inode.MountSource.IncDirentRefs() - } - d := Dirent{ - Inode: inode, - name: name, - children: make(map[string]*refs.WeakRef), - } - d.EnableLeakCheck("fs.Dirent") - return &d -} - -// NewNegativeDirent returns a new root negative Dirent. Otherwise same as NewDirent. -func NewNegativeDirent(name string) *Dirent { - return newDirent(nil, name) -} - -// IsRoot returns true if d is a root Dirent. -func (d *Dirent) IsRoot() bool { - return d.parent == nil -} - -// IsNegative returns true if d represents a path that does not exist. -func (d *Dirent) IsNegative() bool { - return d.Inode == nil -} - -// hashChild will hash child into the children list of its new parent d. -// -// Returns (*WeakRef, true) if hashing child caused a Dirent to be unhashed. The caller must -// validate the returned unhashed weak reference. Common cases: -// -// - Remove: hashing a negative Dirent unhashes a positive Dirent (unimplemented). -// - Create: hashing a positive Dirent unhashes a negative Dirent. -// - Lookup: hashing any Dirent should not unhash any other Dirent. -// -// Preconditions: -// - d.mu must be held. -// - child must be a root Dirent. -func (d *Dirent) hashChild(child *Dirent) (*refs.WeakRef, bool) { - if !child.IsRoot() { - panic("hashChild must be a root Dirent") - } - - // Assign parentage. - child.parent = d - - // Avoid letting negative Dirents take a reference on their parent; these Dirents - // don't have a role outside of the Dirent cache and should not keep their parent - // indefinitely pinned. - if !child.IsNegative() { - // Positive dirents must take a reference on their parent. - d.IncRef() - } - - return d.hashChildParentSet(child) -} - -// hashChildParentSet will rehash child into the children list of its parent d. -// -// Assumes that child.parent = d already. -func (d *Dirent) hashChildParentSet(child *Dirent) (*refs.WeakRef, bool) { - if child.parent != d { - panic("hashChildParentSet assumes the child already belongs to the parent") - } - - // Save any replaced child so our caller can validate it. - old, ok := d.children[child.name] - - // Hash the child. - d.children[child.name] = refs.NewWeakRef(child, nil) - - // Return any replaced child. - return old, ok -} - -// SyncAll iterates through mount points under d and writes back their buffered -// modifications to filesystems. -func (d *Dirent) SyncAll(ctx context.Context) { - d.mu.Lock() - defer d.mu.Unlock() - - // For negative Dirents there is nothing to sync. By definition these are - // leaves (there is nothing left to traverse). - if d.IsNegative() { - return - } - - // There is nothing to sync for a read-only filesystem. - if !d.Inode.MountSource.Flags.ReadOnly { - // NOTE(b/34856369): This should be a mount traversal, not a Dirent - // traversal, because some Inodes that need to be synced may no longer - // be reachable by name (after sys_unlink). - // - // Write out metadata, dirty page cached pages, and sync disk/remote - // caches. - d.Inode.WriteOut(ctx) - } - - // Continue iterating through other mounted filesystems. - for _, w := range d.children { - if child := w.Get(); child != nil { - child.(*Dirent).SyncAll(ctx) - child.DecRef(ctx) - } - } -} - -// BaseName returns the base name of the dirent. -func (d *Dirent) BaseName() string { - p := d.parent - if p == nil { - return d.name - } - p.mu.Lock() - defer p.mu.Unlock() - return d.name -} - -// FullName returns the fully-qualified name and a boolean value representing -// whether this Dirent was a descendant of root. -// If the root argument is nil it is assumed to be the root of the Dirent tree. -func (d *Dirent) FullName(root *Dirent) (string, bool) { - renameMu.RLock() - defer renameMu.RUnlock() - return d.fullName(root) -} - -// fullName returns the fully-qualified name and a boolean value representing -// if the root node was reachable from this Dirent. -func (d *Dirent) fullName(root *Dirent) (string, bool) { - if d == root { - return "/", true - } - - if d.IsRoot() { - if root != nil { - // We reached the top of the Dirent tree but did not encounter - // the given root. Return false for reachable so the caller - // can handle this situation accordingly. - return d.name, false - } - return d.name, true - } - - // Traverse up to parent. - d.parent.mu.Lock() - name := d.name - d.parent.mu.Unlock() - parentName, reachable := d.parent.fullName(root) - s := path.Join(parentName, name) - if d.deleted.Load() != 0 { - return s + " (deleted)", reachable - } - return s, reachable -} - -// MountRoot finds and returns the mount-root for a given dirent. -func (d *Dirent) MountRoot() *Dirent { - renameMu.RLock() - defer renameMu.RUnlock() - - mountRoot := d - for !mountRoot.mounted && mountRoot.parent != nil { - mountRoot = mountRoot.parent - } - mountRoot.IncRef() - return mountRoot -} - -// descendantOf returns true if the receiver dirent is equal to, or a -// descendant of, the argument dirent. -// -// d.mu must be held. -func (d *Dirent) descendantOf(p *Dirent) bool { - if d == p { - return true - } - if d.IsRoot() { - return false - } - return d.parent.descendantOf(p) -} - -// walk walks to path name starting at the dirent, and will not traverse above -// root Dirent. -// -// If walkMayUnlock is true then walk can unlock d.mu to execute a slow -// Inode.Lookup, otherwise walk will keep d.mu locked. -// -// Preconditions: -// - renameMu must be held for reading. -// - d.mu must be held. -// - name must must not contain "/"s. -func (d *Dirent) walk(ctx context.Context, root *Dirent, name string, walkMayUnlock bool) (*Dirent, error) { - if !IsDir(d.Inode.StableAttr) { - return nil, unix.ENOTDIR - } - - if name == "" || name == "." { - d.IncRef() - return d, nil - } else if name == ".." { - // Respect the chroot. Note that in Linux there is no check to enforce - // that d is a descendant of root. - if d == root { - d.IncRef() - return d, nil - } - // Are we already at the root? Then ".." is ".". - if d.IsRoot() { - d.IncRef() - return d, nil - } - d.parent.IncRef() - return d.parent, nil - } - - if w, ok := d.children[name]; ok { - // Try to resolve the weak reference to a hard reference. - if child := w.Get(); child != nil { - cd := child.(*Dirent) - - // Is this a negative Dirent? - if cd.IsNegative() { - // Don't leak a reference; this doesn't matter as much for negative Dirents, - // which don't hold a hard reference on their parent (their parent holds a - // hard reference on them, and they contain virtually no state). But this is - // good house-keeping. - child.DecRef(ctx) - return nil, unix.ENOENT - } - - // Do we need to revalidate this child? - // - // We never allow the file system to revalidate mounts, that could cause them - // to unexpectedly drop out before umount. - if cd.mounted || !cd.Inode.MountSource.Revalidate(ctx, name, d.Inode, cd.Inode) { - // Good to go. This is the fast-path. - return cd, nil - } - - // If we're revalidating a child, we must ensure all inotify watches release - // their pins on the child. Inotify doesn't properly support filesystems that - // revalidate dirents (since watches are lost on revalidation), but if we fail - // to unpin the watches child will never be GCed. - cd.Inode.Watches.Unpin(ctx, cd) - - // This child needs to be revalidated, fallthrough to unhash it. Make sure - // to not leak a reference from Get(). - // - // Note that previous lookups may still have a reference to this stale child; - // this can't be helped, but we can ensure that *new* lookups are up-to-date. - child.DecRef(ctx) - } - - // Either our weak reference expired or we need to revalidate it. Unhash child first, we're - // about to replace it. - delete(d.children, name) - w.Drop(ctx) - } - - // Slow path: load the InodeOperations into memory. Since this is a hot path and the lookup may be - // expensive, if possible release the lock and re-acquire it. - if walkMayUnlock { - d.mu.Unlock() // +checklocksforce: results in an inconsistent block. - } - c, err := d.Inode.Lookup(ctx, name) - if walkMayUnlock { - d.mu.Lock() // +checklocksforce: see above. - } - // No dice. - if err != nil { - return nil, err - } - - // Sanity check c, its name must be consistent. - if c.name != name { - panic(fmt.Sprintf("lookup from %q to %q returned unexpected name %q", d.name, name, c.name)) - } - - // Now that we have the lock again, check if we raced. - if w, ok := d.children[name]; ok { - // Someone else looked up or created a child at name before us. - if child := w.Get(); child != nil { - cd := child.(*Dirent) - - // There are active references to the existing child, prefer it to the one we - // retrieved from Lookup. Likely the Lookup happened very close to the insertion - // of child, so considering one stale over the other is fairly arbitrary. - c.DecRef(ctx) - - // The child that was installed could be negative. - if cd.IsNegative() { - // If so, don't leak a reference and short circuit. - child.DecRef(ctx) - return nil, unix.ENOENT - } - - // We make the judgement call that if c raced with cd they are close enough to have - // the same staleness, so we don't attempt to revalidate cd. In Linux revalidations - // can continue indefinitely (see fs/namei.c, retry_estale); we try to avoid this. - return cd, nil - } - - // Weak reference expired. We went through a full cycle of create/destroy in the time - // we did the Inode.Lookup. Fully drop the weak reference and fallback to using the child - // we looked up. - delete(d.children, name) - w.Drop(ctx) - } - - // Give the looked up child a parent. We cannot kick out entries, since we just checked above - // that there is nothing at name in d's children list. - if _, kicked := d.hashChild(c); kicked { - // Yell loudly. - panic(fmt.Sprintf("hashed child %q over existing child", c.name)) - } - - // Is this a negative Dirent? - if c.IsNegative() { - // Don't drop a reference on the negative Dirent, it was just installed and this is the - // only reference we'll ever get. d owns the reference. - return nil, unix.ENOENT - } - - // Return the positive Dirent. - return c, nil -} - -// Walk walks to a new dirent, and will not walk higher than the given root -// Dirent, which must not be nil. -func (d *Dirent) Walk(ctx context.Context, root *Dirent, name string) (*Dirent, error) { - if root == nil { - panic("Dirent.Walk: root must not be nil") - } - - // We could use lockDirectory here, but this is a hot path and we want - // to avoid defer. - renameMu.RLock() - d.dirMu.RLock() - d.mu.Lock() - - child, err := d.walk(ctx, root, name, true /* may unlock */) - - d.mu.Unlock() - d.dirMu.RUnlock() - renameMu.RUnlock() - - return child, err -} - -// exists returns true if name exists in relation to d. -// -// Preconditions: -// - renameMu must be held for reading. -// - d.mu must be held. -// - name must must not contain "/"s. -func (d *Dirent) exists(ctx context.Context, root *Dirent, name string) bool { - child, err := d.walk(ctx, root, name, false /* may unlock */) - if err != nil { - // Child may not exist. - return false - } - // Child exists. - child.DecRef(ctx) - return true -} - -// lockDirectory should be called for any operation that changes this `d`s -// children (creating or removing them). -// +checklocksacquire:d.dirMu -// +checklocksacquire:d.mu -func (d *Dirent) lockDirectory() { - renameMu.RLock() - d.dirMu.Lock() - d.mu.Lock() -} - -// unlockDirectory is the reverse of lockDirectory. -// +checklocksrelease:d.dirMu -// +checklocksrelease:d.mu -func (d *Dirent) unlockDirectory() { - d.mu.Unlock() - d.dirMu.Unlock() - renameMu.RUnlock() // +checklocksforce: see lockDirectory. -} - -// Create creates a new regular file in this directory. -func (d *Dirent) Create(ctx context.Context, root *Dirent, name string, flags FileFlags, perms FilePermissions) (*File, error) { - d.lockDirectory() - defer d.unlockDirectory() - - // Does something already exist? - if d.exists(ctx, root, name) { - return nil, unix.EEXIST - } - - // Try the create. We need to trust the file system to return EEXIST (or something - // that will translate to EEXIST) if name already exists. - file, err := d.Inode.Create(ctx, d, name, flags, perms) - if err != nil { - return nil, err - } - child := file.Dirent - - d.finishCreate(ctx, child, name) - - // Return the reference and the new file. When the last reference to - // the file is dropped, file.Dirent may no longer be cached. - return file, nil -} - -// finishCreate validates the created file, adds it as a child of this dirent, -// and notifies any watchers. -func (d *Dirent) finishCreate(ctx context.Context, child *Dirent, name string) { - // Sanity check c, its name must be consistent. - if child.name != name { - panic(fmt.Sprintf("create from %q to %q returned unexpected name %q", d.name, name, child.name)) - } - - // File systems cannot return a negative Dirent on Create, that makes no sense. - if child.IsNegative() { - panic(fmt.Sprintf("create from %q to %q returned negative Dirent", d.name, name)) - } - - // Hash the child into its parent. We can only kick out a Dirent if it is negative - // (we are replacing something that does not exist with something that now does). - if w, kicked := d.hashChild(child); kicked { - if old := w.Get(); old != nil { - if !old.(*Dirent).IsNegative() { - panic(fmt.Sprintf("hashed child %q over a positive child", child.name)) - } - // Don't leak a reference. - old.DecRef(ctx) - - // Drop d's reference. - old.DecRef(ctx) - } - - // Finally drop the useless weak reference on the floor. - w.Drop(ctx) - } - - d.Inode.Watches.Notify(name, linux.IN_CREATE, 0) - - // Allow the file system to take extra references on c. - child.maybeExtendReference() -} - -// genericCreate executes create if name does not exist. Removes a negative Dirent at name if -// create succeeds. -func (d *Dirent) genericCreate(ctx context.Context, root *Dirent, name string, create func() error) error { - d.lockDirectory() - defer d.unlockDirectory() - - // Does something already exist? - if d.exists(ctx, root, name) { - return unix.EEXIST - } - - // Remove any negative Dirent. We've already asserted above with d.exists - // that the only thing remaining here can be a negative Dirent. - if w, ok := d.children[name]; ok { - // Same as Create. - if old := w.Get(); old != nil { - if !old.(*Dirent).IsNegative() { - panic(fmt.Sprintf("hashed over a positive child %q", old.(*Dirent).name)) - } - // Don't leak a reference. - old.DecRef(ctx) - - // Drop d's reference. - old.DecRef(ctx) - } - - // Unhash the negative Dirent, name needs to exist now. - delete(d.children, name) - - // Finally drop the useless weak reference on the floor. - w.Drop(ctx) - } - - // Execute the create operation. - return create() -} - -// CreateLink creates a new link in this directory. -func (d *Dirent) CreateLink(ctx context.Context, root *Dirent, oldname, newname string) error { - return d.genericCreate(ctx, root, newname, func() error { - if err := d.Inode.CreateLink(ctx, d, oldname, newname); err != nil { - return err - } - d.Inode.Watches.Notify(newname, linux.IN_CREATE, 0) - return nil - }) -} - -// CreateHardLink creates a new hard link in this directory. -func (d *Dirent) CreateHardLink(ctx context.Context, root *Dirent, target *Dirent, name string) error { - // Make sure that target does not span filesystems. - if d.Inode.MountSource != target.Inode.MountSource { - return unix.EXDEV - } - - // Directories are never linkable. See fs/namei.c:vfs_link. - if IsDir(target.Inode.StableAttr) { - return unix.EPERM - } - - return d.genericCreate(ctx, root, name, func() error { - if err := d.Inode.CreateHardLink(ctx, d, target, name); err != nil { - return err - } - target.Inode.Watches.Notify("", linux.IN_ATTRIB, 0) // Link count change. - d.Inode.Watches.Notify(name, linux.IN_CREATE, 0) - return nil - }) -} - -// CreateDirectory creates a new directory under this dirent. -func (d *Dirent) CreateDirectory(ctx context.Context, root *Dirent, name string, perms FilePermissions) error { - return d.genericCreate(ctx, root, name, func() error { - if err := d.Inode.CreateDirectory(ctx, d, name, perms); err != nil { - return err - } - d.Inode.Watches.Notify(name, linux.IN_ISDIR|linux.IN_CREATE, 0) - return nil - }) -} - -// Bind satisfies the InodeOperations interface; otherwise same as GetFile. -func (d *Dirent) Bind(ctx context.Context, root *Dirent, name string, data transport.BoundEndpoint, perms FilePermissions) (*Dirent, error) { - var childDir *Dirent - err := d.genericCreate(ctx, root, name, func() error { - var e error - childDir, e = d.Inode.Bind(ctx, d, name, data, perms) - if e != nil { - return e - } - d.finishCreate(ctx, childDir, name) - return nil - }) - if err == unix.EEXIST { - return nil, unix.EADDRINUSE - } - if err != nil { - return nil, err - } - return childDir, err -} - -// CreateFifo creates a new named pipe under this dirent. -func (d *Dirent) CreateFifo(ctx context.Context, root *Dirent, name string, perms FilePermissions) error { - return d.genericCreate(ctx, root, name, func() error { - if err := d.Inode.CreateFifo(ctx, d, name, perms); err != nil { - return err - } - d.Inode.Watches.Notify(name, linux.IN_CREATE, 0) - return nil - }) -} - -// GetDotAttrs returns the DentAttrs corresponding to "." and ".." directories. -func (d *Dirent) GetDotAttrs(root *Dirent) (DentAttr, DentAttr) { - // Get '.'. - sattr := d.Inode.StableAttr - dot := DentAttr{ - Type: sattr.Type, - InodeID: sattr.InodeID, - } - - // Hold d.mu while we call d.descendantOf. - d.mu.Lock() - defer d.mu.Unlock() - - // Get '..'. - if !d.IsRoot() && d.descendantOf(root) { - // Dirent is a descendant of the root. Get its parent's attrs. - psattr := d.parent.Inode.StableAttr - dotdot := DentAttr{ - Type: psattr.Type, - InodeID: psattr.InodeID, - } - return dot, dotdot - } - // Dirent is either root or not a descendant of the root. ".." is the - // same as ".". - return dot, dot -} - -// DirIterator is an open directory containing directory entries that can be read. -type DirIterator interface { - // IterateDir emits directory entries by calling dirCtx.EmitDir, beginning - // with the entry at offset and returning the next directory offset. - // - // Entries for "." and ".." must *not* be included. - // - // If the offset returned is the same as the argument offset, then - // nothing has been serialized. This is equivalent to reaching EOF. - // In this case serializer.Written() should return 0. - // - // The order of entries to emit must be consistent between Readdir - // calls, and must start with the given offset. - // - // The caller must ensure that this operation is permitted. - IterateDir(ctx context.Context, d *Dirent, dirCtx *DirCtx, offset int) (int, error) -} - -// DirentReaddir serializes the directory entries of d including "." and "..". -// -// Arguments: -// -// - d: the Dirent of the directory being read; required to provide "." and "..". -// - it: the directory iterator; which represents an open directory handle. -// - root: fs root; if d is equal to the root, then '..' will refer to d. -// - ctx: context provided to file systems in order to select and serialize entries. -// - offset: the current directory offset. -// -// Returns the offset of the *next* element which was not serialized. -func DirentReaddir(ctx context.Context, d *Dirent, it DirIterator, root *Dirent, dirCtx *DirCtx, offset int64) (int64, error) { - offset, err := direntReaddir(ctx, d, it, root, dirCtx, offset) - // Serializing any directory entries at all means success. - if dirCtx.Serializer.Written() > 0 { - return offset, nil - } - return offset, err -} - -func direntReaddir(ctx context.Context, d *Dirent, it DirIterator, root *Dirent, dirCtx *DirCtx, offset int64) (int64, error) { - if root == nil { - panic("Dirent.Readdir: root must not be nil") - } - if dirCtx.Serializer == nil { - panic("Dirent.Readdir: serializer must not be nil") - } - - // Check that this is actually a directory before emitting anything. - // Once we have written entries for "." and "..", future errors from - // IterateDir will be hidden. - if !IsDir(d.Inode.StableAttr) { - return 0, linuxerr.ENOTDIR - } - - // This is a special case for lseek(fd, 0, SEEK_END). - // See SeekWithDirCursor for more details. - if offset == FileMaxOffset { - return offset, nil - } - - // Collect attrs for "." and "..". - dot, dotdot := d.GetDotAttrs(root) - - // Emit "." and ".." if the offset is low enough. - if offset == 0 { - // Serialize ".". - if err := dirCtx.DirEmit(".", dot); err != nil { - return offset, err - } - offset++ - } - if offset == 1 { - // Serialize "..". - if err := dirCtx.DirEmit("..", dotdot); err != nil { - return offset, err - } - offset++ - } - - // it.IterateDir should be passed an offset that does not include the - // initial dot elements. We will add them back later. - offset -= 2 - newOffset, err := it.IterateDir(ctx, d, dirCtx, int(offset)) - if int64(newOffset) < offset { - panic(fmt.Sprintf("node.Readdir returned offset %v less than input offset %v", newOffset, offset)) - } - // Add the initial nodes back to the offset count. - newOffset += 2 - return int64(newOffset), err -} - -// flush flushes all weak references recursively, and removes any cached -// references to children. -// -// Preconditions: d.mu must be held. -func (d *Dirent) flush(ctx context.Context) { - expired := make(map[string]*refs.WeakRef) - for n, w := range d.children { - // Call flush recursively on each child before removing our - // reference on it, and removing the cache's reference. - if child := w.Get(); child != nil { - cd := child.(*Dirent) - - if !cd.IsNegative() { - // Flush the child. - cd.mu.Lock() - cd.flush(ctx) - cd.mu.Unlock() - - // Allow the file system to drop extra references on child. - cd.dropExtendedReference() - } - - // Don't leak a reference. - child.DecRef(ctx) - } - // Check if the child dirent is closed, and mark it as expired if it is. - // We must call w.Get() again here, since the child could have been closed - // by the calls to flush() and cache.Remove() in the above if-block. - if child := w.Get(); child != nil { - child.DecRef(ctx) - } else { - expired[n] = w - } - } - - // Remove expired entries. - for n, w := range expired { - delete(d.children, n) - w.Drop(ctx) - } -} - -// isMountPoint returns true if the dirent is a mount point or the root. -func (d *Dirent) isMountPoint() bool { - d.mu.Lock() - defer d.mu.Unlock() - return d.isMountPointLocked() -} - -func (d *Dirent) isMountPointLocked() bool { - return d.mounted || d.parent == nil -} - -// mount mounts a new dirent with the given inode over d. -// -// Precondition: must be called with mm.withMountLocked held on `d`. -func (d *Dirent) mount(ctx context.Context, inode *Inode) (newChild *Dirent, err error) { - // Did we race with deletion? - if d.deleted.Load() != 0 { - return nil, linuxerr.ENOENT - } - - // Refuse to mount a symlink. - // - // See Linux equivalent in fs/namespace.c:do_add_mount. - if IsSymlink(inode.StableAttr) { - return nil, linuxerr.EINVAL - } - - // Dirent that'll replace d. - // - // Note that NewDirent returns with one reference taken; the reference - // is donated to the caller as the mount reference. - replacement := NewDirent(ctx, inode, d.name) - replacement.mounted = true - - weakRef, ok := d.parent.hashChild(replacement) - if !ok { - panic("mount must mount over an existing dirent") - } - weakRef.Drop(ctx) - - // Note that even though `d` is now hidden, it still holds a reference - // to its parent. - return replacement, nil -} - -// unmount unmounts `d` and replaces it with the last Dirent that was in its -// place, supplied by the MountNamespace as `replacement`. -// -// Precondition: must be called with mm.withMountLocked held on `d`. -func (d *Dirent) unmount(ctx context.Context, replacement *Dirent) error { - // Did we race with deletion? - if d.deleted.Load() != 0 { - return linuxerr.ENOENT - } - - // Remount our former child in its place. - // - // As replacement used to be our child, it must already have the right - // parent. - weakRef, ok := d.parent.hashChildParentSet(replacement) - if !ok { - panic("mount must mount over an existing dirent") - } - weakRef.Drop(ctx) - - // d is not reachable anymore, and hence not mounted anymore. - d.mounted = false - - // Drop mount reference. - d.DecRef(ctx) - return nil -} - -// Remove removes the given file or symlink. The root dirent is used to -// resolve name, and must not be nil. -func (d *Dirent) Remove(ctx context.Context, root *Dirent, name string, dirPath bool) error { - // Check the root. - if root == nil { - panic("Dirent.Remove: root must not be nil") - } - - d.lockDirectory() - defer d.unlockDirectory() - - // Try to walk to the node. - child, err := d.walk(ctx, root, name, false /* may unlock */) - if err != nil { - // Child does not exist. - return err - } - defer child.DecRef(ctx) - - // Remove cannot remove directories. - if IsDir(child.Inode.StableAttr) { - return unix.EISDIR - } else if dirPath { - return unix.ENOTDIR - } - - // Remove cannot remove a mount point. - if child.isMountPoint() { - return unix.EBUSY - } - - // Try to remove name on the file system. - if err := d.Inode.Remove(ctx, d, child); err != nil { - return err - } - - // Link count changed, this only applies to non-directory nodes. - child.Inode.Watches.Notify("", linux.IN_ATTRIB, 0) - - // Mark name as deleted and remove from children. - child.deleted.Store(1) - if w, ok := d.children[name]; ok { - delete(d.children, name) - w.Drop(ctx) - } - - // Allow the file system to drop extra references on child. - child.dropExtendedReference() - - // Finally, let inotify know the child is being unlinked. Drop any extra - // refs from inotify to this child dirent. This doesn't necessarily mean the - // watches on the underlying inode will be destroyed, since the underlying - // inode may have other links. If this was the last link, the events for the - // watch removal will be queued by the inode destructor. - child.Inode.Watches.MarkUnlinked() - child.Inode.Watches.Unpin(ctx, child) - d.Inode.Watches.Notify(name, linux.IN_DELETE, 0) - - return nil -} - -// RemoveDirectory removes the given directory. The root dirent is used to -// resolve name, and must not be nil. -func (d *Dirent) RemoveDirectory(ctx context.Context, root *Dirent, name string) error { - // Check the root. - if root == nil { - panic("Dirent.Remove: root must not be nil") - } - - d.lockDirectory() - defer d.unlockDirectory() - - // Check for dots. - if name == "." { - // Rejected as the last component by rmdir(2). - return unix.EINVAL - } - if name == ".." { - // If d was found, then its parent is not empty. - return unix.ENOTEMPTY - } - - // Try to walk to the node. - child, err := d.walk(ctx, root, name, false /* may unlock */) - if err != nil { - // Child does not exist. - return err - } - defer child.DecRef(ctx) - - // RemoveDirectory can only remove directories. - if !IsDir(child.Inode.StableAttr) { - return unix.ENOTDIR - } - - // Remove cannot remove a mount point. - if child.isMountPoint() { - return unix.EBUSY - } - - // Try to remove name on the file system. - if err := d.Inode.Remove(ctx, d, child); err != nil { - return err - } - - // Mark name as deleted and remove from children. - child.deleted.Store(1) - if w, ok := d.children[name]; ok { - delete(d.children, name) - w.Drop(ctx) - } - - // Allow the file system to drop extra references on child. - child.dropExtendedReference() - - // Finally, let inotify know the child is being unlinked. Drop any extra - // refs from inotify to this child dirent. - child.Inode.Watches.MarkUnlinked() - child.Inode.Watches.Unpin(ctx, child) - d.Inode.Watches.Notify(name, linux.IN_ISDIR|linux.IN_DELETE, 0) - - return nil -} - -// destroy closes this node and all children. -func (d *Dirent) destroy(ctx context.Context) { - if d.IsNegative() { - // Nothing to tear-down and no parent references to drop, since a negative - // Dirent does not take a references on its parent, has no Inode and no children. - return - } - - d.mu.Lock() - defer d.mu.Unlock() - - // Drop all weak references. - for _, w := range d.children { - if c := w.Get(); c != nil { - if c.(*Dirent).IsNegative() { - // The parent holds both weak and strong refs in the case of - // negative dirents. - c.DecRef(ctx) - } - // Drop the reference we just acquired in WeakRef.Get. - c.DecRef(ctx) - } - w.Drop(ctx) - } - d.children = nil - - allDirents.remove(d) - - // Drop our reference to the Inode. - d.Inode.DecRef(ctx) - - // Allow the Dirent to be GC'ed after this point, since the Inode may still - // be referenced after the Dirent is destroyed (for instance by filesystem - // internal caches or hard links). - d.Inode = nil - - // Drop the reference we have on our parent if we took one. renameMu doesn't need to be - // held because d can't be reparented without any references to it left. - if d.parent != nil { - d.parent.DecRef(ctx) - } -} - -// IncRef increases the Dirent's refcount as well as its mount's refcount. -// -// IncRef implements RefCounter.IncRef. -func (d *Dirent) IncRef() { - if d.Inode != nil { - d.Inode.MountSource.IncDirentRefs() - } - d.AtomicRefCount.IncRef() -} - -// TryIncRef implements RefCounter.TryIncRef. -func (d *Dirent) TryIncRef() bool { - ok := d.AtomicRefCount.TryIncRef() - if ok && d.Inode != nil { - d.Inode.MountSource.IncDirentRefs() - } - return ok -} - -// DecRef decreases the Dirent's refcount and drops its reference on its mount. -// -// DecRef implements RefCounter.DecRef with destructor d.destroy. -func (d *Dirent) DecRef(ctx context.Context) { - if d.Inode != nil { - // Keep mount around, since DecRef may destroy d.Inode. - msrc := d.Inode.MountSource - d.DecRefWithDestructor(ctx, d.destroy) - msrc.DecDirentRefs() - } else { - d.DecRefWithDestructor(ctx, d.destroy) - } -} - -// InotifyEvent notifies all watches on the inode for this dirent and its parent -// of potential events. The events may not actually propagate up to the user, -// depending on the event masks. InotifyEvent automatically provides the name of -// the current dirent as the subject of the event as required, and adds the -// IN_ISDIR flag for dirents that refer to directories. -func (d *Dirent) InotifyEvent(events, cookie uint32) { - // N.B. We don't defer the unlocks because InotifyEvent is in the hot - // path of all IO operations, and the defers cost too much for small IO - // operations. - renameMu.RLock() - - if IsDir(d.Inode.StableAttr) { - events |= linux.IN_ISDIR - } - - // The ordering below is important, Linux always notifies the parent first. - if d.parent != nil { - // name is immediately stale w.r.t. renames (renameMu doesn't - // protect against renames in the same directory). Holding - // d.parent.mu around Notify() wouldn't matter since Notify - // doesn't provide a synchronous mechanism for reading the name - // anyway. - d.parent.mu.Lock() - name := d.name - d.parent.mu.Unlock() - d.parent.Inode.Watches.Notify(name, events, cookie) - } - d.Inode.Watches.Notify("", events, cookie) - - renameMu.RUnlock() -} - -// maybeExtendReference caches a reference on this Dirent if -// MountSourceOperations.Keep returns true. -func (d *Dirent) maybeExtendReference() { - if msrc := d.Inode.MountSource; msrc.Keep(d) { - msrc.fscache.Add(d) - } -} - -// dropExtendedReference drops any cached reference held by the -// MountSource on the dirent. -func (d *Dirent) dropExtendedReference() { - d.Inode.MountSource.fscache.Remove(d) -} - -// lockForRename takes locks on oldParent and newParent as required by Rename. -// On return, unlockForRename must always be called, even with an error. -// +checklocksacquire:oldParent.mu -// +checklocksacquire:newParent.mu -func lockForRename(oldParent *Dirent, oldName string, newParent *Dirent, newName string) error { - renameMu.Lock() - if oldParent == newParent { - oldParent.mu.Lock() - return nil // +checklocksforce: only one lock exists. - } - - // Renaming between directories is a bit subtle: - // - // - A concurrent cross-directory Rename may try to lock in the opposite - // order; take renameMu to prevent this from happening. - // - If either directory is an ancestor of the other, then a concurrent - // Remove may lock the descendant (in DecRef -> closeAll) while holding a - // lock on the ancestor; to avoid this, ensure we take locks in the same - // ancestor-to-descendant order. (Holding renameMu prevents this - // relationship from changing.) - - // First check if newParent is a descendant of oldParent. - child := newParent - for p := newParent.parent; p != nil; p = p.parent { - if p == oldParent { - oldParent.mu.Lock() - newParent.mu.Lock() - var err error - if child.name == oldName { - // newParent is not just a descendant of oldParent, but - // more specifically of oldParent/oldName. That is, we're - // trying to rename something into a subdirectory of - // itself. - err = unix.EINVAL - } - return err - } - child = p - } - - // Otherwise, either oldParent is a descendant of newParent or the two - // have no relationship; in either case we can do this: - newParent.mu.Lock() - oldParent.mu.Lock() - return nil -} - -// unlockForRename is the opposite of lockForRename. -// +checklocksrelease:oldParent.mu -// +checklocksrelease:newParent.mu -func unlockForRename(oldParent, newParent *Dirent) { - if oldParent == newParent { - oldParent.mu.Unlock() - renameMu.Unlock() // +checklocksforce: only one lock exists. - return - } - newParent.mu.Unlock() - oldParent.mu.Unlock() - renameMu.Unlock() // +checklocksforce: not tracked. -} - -func (d *Dirent) checkSticky(ctx context.Context, victim *Dirent) error { - uattr, err := d.Inode.UnstableAttr(ctx) - if err != nil { - return linuxerr.EPERM - } - if !uattr.Perms.Sticky { - return nil - } - - creds := auth.CredentialsFromContext(ctx) - if uattr.Owner.UID == creds.EffectiveKUID { - return nil - } - - vuattr, err := victim.Inode.UnstableAttr(ctx) - if err != nil { - return linuxerr.EPERM - } - if vuattr.Owner.UID == creds.EffectiveKUID { - return nil - } - if victim.Inode.CheckCapability(ctx, linux.CAP_FOWNER) { - return nil - } - return linuxerr.EPERM -} - -// MayDelete determines whether `name`, a child of `d`, can be deleted or -// renamed by `ctx`. -// -// Compare Linux kernel fs/namei.c:may_delete. -func (d *Dirent) MayDelete(ctx context.Context, root *Dirent, name string) error { - if err := d.Inode.CheckPermission(ctx, PermMask{Write: true, Execute: true}); err != nil { - return err - } - - d.lockDirectory() - defer d.unlockDirectory() - - victim, err := d.walk(ctx, root, name, true /* may unlock */) - if err != nil { - return err - } - defer victim.DecRef(ctx) - - return d.mayDelete(ctx, victim) -} - -// mayDelete determines whether `victim`, a child of `dir`, can be deleted or -// renamed by `ctx`. -// -// Preconditions: `dir` is writable and executable by `ctx`. -func (d *Dirent) mayDelete(ctx context.Context, victim *Dirent) error { - if err := d.checkSticky(ctx, victim); err != nil { - return err - } - - if victim.IsRoot() { - return linuxerr.EBUSY - } - - return nil -} - -// Rename atomically converts the child of oldParent named oldName to a -// child of newParent named newName. -func Rename(ctx context.Context, root *Dirent, oldParent *Dirent, oldName string, newParent *Dirent, newName string) error { - if root == nil { - panic("Rename: root must not be nil") - } - if oldParent == newParent && oldName == newName { - return nil - } - - // Acquire global renameMu lock, and mu locks on oldParent/newParent. - err := lockForRename(oldParent, oldName, newParent, newName) - defer unlockForRename(oldParent, newParent) - if err != nil { - return err - } - - // Do we have general permission to remove from oldParent and - // create/replace in newParent? - if err := oldParent.Inode.CheckPermission(ctx, PermMask{Write: true, Execute: true}); err != nil { - return err - } - if err := newParent.Inode.CheckPermission(ctx, PermMask{Write: true, Execute: true}); err != nil { - return err - } - - // renamed is the dirent that will be renamed to something else. - renamed, err := oldParent.walk(ctx, root, oldName, false /* may unlock */) - if err != nil { - return err - } - defer renamed.DecRef(ctx) - - // Check that the renamed dirent is deletable. - if err := oldParent.mayDelete(ctx, renamed); err != nil { - return err - } - - // Check that the renamed dirent is not a mount point. - if renamed.isMountPointLocked() { - return unix.EBUSY - } - - // Source should not be an ancestor of the target. - if newParent.descendantOf(renamed) { - return unix.EINVAL - } - - // Per rename(2): "... EACCES: ... or oldpath is a directory and does not - // allow write permission (needed to update the .. entry)." - if IsDir(renamed.Inode.StableAttr) { - if err := renamed.Inode.CheckPermission(ctx, PermMask{Write: true}); err != nil { - return err - } - } - - // replaced is the dirent that is being overwritten by rename. - replaced, err := newParent.walk(ctx, root, newName, false /* may unlock */) - if err != nil { - if !linuxerr.Equals(linuxerr.ENOENT, err) { - return err - } - - // newName doesn't exist; simply create it below. - replaced = nil - } else { - // Check constraints on the dirent being replaced. - - // NOTE(b/111808347): We don't want to keep replaced alive - // across the Rename, so must call DecRef manually (no defer). - - // Check that we can delete replaced. - if err := newParent.mayDelete(ctx, replaced); err != nil { - replaced.DecRef(ctx) - return err - } - - // Target should not be an ancestor of source. - if oldParent.descendantOf(replaced) { - replaced.DecRef(ctx) - - // Note that Linux returns EINVAL if the source is an - // ancestor of target, but ENOTEMPTY if the target is - // an ancestor of source (unless RENAME_EXCHANGE flag - // is present). See fs/namei.c:renameat2. - return unix.ENOTEMPTY - } - - // Check that replaced is not a mount point. - if replaced.isMountPointLocked() { - replaced.DecRef(ctx) - return unix.EBUSY - } - - // Require that a directory is replaced by a directory. - oldIsDir := IsDir(renamed.Inode.StableAttr) - newIsDir := IsDir(replaced.Inode.StableAttr) - if !newIsDir && oldIsDir { - replaced.DecRef(ctx) - return unix.ENOTDIR - } - if !oldIsDir && newIsDir { - replaced.DecRef(ctx) - return unix.EISDIR - } - - // Allow the file system to drop extra references on replaced. - replaced.dropExtendedReference() - - // NOTE(b/31798319,b/31867149,b/31867671): Keeping a dirent - // open across renames is currently broken for multiple - // reasons, so we flush all references on the replaced node and - // its children. - replaced.Inode.Watches.Unpin(ctx, replaced) - replaced.mu.Lock() - replaced.flush(ctx) - replaced.mu.Unlock() - - // Done with replaced. - replaced.DecRef(ctx) - } - - if err := renamed.Inode.Rename(ctx, oldParent, renamed, newParent, newName, replaced != nil); err != nil { - return err - } - - renamed.name = newName - renamed.parent = newParent - if oldParent != newParent { - // Reparent the reference held by renamed.parent. oldParent.DecRef - // can't destroy oldParent (and try to retake its lock) because - // Rename's caller must be holding a reference. - newParent.IncRef() - oldParent.DecRef(ctx) - } - if w, ok := newParent.children[newName]; ok { - w.Drop(ctx) - delete(newParent.children, newName) - } - if w, ok := oldParent.children[oldName]; ok { - w.Drop(ctx) - delete(oldParent.children, oldName) - } - - // Add a weak reference from the new parent. This ensures that the child - // can still be found from the new parent if a prior hard reference is - // held on renamed. - // - // This is required for file lock correctness because file locks are per-Dirent - // and without maintaining the a cached child (via a weak reference) for renamed, - // multiple Dirents can correspond to the same resource (by virtue of the renamed - // Dirent being unreachable by its parent and it being looked up). - newParent.children[newName] = refs.NewWeakRef(renamed, nil) - - // Queue inotify events for the rename. - var ev uint32 - if IsDir(renamed.Inode.StableAttr) { - ev |= linux.IN_ISDIR - } - - cookie := uniqueid.InotifyCookie(ctx) - oldParent.Inode.Watches.Notify(oldName, ev|linux.IN_MOVED_FROM, cookie) - newParent.Inode.Watches.Notify(newName, ev|linux.IN_MOVED_TO, cookie) - // Somewhat surprisingly, self move events do not have a cookie. - renamed.Inode.Watches.Notify("", linux.IN_MOVE_SELF, 0) - - // Allow the file system to drop extra references on renamed. - renamed.dropExtendedReference() - - // Same as replaced.flush above. - renamed.mu.Lock() - renamed.flush(ctx) - renamed.mu.Unlock() - - return nil -} diff --git a/pkg/sentry/fs/dirent_cache.go b/pkg/sentry/fs/dirent_cache.go deleted file mode 100644 index 7d9dd717e..000000000 --- a/pkg/sentry/fs/dirent_cache.go +++ /dev/null @@ -1,175 +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" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sync" -) - -// DirentCache is an LRU cache of Dirents. The Dirent's refCount is -// incremented when it is added to the cache, and decremented when it is -// removed. -// -// A nil DirentCache corresponds to a cache with size 0. All methods can be -// called, but nothing is actually cached. -// -// +stateify savable -type DirentCache struct { - // Maximum size of the cache. This must be saved manually, to handle the case - // when cache is nil. - maxSize uint64 - - // limit restricts the number of entries in the cache amoung multiple caches. - // It may be nil if there are no global limit for this cache. - limit *DirentCacheLimiter - - // mu protects currentSize and direntList. - mu sync.Mutex `state:"nosave"` - - // currentSize is the number of elements in the cache. It must be zero (i.e. - // the cache must be empty) on Save. - currentSize uint64 `state:"zerovalue"` - - // list is a direntList, an ilist of Dirents. New Dirents are added - // to the front of the list. Old Dirents are removed from the back of - // the list. It must be zerovalue (i.e. the cache must be empty) on Save. - list direntList `state:"zerovalue"` -} - -// NewDirentCache returns a new DirentCache with the given maxSize. -func NewDirentCache(maxSize uint64) *DirentCache { - return &DirentCache{ - maxSize: maxSize, - } -} - -// Add adds the element to the cache and increments the refCount. If the -// argument is already in the cache, it is moved to the front. An element is -// removed from the back if the cache is over capacity. -func (c *DirentCache) Add(d *Dirent) { - if c == nil || c.maxSize == 0 { - return - } - - c.mu.Lock() - if c.contains(d) { - // d is already in cache. Bump it to the front. - // currentSize and refCount are unaffected. - c.list.Remove(d) - c.list.PushFront(d) - c.mu.Unlock() - return - } - - // First check against the global limit. - for c.limit != nil && !c.limit.tryInc() { - if c.currentSize == 0 { - // If the global limit is reached, but there is nothing more to drop from - // this cache, there is not much else to do. - c.mu.Unlock() - return - } - c.remove(c.list.Back()) - } - - // d is not in cache. Add it and take a reference. - c.list.PushFront(d) - d.IncRef() - c.currentSize++ - - c.maybeShrink() - - c.mu.Unlock() -} - -func (c *DirentCache) remove(d *Dirent) { - if !c.contains(d) { - panic(fmt.Sprintf("trying to remove %v, which is not in the dirent cache", d)) - } - c.list.Remove(d) - d.DecRef(context.Background()) - c.currentSize-- - if c.limit != nil { - c.limit.dec() - } -} - -// Remove removes the element from the cache and decrements its refCount. It -// also sets the previous and next elements to nil, which allows us to -// determine if a given element is in the cache. -func (c *DirentCache) Remove(d *Dirent) { - if c == nil || c.maxSize == 0 { - return - } - c.mu.Lock() - if !c.contains(d) { - c.mu.Unlock() - return - } - c.remove(d) - c.mu.Unlock() -} - -// Size returns the number of elements in the cache. -func (c *DirentCache) Size() uint64 { - if c == nil { - return 0 - } - c.mu.Lock() - size := c.currentSize - c.mu.Unlock() - return size -} - -func (c *DirentCache) contains(d *Dirent) bool { - // If d has a Prev or Next element, then it is in the cache. - if d.Prev() != nil || d.Next() != nil { - return true - } - // Otherwise, d is in the cache if it is the only element (and thus the - // first element). - return c.list.Front() == d -} - -// Invalidate removes all Dirents from the cache, calling DecRef on each. -func (c *DirentCache) Invalidate() { - if c == nil { - return - } - c.mu.Lock() - for c.list.Front() != nil { - c.remove(c.list.Front()) - } - c.mu.Unlock() -} - -// setMaxSize sets cache max size. If current size is larger than max size, the -// cache shrinks to accommodate the new max. -func (c *DirentCache) setMaxSize(max uint64) { - c.mu.Lock() - c.maxSize = max - c.maybeShrink() - c.mu.Unlock() -} - -// shrink removes the oldest element until the list is under the size limit. -func (c *DirentCache) maybeShrink() { - for c.maxSize > 0 && c.currentSize > c.maxSize { - c.remove(c.list.Back()) - } -} diff --git a/pkg/sentry/fs/dirent_cache_limiter.go b/pkg/sentry/fs/dirent_cache_limiter.go deleted file mode 100644 index 525ee25f9..000000000 --- a/pkg/sentry/fs/dirent_cache_limiter.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 fs - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/sync" -) - -// DirentCacheLimiter acts as a global limit for all dirent caches in the -// process. -// -// +stateify savable -type DirentCacheLimiter struct { - mu sync.Mutex `state:"nosave"` - max uint64 - count uint64 `state:"zerovalue"` -} - -// NewDirentCacheLimiter creates a new DirentCacheLimiter. -func NewDirentCacheLimiter(max uint64) *DirentCacheLimiter { - return &DirentCacheLimiter{max: max} -} - -func (d *DirentCacheLimiter) tryInc() bool { - d.mu.Lock() - if d.count >= d.max { - d.mu.Unlock() - return false - } - d.count++ - d.mu.Unlock() - return true -} - -func (d *DirentCacheLimiter) dec() { - d.mu.Lock() - if d.count == 0 { - panic(fmt.Sprintf("underflowing DirentCacheLimiter count: %+v", d)) - } - d.count-- - d.mu.Unlock() -} diff --git a/pkg/sentry/fs/dirent_state.go b/pkg/sentry/fs/dirent_state.go deleted file mode 100644 index 7aaf8918f..000000000 --- a/pkg/sentry/fs/dirent_state.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 fs - -import ( - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/refs" -) - -// beforeSave is invoked by stateify. -func (d *Dirent) beforeSave() { - // Refuse to save if the file is on a non-virtual file system and has - // already been deleted (but still has open fds, which is why the Dirent - // is still accessible). We know the the restore re-opening of the file - // will always fail. This condition will last until all the open fds and - // this Dirent are closed and released. - // - // Such "dangling" open files on virtual file systems (e.g., tmpfs) is - // OK to save as their restore does not require re-opening the files. - // - // Note that this is rejection rather than failure---it would be - // perfectly OK to save---we are simply disallowing it here to prevent - // generating non-restorable state dumps. As the program continues its - // execution, it may become allowed to save again. - if !d.Inode.IsVirtual() && d.deleted.Load() != 0 { - n, _ := d.FullName(nil /* root */) - panic(ErrSaveRejection{fmt.Errorf("deleted file %q still has open fds", n)}) - } -} - -// saveChildren is invoked by stateify. -func (d *Dirent) saveChildren() map[string]*Dirent { - c := make(map[string]*Dirent) - for name, w := range d.children { - if rc := w.Get(); rc != nil { - // Drop the reference count obtain in w.Get() - rc.DecRef(context.Background()) - - cd := rc.(*Dirent) - if cd.IsNegative() { - // Don't bother saving negative Dirents. - continue - } - c[name] = cd - } - } - return c -} - -// loadChildren is invoked by stateify. -func (d *Dirent) loadChildren(children map[string]*Dirent) { - d.children = make(map[string]*refs.WeakRef) - for name, c := range children { - d.children[name] = refs.NewWeakRef(c, nil) - } -} - -// afterLoad is invoked by stateify. -func (d *Dirent) afterLoad() { - if d.userVisible { - allDirents.add(d) - } -} diff --git a/pkg/sentry/fs/file.go b/pkg/sentry/fs/file.go deleted file mode 100644 index 9537503f0..000000000 --- a/pkg/sentry/fs/file.go +++ /dev/null @@ -1,538 +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 ( - "math" - - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/refs" - "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" - "gvisor.dev/gvisor/pkg/sentry/uniqueid" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// FileMaxOffset is the maximum possible file offset. -const FileMaxOffset = math.MaxInt64 - -// File is an open file handle. It is thread-safe. -// -// File provides stronger synchronization guarantees than Linux. Linux -// synchronizes lseek(2), read(2), and write(2) with respect to the file -// offset for regular files and only for those interfaces. See -// fs/read_write.c:fdget_pos, fs.read_write.c:fdput_pos and FMODE_ATOMIC_POS. -// -// In contrast, File synchronizes any operation that could take a long time -// under a single abortable mutex which also synchronizes lseek(2), read(2), -// and write(2). -// -// FIXME(b/38451980): Split synchronization from cancellation. -// -// +stateify savable -type File struct { - refs.AtomicRefCount - - // UniqueID is the globally unique identifier of the File. - UniqueID uint64 - - // Dirent is the Dirent backing this File. This encodes the name - // of the File via Dirent.FullName() as well as its identity via the - // Dirent's Inode. The Dirent is non-nil. - // - // A File holds a reference to this Dirent. Using the returned Dirent is - // only safe as long as a reference on the File is held. The association - // between a File and a Dirent is immutable. - // - // Files that are not parented in a filesystem return a root Dirent - // that holds a reference to their Inode. - // - // The name of the Dirent may reflect parentage if the Dirent is not a - // root Dirent or the identity of the File on a pseudo filesystem (pipefs, - // sockfs, etc). - // - // Multiple Files may hold a reference to the same Dirent. This is the - // common case for Files that are parented and maintain consistency with - // other files via the Dirent cache. - Dirent *Dirent - - // flagsMu protects flags and async below. - flagsMu sync.Mutex `state:"nosave"` - - // flags are the File's flags. Setting or getting flags is fully atomic - // and is not protected by mu (below). - flags FileFlags - - // async handles O_ASYNC notifications. - async FileAsync - - // saving indicates that this file is in the process of being saved. - saving bool `state:"nosave"` - - // mu is dual-purpose: first, to make read(2) and write(2) thread-safe - // in conformity with POSIX, and second, to cancel operations before they - // begin in response to interruptions (i.e. signals). - mu sync.Mutex `state:"nosave"` - - // FileOperations implements file system specific behavior for this File. - FileOperations FileOperations `state:"wait"` - - // offset is the File's offset. Updating offset is protected by mu but - // can be read atomically via File.Offset() outside of mu. - offset atomicbitops.Int64 -} - -// NewFile returns a File. It takes a reference on the Dirent and owns the -// lifetime of the FileOperations. Files that do not support reading and -// writing at an arbitrary offset should set flags.Pread and flags.Pwrite -// to false respectively. -func NewFile(ctx context.Context, dirent *Dirent, flags FileFlags, fops FileOperations) *File { - dirent.IncRef() - f := File{ - UniqueID: uniqueid.GlobalFromContext(ctx), - Dirent: dirent, - FileOperations: fops, - flags: flags, - } - f.EnableLeakCheck("fs.File") - return &f -} - -// DecRef destroys the File when it is no longer referenced. -func (f *File) DecRef(ctx context.Context) { - f.DecRefWithDestructor(ctx, func(context.Context) { - // Drop BSD style locks. - lockRng := lock.LockRange{Start: 0, End: lock.LockEOF} - f.Dirent.Inode.LockCtx.BSD.UnlockRegion(f, lockRng) - - // Release resources held by the FileOperations. - f.FileOperations.Release(ctx) - - // Release a reference on the Dirent. - f.Dirent.DecRef(ctx) - - // Only unregister if we are currently registered. There is nothing - // to register if f.async is nil (this happens when async mode is - // enabled without setting an owner). Also, we unregister during - // save. - f.flagsMu.Lock() - if !f.saving && f.flags.Async && f.async != nil { - f.async.Unregister(f) - } - f.async = nil - f.flagsMu.Unlock() - }) -} - -// Flags atomically loads the File's flags. -func (f *File) Flags() FileFlags { - f.flagsMu.Lock() - flags := f.flags - f.flagsMu.Unlock() - return flags -} - -// SetFlags atomically changes the File's flags to the values contained -// in newFlags. See SettableFileFlags for values that can be set. -func (f *File) SetFlags(newFlags SettableFileFlags) { - f.flagsMu.Lock() - f.flags.Direct = newFlags.Direct - f.flags.NonBlocking = newFlags.NonBlocking - f.flags.Append = newFlags.Append - if f.async != nil { - if newFlags.Async && !f.flags.Async { - // Ignore error given that VFS1 will not be here much longer. - _ = f.async.Register(f) - } - if !newFlags.Async && f.flags.Async { - f.async.Unregister(f) - } - } - f.flags.Async = newFlags.Async - f.flagsMu.Unlock() -} - -// Offset atomically loads the File's offset. -func (f *File) Offset() int64 { - return f.offset.Load() -} - -// Readiness implements waiter.Waitable.Readiness. -func (f *File) Readiness(mask waiter.EventMask) waiter.EventMask { - return f.FileOperations.Readiness(mask) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (f *File) EventRegister(e *waiter.Entry) error { - return f.FileOperations.EventRegister(e) -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (f *File) EventUnregister(e *waiter.Entry) { - f.FileOperations.EventUnregister(e) -} - -// Seek calls f.FileOperations.Seek with f as the File, updating the file -// offset to the value returned by f.FileOperations.Seek if the operation -// is successful. -// -// Returns linuxerr.ErrInterrupted if seeking was interrupted. -func (f *File) Seek(ctx context.Context, whence SeekWhence, offset int64) (int64, error) { - f.mu.Lock() - defer f.mu.Unlock() - - newOffset, err := f.FileOperations.Seek(ctx, f, whence, offset) - if err == nil { - f.offset.Store(newOffset) - } - return newOffset, err -} - -// Readdir reads the directory entries of this File and writes them out -// to the DentrySerializer until entries can no longer be written. If even -// a single directory entry is written then Readdir returns a nil error -// and the directory offset is advanced. -// -// Readdir unconditionally updates the access time on the File's Inode, -// see fs/readdir.c:iterate_dir. -// -// Returns linuxerr.ErrInterrupted if reading was interrupted. -func (f *File) Readdir(ctx context.Context, serializer DentrySerializer) error { - f.mu.Lock() - defer f.mu.Unlock() - - offset, err := f.FileOperations.Readdir(ctx, f, serializer) - f.offset.Store(offset) - return err -} - -// Readv calls f.FileOperations.Read with f as the File, advancing the file -// offset if f.FileOperations.Read returns bytes read > 0. -// -// Returns linuxerr.ErrInterrupted if reading was interrupted. -func (f *File) Readv(ctx context.Context, dst usermem.IOSequence) (int64, error) { - start := fsmetric.StartReadWait() - defer fsmetric.FinishReadWait(fsmetric.ReadWait, start) - f.mu.Lock() - defer f.mu.Unlock() - fsmetric.Reads.Increment() - n, err := f.FileOperations.Read(ctx, f, dst, f.offset.RacyLoad()) - if n > 0 && !f.flags.NonSeekable { - f.offset.Add(n) - } - return n, err -} - -// Preadv calls f.FileOperations.Read with f as the File. It does not -// advance the file offset. If !f.Flags().Pread, Preadv should not be -// called. -// -// Otherwise same as Readv. -func (f *File) Preadv(ctx context.Context, dst usermem.IOSequence, offset int64) (int64, error) { - start := fsmetric.StartReadWait() - defer fsmetric.FinishReadWait(fsmetric.ReadWait, start) - f.mu.Lock() - defer f.mu.Unlock() - fsmetric.Reads.Increment() - n, err := f.FileOperations.Read(ctx, f, dst, offset) - return n, err -} - -// Writev calls f.FileOperations.Write with f as the File, advancing the -// file offset if f.FileOperations.Write returns bytes written > 0. -// -// Writev positions the write offset at EOF if f.Flags().Append. This is -// unavoidably racy for network file systems. Writev also truncates src -// to avoid overrunning the current file size limit if necessary. -// -// Returns linuxerr.ErrInterrupted if writing was interrupted. -func (f *File) Writev(ctx context.Context, src usermem.IOSequence) (int64, error) { - f.mu.Lock() - defer f.mu.Unlock() - unlockAppendMu := f.Dirent.Inode.lockAppendMu(f.Flags().Append) - // Handle append mode. - if f.Flags().Append { - if err := f.offsetForAppend(ctx, &f.offset); err != nil { - unlockAppendMu() - return 0, err - } - } - - // Enforce file limits. - limit, ok := f.checkLimit(ctx, f.offset.RacyLoad()) - switch { - case ok && limit == 0: - unlockAppendMu() - return 0, linuxerr.ErrExceedsFileSizeLimit - case ok: - src = src.TakeFirst64(limit) - } - - // We must hold the lock during the write. - n, err := f.FileOperations.Write(ctx, f, src, f.offset.RacyLoad()) - if n >= 0 && !f.flags.NonSeekable { - f.offset.Store(f.offset.RacyLoad() + n) - } - unlockAppendMu() - return n, err -} - -// Pwritev calls f.FileOperations.Write with f as the File. It does not -// advance the file offset. If !f.Flags().Pwritev, Pwritev should not be -// called. -// -// Otherwise same as Writev. -func (f *File) Pwritev(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { - // "POSIX requires that opening a file with the O_APPEND flag should - // have no effect on the location at which pwrite() writes data. - // However, on Linux, if a file is opened with O_APPEND, pwrite() - // appends data to the end of the file, regardless of the value of - // offset." - unlockAppendMu := f.Dirent.Inode.lockAppendMu(f.Flags().Append) - defer unlockAppendMu() - if f.Flags().Append { - off := atomicbitops.FromInt64(offset) - if err := f.offsetForAppend(ctx, &off); err != nil { - return 0, err - } - } - - // Enforce file limits. - limit, ok := f.checkLimit(ctx, offset) - switch { - case ok && limit == 0: - return 0, linuxerr.ErrExceedsFileSizeLimit - case ok: - src = src.TakeFirst64(limit) - } - - return f.FileOperations.Write(ctx, f, src, offset) -} - -// offsetForAppend atomically sets the given offset to the end of the file. -// -// Precondition: the file.Dirent.Inode.appendMu mutex should be held for -// writing. -func (f *File) offsetForAppend(ctx context.Context, offset *atomicbitops.Int64) error { - uattr, err := f.Dirent.Inode.UnstableAttr(ctx) - if err != nil { - // This is an odd error, we treat it as evidence that - // something is terribly wrong with the filesystem. - return linuxerr.EIO - } - - // Update the offset. - offset.Store(uattr.Size) - - return nil -} - -// checkLimit checks the offset that the write will be performed at. The -// returned boolean indicates that the write must be limited. The returned -// integer indicates the new maximum write length. -func (f *File) checkLimit(ctx context.Context, offset int64) (int64, bool) { - if IsRegular(f.Dirent.Inode.StableAttr) { - // Enforce size limits. - fileSizeLimit := limits.FromContext(ctx).Get(limits.FileSize).Cur - if fileSizeLimit <= math.MaxInt64 { - if offset >= int64(fileSizeLimit) { - return 0, true - } - return int64(fileSizeLimit) - offset, true - } - } - - return 0, false -} - -// Fsync calls f.FileOperations.Fsync with f as the File. -// -// Returns linuxerr.ErrInterrupted if syncing was interrupted. -func (f *File) Fsync(ctx context.Context, start int64, end int64, syncType SyncType) error { - f.mu.Lock() - defer f.mu.Unlock() - return f.FileOperations.Fsync(ctx, f, start, end, syncType) -} - -// Flush calls f.FileOperations.Flush with f as the File. -// -// Returns linuxerr.ErrInterrupted if syncing was interrupted. -func (f *File) Flush(ctx context.Context) error { - f.mu.Lock() - defer f.mu.Unlock() - return f.FileOperations.Flush(ctx, f) -} - -// ConfigureMMap calls f.FileOperations.ConfigureMMap with f as the File. -// -// Returns linuxerr.ErrInterrupted if interrupted. -func (f *File) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error { - f.mu.Lock() - defer f.mu.Unlock() - return f.FileOperations.ConfigureMMap(ctx, f, opts) -} - -// UnstableAttr calls f.FileOperations.UnstableAttr with f as the File. -// -// Returns linuxerr.ErrInterrupted if interrupted. -func (f *File) UnstableAttr(ctx context.Context) (UnstableAttr, error) { - f.mu.Lock() - defer f.mu.Unlock() - return f.FileOperations.UnstableAttr(ctx, f) -} - -// MappedName implements memmap.MappingIdentity.MappedName. -func (f *File) MappedName(ctx context.Context) string { - root := RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - name, _ := f.Dirent.FullName(root) - return name -} - -// DeviceID implements memmap.MappingIdentity.DeviceID. -func (f *File) DeviceID() uint64 { - return f.Dirent.Inode.StableAttr.DeviceID -} - -// InodeID implements memmap.MappingIdentity.InodeID. -func (f *File) InodeID() uint64 { - return f.Dirent.Inode.StableAttr.InodeID -} - -// Msync implements memmap.MappingIdentity.Msync. -func (f *File) Msync(ctx context.Context, mr memmap.MappableRange) error { - return f.Fsync(ctx, int64(mr.Start), int64(mr.End-1), SyncData) -} - -// A FileAsync sends signals to its owner when w is ready for IO. -type FileAsync interface { - Register(w waiter.Waitable) error - Unregister(w waiter.Waitable) -} - -// Async gets the stored FileAsync or creates a new one with the supplied -// function. If the supplied function is nil, no FileAsync is created and the -// current value is returned. -func (f *File) Async(newAsync func() FileAsync) (FileAsync, error) { - f.flagsMu.Lock() - defer f.flagsMu.Unlock() - if f.async == nil && newAsync != nil { - f.async = newAsync() - if f.flags.Async { - if err := f.async.Register(f); err != nil { - return nil, err - } - } - } - return f.async, nil -} - -// lockedReader implements io.Reader and io.ReaderAt. -// -// Note this reads the underlying file using the file operations directly. It -// is the responsibility of the caller to ensure that locks are appropriately -// held and offsets updated if required. This should be used only by internal -// functions that perform these operations and checks at other times. -type lockedReader struct { - // Ctx is the context for the file reader. - Ctx context.Context - - // File is the file to read from. - File *File - - // Offset is the offset to start at. - // - // This applies only to Read, not ReadAt. - Offset int64 -} - -// Read implements io.Reader.Read. -func (r *lockedReader) Read(buf []byte) (int, error) { - if r.Ctx.Interrupted() { - return 0, linuxerr.ErrInterrupted - } - n, err := r.File.FileOperations.Read(r.Ctx, r.File, usermem.BytesIOSequence(buf), r.Offset) - r.Offset += n - return int(n), err -} - -// ReadAt implements io.Reader.ReadAt. -func (r *lockedReader) ReadAt(buf []byte, offset int64) (int, error) { - if r.Ctx.Interrupted() { - return 0, linuxerr.ErrInterrupted - } - n, err := r.File.FileOperations.Read(r.Ctx, r.File, usermem.BytesIOSequence(buf), offset) - return int(n), err -} - -// lockedWriter implements io.Writer and io.WriterAt. -// -// The same constraints as lockedReader apply; see above. -type lockedWriter struct { - // Ctx is the context for the file writer. - Ctx context.Context - - // File is the file to write to. - File *File - - // Offset is the offset to start at. - // - // This applies only to Write, not WriteAt. - Offset int64 -} - -// Write implements io.Writer.Write. -func (w *lockedWriter) Write(buf []byte) (int, error) { - if w.Ctx.Interrupted() { - return 0, linuxerr.ErrInterrupted - } - n, err := w.WriteAt(buf, w.Offset) - w.Offset += int64(n) - return int(n), err -} - -// WriteAt implements io.Writer.WriteAt. -func (w *lockedWriter) WriteAt(buf []byte, offset int64) (int, error) { - var ( - written int - err error - ) - // The io.Writer contract requires that Write writes all available - // bytes and does not return short writes. This causes errors with - // io.Copy, since our own Write interface does not have this same - // contract. Enforce that here. - for written < len(buf) { - if w.Ctx.Interrupted() { - return written, linuxerr.ErrInterrupted - } - var n int64 - n, err = w.File.FileOperations.Write(w.Ctx, w.File, usermem.BytesIOSequence(buf[written:]), offset+int64(written)) - if n > 0 { - written += int(n) - } - if err != nil { - break - } - } - return written, err -} diff --git a/pkg/sentry/fs/file_operations.go b/pkg/sentry/fs/file_operations.go deleted file mode 100644 index 2765a705f..000000000 --- a/pkg/sentry/fs/file_operations.go +++ /dev/null @@ -1,177 +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 ( - "io" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// SpliceOpts define how a splice works. -type SpliceOpts struct { - // Length is the length of the splice operation. - Length int64 - - // SrcOffset indicates whether the existing source file offset should - // be used. If this is true, then the Start value below is used. - // - // When passed to FileOperations object, this should always be true as - // the offset will be provided by a layer above, unless the object in - // question is a pipe or socket. This value can be relied upon for such - // an indicator. - SrcOffset bool - - // SrcStart is the start of the source file. This is used only if - // SrcOffset is false. - SrcStart int64 - - // Dup indicates that the contents should not be consumed from the - // source (e.g. in the case of a socket or a pipe), but duplicated. - Dup bool - - // DstOffset indicates that the destination file offset should be used. - // - // See SrcOffset for additional information. - DstOffset bool - - // DstStart is the start of the destination file. This is used only if - // DstOffset is false. - DstStart int64 -} - -// FileOperations are operations on a File that diverge per file system. -// -// Operations that take a *File may use only the following interfaces: -// -// - File.UniqueID: Operations may only read this value. -// - File.Dirent: Operations must not take or drop a reference. -// - File.Offset(): This value is guaranteed to not change for the -// duration of the operation. -// - File.Flags(): This value may change during the operation. -type FileOperations interface { - // Release release resources held by FileOperations. - Release(ctx context.Context) - - // Waitable defines how this File can be waited on for read and - // write readiness. - waiter.Waitable - - // Seek seeks to offset based on SeekWhence. Returns the new - // offset or no change in the offset and an error. - Seek(ctx context.Context, file *File, whence SeekWhence, offset int64) (int64, error) - - // Readdir reads the directory entries of file and serializes them - // using serializer. - // - // Returns the new directory offset or no change in the offset and - // an error. The offset returned must not be less than file.Offset(). - // - // Serialization of directory entries must not happen asynchronously. - Readdir(ctx context.Context, file *File, serializer DentrySerializer) (int64, error) - - // Read reads from file into dst at offset and returns the number - // of bytes read which must be greater than or equal to 0. File - // systems that do not support reading at an offset, (i.e. pipefs, - // sockfs) may ignore the offset. These file systems are expected - // to construct Files with !FileFlags.Pread. - // - // Read may return a nil error and only partially fill dst (at or - // before EOF). If the file represents a symlink, Read reads the target - // value of the symlink. - // - // Read does not check permissions nor flags. - // - // Read must not be called if !FileFlags.Read. - Read(ctx context.Context, file *File, dst usermem.IOSequence, offset int64) (int64, error) - - // WriteTo is a variant of read that takes another file as a - // destination. For a splice (copy or move from one file to another), - // first a WriteTo on the source is attempted, followed by a ReadFrom - // on the destination, following by a buffered copy with standard Read - // and Write operations. - // - // If dup is set, the data should be duplicated into the destination - // and retained. - // - // The same preconditions as Read apply. - WriteTo(ctx context.Context, file *File, dst io.Writer, count int64, dup bool) (int64, error) - - // Write writes src to file at offset and returns the number of bytes - // written which must be greater than or equal to 0. Like Read, file - // systems that do not support writing at an offset (i.e. pipefs, sockfs) - // may ignore the offset. These file systems are expected to construct - // Files with !FileFlags.Pwrite. - // - // If only part of src could be written, Write must return an error - // indicating why (e.g. linuxerr.ErrWouldBlock). - // - // Write does not check permissions nor flags. - // - // Write must not be called if !FileFlags.Write. - Write(ctx context.Context, file *File, src usermem.IOSequence, offset int64) (int64, error) - - // ReadFrom is a variant of write that takes a another file as a - // source. See WriteTo for details regarding how this is called. - // - // The same preconditions as Write apply; FileFlags.Write must be set. - ReadFrom(ctx context.Context, file *File, src io.Reader, count int64) (int64, error) - - // Fsync writes buffered modifications of file and/or flushes in-flight - // operations to backing storage based on syncType. The range to sync is - // [start, end]. The end is inclusive so that the last byte of a maximally - // sized file can be synced. - Fsync(ctx context.Context, file *File, start, end int64, syncType SyncType) error - - // Flush this file's buffers/state (on close(2)). - Flush(ctx context.Context, file *File) error - - // ConfigureMMap mutates opts to implement mmap(2) for the file. Most - // implementations can either embed fsutil.FileNoMMap (if they don't support - // memory mapping) or call fsutil.GenericConfigureMMap with the appropriate - // memmap.Mappable. - ConfigureMMap(ctx context.Context, file *File, opts *memmap.MMapOpts) error - - // UnstableAttr returns the "unstable" attributes of the inode represented - // by the file. Most implementations can embed - // fsutil.FileUseInodeUnstableAttr, which delegates to - // InodeOperations.UnstableAttr. - UnstableAttr(ctx context.Context, file *File) (UnstableAttr, error) - - // Ioctl implements the ioctl(2) linux syscall. - // - // io provides access to the virtual memory space to which pointers in args - // refer. - // - // Preconditions: - // * The AddressSpace (if any) that io refers to is activated. - // * Must only be called from a task goroutine. - Ioctl(ctx context.Context, file *File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) -} - -// FifoSizer is an interface for setting and getting the size of a pipe. -type FifoSizer interface { - // FifoSize returns the pipe capacity in bytes. - FifoSize(ctx context.Context, file *File) (int64, error) - - // SetFifoSize sets the new pipe capacity in bytes. - // - // The new size is returned (which may be capped). - SetFifoSize(size int64) (int64, error) -} diff --git a/pkg/sentry/fs/file_overlay.go b/pkg/sentry/fs/file_overlay.go deleted file mode 100644 index 52e1be1f2..000000000 --- a/pkg/sentry/fs/file_overlay.go +++ /dev/null @@ -1,555 +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 ( - "io" - "math" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// overlayFile gets a handle to a file from the upper or lower filesystem -// in an overlay. The caller is responsible for calling File.DecRef on -// the returned file. -func overlayFile(ctx context.Context, inode *Inode, flags FileFlags) (*File, error) { - // Do a song and dance to eventually get to: - // - // File -> single reference - // Dirent -> single reference - // Inode -> multiple references - // - // So that File.DecRef() -> File.destroy -> Dirent.DecRef -> Dirent.destroy, - // and both the transitory File and Dirent can be GC'ed but the Inode - // remains. - - // Take another reference on the Inode. - inode.IncRef() - - // Start with a single reference on the Dirent. It inherits the reference - // we just took on the Inode above. - dirent := NewTransientDirent(inode) - - // Get a File. This will take another reference on the Dirent. - f, err := inode.GetFile(ctx, dirent, flags) - - // Drop the extra reference on the Dirent. Now there's only one reference - // on the dirent, either owned by f (if non-nil), or the Dirent is about - // to be destroyed (if GetFile failed). - dirent.DecRef(ctx) - - return f, err -} - -// overlayFileOperations implements FileOperations for a file in an overlay. -// -// +stateify savable -type overlayFileOperations struct { - // upperMu protects upper below. In contrast lower is stable. - upperMu sync.Mutex `state:"nosave"` - - // We can't share Files in upper and lower filesystems between all Files - // in an overlay because some file systems expect to get distinct handles - // that are not consistent with each other on open(2). - // - // So we lazily acquire an upper File when the overlayEntry acquires an - // upper Inode (it might have one from the start). This synchronizes with - // copy up. - // - // If upper is non-nil and this is not a directory, then lower is ignored. - // - // For directories, upper and lower are ignored because it is always - // necessary to acquire new directory handles so that the directory cursors - // of the upper and lower Files are not exhausted. - upper *File - lower *File - - // dirCursor is a directory cursor for a directory in an overlay. It is - // protected by File.mu of the owning file, which is held during - // Readdir and Seek calls. - dirCursor string -} - -// Release implements FileOperations.Release. -func (f *overlayFileOperations) Release(ctx context.Context) { - if f.upper != nil { - f.upper.DecRef(ctx) - } - if f.lower != nil { - f.lower.DecRef(ctx) - } -} - -// EventRegister implements FileOperations.EventRegister. -func (f *overlayFileOperations) EventRegister(we *waiter.Entry) error { - f.upperMu.Lock() - defer f.upperMu.Unlock() - if f.upper != nil { - return f.upper.EventRegister(we) - } - return f.lower.EventRegister(we) -} - -// EventUnregister implements FileOperations.Unregister. -func (f *overlayFileOperations) EventUnregister(we *waiter.Entry) { - f.upperMu.Lock() - defer f.upperMu.Unlock() - if f.upper != nil { - f.upper.EventUnregister(we) - return - } - f.lower.EventUnregister(we) -} - -// Readiness implements FileOperations.Readiness. -func (f *overlayFileOperations) Readiness(mask waiter.EventMask) waiter.EventMask { - f.upperMu.Lock() - defer f.upperMu.Unlock() - if f.upper != nil { - return f.upper.Readiness(mask) - } - return f.lower.Readiness(mask) -} - -// Seek implements FileOperations.Seek. -func (f *overlayFileOperations) Seek(ctx context.Context, file *File, whence SeekWhence, offset int64) (int64, error) { - f.upperMu.Lock() - defer f.upperMu.Unlock() - - var seekDir bool - var n int64 - if f.upper != nil { - var err error - if n, err = f.upper.FileOperations.Seek(ctx, file, whence, offset); err != nil { - return n, err - } - seekDir = IsDir(f.upper.Dirent.Inode.StableAttr) - } else { - var err error - if n, err = f.lower.FileOperations.Seek(ctx, file, whence, offset); err != nil { - return n, err - } - seekDir = IsDir(f.lower.Dirent.Inode.StableAttr) - } - - // If this was a seek on a directory, we must update the cursor. - if seekDir && whence == SeekSet && offset == 0 { - // Currently only seeking to 0 on a directory is supported. - // FIXME(b/33075855): Lift directory seeking limitations. - f.dirCursor = "" - } - return n, nil -} - -// Readdir implements FileOperations.Readdir. -func (f *overlayFileOperations) Readdir(ctx context.Context, file *File, serializer DentrySerializer) (int64, error) { - root := RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - - dirCtx := &DirCtx{ - Serializer: serializer, - DirCursor: &f.dirCursor, - } - return DirentReaddir(ctx, file.Dirent, f, root, dirCtx, file.Offset()) -} - -// IterateDir implements DirIterator.IterateDir. -func (f *overlayFileOperations) IterateDir(ctx context.Context, d *Dirent, dirCtx *DirCtx, offset int) (int, error) { - o := d.Inode.overlay - o.copyMu.RLock() - defer o.copyMu.RUnlock() - return overlayIterateDirLocked(ctx, o, d, dirCtx, offset) -} - -// Preconditions: o.copyMu must be locked. -func overlayIterateDirLocked(ctx context.Context, o *overlayEntry, d *Dirent, dirCtx *DirCtx, offset int) (int, error) { - if !d.Inode.MountSource.CacheReaddir() { - // Can't use the dirCache. Simply read the entries. - entries, err := readdirEntriesLocked(ctx, o) - if err != nil { - return offset, err - } - n, err := GenericReaddir(dirCtx, entries) - return offset + n, err - } - - // Otherwise, use or create cached entries. - - o.dirCacheMu.RLock() - if o.dirCache != nil { - n, err := GenericReaddir(dirCtx, o.dirCache) - o.dirCacheMu.RUnlock() - return offset + n, err - } - o.dirCacheMu.RUnlock() - - // We must hold dirCacheMu around both readdirEntries and setting - // o.dirCache to synchronize with dirCache invalidations done by - // Create, Remove, Rename. - o.dirCacheMu.Lock() - - // We expect dirCache to be nil (we just checked above), but there is a - // chance that a racing call managed to just set it, in which case we - // can use that new value. - if o.dirCache == nil { - dirCache, err := readdirEntriesLocked(ctx, o) - if err != nil { - o.dirCacheMu.Unlock() - return offset, err - } - o.dirCache = dirCache - } - - o.dirCacheMu.DowngradeLock() - n, err := GenericReaddir(dirCtx, o.dirCache) - o.dirCacheMu.RUnlock() - - return offset + n, err -} - -// onTop performs the given operation on the top-most available layer. -func (f *overlayFileOperations) onTop(ctx context.Context, file *File, fn func(*File, FileOperations) error) error { - file.Dirent.Inode.overlay.copyMu.RLock() - defer file.Dirent.Inode.overlay.copyMu.RUnlock() - - // Only lower layer is available. - if file.Dirent.Inode.overlay.upper == nil { - return fn(f.lower, f.lower.FileOperations) - } - - f.upperMu.Lock() - if f.upper == nil { - upper, err := overlayFile(ctx, file.Dirent.Inode.overlay.upper, file.Flags()) - if err != nil { - // Something very wrong; return a generic filesystem - // error to avoid propagating internals. - f.upperMu.Unlock() - return linuxerr.EIO - } - - // Save upper file. - f.upper = upper - } - f.upperMu.Unlock() - - return fn(f.upper, f.upper.FileOperations) -} - -// Read implements FileOperations.Read. -func (f *overlayFileOperations) Read(ctx context.Context, file *File, dst usermem.IOSequence, offset int64) (n int64, err error) { - err = f.onTop(ctx, file, func(file *File, ops FileOperations) error { - n, err = ops.Read(ctx, file, dst, offset) - return err // Will overwrite itself. - }) - return -} - -// WriteTo implements FileOperations.WriteTo. -func (f *overlayFileOperations) WriteTo(ctx context.Context, file *File, dst io.Writer, count int64, dup bool) (n int64, err error) { - err = f.onTop(ctx, file, func(file *File, ops FileOperations) error { - n, err = ops.WriteTo(ctx, file, dst, count, dup) - return err // Will overwrite itself. - }) - return -} - -// Write implements FileOperations.Write. -func (f *overlayFileOperations) Write(ctx context.Context, file *File, src usermem.IOSequence, offset int64) (int64, error) { - // f.upper must be non-nil. See inode_overlay.go:overlayGetFile, where the - // file is copied up and opened in the upper filesystem if FileFlags.Write. - // Write cannot be called if !FileFlags.Write, see FileOperations.Write. - return f.upper.FileOperations.Write(ctx, f.upper, src, offset) -} - -// ReadFrom implements FileOperations.ReadFrom. -func (f *overlayFileOperations) ReadFrom(ctx context.Context, file *File, src io.Reader, count int64) (n int64, err error) { - // See above; f.upper must be non-nil. - return f.upper.FileOperations.ReadFrom(ctx, f.upper, src, count) -} - -// Fsync implements FileOperations.Fsync. -func (f *overlayFileOperations) Fsync(ctx context.Context, file *File, start, end int64, syncType SyncType) (err error) { - f.upperMu.Lock() - if f.upper != nil { - err = f.upper.FileOperations.Fsync(ctx, f.upper, start, end, syncType) - } - f.upperMu.Unlock() - if err == nil && f.lower != nil { - // N.B. Fsync on the lower filesystem can cause writes of file - // attributes (i.e. access time) despite the fact that we must - // treat the lower filesystem as read-only. - // - // This matches the semantics of fsync(2) in Linux overlayfs. - err = f.lower.FileOperations.Fsync(ctx, f.lower, start, end, syncType) - } - return err -} - -// Flush implements FileOperations.Flush. -func (f *overlayFileOperations) Flush(ctx context.Context, file *File) (err error) { - // Flush whatever handles we have. - f.upperMu.Lock() - if f.upper != nil { - err = f.upper.FileOperations.Flush(ctx, f.upper) - } - f.upperMu.Unlock() - if err == nil && f.lower != nil { - err = f.lower.FileOperations.Flush(ctx, f.lower) - } - return err -} - -// ConfigureMMap implements FileOperations.ConfigureMMap. -func (*overlayFileOperations) ConfigureMMap(ctx context.Context, file *File, opts *memmap.MMapOpts) error { - o := file.Dirent.Inode.overlay - - o.copyMu.RLock() - defer o.copyMu.RUnlock() - - // If there is no lower inode, the overlay will never need to do a - // copy-up, and thus will never need to invalidate any mappings. We can - // call ConfigureMMap directly on the upper file. - if o.lower == nil { - f := file.FileOperations.(*overlayFileOperations) - if err := f.upper.ConfigureMMap(ctx, opts); err != nil { - return err - } - - // ConfigureMMap will set the MappableIdentity to the upper - // file and take a reference on it, but we must also hold a - // reference to the overlay file during the lifetime of the - // Mappable. If we do not do this, the overlay file can be - // Released before the upper file is Released, and we will be - // unable to traverse to the upper file during Save, thus - // preventing us from saving a proper inode mapping for the - // file. - file.IncRef() - id := overlayMappingIdentity{ - id: opts.MappingIdentity, - overlayFile: file, - } - id.EnableLeakCheck("fs.overlayMappingIdentity") - - // Swap out the old MappingIdentity for the wrapped one. - opts.MappingIdentity = &id - return nil - } - - if !o.isMappableLocked() { - return linuxerr.ENODEV - } - - // TODO(gvisor.dev/issue/1624): This is a copy/paste of - // fsutil.GenericConfigureMMap, which we can't use because the overlay - // implementation is in package fs, so depending on fs/fsutil would create - // a circular dependency. VFS2 overlay doesn't have this issue. - if opts.Offset+opts.Length > math.MaxInt64 { - return linuxerr.EOVERFLOW - } - opts.Mappable = o - opts.MappingIdentity = file - file.IncRef() - return nil -} - -// UnstableAttr implements fs.FileOperations.UnstableAttr. -func (f *overlayFileOperations) UnstableAttr(ctx context.Context, file *File) (UnstableAttr, error) { - // Hot path. Avoid defers. - f.upperMu.Lock() - if f.upper != nil { - attr, err := f.upper.UnstableAttr(ctx) - f.upperMu.Unlock() - return attr, err - } - f.upperMu.Unlock() - - // It's possible that copy-up has occurred, but we haven't opened a upper - // file yet. If this is the case, just use the upper inode's UnstableAttr - // rather than opening a file. - o := file.Dirent.Inode.overlay - o.copyMu.RLock() - if o.upper != nil { - attr, err := o.upper.UnstableAttr(ctx) - o.copyMu.RUnlock() - return attr, err - } - o.copyMu.RUnlock() - - return f.lower.UnstableAttr(ctx) -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (f *overlayFileOperations) Ioctl(ctx context.Context, overlayFile *File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - f.upperMu.Lock() - defer f.upperMu.Unlock() - - if f.upper == nil { - // It's possible that ioctl changes the file. Since we don't know all - // possible ioctls, only allow them to propagate to the upper. Triggering a - // copy up on any ioctl would be too drastic. In the future, it can have a - // list of ioctls that are safe to send to lower and a list that triggers a - // copy up. - return 0, linuxerr.ENOTTY - } - return f.upper.FileOperations.Ioctl(ctx, f.upper, io, args) -} - -// FifoSize implements FifoSizer.FifoSize. -func (f *overlayFileOperations) FifoSize(ctx context.Context, overlayFile *File) (rv int64, err error) { - err = f.onTop(ctx, overlayFile, func(file *File, ops FileOperations) error { - sz, ok := ops.(FifoSizer) - if !ok { - return linuxerr.EINVAL - } - rv, err = sz.FifoSize(ctx, file) - return err - }) - return -} - -// SetFifoSize implements FifoSizer.SetFifoSize. -func (f *overlayFileOperations) SetFifoSize(size int64) (rv int64, err error) { - f.upperMu.Lock() - defer f.upperMu.Unlock() - - if f.upper == nil { - // Named pipes cannot be copied up and changes to the lower are prohibited. - return 0, linuxerr.EINVAL - } - sz, ok := f.upper.FileOperations.(FifoSizer) - if !ok { - return 0, linuxerr.EINVAL - } - return sz.SetFifoSize(size) -} - -// readdirEntriesLocked returns a sorted map of directory entries from the -// upper and/or lower filesystem. -// -// Preconditions: o.copyMu must be locked. -func readdirEntriesLocked(ctx context.Context, o *overlayEntry) (*SortedDentryMap, error) { - // Assert that there is at least one upper or lower entry. - if o.upper == nil && o.lower == nil { - panic("invalid overlayEntry, needs at least one Inode") - } - entries := make(map[string]DentAttr) - - // Try the upper filesystem first. - if o.upper != nil { - var err error - entries, err = readdirOne(ctx, NewTransientDirent(o.upper)) - if err != nil { - return nil, err - } - } - - // Try the lower filesystem next. - if o.lower != nil { - lowerEntries, err := readdirOne(ctx, NewTransientDirent(o.lower)) - if err != nil { - return nil, err - } - for name, entry := range lowerEntries { - // Skip this name if it is a negative entry in the - // upper or there exists a whiteout for it. - if o.upper != nil { - if overlayHasWhiteout(ctx, o.upper, name) { - continue - } - } - // Prefer the entries from the upper filesystem - // when names overlap. - if _, ok := entries[name]; !ok { - entries[name] = entry - } - } - } - - // Sort and return the entries. - return NewSortedDentryMap(entries), nil -} - -// readdirOne reads all of the directory entries from d. -func readdirOne(ctx context.Context, d *Dirent) (map[string]DentAttr, error) { - dir, err := d.Inode.GetFile(ctx, d, FileFlags{Read: true}) - if err != nil { - return nil, err - } - defer dir.DecRef(ctx) - - // Use a stub serializer to read the entries into memory. - stubSerializer := &CollectEntriesSerializer{} - if err := dir.Readdir(ctx, stubSerializer); err != nil { - return nil, err - } - // The "." and ".." entries are from the overlay Inode's Dirent, not the stub. - delete(stubSerializer.Entries, ".") - delete(stubSerializer.Entries, "..") - return stubSerializer.Entries, nil -} - -// overlayMappingIdentity wraps a MappingIdentity, and also holds a reference -// on a file during its lifetime. -// -// +stateify savable -type overlayMappingIdentity struct { - refs.AtomicRefCount - id memmap.MappingIdentity - overlayFile *File -} - -// DecRef implements AtomicRefCount.DecRef. -func (omi *overlayMappingIdentity) DecRef(ctx context.Context) { - omi.AtomicRefCount.DecRefWithDestructor(ctx, func(context.Context) { - omi.overlayFile.DecRef(ctx) - omi.id.DecRef(ctx) - }) -} - -// DeviceID implements MappingIdentity.DeviceID using the device id from the -// overlayFile. -func (omi *overlayMappingIdentity) DeviceID() uint64 { - return omi.overlayFile.Dirent.Inode.StableAttr.DeviceID -} - -// DeviceID implements MappingIdentity.InodeID using the inode id from the -// overlayFile. -func (omi *overlayMappingIdentity) InodeID() uint64 { - return omi.overlayFile.Dirent.Inode.StableAttr.InodeID -} - -// MappedName implements MappingIdentity.MappedName. -func (omi *overlayMappingIdentity) MappedName(ctx context.Context) string { - root := RootFromContext(ctx) - if root != nil { - defer root.DecRef(ctx) - } - name, _ := omi.overlayFile.Dirent.FullName(root) - return name -} - -// Msync implements MappingIdentity.Msync. -func (omi *overlayMappingIdentity) Msync(ctx context.Context, mr memmap.MappableRange) error { - return omi.id.Msync(ctx, mr) -} diff --git a/pkg/sentry/fs/file_state.go b/pkg/sentry/fs/file_state.go deleted file mode 100644 index 032e94629..000000000 --- a/pkg/sentry/fs/file_state.go +++ /dev/null @@ -1,34 +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" - -// beforeSave is invoked by stateify. -func (f *File) beforeSave() { - f.saving = true - if f.flags.Async && f.async != nil { - f.async.Unregister(f) - } -} - -// afterLoad is invoked by stateify. -func (f *File) afterLoad() { - if f.flags.Async && f.async != nil { - if err := f.async.Register(f); err != nil { - panic(fmt.Sprint("async.Register:", err)) - } - } -} diff --git a/pkg/sentry/fs/filesystems.go b/pkg/sentry/fs/filesystems.go deleted file mode 100644 index 25c719caa..000000000 --- a/pkg/sentry/fs/filesystems.go +++ /dev/null @@ -1,160 +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" - "sort" - "strings" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sync" -) - -// FilesystemFlags matches include/linux/fs.h:file_system_type.fs_flags. -type FilesystemFlags int - -const ( - // FilesystemRequiresDev indicates that the file system requires a device name - // on mount. It is used to construct the output of /proc/filesystems. - FilesystemRequiresDev FilesystemFlags = 1 - - // Currently other flags are not used, but can be pulled in from - // include/linux/fs.h:file_system_type as needed. -) - -// Filesystem is a mountable file system. -type Filesystem interface { - // Name is the unique identifier of the file system. It corresponds to the - // filesystemtype argument of sys_mount and will appear in the output of - // /proc/filesystems. - Name() string - - // Flags indicate common properties of the file system. - Flags() FilesystemFlags - - // Mount generates a mountable Inode backed by device and configured - // using file system independent flags and file system dependent - // data options. - // - // Mount may return arbitrary errors. They do not need syserr translations. - Mount(ctx context.Context, device string, flags MountSourceFlags, data string, dataObj any) (*Inode, error) - - // AllowUserMount determines whether mount(2) is allowed to mount a - // file system of this type. - AllowUserMount() bool - - // AllowUserList determines whether this filesystem is listed in - // /proc/filesystems - AllowUserList() bool -} - -// filesystems is the global set of registered file systems. It does not need -// to be saved. Packages registering and unregistering file systems must do so -// before calling save/restore methods. -var filesystems = struct { - // mu protects registered below. - mu sync.Mutex - - // registered is a set of registered Filesystems. - registered map[string]Filesystem -}{ - registered: make(map[string]Filesystem), -} - -// RegisterFilesystem registers a new file system that is visible to mount and -// the /proc/filesystems list. Packages implementing Filesystem should call -// RegisterFilesystem in init(). -func RegisterFilesystem(f Filesystem) { - filesystems.mu.Lock() - defer filesystems.mu.Unlock() - - if _, ok := filesystems.registered[f.Name()]; ok { - panic(fmt.Sprintf("filesystem already registered at %q", f.Name())) - } - filesystems.registered[f.Name()] = f -} - -// FindFilesystem returns a Filesystem registered at name or (nil, false) if name -// is not a file system type that can be found in /proc/filesystems. -func FindFilesystem(name string) (Filesystem, bool) { - filesystems.mu.Lock() - defer filesystems.mu.Unlock() - - f, ok := filesystems.registered[name] - return f, ok -} - -// GetFilesystems returns the set of registered filesystems in a consistent order. -func GetFilesystems() []Filesystem { - filesystems.mu.Lock() - defer filesystems.mu.Unlock() - - var ss []Filesystem - for _, s := range filesystems.registered { - ss = append(ss, s) - } - sort.Slice(ss, func(i, j int) bool { return ss[i].Name() < ss[j].Name() }) - return ss -} - -// MountSourceFlags represents all mount option flags as a struct. -// -// +stateify savable -type MountSourceFlags struct { - // ReadOnly corresponds to mount(2)'s "MS_RDONLY" and indicates that - // the filesystem should be mounted read-only. - ReadOnly bool - - // NoAtime corresponds to mount(2)'s "MS_NOATIME" and indicates that - // the filesystem should not update access time in-place. - NoAtime bool - - // ForcePageCache causes all filesystem I/O operations to use the page - // cache, even when the platform supports direct mapped I/O. This - // doesn't correspond to any Linux mount options. - ForcePageCache bool - - // NoExec corresponds to mount(2)'s "MS_NOEXEC" and indicates that - // binaries from this file system can't be executed. - NoExec bool -} - -// GenericMountSourceOptions splits a string containing comma separated tokens of the -// format 'key=value' or 'key' into a map of keys and values. For example: -// -// data = "key0=value0,key1,key2=value2" -> map{'key0':'value0','key1':”,'key2':'value2'} -// -// If data contains duplicate keys, then the last token wins. -func GenericMountSourceOptions(data string) map[string]string { - options := make(map[string]string) - if len(data) == 0 { - // Don't return a nil map, callers might not be expecting that. - return options - } - - // Parse options and skip empty ones. - for _, opt := range strings.Split(data, ",") { - if len(opt) > 0 { - res := strings.SplitN(opt, "=", 2) - if len(res) == 2 { - options[res[0]] = res[1] - } else { - options[opt] = "" - } - } - } - return options -} diff --git a/pkg/sentry/fs/flags.go b/pkg/sentry/fs/flags.go deleted file mode 100644 index 4338ae1fa..000000000 --- a/pkg/sentry/fs/flags.go +++ /dev/null @@ -1,138 +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 ( - "gvisor.dev/gvisor/pkg/abi/linux" -) - -// FileFlags encodes file flags. -// -// +stateify savable -type FileFlags struct { - // Direct indicates that I/O should be done directly. - Direct bool - - // NonBlocking indicates that I/O should not block. - NonBlocking bool - - // DSync indicates that each write will flush data and metadata required to - // read the file's contents. - DSync bool - - // Sync indicates that each write will flush data and all file metadata. - Sync bool - - // Append indicates this file is append only. - Append bool - - // Read indicates this file is readable. - Read bool - - // Write indicates this file is writeable. - Write bool - - // Pread indicates this file is readable at an arbitrary offset. - Pread bool - - // Pwrite indicates this file is writable at an arbitrary offset. - Pwrite bool - - // Directory indicates that this file must be a directory. - Directory bool - - // Async indicates that this file sends signals on IO events. - Async bool - - // LargeFile indicates that this file should be opened even if it has - // size greater than linux's off_t. When running in 64-bit mode, - // Linux sets this flag for all files. Since gVisor is only compatible - // with 64-bit Linux, it also sets this flag for all files. - LargeFile bool - - // NonSeekable indicates that file.offset isn't used. - NonSeekable bool - - // Truncate indicates that the file should be truncated before opened. - // This is only applicable if the file is regular. - Truncate bool -} - -// SettableFileFlags is a subset of FileFlags above that can be changed -// via fcntl(2) using the F_SETFL command. -type SettableFileFlags struct { - // Direct indicates that I/O should be done directly. - Direct bool - - // NonBlocking indicates that I/O should not block. - NonBlocking bool - - // Append indicates this file is append only. - Append bool - - // Async indicates that this file sends signals on IO events. - Async bool -} - -// Settable returns the subset of f that are settable. -func (f FileFlags) Settable() SettableFileFlags { - return SettableFileFlags{ - Direct: f.Direct, - NonBlocking: f.NonBlocking, - Append: f.Append, - Async: f.Async, - } -} - -// ToLinux converts a FileFlags object to a Linux representation. -func (f FileFlags) ToLinux() (mask uint) { - if f.Direct { - mask |= linux.O_DIRECT - } - if f.NonBlocking { - mask |= linux.O_NONBLOCK - } - if f.DSync { - mask |= linux.O_DSYNC - } - if f.Sync { - mask |= linux.O_SYNC - } - if f.Append { - mask |= linux.O_APPEND - } - if f.Directory { - mask |= linux.O_DIRECTORY - } - if f.Async { - mask |= linux.O_ASYNC - } - if f.LargeFile { - mask |= linux.O_LARGEFILE - } - if f.Truncate { - mask |= linux.O_TRUNC - } - - switch { - case f.Read && f.Write: - mask |= linux.O_RDWR - case f.Write: - mask |= linux.O_WRONLY - case f.Read: - mask |= linux.O_RDONLY - } - return -} diff --git a/pkg/sentry/fs/fs.go b/pkg/sentry/fs/fs.go deleted file mode 100644 index af9fddf3d..000000000 --- a/pkg/sentry/fs/fs.go +++ /dev/null @@ -1,174 +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 implements a virtual filesystem layer. -// -// Specific filesystem implementations must implement the InodeOperations -// interface (inode.go). -// -// The MountNamespace (mounts.go) is used to create a collection of mounts in -// a filesystem rooted at a given Inode. -// -// MountSources (mount.go) form a tree, with each mount holding pointers to its -// parent and children. -// -// Dirents (dirents.go) wrap Inodes in a caching layer. -// -// When multiple locks are to be held at the same time, they should be acquired -// in the following order. -// -// Either: -// -// File.mu -// Locks in FileOperations implementations -// goto Dirent-Locks -// -// Or: -// -// MountNamespace.mu -// goto Dirent-Locks -// -// Dirent-Locks: -// -// renameMu -// Dirent.dirMu -// Dirent.mu -// DirentCache.mu -// Inode.Watches.mu (see `Inotify` for other lock ordering) -// MountSource.mu -// Inode.appendMu -// Locks in InodeOperations implementations or overlayEntry -// -// If multiple Dirent or MountSource locks must be taken, locks in the parent must be -// taken before locks in their children. -// -// If locks must be taken on multiple unrelated Dirents, renameMu must be taken -// first. See lockForRename. -package fs - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sync" -) - -var ( - // workMu is used to synchronize pending asynchronous work. Async work - // runs with the lock held for reading. AsyncBarrier will take the lock - // for writing, thus ensuring that all Async work completes before - // AsyncBarrier returns. - workMu sync.CrossGoroutineRWMutex - - // asyncError is used to store up to one asynchronous execution error. - asyncError = make(chan error, 1) -) - -// AsyncBarrier waits for all outstanding asynchronous work to complete. -func AsyncBarrier() { - workMu.Lock() - workMu.Unlock() -} - -// Async executes a function asynchronously. -// -// Async must not be called recursively. -// +checklocksignore -func Async(f func()) { - workMu.RLock() - go asyncWork(f) // S/R-SAFE: AsyncBarrier must be called. -} - -// +checklocksignore -func asyncWork(f func()) { - // Ensure RUnlock in case of panic. - defer workMu.RUnlock() - f() -} - -// AsyncWithContext is just like Async, except that it calls the asynchronous -// function with the given context as argument. This function exists to avoid -// needing to allocate an extra function on the heap in a hot path. -// +checklocksignore -func AsyncWithContext(ctx context.Context, f func(context.Context)) { - workMu.RLock() - go asyncWorkWithContext(ctx, f) -} - -// +checklocksignore -func asyncWorkWithContext(ctx context.Context, f func(context.Context)) { - // Ensure RUnlock in case of panic. - defer workMu.RUnlock() - f(ctx) -} - -// AsyncErrorBarrier waits for all outstanding asynchronous work to complete, or -// the first async error to arrive. Other unfinished async executions will -// continue in the background. Other past and future async errors are ignored. -func AsyncErrorBarrier() error { - wait := make(chan struct{}, 1) - go func() { // S/R-SAFE: Does not touch persistent state. - AsyncBarrier() - wait <- struct{}{} - }() - select { - case <-wait: - select { - case err := <-asyncError: - return err - default: - return nil - } - case err := <-asyncError: - return err - } -} - -// CatchError tries to capture the potential async error returned by the -// function. At most one async error will be captured globally so excessive -// errors will be dropped. -func CatchError(f func() error) func() { - return func() { - if err := f(); err != nil { - select { - case asyncError <- err: - default: - log.Warningf("excessive async error dropped: %v", err) - } - } - } -} - -// ErrSaveRejection indicates a failed save due to unsupported file system state -// such as dangling open fd, etc. -type ErrSaveRejection struct { - // Err is the wrapped error. - Err error -} - -// Error returns a sensible description of the save rejection error. -func (e *ErrSaveRejection) Error() string { - return "save rejected due to unsupported file system state: " + e.Err.Error() -} - -// ErrCorruption indicates a failed restore due to external file system state in -// corruption. -type ErrCorruption struct { - // Err is the wrapped error. - Err error -} - -// Error returns a sensible description of the restore error. -func (e ErrCorruption) Error() string { - return "restore failed due to external file system state in corruption: " + e.Err.Error() -} diff --git a/pkg/sentry/fs/fsutil/file.go b/pkg/sentry/fs/fsutil/file.go deleted file mode 100644 index 38e3ed42d..000000000 --- a/pkg/sentry/fs/fsutil/file.go +++ /dev/null @@ -1,400 +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 fsutil - -import ( - "io" - "math" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// FileNoopRelease implements fs.FileOperations.Release for files that have no -// resources to release. -type FileNoopRelease struct{} - -// Release is a no-op. -func (FileNoopRelease) Release(context.Context) {} - -// SeekWithDirCursor is used to implement fs.FileOperations.Seek. If dirCursor -// is not nil and the seek was on a directory, the cursor will be updated. -// -// Currently only seeking to 0 on a directory is supported. -// -// FIXME(b/33075855): Lift directory seeking limitations. -func SeekWithDirCursor(ctx context.Context, file *fs.File, whence fs.SeekWhence, offset int64, dirCursor *string) (int64, error) { - inode := file.Dirent.Inode - current := file.Offset() - - // Does the Inode represents a non-seekable type? - if fs.IsPipe(inode.StableAttr) || fs.IsSocket(inode.StableAttr) { - return current, linuxerr.ESPIPE - } - - // Does the Inode represent a character device? - if fs.IsCharDevice(inode.StableAttr) { - // Ignore seek requests. - // - // FIXME(b/34716638): This preserves existing - // behavior but is not universally correct. - return 0, nil - } - - // Otherwise compute the new offset. - switch whence { - case fs.SeekSet: - switch inode.StableAttr.Type { - case fs.RegularFile, fs.SpecialFile, fs.BlockDevice: - if offset < 0 { - return current, linuxerr.EINVAL - } - return offset, nil - case fs.Directory, fs.SpecialDirectory: - if offset != 0 { - return current, linuxerr.EINVAL - } - // SEEK_SET to 0 moves the directory "cursor" to the beginning. - if dirCursor != nil { - *dirCursor = "" - } - return 0, nil - default: - return current, linuxerr.EINVAL - } - case fs.SeekCurrent: - switch inode.StableAttr.Type { - case fs.RegularFile, fs.SpecialFile, fs.BlockDevice: - if current+offset < 0 { - return current, linuxerr.EINVAL - } - return current + offset, nil - case fs.Directory, fs.SpecialDirectory: - if offset != 0 { - return current, linuxerr.EINVAL - } - return current, nil - default: - return current, linuxerr.EINVAL - } - case fs.SeekEnd: - switch inode.StableAttr.Type { - case fs.RegularFile, fs.BlockDevice: - // Allow the file to determine the end. - uattr, err := inode.UnstableAttr(ctx) - if err != nil { - return current, err - } - sz := uattr.Size - if sz+offset < 0 { - return current, linuxerr.EINVAL - } - return sz + offset, nil - // FIXME(b/34778850): This is not universally correct. - // Remove SpecialDirectory. - case fs.SpecialDirectory: - if offset != 0 { - return current, linuxerr.EINVAL - } - // SEEK_END to 0 moves the directory "cursor" to the end. - // - // FIXME(b/35442290): The ensures that after the seek, - // reading on the directory will get EOF. But it is not - // correct in general because the directory can grow in - // size; attempting to read those new entries will be - // futile (EOF will always be the result). - return fs.FileMaxOffset, nil - default: - return current, linuxerr.EINVAL - } - } - - // Not a valid seek request. - return current, linuxerr.EINVAL -} - -// FileGenericSeek implements fs.FileOperations.Seek for files that use a -// generic seek implementation. -type FileGenericSeek struct{} - -// Seek implements fs.FileOperations.Seek. -func (FileGenericSeek) Seek(ctx context.Context, file *fs.File, whence fs.SeekWhence, offset int64) (int64, error) { - return SeekWithDirCursor(ctx, file, whence, offset, nil) -} - -// FileZeroSeek implements fs.FileOperations.Seek for files that maintain a -// constant zero-value offset and require a no-op Seek. -type FileZeroSeek struct{} - -// Seek implements fs.FileOperations.Seek. -func (FileZeroSeek) Seek(context.Context, *fs.File, fs.SeekWhence, int64) (int64, error) { - return 0, nil -} - -// FileNoSeek implements fs.FileOperations.Seek to return EINVAL. -type FileNoSeek struct{} - -// Seek implements fs.FileOperations.Seek. -func (FileNoSeek) Seek(context.Context, *fs.File, fs.SeekWhence, int64) (int64, error) { - return 0, linuxerr.EINVAL -} - -// FilePipeSeek implements fs.FileOperations.Seek and can be used for files -// that behave like pipes (seeking is not supported). -type FilePipeSeek struct{} - -// Seek implements fs.FileOperations.Seek. -func (FilePipeSeek) Seek(context.Context, *fs.File, fs.SeekWhence, int64) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// FileNotDirReaddir implements fs.FileOperations.Readdir for non-directories. -type FileNotDirReaddir struct{} - -// Readdir implements fs.FileOperations.FileNotDirReaddir. -func (FileNotDirReaddir) Readdir(context.Context, *fs.File, fs.DentrySerializer) (int64, error) { - return 0, linuxerr.ENOTDIR -} - -// FileNoFsync implements fs.FileOperations.Fsync for files that don't support -// syncing. -type FileNoFsync struct{} - -// Fsync implements fs.FileOperations.Fsync. -func (FileNoFsync) Fsync(context.Context, *fs.File, int64, int64, fs.SyncType) error { - return linuxerr.EINVAL -} - -// FileNoopFsync implements fs.FileOperations.Fsync for files that don't need -// to synced. -type FileNoopFsync struct{} - -// Fsync implements fs.FileOperations.Fsync. -func (FileNoopFsync) Fsync(context.Context, *fs.File, int64, int64, fs.SyncType) error { - return nil -} - -// FileNoopFlush implements fs.FileOperations.Flush as a no-op. -type FileNoopFlush struct{} - -// Flush implements fs.FileOperations.Flush. -func (FileNoopFlush) Flush(context.Context, *fs.File) error { - return nil -} - -// FileNoMMap implements fs.FileOperations.Mappable for files that cannot -// be memory mapped. -type FileNoMMap struct{} - -// ConfigureMMap implements fs.FileOperations.ConfigureMMap. -func (FileNoMMap) ConfigureMMap(context.Context, *fs.File, *memmap.MMapOpts) error { - return linuxerr.ENODEV -} - -// GenericConfigureMMap implements fs.FileOperations.ConfigureMMap for most -// filesystems that support memory mapping. -func GenericConfigureMMap(file *fs.File, m memmap.Mappable, opts *memmap.MMapOpts) error { - if opts.Offset+opts.Length > math.MaxInt64 { - return linuxerr.EOVERFLOW - } - opts.Mappable = m - opts.MappingIdentity = file - file.IncRef() - return nil -} - -// FileNoIoctl implements fs.FileOperations.Ioctl for files that don't -// implement the ioctl syscall. -type FileNoIoctl struct{} - -// Ioctl implements fs.FileOperations.Ioctl. -func (FileNoIoctl) Ioctl(context.Context, *fs.File, usermem.IO, arch.SyscallArguments) (uintptr, error) { - return 0, linuxerr.ENOTTY -} - -// FileNoSplice implements fs.FileOperations.ReadFrom and -// fs.FileOperations.WriteTo for files that don't support splice. -type FileNoSplice struct{} - -// WriteTo implements fs.FileOperations.WriteTo. -func (FileNoSplice) WriteTo(context.Context, *fs.File, io.Writer, int64, bool) (int64, error) { - return 0, linuxerr.ENOSYS -} - -// ReadFrom implements fs.FileOperations.ReadFrom. -func (FileNoSplice) ReadFrom(context.Context, *fs.File, io.Reader, int64) (int64, error) { - return 0, linuxerr.ENOSYS -} - -// DirFileOperations implements most of fs.FileOperations for directories, -// except for Readdir and UnstableAttr which the embedding type must implement. -type DirFileOperations struct { - waiter.AlwaysReady - FileGenericSeek - FileNoIoctl - FileNoMMap - FileNoopFlush - FileNoopFsync - FileNoopRelease - FileNoSplice -} - -// Read implements fs.FileOperations.Read -func (*DirFileOperations) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EISDIR -} - -// Write implements fs.FileOperations.Write. -func (*DirFileOperations) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EISDIR -} - -// StaticDirFileOperations implements fs.FileOperations for directories with -// static children. -// -// +stateify savable -type StaticDirFileOperations struct { - DirFileOperations `state:"nosave"` - FileUseInodeUnstableAttr `state:"nosave"` - - // dentryMap is a SortedDentryMap used to implement Readdir. - dentryMap *fs.SortedDentryMap - - // dirCursor contains the name of the last directory entry that was - // serialized. - dirCursor string -} - -// NewStaticDirFileOperations returns a new StaticDirFileOperations that will -// iterate the given denty map. -func NewStaticDirFileOperations(dentries *fs.SortedDentryMap) *StaticDirFileOperations { - return &StaticDirFileOperations{ - dentryMap: dentries, - } -} - -// IterateDir implements DirIterator.IterateDir. -func (sdfo *StaticDirFileOperations) IterateDir(ctx context.Context, d *fs.Dirent, dirCtx *fs.DirCtx, offset int) (int, error) { - n, err := fs.GenericReaddir(dirCtx, sdfo.dentryMap) - return offset + n, err -} - -// Readdir implements fs.FileOperations.Readdir. -func (sdfo *StaticDirFileOperations) 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: &sdfo.dirCursor, - } - return fs.DirentReaddir(ctx, file.Dirent, sdfo, root, dirCtx, file.Offset()) -} - -// NoReadWriteFile is a file that does not support reading or writing. -// -// +stateify savable -type NoReadWriteFile struct { - waiter.AlwaysReady `state:"nosave"` - FileGenericSeek `state:"nosave"` - FileNoIoctl `state:"nosave"` - FileNoMMap `state:"nosave"` - FileNoopFsync `state:"nosave"` - FileNoopFlush `state:"nosave"` - FileNoopRelease `state:"nosave"` - FileNoRead `state:"nosave"` - FileNoWrite `state:"nosave"` - FileNotDirReaddir `state:"nosave"` - FileUseInodeUnstableAttr `state:"nosave"` - FileNoSplice `state:"nosave"` -} - -var _ fs.FileOperations = (*NoReadWriteFile)(nil) - -// FileStaticContentReader is a helper to implement fs.FileOperations.Read with -// static content. -// -// +stateify savable -type FileStaticContentReader struct { - // content is immutable. - content []byte -} - -// NewFileStaticContentReader initializes a FileStaticContentReader with the -// given content. -func NewFileStaticContentReader(b []byte) FileStaticContentReader { - return FileStaticContentReader{ - content: b, - } -} - -// Read implements fs.FileOperations.Read. -func (scr *FileStaticContentReader) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if offset < 0 { - return 0, linuxerr.EINVAL - } - if offset >= int64(len(scr.content)) { - return 0, nil - } - n, err := dst.CopyOut(ctx, scr.content[offset:]) - return int64(n), err -} - -// FileNoopWrite implements fs.FileOperations.Write as a noop. -type FileNoopWrite struct{} - -// Write implements fs.FileOperations.Write. -func (FileNoopWrite) Write(_ context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - return src.NumBytes(), nil -} - -// FileNoRead implements fs.FileOperations.Read to return EINVAL. -type FileNoRead struct{} - -// Read implements fs.FileOperations.Read. -func (FileNoRead) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EINVAL -} - -// FileNoWrite implements fs.FileOperations.Write to return EINVAL. -type FileNoWrite struct{} - -// Write implements fs.FileOperations.Write. -func (FileNoWrite) Write(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EINVAL -} - -// FileNoopRead implement fs.FileOperations.Read as a noop. -type FileNoopRead struct{} - -// Read implements fs.FileOperations.Read. -func (FileNoopRead) Read(context.Context, *fs.File, usermem.IOSequence, int64) (int64, error) { - return 0, nil -} - -// FileUseInodeUnstableAttr implements fs.FileOperations.UnstableAttr by calling -// InodeOperations.UnstableAttr. -type FileUseInodeUnstableAttr struct{} - -// UnstableAttr implements fs.FileOperations.UnstableAttr. -func (FileUseInodeUnstableAttr) UnstableAttr(ctx context.Context, file *fs.File) (fs.UnstableAttr, error) { - return file.Dirent.Inode.UnstableAttr(ctx) -} diff --git a/pkg/sentry/fs/fsutil/fsutil.go b/pkg/sentry/fs/fsutil/fsutil.go deleted file mode 100644 index f97a8b2f2..000000000 --- a/pkg/sentry/fs/fsutil/fsutil.go +++ /dev/null @@ -1,24 +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 fsutil provides utilities for implementing fs.InodeOperations -// and fs.FileOperations: -// -// - For embeddable utilities, see inode.go and file.go. -// -// - For fs.Inodes that require a page cache to be memory mapped, see -// inode_cache.go. -// -// - For anon fs.Inodes, see anon.go. -package fsutil diff --git a/pkg/sentry/fs/fsutil/host_mappable.go b/pkg/sentry/fs/fsutil/host_mappable.go deleted file mode 100644 index d737f2906..000000000 --- a/pkg/sentry/fs/fsutil/host_mappable.go +++ /dev/null @@ -1,231 +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 fsutil - -import ( - "math" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/usermem" -) - -// HostMappable implements memmap.Mappable and memmap.File over a -// CachedFileObject. -// -// Lock order (compare the lock order model in mm/mm.go): -// -// truncateMu ("fs locks") -// mu ("memmap.Mappable locks not taken by Translate") -// ("memmap.File locks") -// backingFile ("CachedFileObject locks") -// -// +stateify savable -type HostMappable struct { - hostFileMapper *HostFileMapper - - backingFile CachedFileObject - - mu sync.Mutex `state:"nosave"` - - // mappings tracks mappings of the cached file object into - // memmap.MappingSpaces so it can invalidated upon save. Protected by mu. - mappings memmap.MappingSet - - // truncateMu protects writes and truncations. See Truncate() for details. - truncateMu sync.RWMutex `state:"nosave"` -} - -// NewHostMappable creates a new mappable that maps directly to host FD. -func NewHostMappable(backingFile CachedFileObject) *HostMappable { - return &HostMappable{ - hostFileMapper: NewHostFileMapper(), - backingFile: backingFile, - } -} - -// AddMapping implements memmap.Mappable.AddMapping. -func (h *HostMappable) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error { - // Hot path. Avoid defers. - h.mu.Lock() - mapped := h.mappings.AddMapping(ms, ar, offset, writable) - for _, r := range mapped { - h.hostFileMapper.IncRefOn(r) - } - h.mu.Unlock() - return nil -} - -// RemoveMapping implements memmap.Mappable.RemoveMapping. -func (h *HostMappable) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) { - // Hot path. Avoid defers. - h.mu.Lock() - unmapped := h.mappings.RemoveMapping(ms, ar, offset, writable) - for _, r := range unmapped { - h.hostFileMapper.DecRefOn(r) - } - h.mu.Unlock() -} - -// CopyMapping implements memmap.Mappable.CopyMapping. -func (h *HostMappable) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error { - return h.AddMapping(ctx, ms, dstAR, offset, writable) -} - -// Translate implements memmap.Mappable.Translate. -func (h *HostMappable) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) { - return []memmap.Translation{ - { - Source: optional, - File: h, - Offset: optional.Start, - Perms: hostarch.AnyAccess, - }, - }, nil -} - -// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable. -func (h *HostMappable) InvalidateUnsavable(_ context.Context) error { - h.mu.Lock() - h.mappings.InvalidateAll(memmap.InvalidateOpts{}) - h.mu.Unlock() - return nil -} - -// NotifyChangeFD must be called after the file description represented by -// CachedFileObject.FD() changes. -func (h *HostMappable) NotifyChangeFD() error { - // Update existing sentry mappings to refer to the new file description. - if err := h.hostFileMapper.RegenerateMappings(h.backingFile.FD()); err != nil { - return err - } - - // Shoot down existing application mappings of the old file description; - // they will be remapped with the new file description on demand. - h.mu.Lock() - defer h.mu.Unlock() - - h.mappings.InvalidateAll(memmap.InvalidateOpts{}) - return nil -} - -// MapInternal implements memmap.File.MapInternal. -func (h *HostMappable) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) { - return h.hostFileMapper.MapInternal(fr, h.backingFile.FD(), at.Write) -} - -// FD implements memmap.File.FD. -func (h *HostMappable) FD() int { - return h.backingFile.FD() -} - -// IncRef implements memmap.File.IncRef. -func (h *HostMappable) IncRef(fr memmap.FileRange) { - mr := memmap.MappableRange{Start: fr.Start, End: fr.End} - h.hostFileMapper.IncRefOn(mr) -} - -// DecRef implements memmap.File.DecRef. -func (h *HostMappable) DecRef(fr memmap.FileRange) { - mr := memmap.MappableRange{Start: fr.Start, End: fr.End} - h.hostFileMapper.DecRefOn(mr) -} - -// Truncate truncates the file, invalidating any mapping that may have been -// removed after the size change. -// -// Truncation and writes are synchronized to prevent races where writes make the -// file grow between truncation and invalidation below: -// -// T1: Calls SetMaskedAttributes and stalls -// T2: Appends to file causing it to grow -// T2: Writes to mapped pages and COW happens -// T1: Continues and wronly invalidates the page mapped in step above. -func (h *HostMappable) Truncate(ctx context.Context, newSize int64, uattr fs.UnstableAttr) error { - h.truncateMu.Lock() - defer h.truncateMu.Unlock() - - mask := fs.AttrMask{Size: true} - attr := fs.UnstableAttr{Size: newSize} - - // Truncating a file clears privilege bits. - if uattr.Perms.HasSetUIDOrGID() { - mask.Perms = true - attr.Perms = uattr.Perms - attr.Perms.DropSetUIDAndMaybeGID() - } - - if err := h.backingFile.SetMaskedAttributes(ctx, mask, attr, false); err != nil { - return err - } - - // Invalidate COW mappings that may exist beyond the new size in case the file - // is being shrunk. Other mappings don't need to be invalidated because - // translate will just return identical mappings after invalidation anyway, - // and SIGBUS will be raised and handled when the mappings are touched. - // - // Compare Linux's mm/truncate.c:truncate_setsize() => - // truncate_pagecache() => - // mm/memory.c:unmap_mapping_range(evencows=1). - h.mu.Lock() - defer h.mu.Unlock() - mr := memmap.MappableRange{ - Start: fs.OffsetPageEnd(newSize), - End: fs.OffsetPageEnd(math.MaxInt64), - } - h.mappings.Invalidate(mr, memmap.InvalidateOpts{InvalidatePrivate: true}) - - return nil -} - -// Allocate reserves space in the backing file. -func (h *HostMappable) Allocate(ctx context.Context, offset int64, length int64) error { - h.truncateMu.RLock() - err := h.backingFile.Allocate(ctx, offset, length) - h.truncateMu.RUnlock() - return err -} - -// Write writes to the file backing this mappable. -func (h *HostMappable) Write(ctx context.Context, src usermem.IOSequence, offset int64, uattr fs.UnstableAttr) (int64, error) { - h.truncateMu.RLock() - defer h.truncateMu.RUnlock() - n, err := src.CopyInTo(ctx, &writer{ctx: ctx, hostMappable: h, off: offset}) - if n > 0 && uattr.Perms.HasSetUIDOrGID() { - mask := fs.AttrMask{Perms: true} - uattr.Perms.DropSetUIDAndMaybeGID() - if err := h.backingFile.SetMaskedAttributes(ctx, mask, uattr, false); err != nil { - return n, err - } - } - return n, err -} - -type writer struct { - ctx context.Context - hostMappable *HostMappable - off int64 -} - -// WriteFromBlocks implements safemem.Writer.WriteFromBlocks. -func (w *writer) WriteFromBlocks(src safemem.BlockSeq) (uint64, error) { - n, err := w.hostMappable.backingFile.WriteFromBlocksAt(w.ctx, src, uint64(w.off)) - w.off += int64(n) - return n, err -} diff --git a/pkg/sentry/fs/fsutil/inode.go b/pkg/sentry/fs/fsutil/inode.go deleted file mode 100644 index b3d974f37..000000000 --- a/pkg/sentry/fs/fsutil/inode.go +++ /dev/null @@ -1,532 +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 fsutil - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/waiter" -) - -// SimpleFileInode is a simple implementation of InodeOperations. -// -// +stateify savable -type SimpleFileInode struct { - InodeGenericChecker `state:"nosave"` - InodeNoExtendedAttributes `state:"nosave"` - InodeNoopRelease `state:"nosave"` - InodeNoopWriteOut `state:"nosave"` - InodeNotAllocatable `state:"nosave"` - InodeNotDirectory `state:"nosave"` - InodeNotMappable `state:"nosave"` - InodeNotOpenable `state:"nosave"` - InodeNotSocket `state:"nosave"` - InodeNotSymlink `state:"nosave"` - InodeNotTruncatable `state:"nosave"` - InodeNotVirtual `state:"nosave"` - - InodeSimpleAttributes -} - -// NewSimpleFileInode returns a new SimpleFileInode. -func NewSimpleFileInode(ctx context.Context, owner fs.FileOwner, perms fs.FilePermissions, typ uint64) *SimpleFileInode { - return &SimpleFileInode{ - InodeSimpleAttributes: NewInodeSimpleAttributes(ctx, owner, perms, typ), - } -} - -// NoReadWriteFileInode is an implementation of InodeOperations that supports -// opening files that are not readable or writeable. -// -// +stateify savable -type NoReadWriteFileInode struct { - InodeGenericChecker `state:"nosave"` - InodeNoExtendedAttributes `state:"nosave"` - InodeNoopRelease `state:"nosave"` - InodeNoopWriteOut `state:"nosave"` - InodeNotAllocatable `state:"nosave"` - InodeNotDirectory `state:"nosave"` - InodeNotMappable `state:"nosave"` - InodeNotSocket `state:"nosave"` - InodeNotSymlink `state:"nosave"` - InodeNotTruncatable `state:"nosave"` - InodeNotVirtual `state:"nosave"` - - InodeSimpleAttributes -} - -// NewNoReadWriteFileInode returns a new NoReadWriteFileInode. -func NewNoReadWriteFileInode(ctx context.Context, owner fs.FileOwner, perms fs.FilePermissions, typ uint64) *NoReadWriteFileInode { - return &NoReadWriteFileInode{ - InodeSimpleAttributes: NewInodeSimpleAttributes(ctx, owner, perms, typ), - } -} - -// GetFile implements fs.InodeOperations.GetFile. -func (*NoReadWriteFileInode) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &NoReadWriteFile{}), nil -} - -// InodeSimpleAttributes implements methods for updating in-memory unstable -// attributes. -// -// +stateify savable -type InodeSimpleAttributes struct { - // fsType is the immutable filesystem type that will be returned by - // StatFS. - fsType uint64 - - // mu protects unstable. - mu sync.RWMutex `state:"nosave"` - unstable fs.UnstableAttr -} - -// NewInodeSimpleAttributes returns a new InodeSimpleAttributes with the given -// owner and permissions, and all timestamps set to the current time. -func NewInodeSimpleAttributes(ctx context.Context, owner fs.FileOwner, perms fs.FilePermissions, typ uint64) InodeSimpleAttributes { - return NewInodeSimpleAttributesWithUnstable(fs.WithCurrentTime(ctx, fs.UnstableAttr{ - Owner: owner, - Perms: perms, - }), typ) -} - -// NewInodeSimpleAttributesWithUnstable returns a new InodeSimpleAttributes -// with the given unstable attributes. -func NewInodeSimpleAttributesWithUnstable(uattr fs.UnstableAttr, typ uint64) InodeSimpleAttributes { - return InodeSimpleAttributes{ - fsType: typ, - unstable: uattr, - } -} - -// UnstableAttr implements fs.InodeOperations.UnstableAttr. -func (i *InodeSimpleAttributes) UnstableAttr(ctx context.Context, _ *fs.Inode) (fs.UnstableAttr, error) { - i.mu.RLock() - u := i.unstable - i.mu.RUnlock() - return u, nil -} - -// SetPermissions implements fs.InodeOperations.SetPermissions. -func (i *InodeSimpleAttributes) SetPermissions(ctx context.Context, _ *fs.Inode, p fs.FilePermissions) bool { - i.mu.Lock() - i.unstable.SetPermissions(ctx, p) - i.mu.Unlock() - return true -} - -// SetOwner implements fs.InodeOperations.SetOwner. -func (i *InodeSimpleAttributes) SetOwner(ctx context.Context, _ *fs.Inode, owner fs.FileOwner) error { - i.mu.Lock() - i.unstable.SetOwner(ctx, owner) - i.mu.Unlock() - return nil -} - -// SetTimestamps implements fs.InodeOperations.SetTimestamps. -func (i *InodeSimpleAttributes) SetTimestamps(ctx context.Context, _ *fs.Inode, ts fs.TimeSpec) error { - i.mu.Lock() - i.unstable.SetTimestamps(ctx, ts) - i.mu.Unlock() - return nil -} - -// AddLink implements fs.InodeOperations.AddLink. -func (i *InodeSimpleAttributes) AddLink() { - i.mu.Lock() - i.unstable.Links++ - i.mu.Unlock() -} - -// DropLink implements fs.InodeOperations.DropLink. -func (i *InodeSimpleAttributes) DropLink() { - i.mu.Lock() - i.unstable.Links-- - i.mu.Unlock() -} - -// StatFS implements fs.InodeOperations.StatFS. -func (i *InodeSimpleAttributes) StatFS(context.Context) (fs.Info, error) { - if i.fsType == 0 { - return fs.Info{}, linuxerr.ENOSYS - } - return fs.Info{Type: i.fsType}, nil -} - -// NotifyAccess updates the access time. -func (i *InodeSimpleAttributes) NotifyAccess(ctx context.Context) { - i.mu.Lock() - i.unstable.AccessTime = ktime.NowFromContext(ctx) - i.mu.Unlock() -} - -// NotifyModification updates the modification time. -func (i *InodeSimpleAttributes) NotifyModification(ctx context.Context) { - i.mu.Lock() - i.unstable.ModificationTime = ktime.NowFromContext(ctx) - i.mu.Unlock() -} - -// NotifyStatusChange updates the status change time. -func (i *InodeSimpleAttributes) NotifyStatusChange(ctx context.Context) { - i.mu.Lock() - i.unstable.StatusChangeTime = ktime.NowFromContext(ctx) - i.mu.Unlock() -} - -// NotifyModificationAndStatusChange updates the modification and status change -// times. -func (i *InodeSimpleAttributes) NotifyModificationAndStatusChange(ctx context.Context) { - i.mu.Lock() - now := ktime.NowFromContext(ctx) - i.unstable.ModificationTime = now - i.unstable.StatusChangeTime = now - i.mu.Unlock() -} - -// InodeSimpleExtendedAttributes implements -// fs.InodeOperations.{Get,Set,List}Xattr. -// -// +stateify savable -type InodeSimpleExtendedAttributes struct { - // mu protects xattrs. - mu sync.RWMutex `state:"nosave"` - xattrs map[string]string -} - -// GetXattr implements fs.InodeOperations.GetXattr. -func (i *InodeSimpleExtendedAttributes) GetXattr(_ context.Context, _ *fs.Inode, name string, _ uint64) (string, error) { - i.mu.RLock() - value, ok := i.xattrs[name] - i.mu.RUnlock() - if !ok { - return "", linuxerr.ENOATTR - } - return value, nil -} - -// SetXattr implements fs.InodeOperations.SetXattr. -func (i *InodeSimpleExtendedAttributes) SetXattr(_ context.Context, _ *fs.Inode, name, value string, flags uint32) error { - i.mu.Lock() - defer i.mu.Unlock() - if i.xattrs == nil { - if flags&linux.XATTR_REPLACE != 0 { - return linuxerr.ENODATA - } - i.xattrs = make(map[string]string) - } - - _, ok := i.xattrs[name] - if ok && flags&linux.XATTR_CREATE != 0 { - return linuxerr.EEXIST - } - if !ok && flags&linux.XATTR_REPLACE != 0 { - return linuxerr.ENODATA - } - - i.xattrs[name] = value - return nil -} - -// ListXattr implements fs.InodeOperations.ListXattr. -func (i *InodeSimpleExtendedAttributes) ListXattr(context.Context, *fs.Inode, uint64) (map[string]struct{}, error) { - i.mu.RLock() - names := make(map[string]struct{}, len(i.xattrs)) - for name := range i.xattrs { - names[name] = struct{}{} - } - i.mu.RUnlock() - return names, nil -} - -// RemoveXattr implements fs.InodeOperations.RemoveXattr. -func (i *InodeSimpleExtendedAttributes) RemoveXattr(_ context.Context, _ *fs.Inode, name string) error { - i.mu.Lock() - defer i.mu.Unlock() - if _, ok := i.xattrs[name]; ok { - delete(i.xattrs, name) - return nil - } - return linuxerr.ENOATTR -} - -// staticFile is a file with static contents. It is returned by -// InodeStaticFileGetter.GetFile. -// -// +stateify savable -type staticFile struct { - FileGenericSeek `state:"nosave"` - FileNoIoctl `state:"nosave"` - FileNoMMap `state:"nosave"` - FileNoSplice `state:"nosave"` - FileNoopFsync `state:"nosave"` - FileNoopFlush `state:"nosave"` - FileNoopRelease `state:"nosave"` - FileNoopWrite `state:"nosave"` - FileNotDirReaddir `state:"nosave"` - FileUseInodeUnstableAttr `state:"nosave"` - waiter.AlwaysReady `state:"nosave"` - - FileStaticContentReader -} - -// InodeNoStatFS implement StatFS by retuning ENOSYS. -type InodeNoStatFS struct{} - -// StatFS implements fs.InodeOperations.StatFS. -func (InodeNoStatFS) StatFS(context.Context) (fs.Info, error) { - return fs.Info{}, linuxerr.ENOSYS -} - -// InodeStaticFileGetter implements GetFile for a file with static contents. -// -// +stateify savable -type InodeStaticFileGetter struct { - Contents []byte -} - -// GetFile implements fs.InodeOperations.GetFile. -func (i *InodeStaticFileGetter) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - return fs.NewFile(ctx, dirent, flags, &staticFile{ - FileStaticContentReader: NewFileStaticContentReader(i.Contents), - }), nil -} - -// InodeNotMappable returns a nil memmap.Mappable. -type InodeNotMappable struct{} - -// Mappable implements fs.InodeOperations.Mappable. -func (InodeNotMappable) Mappable(*fs.Inode) memmap.Mappable { - return nil -} - -// InodeNoopWriteOut is a no-op implementation of fs.InodeOperations.WriteOut. -type InodeNoopWriteOut struct{} - -// WriteOut is a no-op. -func (InodeNoopWriteOut) WriteOut(context.Context, *fs.Inode) error { - return nil -} - -// InodeNotDirectory can be used by Inodes that are not directories. -type InodeNotDirectory struct{} - -// Lookup implements fs.InodeOperations.Lookup. -func (InodeNotDirectory) Lookup(context.Context, *fs.Inode, string) (*fs.Dirent, error) { - return nil, linuxerr.ENOTDIR -} - -// Create implements fs.InodeOperations.Create. -func (InodeNotDirectory) Create(context.Context, *fs.Inode, string, fs.FileFlags, fs.FilePermissions) (*fs.File, error) { - return nil, linuxerr.ENOTDIR -} - -// CreateLink implements fs.InodeOperations.CreateLink. -func (InodeNotDirectory) CreateLink(context.Context, *fs.Inode, string, string) error { - return linuxerr.ENOTDIR -} - -// CreateHardLink implements fs.InodeOperations.CreateHardLink. -func (InodeNotDirectory) CreateHardLink(context.Context, *fs.Inode, *fs.Inode, string) error { - return linuxerr.ENOTDIR -} - -// CreateDirectory implements fs.InodeOperations.CreateDirectory. -func (InodeNotDirectory) CreateDirectory(context.Context, *fs.Inode, string, fs.FilePermissions) error { - return linuxerr.ENOTDIR -} - -// Bind implements fs.InodeOperations.Bind. -func (InodeNotDirectory) Bind(context.Context, *fs.Inode, string, transport.BoundEndpoint, fs.FilePermissions) (*fs.Dirent, error) { - return nil, linuxerr.ENOTDIR -} - -// CreateFifo implements fs.InodeOperations.CreateFifo. -func (InodeNotDirectory) CreateFifo(context.Context, *fs.Inode, string, fs.FilePermissions) error { - return linuxerr.ENOTDIR -} - -// Remove implements fs.InodeOperations.Remove. -func (InodeNotDirectory) Remove(context.Context, *fs.Inode, string) error { - return linuxerr.ENOTDIR -} - -// RemoveDirectory implements fs.InodeOperations.RemoveDirectory. -func (InodeNotDirectory) RemoveDirectory(context.Context, *fs.Inode, string) error { - return linuxerr.ENOTDIR -} - -// Rename implements fs.FileOperations.Rename. -func (InodeNotDirectory) Rename(context.Context, *fs.Inode, *fs.Inode, string, *fs.Inode, string, bool) error { - return linuxerr.EINVAL -} - -// InodeNotSocket can be used by Inodes that are not sockets. -type InodeNotSocket struct{} - -// BoundEndpoint implements fs.InodeOperations.BoundEndpoint. -func (InodeNotSocket) BoundEndpoint(*fs.Inode, string) transport.BoundEndpoint { - return nil -} - -// InodeNotTruncatable can be used by Inodes that cannot be truncated. -type InodeNotTruncatable struct{} - -// Truncate implements fs.InodeOperations.Truncate. -func (InodeNotTruncatable) Truncate(context.Context, *fs.Inode, int64) error { - return linuxerr.EINVAL -} - -// InodeIsDirTruncate implements fs.InodeOperations.Truncate for directories. -type InodeIsDirTruncate struct{} - -// Truncate implements fs.InodeOperations.Truncate. -func (InodeIsDirTruncate) Truncate(context.Context, *fs.Inode, int64) error { - return linuxerr.EISDIR -} - -// InodeNoopTruncate implements fs.InodeOperations.Truncate as a noop. -type InodeNoopTruncate struct{} - -// Truncate implements fs.InodeOperations.Truncate. -func (InodeNoopTruncate) Truncate(context.Context, *fs.Inode, int64) error { - return nil -} - -// InodeNotRenameable can be used by Inodes that cannot be truncated. -type InodeNotRenameable struct{} - -// Rename implements fs.InodeOperations.Rename. -func (InodeNotRenameable) Rename(context.Context, *fs.Inode, *fs.Inode, string, *fs.Inode, string, bool) error { - return linuxerr.EINVAL -} - -// InodeNotOpenable can be used by Inodes that cannot be opened. -type InodeNotOpenable struct{} - -// GetFile implements fs.InodeOperations.GetFile. -func (InodeNotOpenable) GetFile(context.Context, *fs.Dirent, fs.FileFlags) (*fs.File, error) { - return nil, linuxerr.EIO -} - -// InodeNotVirtual can be used by Inodes that are not virtual. -type InodeNotVirtual struct{} - -// IsVirtual implements fs.InodeOperations.IsVirtual. -func (InodeNotVirtual) IsVirtual() bool { - return false -} - -// InodeVirtual can be used by Inodes that are virtual. -type InodeVirtual struct{} - -// IsVirtual implements fs.InodeOperations.IsVirtual. -func (InodeVirtual) IsVirtual() bool { - return true -} - -// InodeNotSymlink can be used by Inodes that are not symlinks. -type InodeNotSymlink struct{} - -// Readlink implements fs.InodeOperations.Readlink. -func (InodeNotSymlink) Readlink(context.Context, *fs.Inode) (string, error) { - return "", linuxerr.ENOLINK -} - -// Getlink implements fs.InodeOperations.Getlink. -func (InodeNotSymlink) Getlink(context.Context, *fs.Inode) (*fs.Dirent, error) { - return nil, linuxerr.ENOLINK -} - -// InodeNoExtendedAttributes can be used by Inodes that do not support -// extended attributes. -type InodeNoExtendedAttributes struct{} - -// GetXattr implements fs.InodeOperations.GetXattr. -func (InodeNoExtendedAttributes) GetXattr(context.Context, *fs.Inode, string, uint64) (string, error) { - return "", linuxerr.EOPNOTSUPP -} - -// SetXattr implements fs.InodeOperations.SetXattr. -func (InodeNoExtendedAttributes) SetXattr(context.Context, *fs.Inode, string, string, uint32) error { - return linuxerr.EOPNOTSUPP -} - -// ListXattr implements fs.InodeOperations.ListXattr. -func (InodeNoExtendedAttributes) ListXattr(context.Context, *fs.Inode, uint64) (map[string]struct{}, error) { - return nil, linuxerr.EOPNOTSUPP -} - -// RemoveXattr implements fs.InodeOperations.RemoveXattr. -func (InodeNoExtendedAttributes) RemoveXattr(context.Context, *fs.Inode, string) error { - return linuxerr.EOPNOTSUPP -} - -// InodeNoopRelease implements fs.InodeOperations.Release as a noop. -type InodeNoopRelease struct{} - -// Release implements fs.InodeOperations.Release. -func (InodeNoopRelease) Release(context.Context) {} - -// InodeGenericChecker implements fs.InodeOperations.Check with a generic -// implementation. -type InodeGenericChecker struct{} - -// Check implements fs.InodeOperations.Check. -func (InodeGenericChecker) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { - return fs.ContextCanAccessFile(ctx, inode, p) -} - -// InodeDenyWriteChecker implements fs.InodeOperations.Check which denies all -// write operations. -type InodeDenyWriteChecker struct{} - -// Check implements fs.InodeOperations.Check. -func (InodeDenyWriteChecker) Check(ctx context.Context, inode *fs.Inode, p fs.PermMask) bool { - if p.Write { - return false - } - return fs.ContextCanAccessFile(ctx, inode, p) -} - -// InodeNotAllocatable can be used by Inodes that do not support Allocate(). -type InodeNotAllocatable struct{} - -// Allocate implements fs.InodeOperations.Allocate. -func (InodeNotAllocatable) Allocate(_ context.Context, _ *fs.Inode, _, _ int64) error { - return linuxerr.EOPNOTSUPP -} - -// InodeNoopAllocate implements fs.InodeOperations.Allocate as a noop. -type InodeNoopAllocate struct{} - -// Allocate implements fs.InodeOperations.Allocate. -func (InodeNoopAllocate) Allocate(_ context.Context, _ *fs.Inode, _, _ int64) error { - return nil -} - -// InodeIsDirAllocate implements fs.InodeOperations.Allocate for directories. -type InodeIsDirAllocate struct{} - -// Allocate implements fs.InodeOperations.Allocate. -func (InodeIsDirAllocate) Allocate(_ context.Context, _ *fs.Inode, _, _ int64) error { - return linuxerr.EISDIR -} diff --git a/pkg/sentry/fs/fsutil/inode_cached.go b/pkg/sentry/fs/fsutil/inode_cached.go deleted file mode 100644 index fb2750b55..000000000 --- a/pkg/sentry/fs/fsutil/inode_cached.go +++ /dev/null @@ -1,1076 +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 fsutil - -import ( - "fmt" - "io" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs" - 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" -) - -// Lock order (compare the lock order model in mm/mm.go): -// -// CachingInodeOperations.attrMu ("fs locks") -// CachingInodeOperations.mapsMu ("memmap.Mappable locks not taken by Translate") -// CachingInodeOperations.dataMu ("memmap.Mappable locks taken by Translate") -// CachedFileObject locks - -// CachingInodeOperations caches the metadata and content of a CachedFileObject. -// It implements a subset of InodeOperations. As a utility it can be used to -// implement the full set of InodeOperations. Generally it should not be -// embedded to avoid unexpected inherited behavior. -// -// CachingInodeOperations implements Mappable for the CachedFileObject: -// -// - If CachedFileObject.FD returns a value >= 0 then the file descriptor -// will be memory mapped on the host. -// -// - Otherwise, the contents of CachedFileObject are buffered into memory -// managed by the CachingInodeOperations. -// -// Implementations of FileOperations for a CachedFileObject must read and -// write through CachingInodeOperations using Read and Write respectively. -// -// Implementations of InodeOperations.WriteOut must call Sync to write out -// in-memory modifications of data and metadata to the CachedFileObject. -// -// +stateify savable -type CachingInodeOperations struct { - // backingFile is a handle to a cached file object. - backingFile CachedFileObject - - // mfp is used to allocate memory that caches backingFile's contents. - mfp pgalloc.MemoryFileProvider - - // opts contains options. opts is immutable. - opts CachingInodeOperationsOptions - - attrMu sync.Mutex `state:"nosave"` - - // attr is unstable cached metadata. - // - // 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 - - // dirtyAttr is metadata that was updated in-place but hasn't yet - // been successfully written out. - // - // dirtyAttr is protected by attrMu. - dirtyAttr fs.AttrMask - - mapsMu sync.Mutex `state:"nosave"` - - // mappings tracks mappings of the cached file object into - // memmap.MappingSpaces. - // - // mappings is protected by mapsMu. - mappings memmap.MappingSet - - dataMu sync.RWMutex `state:"nosave"` - - // cache maps offsets into the cached file to offsets into - // mfp.MemoryFile() that store the file's data. - // - // cache is protected by dataMu. - cache FileRangeSet - - // dirty tracks dirty segments in cache. - // - // dirty is protected by dataMu. - dirty DirtySet - - // hostFileMapper caches internal mappings of backingFile.FD(). - hostFileMapper *HostFileMapper - - // refs tracks active references to data in the cache. - // - // refs is protected by dataMu. - refs FrameRefSet -} - -// CachingInodeOperationsOptions configures a CachingInodeOperations. -// -// +stateify savable -type CachingInodeOperationsOptions struct { - // If ForcePageCache is true, use the sentry page cache even if a host file - // descriptor is available. - ForcePageCache bool - - // If LimitHostFDTranslation is true, apply maxFillRange() constraints to - // host file descriptor mappings returned by - // CachingInodeOperations.Translate(). - LimitHostFDTranslation bool -} - -// CachedFileObject is a file that may require caching. -type CachedFileObject interface { - // ReadToBlocksAt reads up to dsts.NumBytes() bytes from the file to dsts, - // starting at offset, and returns the number of bytes read. ReadToBlocksAt - // may return a partial read without an error. - ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) - - // WriteFromBlocksAt writes up to srcs.NumBytes() bytes from srcs to the - // file, starting at offset, and returns the number of bytes written. - // WriteFromBlocksAt may return a partial write without an error. - WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) - - // SetMaskedAttributes sets the attributes in attr that are true in - // mask on the backing file. If the mask contains only ATime or MTime - // and the CachedFileObject has an FD to the file, then this operation - // is a noop unless forceSetTimestamps is true. This avoids an extra - // RPC to the gofer in the open-read/write-close case, when the - // timestamps on the file will be updated by the host kernel for us. - // - // SetMaskedAttributes may be called at any point, regardless of whether - // the file was opened. - SetMaskedAttributes(ctx context.Context, mask fs.AttrMask, attr fs.UnstableAttr, forceSetTimestamps bool) error - - // Allocate allows the caller to reserve disk space for the inode. - // It's equivalent to fallocate(2) with 'mode=0'. - Allocate(ctx context.Context, offset int64, length int64) error - - // Sync instructs the remote filesystem to sync the file to stable storage. - Sync(ctx context.Context) error - - // FD returns a host file descriptor. If it is possible for - // CachingInodeOperations.AddMapping to have ever been called with writable - // = true, the FD must have been opened O_RDWR; otherwise, it may have been - // opened O_RDONLY or O_RDWR. (mmap unconditionally requires that mapped - // files are readable.) If no host file descriptor is available, FD returns - // a negative number. - // - // For any given CachedFileObject, if FD() ever succeeds (returns a - // non-negative number), it must always succeed. - // - // FD is called iff the file has been memory mapped. This implies that - // the file was opened (see fs.InodeOperations.GetFile). - FD() int -} - -// NewCachingInodeOperations returns a new CachingInodeOperations backed by -// a CachedFileObject and its initial unstable attributes. -func NewCachingInodeOperations(ctx context.Context, backingFile CachedFileObject, uattr fs.UnstableAttr, opts CachingInodeOperationsOptions) *CachingInodeOperations { - mfp := pgalloc.MemoryFileProviderFromContext(ctx) - if mfp == nil { - panic(fmt.Sprintf("context.Context %T lacks non-nil value for key %T", ctx, pgalloc.CtxMemoryFileProvider)) - } - return &CachingInodeOperations{ - backingFile: backingFile, - mfp: mfp, - opts: opts, - attr: uattr, - hostFileMapper: NewHostFileMapper(), - } -} - -// Release implements fs.InodeOperations.Release. -func (c *CachingInodeOperations) Release() { - c.mapsMu.Lock() - defer c.mapsMu.Unlock() - c.dataMu.Lock() - defer c.dataMu.Unlock() - - // Something has gone terribly wrong if we're releasing an inode that is - // still memory-mapped. - if !c.mappings.IsEmpty() { - panic(fmt.Sprintf("Releasing CachingInodeOperations with mappings:\n%s", &c.mappings)) - } - - // Drop any cached pages that are still awaiting MemoryFile eviction. (This - // means that MemoryFile no longer needs to evict them.) - mf := c.mfp.MemoryFile() - mf.MarkAllUnevictable(c) - if err := SyncDirtyAll(context.Background(), &c.cache, &c.dirty, uint64(c.attr.Size), mf, c.backingFile.WriteFromBlocksAt); err != nil { - panic(fmt.Sprintf("Failed to writeback cached data: %v", err)) - } - c.cache.DropAll(mf) - c.dirty.RemoveAll() -} - -// UnstableAttr implements fs.InodeOperations.UnstableAttr. -func (c *CachingInodeOperations) UnstableAttr(ctx context.Context, inode *fs.Inode) (fs.UnstableAttr, error) { - c.attrMu.Lock() - attr := c.attr - c.attrMu.Unlock() - return attr, nil -} - -// SetPermissions implements fs.InodeOperations.SetPermissions. -func (c *CachingInodeOperations) SetPermissions(ctx context.Context, inode *fs.Inode, perms fs.FilePermissions) bool { - c.attrMu.Lock() - defer c.attrMu.Unlock() - - now := ktime.NowFromContext(ctx) - masked := fs.AttrMask{Perms: true} - if err := c.backingFile.SetMaskedAttributes(ctx, masked, fs.UnstableAttr{Perms: perms}, false); err != nil { - return false - } - c.attr.Perms = perms - c.touchStatusChangeTimeLocked(now) - return true -} - -// SetOwner implements fs.InodeOperations.SetOwner. -func (c *CachingInodeOperations) SetOwner(ctx context.Context, inode *fs.Inode, owner fs.FileOwner) error { - if !owner.UID.Ok() && !owner.GID.Ok() { - return nil - } - - c.attrMu.Lock() - defer c.attrMu.Unlock() - - now := ktime.NowFromContext(ctx) - masked := fs.AttrMask{ - UID: owner.UID.Ok(), - GID: owner.GID.Ok(), - } - if err := c.backingFile.SetMaskedAttributes(ctx, masked, fs.UnstableAttr{Owner: owner}, false); err != nil { - return err - } - if owner.UID.Ok() { - c.attr.Owner.UID = owner.UID - } - if owner.GID.Ok() { - c.attr.Owner.GID = owner.GID - } - c.touchStatusChangeTimeLocked(now) - return nil -} - -// SetTimestamps implements fs.InodeOperations.SetTimestamps. -func (c *CachingInodeOperations) SetTimestamps(ctx context.Context, inode *fs.Inode, ts fs.TimeSpec) error { - if ts.ATimeOmit && ts.MTimeOmit { - return nil - } - - c.attrMu.Lock() - defer c.attrMu.Unlock() - - // Replace requests to use the "system time" with the current time to - // ensure that cached timestamps remain consistent with the remote - // filesystem. - now := ktime.NowFromContext(ctx) - if ts.ATimeSetSystemTime { - ts.ATime = now - } - if ts.MTimeSetSystemTime { - ts.MTime = now - } - masked := fs.AttrMask{ - AccessTime: !ts.ATimeOmit, - ModificationTime: !ts.MTimeOmit, - } - // Call SetMaskedAttributes with forceSetTimestamps = true to make sure - // the timestamp is updated. - if err := c.backingFile.SetMaskedAttributes(ctx, masked, fs.UnstableAttr{AccessTime: ts.ATime, ModificationTime: ts.MTime}, true); err != nil { - return err - } - if !ts.ATimeOmit { - c.attr.AccessTime = ts.ATime - } - if !ts.MTimeOmit { - c.attr.ModificationTime = ts.MTime - } - c.touchStatusChangeTimeLocked(now) - return nil -} - -// Truncate implements fs.InodeOperations.Truncate. -func (c *CachingInodeOperations) Truncate(ctx context.Context, inode *fs.Inode, size int64) error { - c.attrMu.Lock() - defer c.attrMu.Unlock() - - // c.attr.Size is protected by both c.attrMu and c.dataMu. - c.dataMu.Lock() - now := ktime.NowFromContext(ctx) - masked := fs.AttrMask{Size: true} - attr := fs.UnstableAttr{Size: size} - if c.attr.Perms.HasSetUIDOrGID() { - masked.Perms = true - attr.Perms = c.attr.Perms - attr.Perms.DropSetUIDAndMaybeGID() - c.attr.Perms = attr.Perms - } - if err := c.backingFile.SetMaskedAttributes(ctx, masked, attr, false); err != nil { - c.dataMu.Unlock() - return err - } - oldSize := c.attr.Size - c.attr.Size = size - c.touchModificationAndStatusChangeTimeLocked(now) - - // We drop c.dataMu here so that we can lock c.mapsMu and invalidate - // mappings below. This allows concurrent calls to Read/Translate/etc. - // These functions synchronize with an in-progress Truncate by refusing to - // use cache contents beyond the new c.attr.Size. (We are still holding - // c.attrMu, so we can't race with Truncate/Write.) - c.dataMu.Unlock() - - // Nothing left to do unless shrinking the file. - if size >= oldSize { - return nil - } - - oldpgend := fs.OffsetPageEnd(oldSize) - newpgend := fs.OffsetPageEnd(size) - - // Invalidate past translations of truncated pages. - if newpgend != oldpgend { - c.mapsMu.Lock() - c.mappings.Invalidate(memmap.MappableRange{newpgend, oldpgend}, memmap.InvalidateOpts{ - // Compare Linux's mm/truncate.c:truncate_setsize() => - // truncate_pagecache() => - // mm/memory.c:unmap_mapping_range(evencows=1). - InvalidatePrivate: true, - }) - c.mapsMu.Unlock() - } - - // We are now guaranteed that there are no translations of truncated pages, - // and can remove them from the cache. Since truncated pages have been - // removed from the backing file, they should be dropped without being - // written back. - c.dataMu.Lock() - defer c.dataMu.Unlock() - c.cache.Truncate(uint64(size), c.mfp.MemoryFile()) - c.dirty.KeepClean(memmap.MappableRange{uint64(size), oldpgend}) - - return nil -} - -// Allocate implements fs.InodeOperations.Allocate. -func (c *CachingInodeOperations) Allocate(ctx context.Context, offset, length int64) error { - newSize := offset + length - - // c.attr.Size is protected by both c.attrMu and c.dataMu. - c.attrMu.Lock() - defer c.attrMu.Unlock() - c.dataMu.Lock() - defer c.dataMu.Unlock() - - if newSize <= c.attr.Size { - return nil - } - - now := ktime.NowFromContext(ctx) - if err := c.backingFile.Allocate(ctx, offset, length); err != nil { - return err - } - - c.attr.Size = newSize - c.touchModificationAndStatusChangeTimeLocked(now) - return nil -} - -// WriteDirtyPagesAndAttrs will write the dirty pages and attributes to the -// gofer without calling Fsync on the remote file. -func (c *CachingInodeOperations) WriteDirtyPagesAndAttrs(ctx context.Context, inode *fs.Inode) error { - c.attrMu.Lock() - defer c.attrMu.Unlock() - c.dataMu.Lock() - defer c.dataMu.Unlock() - - // Write dirty pages back. - err := SyncDirtyAll(ctx, &c.cache, &c.dirty, uint64(c.attr.Size), c.mfp.MemoryFile(), c.backingFile.WriteFromBlocksAt) - if err != nil { - return err - } - - // SyncDirtyAll above would have grown if needed. On shrinks, the backing - // file is called directly, so size is never needs to be updated. - c.dirtyAttr.Size = false - - // Write out cached attributes. - if err := c.backingFile.SetMaskedAttributes(ctx, c.dirtyAttr, c.attr, false); err != nil { - return err - } - c.dirtyAttr = fs.AttrMask{} - - return nil -} - -// WriteOut implements fs.InodeOperations.WriteOut. -func (c *CachingInodeOperations) WriteOut(ctx context.Context, inode *fs.Inode) error { - if err := c.WriteDirtyPagesAndAttrs(ctx, inode); err != nil { - return err - } - - // Fsync the remote file. - return c.backingFile.Sync(ctx) -} - -// IncLinks increases the link count and updates cached modification time. -func (c *CachingInodeOperations) IncLinks(ctx context.Context) { - c.attrMu.Lock() - c.attr.Links++ - c.touchModificationAndStatusChangeTimeLocked(ktime.NowFromContext(ctx)) - c.attrMu.Unlock() -} - -// DecLinks decreases the link count and updates cached modification time. -func (c *CachingInodeOperations) DecLinks(ctx context.Context) { - c.attrMu.Lock() - c.attr.Links-- - c.touchModificationAndStatusChangeTimeLocked(ktime.NowFromContext(ctx)) - c.attrMu.Unlock() -} - -// TouchAccessTime updates the cached access time in-place to the -// current time. It does not update status change time in-place. See -// mm/filemap.c:do_generic_file_read -> include/linux/h:file_accessed. -func (c *CachingInodeOperations) TouchAccessTime(ctx context.Context, inode *fs.Inode) { - if inode.MountSource.Flags.NoAtime { - return - } - - c.attrMu.Lock() - c.touchAccessTimeLocked(ktime.NowFromContext(ctx)) - c.attrMu.Unlock() -} - -// touchAccesstimeLocked updates the cached access time in-place to the current -// time. -// -// Preconditions: c.attrMu is locked for writing. -func (c *CachingInodeOperations) touchAccessTimeLocked(now ktime.Time) { - c.attr.AccessTime = now - c.dirtyAttr.AccessTime = true -} - -// TouchModificationAndStatusChangeTime updates the cached modification and -// status change times in-place to the current time. -func (c *CachingInodeOperations) TouchModificationAndStatusChangeTime(ctx context.Context) { - c.attrMu.Lock() - c.touchModificationAndStatusChangeTimeLocked(ktime.NowFromContext(ctx)) - c.attrMu.Unlock() -} - -// touchModificationAndStatusChangeTimeLocked updates the cached modification -// and status change times in-place to the current time. -// -// Preconditions: c.attrMu is locked for writing. -func (c *CachingInodeOperations) touchModificationAndStatusChangeTimeLocked(now ktime.Time) { - c.attr.ModificationTime = now - c.dirtyAttr.ModificationTime = true - c.attr.StatusChangeTime = now - c.dirtyAttr.StatusChangeTime = true -} - -// TouchStatusChangeTime updates the cached status change time in-place to the -// current time. -func (c *CachingInodeOperations) TouchStatusChangeTime(ctx context.Context) { - c.attrMu.Lock() - c.touchStatusChangeTimeLocked(ktime.NowFromContext(ctx)) - c.attrMu.Unlock() -} - -// touchStatusChangeTimeLocked updates the cached status change time -// in-place to the current time. -// -// Preconditions: c.attrMu is locked for writing. -func (c *CachingInodeOperations) touchStatusChangeTimeLocked(now ktime.Time) { - c.attr.StatusChangeTime = now - c.dirtyAttr.StatusChangeTime = true -} - -// UpdateUnstable updates the cached unstable attributes. Only non-dirty -// attributes are updated. -func (c *CachingInodeOperations) UpdateUnstable(attr fs.UnstableAttr) { - // All attributes are protected by attrMu. - c.attrMu.Lock() - - if !c.dirtyAttr.Usage { - c.attr.Usage = attr.Usage - } - if !c.dirtyAttr.Perms { - c.attr.Perms = attr.Perms - } - if !c.dirtyAttr.UID { - c.attr.Owner.UID = attr.Owner.UID - } - if !c.dirtyAttr.GID { - c.attr.Owner.GID = attr.Owner.GID - } - if !c.dirtyAttr.AccessTime { - c.attr.AccessTime = attr.AccessTime - } - if !c.dirtyAttr.ModificationTime { - c.attr.ModificationTime = attr.ModificationTime - } - if !c.dirtyAttr.StatusChangeTime { - c.attr.StatusChangeTime = attr.StatusChangeTime - } - if !c.dirtyAttr.Links { - c.attr.Links = attr.Links - } - - // Size requires holding attrMu and dataMu. - c.dataMu.Lock() - if !c.dirtyAttr.Size { - c.attr.Size = attr.Size - } - c.dataMu.Unlock() - - c.attrMu.Unlock() -} - -// Read reads from frames and otherwise directly from the backing file -// into dst starting at offset until dst is full, EOF is reached, or an -// error is encountered. -// -// Read may partially fill dst and return a nil error. -func (c *CachingInodeOperations) Read(ctx context.Context, file *fs.File, dst usermem.IOSequence, offset int64) (int64, error) { - if dst.NumBytes() == 0 { - return 0, nil - } - - // Have we reached EOF? We check for this again in - // inodeReadWriter.ReadToBlocks to avoid holding c.attrMu (which would - // serialize reads) or c.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 c.attr.Size and use atomics instead of - // c.dataMu. - c.dataMu.RLock() - size := c.attr.Size - c.dataMu.RUnlock() - if offset >= size { - return 0, io.EOF - } - - n, err := dst.CopyOutFrom(ctx, &inodeReadWriter{ctx, c, offset}) - // Compare Linux's mm/filemap.c:do_generic_file_read() => file_accessed(). - c.TouchAccessTime(ctx, file.Dirent.Inode) - return n, err -} - -// Write writes to frames and otherwise directly to the backing file -// from src starting at offset and until src is empty or an error is -// encountered. -// -// If Write partially fills src, a non-nil error is returned. -func (c *CachingInodeOperations) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) { - // Hot path. Avoid defers. - if src.NumBytes() == 0 { - return 0, nil - } - - c.attrMu.Lock() - // Compare Linux's mm/filemap.c:__generic_file_write_iter() => file_update_time(). - c.touchModificationAndStatusChangeTimeLocked(ktime.NowFromContext(ctx)) - n, err := src.CopyInTo(ctx, &inodeReadWriter{ctx, c, offset}) - c.attrMu.Unlock() - return n, err -} - -type inodeReadWriter struct { - ctx context.Context - c *CachingInodeOperations - offset int64 -} - -// ReadToBlocks implements safemem.Reader.ReadToBlocks. -func (rw *inodeReadWriter) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) { - mem := rw.c.mfp.MemoryFile() - fillCache := !rw.c.useHostPageCache() && mem.ShouldCacheEvictable() - - // Hot path. Avoid defers. - var unlock func() - if fillCache { - rw.c.dataMu.Lock() - unlock = rw.c.dataMu.Unlock - } else { - rw.c.dataMu.RLock() - unlock = rw.c.dataMu.RUnlock - } - - // Compute the range to read. - if rw.offset >= rw.c.attr.Size { - unlock() - return 0, io.EOF - } - end := fs.ReadEndOffset(rw.offset, int64(dsts.NumBytes()), rw.c.attr.Size) - if end == rw.offset { // dsts.NumBytes() == 0? - unlock() - return 0, nil - } - - var done uint64 - seg, gap := rw.c.cache.Find(uint64(rw.offset)) - for rw.offset < end { - mr := memmap.MappableRange{uint64(rw.offset), uint64(end)} - switch { - case seg.Ok(): - // Get internal mappings from the cache. - ims, err := mem.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), hostarch.Read) - if err != nil { - unlock() - 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 { - unlock() - return done, err - } - - // Continue. - seg, gap = seg.NextNonEmpty() - - case gap.Ok(): - gapMR := gap.Range().Intersect(mr) - if fillCache { - // Read into the cache, then re-enter the loop to read from the - // cache. - reqMR := memmap.MappableRange{ - Start: uint64(hostarch.Addr(gapMR.Start).RoundDown()), - End: fs.OffsetPageEnd(int64(gapMR.End)), - } - optMR := gap.Range() - _, err := rw.c.cache.Fill(rw.ctx, reqMR, maxFillRange(reqMR, optMR), uint64(rw.c.attr.Size), mem, usage.PageCache, false /* populate */, rw.c.backingFile.ReadToBlocksAt) - mem.MarkEvictable(rw.c, pgalloc.EvictableRange{optMR.Start, optMR.End}) - seg, gap = rw.c.cache.Find(uint64(rw.offset)) - if !seg.Ok() { - unlock() - return done, err - } - // err might have occurred in part of gap.Range() outside - // gapMR. Forget about it for now; if the error matters and - // persists, we'll run into it again in a later iteration of - // this loop. - } else { - // Read directly from the backing file. - dst := dsts.TakeFirst64(gapMR.Length()) - n, err := rw.c.backingFile.ReadToBlocksAt(rw.ctx, dst, gapMR.Start) - done += n - rw.offset += int64(n) - dsts = dsts.DropFirst64(n) - // Partial reads are fine. But we must stop reading. - if n != dst.NumBytes() || err != nil { - unlock() - return done, err - } - - // Continue. - seg, gap = gap.NextSegment(), FileRangeGapIterator{} - } - } - } - unlock() - return done, nil -} - -// maybeUpdateAttrs updates the file's attributes after a write. It updates -// size if data has been written past the old size, and setuid/setgid if any -// bytes were written. -// -// Preconditions: -// - rw.c.attrMu must be locked. -// - rw.c.dataMu must be locked. -func (rw *inodeReadWriter) maybeUpdateAttrs(nwritten uint64) { - // If the write ends beyond the file's previous size, it causes the - // file to grow. - if rw.offset > rw.c.attr.Size { - rw.c.attr.Size = rw.offset - rw.c.dirtyAttr.Size = true - } - if rw.offset > rw.c.attr.Usage { - // This is incorrect if CachingInodeOperations is caching a sparse - // file. (In Linux, keeping inode::i_blocks up to date is the - // filesystem's responsibility.) - rw.c.attr.Usage = rw.offset - rw.c.dirtyAttr.Usage = true - } - - // If bytes were written, ensure setuid and setgid are cleared. - if nwritten > 0 && rw.c.attr.Perms.HasSetUIDOrGID() { - rw.c.dirtyAttr.Perms = true - rw.c.attr.Perms.DropSetUIDAndMaybeGID() - } -} - -// WriteFromBlocks implements safemem.Writer.WriteFromBlocks. -// -// Preconditions: rw.c.attrMu must be locked. -func (rw *inodeReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) { - // Hot path. Avoid defers. - rw.c.dataMu.Lock() - - // Compute the range to write. - end := fs.WriteEndOffset(rw.offset, int64(srcs.NumBytes())) - if end == rw.offset { // srcs.NumBytes() == 0? - rw.c.dataMu.Unlock() - return 0, nil - } - - mf := rw.c.mfp.MemoryFile() - var done uint64 - seg, gap := rw.c.cache.Find(uint64(rw.offset)) - for rw.offset < end { - mr := memmap.MappableRange{uint64(rw.offset), uint64(end)} - switch { - case seg.Ok() && seg.Start() < mr.End: - // Get internal mappings from the cache. - segMR := seg.Range().Intersect(mr) - ims, err := mf.MapInternal(seg.FileRangeOf(segMR), hostarch.Write) - if err != nil { - rw.maybeUpdateAttrs(done) - rw.c.dataMu.Unlock() - return done, err - } - - // Copy to internal mappings. - n, err := safemem.CopySeq(ims, srcs) - done += n - rw.offset += int64(n) - srcs = srcs.DropFirst64(n) - rw.c.dirty.MarkDirty(segMR) - if err != nil { - rw.maybeUpdateAttrs(done) - rw.c.dataMu.Unlock() - return done, err - } - - // Continue. - seg, gap = seg.NextNonEmpty() - - case gap.Ok() && gap.Start() < mr.End: - // Write directly to the backing file. At present, we never fill - // the cache when writing, since doing so can convert small writes - // into inefficient read-modify-write cycles, and we have no - // mechanism for detecting or avoiding this. - gapmr := gap.Range().Intersect(mr) - src := srcs.TakeFirst64(gapmr.Length()) - n, err := rw.c.backingFile.WriteFromBlocksAt(rw.ctx, src, gapmr.Start) - done += n - rw.offset += int64(n) - srcs = srcs.DropFirst64(n) - // Partial writes are fine. But we must stop writing. - if n != src.NumBytes() || err != nil { - rw.maybeUpdateAttrs(done) - rw.c.dataMu.Unlock() - return done, err - } - - // Continue. - seg, gap = gap.NextSegment(), FileRangeGapIterator{} - } - } - rw.maybeUpdateAttrs(done) - rw.c.dataMu.Unlock() - return done, nil -} - -// useHostPageCache returns true if c uses c.backingFile.FD() for all file I/O -// and memory mappings, and false if c.cache may contain data cached from -// c.backingFile. -func (c *CachingInodeOperations) useHostPageCache() bool { - return !c.opts.ForcePageCache && c.backingFile.FD() >= 0 -} - -// AddMapping implements memmap.Mappable.AddMapping. -func (c *CachingInodeOperations) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error { - // Hot path. Avoid defers. - c.mapsMu.Lock() - mapped := c.mappings.AddMapping(ms, ar, offset, writable) - // Do this unconditionally since whether we have c.backingFile.FD() >= 0 - // can change across save/restore. - for _, r := range mapped { - c.hostFileMapper.IncRefOn(r) - } - if !c.useHostPageCache() { - // c.Evict() will refuse to evict memory-mapped pages, so tell the - // MemoryFile to not bother trying. - mf := c.mfp.MemoryFile() - for _, r := range mapped { - mf.MarkUnevictable(c, pgalloc.EvictableRange{r.Start, r.End}) - } - } - c.mapsMu.Unlock() - return nil -} - -// RemoveMapping implements memmap.Mappable.RemoveMapping. -func (c *CachingInodeOperations) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) { - // Hot path. Avoid defers. - c.mapsMu.Lock() - unmapped := c.mappings.RemoveMapping(ms, ar, offset, writable) - for _, r := range unmapped { - c.hostFileMapper.DecRefOn(r) - } - if c.useHostPageCache() { - c.mapsMu.Unlock() - return - } - - // Pages that are no longer referenced by any application memory mappings - // are now considered unused; allow MemoryFile to evict them when - // necessary. - mf := c.mfp.MemoryFile() - c.dataMu.Lock() - for _, r := range unmapped { - // Since these pages are no longer mapped, they are no longer - // concurrently dirtyable by a writable memory mapping. - c.dirty.AllowClean(r) - mf.MarkEvictable(c, pgalloc.EvictableRange{r.Start, r.End}) - } - c.dataMu.Unlock() - c.mapsMu.Unlock() -} - -// CopyMapping implements memmap.Mappable.CopyMapping. -func (c *CachingInodeOperations) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error { - return c.AddMapping(ctx, ms, dstAR, offset, writable) -} - -// Translate implements memmap.Mappable.Translate. -func (c *CachingInodeOperations) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) { - // Hot path. Avoid defer. - if c.useHostPageCache() { - mr := optional - if c.opts.LimitHostFDTranslation { - mr = maxFillRange(required, optional) - } - return []memmap.Translation{ - { - Source: mr, - File: c, - Offset: mr.Start, - Perms: hostarch.AnyAccess, - }, - }, nil - } - - c.dataMu.Lock() - - // Constrain translations to c.attr.Size (rounded up) to prevent - // translation to pages that may be concurrently truncated. - pgend := fs.OffsetPageEnd(c.attr.Size) - var beyondEOF bool - if required.End > pgend { - if required.Start >= pgend { - c.dataMu.Unlock() - return nil, &memmap.BusError{io.EOF} - } - beyondEOF = true - required.End = pgend - } - if optional.End > pgend { - optional.End = pgend - } - - mf := c.mfp.MemoryFile() - _, cerr := c.cache.Fill(ctx, required, maxFillRange(required, optional), uint64(c.attr.Size), mf, usage.PageCache, false /* populate */, c.backingFile.ReadToBlocksAt) - - var ts []memmap.Translation - var translatedEnd uint64 - for seg := c.cache.FindSegment(required.Start); seg.Ok() && seg.Start() < required.End; seg, _ = seg.NextNonEmpty() { - segMR := seg.Range().Intersect(optional) - // TODO(jamieliu): Make Translations writable even if writability is - // not required if already kept-dirty by another writable translation. - perms := hostarch.AccessType{ - Read: true, - Execute: true, - } - if at.Write { - // From this point forward, this memory can be dirtied through the - // mapping at any time. - c.dirty.KeepDirty(segMR) - perms.Write = true - } - ts = append(ts, memmap.Translation{ - Source: segMR, - File: mf, - Offset: seg.FileRangeOf(segMR).Start, - Perms: perms, - }) - translatedEnd = segMR.End - } - - c.dataMu.Unlock() - - // Don't return the error returned by c.cache.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 -} - -func maxFillRange(required, optional memmap.MappableRange) memmap.MappableRange { - const maxReadahead = 64 << 10 // 64 KB, chosen arbitrarily - if required.Length() >= maxReadahead { - return required - } - if optional.Length() <= maxReadahead { - return optional - } - optional.Start = required.Start - if optional.Length() <= maxReadahead { - return optional - } - optional.End = optional.Start + maxReadahead - return optional -} - -// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable. -func (c *CachingInodeOperations) InvalidateUnsavable(ctx context.Context) error { - // Whether we have a host fd (and consequently what memmap.File is - // mapped) can change across save/restore, so invalidate all translations - // unconditionally. - c.mapsMu.Lock() - defer c.mapsMu.Unlock() - c.mappings.InvalidateAll(memmap.InvalidateOpts{}) - - // Sync the cache's contents so that if we have a host fd after restore, - // the remote file's contents are coherent. - mf := c.mfp.MemoryFile() - c.dataMu.Lock() - defer c.dataMu.Unlock() - if err := SyncDirtyAll(ctx, &c.cache, &c.dirty, uint64(c.attr.Size), mf, c.backingFile.WriteFromBlocksAt); err != nil { - return err - } - - // Discard the cache so that it's not stored in saved state. This is safe - // because per InvalidateUnsavable invariants, no new translations can have - // been returned after we invalidated all existing translations above. - c.cache.DropAll(mf) - c.dirty.RemoveAll() - - return nil -} - -// NotifyChangeFD must be called after the file description represented by -// CachedFileObject.FD() changes. -func (c *CachingInodeOperations) NotifyChangeFD() error { - // Update existing sentry mappings to refer to the new file description. - if err := c.hostFileMapper.RegenerateMappings(c.backingFile.FD()); err != nil { - return err - } - - // Shoot down existing application mappings of the old file description; - // they will be remapped with the new file description on demand. - c.mapsMu.Lock() - defer c.mapsMu.Unlock() - - c.mappings.InvalidateAll(memmap.InvalidateOpts{}) - return nil -} - -// Evict implements pgalloc.EvictableMemoryUser.Evict. -func (c *CachingInodeOperations) Evict(ctx context.Context, er pgalloc.EvictableRange) { - c.mapsMu.Lock() - defer c.mapsMu.Unlock() - c.dataMu.Lock() - defer c.dataMu.Unlock() - - mr := memmap.MappableRange{er.Start, er.End} - mf := c.mfp.MemoryFile() - // Only allow pages that are no longer memory-mapped to be evicted. - for mgap := c.mappings.LowerBoundGap(mr.Start); mgap.Ok() && mgap.Start() < mr.End; mgap = mgap.NextGap() { - mgapMR := mgap.Range().Intersect(mr) - if mgapMR.Length() == 0 { - continue - } - if err := SyncDirty(ctx, mgapMR, &c.cache, &c.dirty, uint64(c.attr.Size), mf, c.backingFile.WriteFromBlocksAt); err != nil { - log.Warningf("Failed to writeback cached data %v: %v", mgapMR, err) - } - c.cache.Drop(mgapMR, mf) - c.dirty.KeepClean(mgapMR) - } -} - -// IncRef implements memmap.File.IncRef. This is used when we directly map an -// underlying host fd and CachingInodeOperations is used as the memmap.File -// during translation. -func (c *CachingInodeOperations) IncRef(fr memmap.FileRange) { - // Hot path. Avoid defers. - c.dataMu.Lock() - seg, gap := c.refs.Find(fr.Start) - for { - switch { - case seg.Ok() && seg.Start() < fr.End: - seg = c.refs.Isolate(seg, fr) - seg.SetValue(seg.Value() + 1) - seg, gap = seg.NextNonEmpty() - case gap.Ok() && gap.Start() < fr.End: - newRange := gap.Range().Intersect(fr) - usage.MemoryAccounting.Inc(newRange.Length(), usage.Mapped) - seg, gap = c.refs.InsertWithoutMerging(gap, newRange, 1).NextNonEmpty() - default: - c.refs.MergeAdjacent(fr) - c.dataMu.Unlock() - return - } - } -} - -// DecRef implements memmap.File.DecRef. This is used when we directly map an -// underlying host fd and CachingInodeOperations is used as the memmap.File -// during translation. -func (c *CachingInodeOperations) DecRef(fr memmap.FileRange) { - // Hot path. Avoid defers. - c.dataMu.Lock() - seg := c.refs.FindSegment(fr.Start) - - for seg.Ok() && seg.Start() < fr.End { - seg = c.refs.Isolate(seg, fr) - if old := seg.Value(); old == 1 { - usage.MemoryAccounting.Dec(seg.Range().Length(), usage.Mapped) - seg = c.refs.Remove(seg).NextSegment() - } else { - seg.SetValue(old - 1) - seg = seg.NextSegment() - } - } - c.refs.MergeAdjacent(fr) - c.dataMu.Unlock() -} - -// MapInternal implements memmap.File.MapInternal. This is used when we -// directly map an underlying host fd and CachingInodeOperations is used as the -// memmap.File during translation. -func (c *CachingInodeOperations) MapInternal(fr memmap.FileRange, at hostarch.AccessType) (safemem.BlockSeq, error) { - return c.hostFileMapper.MapInternal(fr, c.backingFile.FD(), at.Write) -} - -// FD implements memmap.File.FD. This is used when we directly map an -// underlying host fd and CachingInodeOperations is used as the memmap.File -// during translation. -func (c *CachingInodeOperations) FD() int { - return c.backingFile.FD() -} diff --git a/pkg/sentry/fs/fsutil/inode_cached_test.go b/pkg/sentry/fs/fsutil/inode_cached_test.go deleted file mode 100644 index 25e76d9f2..000000000 --- a/pkg/sentry/fs/fsutil/inode_cached_test.go +++ /dev/null @@ -1,390 +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 fsutil - -import ( - "bytes" - "io" - "testing" - - "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/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/usermem" -) - -type noopBackingFile struct{} - -func (noopBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) { - return dsts.NumBytes(), nil -} - -func (noopBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) { - return srcs.NumBytes(), nil -} - -func (noopBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr, bool) error { - return nil -} - -func (noopBackingFile) Sync(context.Context) error { - return nil -} - -func (noopBackingFile) FD() int { - return -1 -} - -func (noopBackingFile) Allocate(ctx context.Context, offset int64, length int64) error { - return nil -} - -func TestSetPermissions(t *testing.T) { - ctx := contexttest.Context(t) - - uattr := fs.WithCurrentTime(ctx, fs.UnstableAttr{ - Perms: fs.FilePermsFromMode(0444), - }) - iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{}) - defer iops.Release() - - perms := fs.FilePermsFromMode(0777) - if !iops.SetPermissions(ctx, nil, perms) { - t.Fatalf("SetPermissions failed, want success") - } - - // Did permissions change? - if iops.attr.Perms != perms { - t.Fatalf("got perms +%v, want +%v", iops.attr.Perms, perms) - } - - // Did status change time change? - if !iops.dirtyAttr.StatusChangeTime { - t.Fatalf("got status change time not dirty, want dirty") - } - if iops.attr.StatusChangeTime.Equal(uattr.StatusChangeTime) { - t.Fatalf("got status change time unchanged") - } -} - -func TestSetTimestamps(t *testing.T) { - ctx := contexttest.Context(t) - for _, test := range []struct { - desc string - ts fs.TimeSpec - wantChanged fs.AttrMask - }{ - { - desc: "noop", - ts: fs.TimeSpec{ - ATimeOmit: true, - MTimeOmit: true, - }, - wantChanged: fs.AttrMask{}, - }, - { - desc: "access time only", - ts: fs.TimeSpec{ - ATime: ktime.NowFromContext(ctx), - MTimeOmit: true, - }, - wantChanged: fs.AttrMask{ - AccessTime: true, - }, - }, - { - desc: "modification time only", - ts: fs.TimeSpec{ - ATimeOmit: true, - MTime: ktime.NowFromContext(ctx), - }, - wantChanged: fs.AttrMask{ - ModificationTime: true, - }, - }, - { - desc: "access and modification time", - ts: fs.TimeSpec{ - ATime: ktime.NowFromContext(ctx), - MTime: ktime.NowFromContext(ctx), - }, - wantChanged: fs.AttrMask{ - AccessTime: true, - ModificationTime: true, - }, - }, - { - desc: "system time access and modification time", - ts: fs.TimeSpec{ - ATimeSetSystemTime: true, - MTimeSetSystemTime: true, - }, - wantChanged: fs.AttrMask{ - AccessTime: true, - ModificationTime: true, - }, - }, - } { - t.Run(test.desc, func(t *testing.T) { - ctx := contexttest.Context(t) - - epoch := ktime.ZeroTime - uattr := fs.UnstableAttr{ - AccessTime: epoch, - ModificationTime: epoch, - StatusChangeTime: epoch, - } - iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{}) - defer iops.Release() - - if err := iops.SetTimestamps(ctx, nil, test.ts); err != nil { - t.Fatalf("SetTimestamps got error %v, want nil", err) - } - if test.wantChanged.AccessTime { - if !iops.attr.AccessTime.After(uattr.AccessTime) { - t.Fatalf("diritied access time did not advance, want %v > %v", iops.attr.AccessTime, uattr.AccessTime) - } - if !iops.dirtyAttr.StatusChangeTime { - t.Fatalf("dirty access time requires dirty status change time") - } - if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) { - t.Fatalf("dirtied status change time did not advance") - } - } - if test.wantChanged.ModificationTime { - if !iops.attr.ModificationTime.After(uattr.ModificationTime) { - t.Fatalf("diritied modification time did not advance") - } - if !iops.dirtyAttr.StatusChangeTime { - t.Fatalf("dirty modification time requires dirty status change time") - } - if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) { - t.Fatalf("dirtied status change time did not advance") - } - } - }) - } -} - -func TestTruncate(t *testing.T) { - ctx := contexttest.Context(t) - - uattr := fs.UnstableAttr{ - Size: 0, - } - iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{}) - defer iops.Release() - - if err := iops.Truncate(ctx, nil, uattr.Size); err != nil { - t.Fatalf("Truncate got error %v, want nil", err) - } - var size int64 = 4096 - if err := iops.Truncate(ctx, nil, size); err != nil { - t.Fatalf("Truncate got error %v, want nil", err) - } - if iops.attr.Size != size { - t.Fatalf("Truncate got %d, want %d", iops.attr.Size, size) - } - if !iops.dirtyAttr.ModificationTime || !iops.dirtyAttr.StatusChangeTime { - t.Fatalf("Truncate did not dirty modification and status change time") - } - if !iops.attr.ModificationTime.After(uattr.ModificationTime) { - t.Fatalf("dirtied modification time did not change") - } - if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) { - t.Fatalf("dirtied status change time did not change") - } -} - -type sliceBackingFile struct { - data []byte -} - -func newSliceBackingFile(data []byte) *sliceBackingFile { - return &sliceBackingFile{data} -} - -func (f *sliceBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) { - r := safemem.BlockSeqReader{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)} - return r.ReadToBlocks(dsts) -} - -func (f *sliceBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) { - w := safemem.BlockSeqWriter{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)} - return w.WriteFromBlocks(srcs) -} - -func (*sliceBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr, bool) error { - return nil -} - -func (*sliceBackingFile) Sync(context.Context) error { - return nil -} - -func (*sliceBackingFile) FD() int { - return -1 -} - -func (f *sliceBackingFile) Allocate(ctx context.Context, offset int64, length int64) error { - return linuxerr.EOPNOTSUPP -} - -type noopMappingSpace struct{} - -// Invalidate implements memmap.MappingSpace.Invalidate. -func (noopMappingSpace) Invalidate(ar hostarch.AddrRange, opts memmap.InvalidateOpts) { -} - -func anonInode(ctx context.Context) *fs.Inode { - return fs.NewInode(ctx, &SimpleFileInode{ - InodeSimpleAttributes: NewInodeSimpleAttributes(ctx, fs.FileOwnerFromContext(ctx), fs.FilePermissions{ - User: fs.PermMask{Read: true, Write: true}, - }, 0), - }, fs.NewPseudoMountSource(ctx), fs.StableAttr{ - Type: fs.Anonymous, - BlockSize: hostarch.PageSize, - }) -} - -func pagesOf(bs ...byte) []byte { - buf := make([]byte, 0, len(bs)*hostarch.PageSize) - for _, b := range bs { - buf = append(buf, bytes.Repeat([]byte{b}, hostarch.PageSize)...) - } - return buf -} - -func TestRead(t *testing.T) { - ctx := contexttest.Context(t) - - // Construct a 3-page file. - buf := pagesOf('a', 'b', 'c') - file := fs.NewFile(ctx, fs.NewDirent(ctx, anonInode(ctx), "anon"), fs.FileFlags{}, nil) - uattr := fs.UnstableAttr{ - Size: int64(len(buf)), - } - iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{}) - defer iops.Release() - - // Expect the cache to be initially empty. - if cached := iops.cache.Span(); cached != 0 { - t.Errorf("Span got %d, want 0", cached) - } - - // Create a memory mapping of the second page (as CachingInodeOperations - // expects to only cache mapped pages), then call Translate to force it to - // be cached. - var ms noopMappingSpace - ar := hostarch.AddrRange{hostarch.PageSize, 2 * hostarch.PageSize} - if err := iops.AddMapping(ctx, ms, ar, hostarch.PageSize, true); err != nil { - t.Fatalf("AddMapping got %v, want nil", err) - } - mr := memmap.MappableRange{hostarch.PageSize, 2 * hostarch.PageSize} - if _, err := iops.Translate(ctx, mr, mr, hostarch.Read); err != nil { - t.Fatalf("Translate got %v, want nil", err) - } - if cached := iops.cache.Span(); cached != hostarch.PageSize { - t.Errorf("SpanRange got %d, want %d", cached, hostarch.PageSize) - } - - // Try to read 4 pages. The first and third pages should be read directly - // from the "file", the second page should be read from the cache, and only - // 3 pages (the size of the file) should be readable. - rbuf := make([]byte, 4*hostarch.PageSize) - dst := usermem.BytesIOSequence(rbuf) - n, err := iops.Read(ctx, file, dst, 0) - if n != 3*hostarch.PageSize || (err != nil && err != io.EOF) { - t.Fatalf("Read got (%d, %v), want (%d, nil or EOF)", n, err, 3*hostarch.PageSize) - } - rbuf = rbuf[:3*hostarch.PageSize] - - // Did we get the bytes we expect? - if !bytes.Equal(rbuf, buf) { - t.Errorf("Read back bytes %v, want %v", rbuf, buf) - } - - // Delete the memory mapping before iops.Release(). The cached page will - // either be evicted by ctx's pgalloc.MemoryFile, or dropped by - // iops.Release(). - iops.RemoveMapping(ctx, ms, ar, hostarch.PageSize, true) -} - -func TestWrite(t *testing.T) { - ctx := contexttest.Context(t) - - // Construct a 4-page file. - buf := pagesOf('a', 'b', 'c', 'd') - orig := append([]byte(nil), buf...) - inode := anonInode(ctx) - uattr := fs.UnstableAttr{ - Size: int64(len(buf)), - } - iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{}) - defer iops.Release() - - // Expect the cache to be initially empty. - if cached := iops.cache.Span(); cached != 0 { - t.Errorf("Span got %d, want 0", cached) - } - - // Create a memory mapping of the second and third pages (as - // CachingInodeOperations expects to only cache mapped pages), then call - // Translate to force them to be cached. - var ms noopMappingSpace - ar := hostarch.AddrRange{hostarch.PageSize, 3 * hostarch.PageSize} - if err := iops.AddMapping(ctx, ms, ar, hostarch.PageSize, true); err != nil { - t.Fatalf("AddMapping got %v, want nil", err) - } - defer iops.RemoveMapping(ctx, ms, ar, hostarch.PageSize, true) - mr := memmap.MappableRange{hostarch.PageSize, 3 * hostarch.PageSize} - if _, err := iops.Translate(ctx, mr, mr, hostarch.Read); err != nil { - t.Fatalf("Translate got %v, want nil", err) - } - if cached := iops.cache.Span(); cached != 2*hostarch.PageSize { - t.Errorf("SpanRange got %d, want %d", cached, 2*hostarch.PageSize) - } - - // Write to the first 2 pages. - wbuf := pagesOf('e', 'f') - src := usermem.BytesIOSequence(wbuf) - n, err := iops.Write(ctx, src, 0) - if n != 2*hostarch.PageSize || err != nil { - t.Fatalf("Write got (%d, %v), want (%d, nil)", n, err, 2*hostarch.PageSize) - } - - // The first page should have been written directly, since it was not cached. - want := append([]byte(nil), orig...) - copy(want, pagesOf('e')) - if !bytes.Equal(buf, want) { - t.Errorf("File contents are %v, want %v", buf, want) - } - - // Sync back to the "backing file". - if err := iops.WriteOut(ctx, inode); err != nil { - t.Errorf("Sync got %v, want nil", err) - } - - // Now the second page should have been written as well. - copy(want[hostarch.PageSize:], pagesOf('f')) - if !bytes.Equal(buf, want) { - t.Errorf("File contents are %v, want %v", buf, want) - } -} diff --git a/pkg/sentry/fs/inode.go b/pkg/sentry/fs/inode.go deleted file mode 100644 index 61cbfadd7..000000000 --- a/pkg/sentry/fs/inode.go +++ /dev/null @@ -1,475 +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 ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/refs" - "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" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sync" -) - -// Inode is a file system object that can be simultaneously referenced by different -// components of the VFS (Dirent, fs.File, etc). -// -// +stateify savable -type Inode struct { - // AtomicRefCount is our reference count. - refs.AtomicRefCount - - // InodeOperations is the file system specific behavior of the Inode. - InodeOperations InodeOperations - - // StableAttr are stable cached attributes of the Inode. - StableAttr StableAttr - - // LockCtx is the file lock context. It manages its own sychronization and tracks - // regions of the Inode that have locks held. - LockCtx LockCtx - - // Watches is the set of inotify watches for this inode. - Watches *Watches - - // MountSource is the mount source this Inode is a part of. - MountSource *MountSource - - // overlay is the overlay entry for this Inode. - overlay *overlayEntry - - // appendMu is used to synchronize write operations into files which - // have been opened with O_APPEND. Operations which change a file size - // have to take this lock for read. Write operations to files with - // O_APPEND have to take this lock for write. - appendMu sync.RWMutex `state:"nosave"` -} - -// LockCtx is an Inode's lock context and contains different personalities of locks; both -// Posix and BSD style locks are supported. -// -// Note that in Linux fcntl(2) and flock(2) locks are _not_ cooperative, because race and -// deadlock conditions make merging them prohibitive. We do the same and keep them oblivious -// to each other but provide a "context" as a convenient container. -// -// +stateify savable -type LockCtx struct { - // Posix is a set of POSIX-style regional advisory locks, see fcntl(2). - Posix lock.Locks - - // BSD is a set of BSD-style advisory file wide locks, see flock(2). - BSD lock.Locks -} - -// NewInode constructs an Inode from InodeOperations, a MountSource, and stable attributes. -// -// NewInode takes a reference on msrc. -func NewInode(ctx context.Context, iops InodeOperations, msrc *MountSource, sattr StableAttr) *Inode { - msrc.IncRef() - i := Inode{ - InodeOperations: iops, - StableAttr: sattr, - Watches: newWatches(), - MountSource: msrc, - } - i.EnableLeakCheck("fs.Inode") - return &i -} - -// DecRef drops a reference on the Inode. -func (i *Inode) DecRef(ctx context.Context) { - i.DecRefWithDestructor(ctx, i.destroy) -} - -// destroy releases the Inode and releases the msrc reference taken. -func (i *Inode) destroy(ctx context.Context) { - if err := i.WriteOut(ctx); err != nil { - // FIXME(b/65209558): Mark as warning again once noatime is - // properly supported. - log.Debugf("Inode %+v, failed to sync all metadata: %v", i.StableAttr, err) - } - - // If this inode is being destroyed because it was unlinked, queue a - // deletion event. This may not be the case for inodes being revalidated. - if i.Watches.unlinked { - i.Watches.Notify("", linux.IN_DELETE_SELF, 0) - } - - // Remove references from the watch owners to the watches on this inode, - // since the watches are about to be GCed. Note that we don't need to worry - // about the watch pins since if there were any active pins, this inode - // wouldn't be in the destructor. - i.Watches.targetDestroyed() - - if i.overlay != nil { - i.overlay.release(ctx) - } else { - i.InodeOperations.Release(ctx) - } - - i.MountSource.DecRef(ctx) -} - -// Mappable calls i.InodeOperations.Mappable. -func (i *Inode) Mappable() memmap.Mappable { - if i.overlay != nil { - // In an overlay, Mappable is always implemented by - // the overlayEntry metadata to synchronize memory - // access of files with copy up. But first check if - // the Inodes involved would be mappable in the first - // place. - i.overlay.copyMu.RLock() - ok := i.overlay.isMappableLocked() - i.overlay.copyMu.RUnlock() - if !ok { - return nil - } - return i.overlay - } - return i.InodeOperations.Mappable(i) -} - -// WriteOut calls i.InodeOperations.WriteOut with i as the Inode. -func (i *Inode) WriteOut(ctx context.Context) error { - if i.overlay != nil { - return overlayWriteOut(ctx, i.overlay) - } - return i.InodeOperations.WriteOut(ctx, i) -} - -// Lookup calls i.InodeOperations.Lookup with i as the directory. -func (i *Inode) Lookup(ctx context.Context, name string) (*Dirent, error) { - if i.overlay != nil { - d, _, err := overlayLookup(ctx, i.overlay, i, name) - return d, err - } - return i.InodeOperations.Lookup(ctx, i, name) -} - -// Create calls i.InodeOperations.Create with i as the directory. -func (i *Inode) Create(ctx context.Context, d *Dirent, name string, flags FileFlags, perm FilePermissions) (*File, error) { - if i.overlay != nil { - return overlayCreate(ctx, i.overlay, d, name, flags, perm) - } - return i.InodeOperations.Create(ctx, i, name, flags, perm) -} - -// CreateDirectory calls i.InodeOperations.CreateDirectory with i as the directory. -func (i *Inode) CreateDirectory(ctx context.Context, d *Dirent, name string, perm FilePermissions) error { - if i.overlay != nil { - return overlayCreateDirectory(ctx, i.overlay, d, name, perm) - } - return i.InodeOperations.CreateDirectory(ctx, i, name, perm) -} - -// CreateLink calls i.InodeOperations.CreateLink with i as the directory. -func (i *Inode) CreateLink(ctx context.Context, d *Dirent, oldname string, newname string) error { - if i.overlay != nil { - return overlayCreateLink(ctx, i.overlay, d, oldname, newname) - } - return i.InodeOperations.CreateLink(ctx, i, oldname, newname) -} - -// CreateHardLink calls i.InodeOperations.CreateHardLink with i as the directory. -func (i *Inode) CreateHardLink(ctx context.Context, d *Dirent, target *Dirent, name string) error { - if i.overlay != nil { - return overlayCreateHardLink(ctx, i.overlay, d, target, name) - } - return i.InodeOperations.CreateHardLink(ctx, i, target.Inode, name) -} - -// CreateFifo calls i.InodeOperations.CreateFifo with i as the directory. -func (i *Inode) CreateFifo(ctx context.Context, d *Dirent, name string, perm FilePermissions) error { - if i.overlay != nil { - return overlayCreateFifo(ctx, i.overlay, d, name, perm) - } - return i.InodeOperations.CreateFifo(ctx, i, name, perm) -} - -// Remove calls i.InodeOperations.Remove/RemoveDirectory with i as the directory. -func (i *Inode) Remove(ctx context.Context, d *Dirent, remove *Dirent) error { - if i.overlay != nil { - return overlayRemove(ctx, i.overlay, d, remove) - } - switch remove.Inode.StableAttr.Type { - case Directory, SpecialDirectory: - return i.InodeOperations.RemoveDirectory(ctx, i, remove.name) - default: - return i.InodeOperations.Remove(ctx, i, remove.name) - } -} - -// Rename calls i.InodeOperations.Rename with the given arguments. -func (i *Inode) Rename(ctx context.Context, oldParent *Dirent, renamed *Dirent, newParent *Dirent, newName string, replacement bool) error { - if i.overlay != nil { - return overlayRename(ctx, i.overlay, oldParent, renamed, newParent, newName, replacement) - } - return i.InodeOperations.Rename(ctx, renamed.Inode, oldParent.Inode, renamed.name, newParent.Inode, newName, replacement) -} - -// Bind calls i.InodeOperations.Bind with i as the directory. -func (i *Inode) Bind(ctx context.Context, parent *Dirent, name string, data transport.BoundEndpoint, perm FilePermissions) (*Dirent, error) { - if i.overlay != nil { - return overlayBind(ctx, i.overlay, parent, name, data, perm) - } - return i.InodeOperations.Bind(ctx, i, name, data, perm) -} - -// BoundEndpoint calls i.InodeOperations.BoundEndpoint with i as the Inode. -func (i *Inode) BoundEndpoint(path string) transport.BoundEndpoint { - if i.overlay != nil { - return overlayBoundEndpoint(i.overlay, path) - } - return i.InodeOperations.BoundEndpoint(i, path) -} - -// GetFile calls i.InodeOperations.GetFile with the given arguments. -func (i *Inode) GetFile(ctx context.Context, d *Dirent, flags FileFlags) (*File, error) { - if i.overlay != nil { - return overlayGetFile(ctx, i.overlay, d, flags) - } - fsmetric.Opens.Increment() - return i.InodeOperations.GetFile(ctx, d, flags) -} - -// UnstableAttr calls i.InodeOperations.UnstableAttr with i as the Inode. -func (i *Inode) UnstableAttr(ctx context.Context) (UnstableAttr, error) { - if i.overlay != nil { - return overlayUnstableAttr(ctx, i.overlay) - } - return i.InodeOperations.UnstableAttr(ctx, i) -} - -// GetXattr calls i.InodeOperations.GetXattr with i as the Inode. -func (i *Inode) GetXattr(ctx context.Context, name string, size uint64) (string, error) { - if i.overlay != nil { - return overlayGetXattr(ctx, i.overlay, name, size) - } - return i.InodeOperations.GetXattr(ctx, i, name, size) -} - -// SetXattr calls i.InodeOperations.SetXattr with i as the Inode. -func (i *Inode) SetXattr(ctx context.Context, d *Dirent, name, value string, flags uint32) error { - if i.overlay != nil { - return overlaySetXattr(ctx, i.overlay, d, name, value, flags) - } - return i.InodeOperations.SetXattr(ctx, i, name, value, flags) -} - -// ListXattr calls i.InodeOperations.ListXattr with i as the Inode. -func (i *Inode) ListXattr(ctx context.Context, size uint64) (map[string]struct{}, error) { - if i.overlay != nil { - return overlayListXattr(ctx, i.overlay, size) - } - return i.InodeOperations.ListXattr(ctx, i, size) -} - -// RemoveXattr calls i.InodeOperations.RemoveXattr with i as the Inode. -func (i *Inode) RemoveXattr(ctx context.Context, d *Dirent, name string) error { - if i.overlay != nil { - return overlayRemoveXattr(ctx, i.overlay, d, name) - } - return i.InodeOperations.RemoveXattr(ctx, i, name) -} - -// CheckPermission will check if the caller may access this file in the -// requested way for reading, writing, or executing. -// -// CheckPermission is like Linux's fs/namei.c:inode_permission. It -// - checks file system mount flags, -// - and utilizes InodeOperations.Check to check capabilities and modes. -func (i *Inode) CheckPermission(ctx context.Context, p PermMask) error { - // First check the outer-most mounted filesystem. - if p.Write && i.MountSource.Flags.ReadOnly { - return linuxerr.EROFS - } - - if i.overlay != nil { - // CheckPermission requires some special handling for - // an overlay. - // - // Writes will always be redirected to an upper filesystem, - // so ignore all lower layers being read-only. - // - // But still honor the upper-most filesystem's mount flags; - // we should not attempt to modify the writable layer if it - // is mounted read-only. - if p.Write && overlayUpperMountSource(i.MountSource).Flags.ReadOnly { - return linuxerr.EROFS - } - } - - return i.check(ctx, p) -} - -func (i *Inode) check(ctx context.Context, p PermMask) error { - if i.overlay != nil { - return overlayCheck(ctx, i.overlay, p) - } - if !i.InodeOperations.Check(ctx, i, p) { - return linuxerr.EACCES - } - return nil -} - -// SetPermissions calls i.InodeOperations.SetPermissions with i as the Inode. -func (i *Inode) SetPermissions(ctx context.Context, d *Dirent, f FilePermissions) bool { - if i.overlay != nil { - return overlaySetPermissions(ctx, i.overlay, d, f) - } - return i.InodeOperations.SetPermissions(ctx, i, f) -} - -// SetOwner calls i.InodeOperations.SetOwner with i as the Inode. -func (i *Inode) SetOwner(ctx context.Context, d *Dirent, o FileOwner) error { - if i.overlay != nil { - return overlaySetOwner(ctx, i.overlay, d, o) - } - return i.InodeOperations.SetOwner(ctx, i, o) -} - -// SetTimestamps calls i.InodeOperations.SetTimestamps with i as the Inode. -func (i *Inode) SetTimestamps(ctx context.Context, d *Dirent, ts TimeSpec) error { - if i.overlay != nil { - return overlaySetTimestamps(ctx, i.overlay, d, ts) - } - return i.InodeOperations.SetTimestamps(ctx, i, ts) -} - -// Truncate calls i.InodeOperations.Truncate with i as the Inode. -func (i *Inode) Truncate(ctx context.Context, d *Dirent, size int64) error { - if IsDir(i.StableAttr) { - return linuxerr.EISDIR - } - - if i.overlay != nil { - return overlayTruncate(ctx, i.overlay, d, size) - } - i.appendMu.RLock() - defer i.appendMu.RUnlock() - return i.InodeOperations.Truncate(ctx, i, size) -} - -// Allocate calls i.InodeOperations.Allocate with i as the Inode. -func (i *Inode) Allocate(ctx context.Context, d *Dirent, offset int64, length int64) error { - if i.overlay != nil { - return overlayAllocate(ctx, i.overlay, d, offset, length) - } - return i.InodeOperations.Allocate(ctx, i, offset, length) -} - -// Readlink calls i.InodeOperations.Readlnk with i as the Inode. -func (i *Inode) Readlink(ctx context.Context) (string, error) { - if i.overlay != nil { - return overlayReadlink(ctx, i.overlay) - } - return i.InodeOperations.Readlink(ctx, i) -} - -// Getlink calls i.InodeOperations.Getlink. -func (i *Inode) Getlink(ctx context.Context) (*Dirent, error) { - if i.overlay != nil { - return overlayGetlink(ctx, i.overlay) - } - return i.InodeOperations.Getlink(ctx, i) -} - -// AddLink calls i.InodeOperations.AddLink. -func (i *Inode) AddLink() { - if i.overlay != nil { - // This interface is only used by ramfs to update metadata of - // children. These filesystems should _never_ have overlay - // Inodes cached as children. So explicitly disallow this - // scenario and avoid plumbing Dirents through to do copy up. - panic("overlay Inodes cached in ramfs directories are not supported") - } - i.InodeOperations.AddLink() -} - -// DropLink calls i.InodeOperations.DropLink. -func (i *Inode) DropLink() { - if i.overlay != nil { - // Same as AddLink. - panic("overlay Inodes cached in ramfs directories are not supported") - } - i.InodeOperations.DropLink() -} - -// IsVirtual calls i.InodeOperations.IsVirtual. -func (i *Inode) IsVirtual() bool { - if i.overlay != nil { - // An overlay configuration does not support virtual files. - return false - } - return i.InodeOperations.IsVirtual() -} - -// StatFS calls i.InodeOperations.StatFS. -func (i *Inode) StatFS(ctx context.Context) (Info, error) { - if i.overlay != nil { - return overlayStatFS(ctx, i.overlay) - } - return i.InodeOperations.StatFS(ctx) -} - -// CheckOwnership checks whether `ctx` owns this Inode or may act as its owner. -// Compare Linux's fs/inode.c:inode_owner_or_capable(). -func (i *Inode) CheckOwnership(ctx context.Context) bool { - uattr, err := i.UnstableAttr(ctx) - if err != nil { - return false - } - creds := auth.CredentialsFromContext(ctx) - if uattr.Owner.UID == creds.EffectiveKUID { - return true - } - if creds.HasCapability(linux.CAP_FOWNER) && creds.UserNamespace.MapFromKUID(uattr.Owner.UID).Ok() { - return true - } - return false -} - -// CheckCapability checks whether `ctx` has capability `cp` with respect to -// operations on this Inode. -// -// Compare Linux's kernel/capability.c:capable_wrt_inode_uidgid(). -func (i *Inode) CheckCapability(ctx context.Context, cp linux.Capability) bool { - uattr, err := i.UnstableAttr(ctx) - if err != nil { - return false - } - creds := auth.CredentialsFromContext(ctx) - if !creds.UserNamespace.MapFromKUID(uattr.Owner.UID).Ok() { - return false - } - if !creds.UserNamespace.MapFromKGID(uattr.Owner.GID).Ok() { - return false - } - return creds.HasCapability(cp) -} - -func (i *Inode) lockAppendMu(appendMode bool) func() { - if appendMode { - i.appendMu.Lock() - return i.appendMu.Unlock - } - i.appendMu.RLock() - return i.appendMu.RUnlock -} diff --git a/pkg/sentry/fs/inode_inotify.go b/pkg/sentry/fs/inode_inotify.go deleted file mode 100644 index 9911a00c2..000000000 --- a/pkg/sentry/fs/inode_inotify.go +++ /dev/null @@ -1,171 +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" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sync" -) - -// Watches is the collection of inotify watches on an inode. -// -// +stateify savable -type Watches struct { - // mu protects the fields below. - mu sync.RWMutex `state:"nosave"` - - // ws is the map of active watches in this collection, keyed by the inotify - // instance id of the owner. - ws map[uint64]*Watch - - // unlinked indicates whether the target inode was ever unlinked. This is a - // hack to figure out if we should queue a IN_DELETE_SELF event when this - // watches collection is being destroyed, since otherwise we have no way of - // knowing if the target inode is going down due to a deletion or - // revalidation. - unlinked bool -} - -func newWatches() *Watches { - return &Watches{} -} - -// MarkUnlinked indicates the target for this set of watches to be unlinked. -// This has implications for the IN_EXCL_UNLINK flag. -func (w *Watches) MarkUnlinked() { - w.mu.Lock() - defer w.mu.Unlock() - w.unlinked = true -} - -// Lookup returns a matching watch with the given id. Returns nil if no such -// watch exists. Note that the result returned by this method only remains valid -// if the inotify instance owning the watch is locked, preventing modification -// of the returned watch and preventing the replacement of the watch by another -// one from the same instance (since there may be at most one watch per -// instance, per target). -func (w *Watches) Lookup(id uint64) *Watch { - w.mu.Lock() - defer w.mu.Unlock() - return w.ws[id] -} - -// Add adds watch into this set of watches. The watch being added must be unique -// - its ID() should not collide with any existing watches. -func (w *Watches) Add(watch *Watch) { - w.mu.Lock() - defer w.mu.Unlock() - - // Sanity check, the new watch shouldn't collide with an existing - // watch. Silently replacing an existing watch would result in a ref leak on - // this inode. We could handle this collision by calling Unpin() on the - // existing watch, but then we end up leaking watch descriptor ids at the - // inotify level. - if _, exists := w.ws[watch.ID()]; exists { - panic(fmt.Sprintf("Watch collision with ID %+v", watch.ID())) - } - if w.ws == nil { - w.ws = make(map[uint64]*Watch) - } - w.ws[watch.ID()] = watch -} - -// Remove removes a watch with the given id from this set of watches. The caller -// is responsible for generating any watch removal event, as appropriate. The -// provided id must match an existing watch in this collection. -func (w *Watches) Remove(id uint64) { - w.mu.Lock() - defer w.mu.Unlock() - - if w.ws == nil { - // This watch set is being destroyed. The thread executing the - // destructor is already in the process of deleting all our watches. We - // got here with no refs on the inode because we raced with the - // destructor notifying all the watch owners of the inode's destruction. - // See the comment in Watches.TargetDestroyed for why this race exists. - return - } - - watch, ok := w.ws[id] - if !ok { - // While there's technically no problem with silently ignoring a missing - // watch, this is almost certainly a bug. - panic(fmt.Sprintf("Attempt to remove a watch, but no watch found with provided id %+v.", id)) - } - delete(w.ws, watch.ID()) -} - -// Notify queues a new event with all watches in this set. -func (w *Watches) Notify(name string, events, cookie uint32) { - // N.B. We don't defer the unlocks because Notify is in the hot path of - // all IO operations, and the defer costs too much for small IO - // operations. - w.mu.RLock() - for _, watch := range w.ws { - if name != "" && w.unlinked && !watch.NotifyParentAfterUnlink() { - // IN_EXCL_UNLINK - By default, when watching events on the children - // of a directory, events are generated for children even after they - // have been unlinked from the directory. This can result in large - // numbers of uninteresting events for some applications (e.g., if - // watching /tmp, in which many applications create temporary files - // whose names are immediately unlinked). Specifying IN_EXCL_UNLINK - // changes the default behavior, so that events are not generated - // for children after they have been unlinked from the watched - // directory. -- inotify(7) - // - // We know we're dealing with events for a parent when the name - // isn't empty. - continue - } - watch.Notify(name, events, cookie) - } - w.mu.RUnlock() -} - -// Unpin unpins dirent from all watches in this set. -func (w *Watches) Unpin(ctx context.Context, d *Dirent) { - w.mu.RLock() - defer w.mu.RUnlock() - for _, watch := range w.ws { - watch.Unpin(ctx, d) - } -} - -// targetDestroyed is called by the inode destructor to notify the watch owners -// of the impending destruction of the watch target. -func (w *Watches) targetDestroyed() { - var ws map[uint64]*Watch - - // We can't hold w.mu while calling watch.TargetDestroyed to preserve lock - // ordering w.r.t to the owner inotify instances. Instead, atomically move - // the watches map into a local variable so we can iterate over it safely. - // - // Because of this however, it is possible for the watches' owners to reach - // this inode while the inode has no refs. This is still safe because the - // owners can only reach the inode until this function finishes calling - // watch.TargetDestroyed() below and the inode is guaranteed to exist in the - // meanwhile. But we still have to be very careful not to rely on inode - // state that may have been already destroyed. - w.mu.Lock() - ws = w.ws - w.ws = nil - w.mu.Unlock() - - for _, watch := range ws { - watch.TargetDestroyed() - } -} diff --git a/pkg/sentry/fs/inode_operations.go b/pkg/sentry/fs/inode_operations.go deleted file mode 100644 index 6d282e829..000000000 --- a/pkg/sentry/fs/inode_operations.go +++ /dev/null @@ -1,326 +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 ( - "errors" - - "gvisor.dev/gvisor/pkg/context" - ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" -) - -var ( - // ErrResolveViaReadlink is a special error value returned by - // InodeOperations.Getlink() to indicate that a link should be - // resolved automatically by walking to the path returned by - // InodeOperations.Readlink(). - ErrResolveViaReadlink = errors.New("link should be resolved via Readlink()") -) - -// TimeSpec contains access and modification timestamps. If either ATimeOmit or -// MTimeOmit is true, then the corresponding timestamp should not be updated. -// If either ATimeSetSystemTime or MTimeSetSystemTime are set then the -// corresponding timestamp should be ignored and the time will be set to the -// current system time. -type TimeSpec struct { - ATime ktime.Time - ATimeOmit bool - ATimeSetSystemTime bool - MTime ktime.Time - MTimeOmit bool - MTimeSetSystemTime bool -} - -// InodeOperations are operations on an Inode that diverge per file system. -// -// Objects that implement InodeOperations may cache file system "private" -// data that is useful for implementing these methods. In contrast, Inode -// contains state that is common to all Inodes; this state may be optionally -// used by InodeOperations. An object that implements InodeOperations may -// not take a reference on an Inode. -type InodeOperations interface { - // Release releases all private file system data held by this object. - // Once Release is called, this object is dead (no other methods will - // ever be called). - Release(context.Context) - - // Lookup loads an Inode at name under dir into a Dirent. The name - // is a valid component path: it contains no "/"s nor is the empty - // string. - // - // Lookup may return one of: - // - // * A nil Dirent and a non-nil error. If the reason that Lookup failed - // was because the name does not exist under Inode, then must return - // linuxerr.ENOENT. - // - // * If name does not exist under dir and the file system wishes this - // fact to be cached, a non-nil Dirent containing a nil Inode and a - // nil error. This is a negative Dirent and must have exactly one - // reference (at-construction reference). - // - // * If name does exist under this dir, a non-nil Dirent containing a - // non-nil Inode, and a nil error. File systems that take extra - // references on this Dirent should implement DirentOperations. - Lookup(ctx context.Context, dir *Inode, name string) (*Dirent, error) - - // Create creates an Inode at name under dir and returns a new File - // whose Dirent backs the new Inode. Implementations must ensure that - // name does not already exist. Create may return one of: - // - // * A nil File and a non-nil error. - // - // * A non-nil File and a nil error. File.Dirent will be a new Dirent, - // with a single reference held by File. File systems that take extra - // references on this Dirent should implement DirentOperations. - // - // The caller must ensure that this operation is permitted. - Create(ctx context.Context, dir *Inode, name string, flags FileFlags, perm FilePermissions) (*File, error) - - // CreateDirectory creates a new directory under this dir. - // CreateDirectory should otherwise do the same as Create. - // - // The caller must ensure that this operation is permitted. - CreateDirectory(ctx context.Context, dir *Inode, name string, perm FilePermissions) error - - // CreateLink creates a symbolic link under dir between newname - // and oldname. CreateLink should otherwise do the same as Create. - // - // The caller must ensure that this operation is permitted. - CreateLink(ctx context.Context, dir *Inode, oldname string, newname string) error - - // CreateHardLink creates a hard link under dir between the target - // Inode and name. - // - // The caller must ensure this operation is permitted. - CreateHardLink(ctx context.Context, dir *Inode, target *Inode, name string) error - - // CreateFifo creates a new named pipe under dir at name. - // - // The caller must ensure that this operation is permitted. - CreateFifo(ctx context.Context, dir *Inode, name string, perm FilePermissions) error - - // Remove removes the given named non-directory under dir. - // - // The caller must ensure that this operation is permitted. - Remove(ctx context.Context, dir *Inode, name string) error - - // RemoveDirectory removes the given named directory under dir. - // - // The caller must ensure that this operation is permitted. - // - // RemoveDirectory should check that the directory to be - // removed is empty. - RemoveDirectory(ctx context.Context, dir *Inode, name string) error - - // Rename atomically renames oldName under oldParent to newName under - // newParent where oldParent and newParent are directories. inode is - // the Inode of this InodeOperations. - // - // If replacement is true, then newName already exists and this call - // will replace it with oldName. - // - // Implementations are responsible for rejecting renames that replace - // non-empty directories. - Rename(ctx context.Context, inode *Inode, oldParent *Inode, oldName string, newParent *Inode, newName string, replacement bool) error - - // Bind binds a new socket under dir at the given name. - // - // The caller must ensure that this operation is permitted. - Bind(ctx context.Context, dir *Inode, name string, data transport.BoundEndpoint, perm FilePermissions) (*Dirent, error) - - // BoundEndpoint returns the socket endpoint at path stored in - // or generated by an Inode. - // - // The path is only relevant for generated endpoint because stored - // endpoints already know their path. It is ok for the endpoint to - // hold onto their path because the only way to change a bind - // address is to rebind the socket. - // - // This is valid iff the type of the Inode is a Socket, which - // generally implies that this Inode was created via CreateSocket. - // - // If there is no socket endpoint available, nil will be returned. - BoundEndpoint(inode *Inode, path string) transport.BoundEndpoint - - // GetFile returns a new open File backed by a Dirent and FileFlags. - // - // Special Inode types may block using ctx.Sleeper. RegularFiles, - // Directories, and Symlinks must not block (see doCopyUp). - // - // The returned File will uniquely back an application fd. - GetFile(ctx context.Context, d *Dirent, flags FileFlags) (*File, error) - - // UnstableAttr returns the most up-to-date "unstable" attributes of - // an Inode, where "unstable" means that they change in response to - // file system events. - UnstableAttr(ctx context.Context, inode *Inode) (UnstableAttr, error) - - // GetXattr retrieves the value of extended attribute specified by name. - // Inodes that do not support extended attributes return EOPNOTSUPP. Inodes - // that support extended attributes but don't have a value at name return - // ENODATA. - // - // If this is called through the getxattr(2) syscall, size indicates the - // size of the buffer that the application has allocated to hold the - // attribute value. If the value is larger than size, implementations may - // return ERANGE to indicate that the buffer is too small, but they are also - // free to ignore the hint entirely (i.e. the value returned may be larger - // than size). All size checking is done independently at the syscall layer. - GetXattr(ctx context.Context, inode *Inode, name string, size uint64) (string, error) - - // SetXattr sets the value of extended attribute specified by name. Inodes - // that do not support extended attributes return EOPNOTSUPP. - SetXattr(ctx context.Context, inode *Inode, name, value string, flags uint32) error - - // ListXattr returns the set of all extended attributes names that - // have values. Inodes that do not support extended attributes return - // EOPNOTSUPP. - // - // If this is called through the listxattr(2) syscall, size indicates the - // size of the buffer that the application has allocated to hold the - // attribute list. If the list would be larger than size, implementations may - // return ERANGE to indicate that the buffer is too small, but they are also - // free to ignore the hint entirely. All size checking is done independently - // at the syscall layer. - ListXattr(ctx context.Context, inode *Inode, size uint64) (map[string]struct{}, error) - - // RemoveXattr removes an extended attribute specified by name. Inodes that - // do not support extended attributes return EOPNOTSUPP. - RemoveXattr(ctx context.Context, inode *Inode, name string) error - - // Check determines whether an Inode can be accessed with the - // requested permission mask using the context (which gives access - // to Credentials and UserNamespace). - Check(ctx context.Context, inode *Inode, p PermMask) bool - - // SetPermissions sets new permissions for an Inode. Returns false - // if it was not possible to set the new permissions. - // - // The caller must ensure that this operation is permitted. - SetPermissions(ctx context.Context, inode *Inode, f FilePermissions) bool - - // SetOwner sets the ownership for this file. - // - // If either UID or GID are set to auth.NoID, its value will not be - // changed. - // - // The caller must ensure that this operation is permitted. - SetOwner(ctx context.Context, inode *Inode, owner FileOwner) error - - // SetTimestamps sets the access and modification timestamps of an - // Inode according to the access and modification times in the TimeSpec. - // - // If either ATimeOmit or MTimeOmit is set, then the corresponding - // timestamp is not updated. - // - // If either ATimeSetSystemTime or MTimeSetSystemTime is true, that - // timestamp is set to the current time instead. - // - // The caller must ensure that this operation is permitted. - SetTimestamps(ctx context.Context, inode *Inode, ts TimeSpec) error - - // Truncate changes the size of an Inode. Truncate should not check - // permissions internally, as it is used for both sys_truncate and - // sys_ftruncate. - // - // Implementations need not check that length >= 0. - Truncate(ctx context.Context, inode *Inode, size int64) error - - // Allocate allows the caller to reserve disk space for the inode. - // It's equivalent to fallocate(2) with 'mode=0'. - Allocate(ctx context.Context, inode *Inode, offset int64, length int64) error - - // WriteOut writes cached Inode state to a backing filesystem in a - // synchronous manner. - // - // File systems that do not cache metadata or data via an Inode - // implement WriteOut as a no-op. File systems that are entirely in - // memory also implement WriteOut as a no-op. Otherwise file systems - // call Inode.Sync to write back page cached data and cached metadata - // followed by syncing writeback handles. - // - // It derives from include/linux/fs.h:super_operations->write_inode. - WriteOut(ctx context.Context, inode *Inode) error - - // Readlink reads the symlink path of an Inode. - // - // Readlink is permitted to return a different path depending on ctx, - // the request originator. - // - // The caller must ensure that this operation is permitted. - // - // Readlink should check that Inode is a symlink and its content is - // at least readable. - Readlink(ctx context.Context, inode *Inode) (string, error) - - // Getlink resolves a symlink to a target *Dirent. - // - // Filesystems that can resolve the link by walking to the path returned - // by Readlink should return (nil, ErrResolveViaReadlink), which - // triggers link resolution via Realink and Lookup. - // - // Some links cannot be followed by Lookup. In this case, Getlink can - // return the Dirent of the link target. The caller holds a reference - // to the Dirent. Filesystems that return a non-nil *Dirent from Getlink - // cannot participate in an overlay because it is impossible for the - // overlay to ascertain whether or not the *Dirent should contain an - // overlayEntry. - // - // Any error returned from Getlink other than ErrResolveViaReadlink - // indicates the caller's inability to traverse this Inode as a link - // (e.g. linuxerr.ENOLINK indicates that the Inode is not a link, - // syscall.EPERM indicates that traversing the link is not allowed, etc). - Getlink(context.Context, *Inode) (*Dirent, error) - - // Mappable returns a memmap.Mappable that provides memory mappings of the - // Inode's data. Mappable may return nil if this is not supported. The - // returned Mappable must remain valid until InodeOperations.Release is - // called. - Mappable(*Inode) memmap.Mappable - - // The below methods require cleanup. - - // AddLink increments the hard link count of an Inode. - // - // Remove in favor of Inode.IncLink. - AddLink() - - // DropLink decrements the hard link count of an Inode. - // - // Remove in favor of Inode.DecLink. - DropLink() - - // NotifyStatusChange sets the status change time to the current time. - // - // Remove in favor of updating the Inode's cached status change time. - NotifyStatusChange(ctx context.Context) - - // IsVirtual indicates whether or not this corresponds to a virtual - // resource. - // - // If IsVirtual returns true, then caching will be disabled for this - // node, and fs.Dirent.Freeze() will not stop operations on the node. - // - // Remove in favor of freezing specific mounts. - IsVirtual() bool - - // StatFS returns a filesystem Info implementation or an error. If - // the filesystem does not support this operation (maybe in the future - // it will), then ENOSYS should be returned. - StatFS(context.Context) (Info, error) -} diff --git a/pkg/sentry/fs/inode_overlay.go b/pkg/sentry/fs/inode_overlay.go deleted file mode 100644 index 21ad7fa69..000000000 --- a/pkg/sentry/fs/inode_overlay.go +++ /dev/null @@ -1,749 +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" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" -) - -func overlayHasWhiteout(ctx context.Context, parent *Inode, name string) bool { - s, err := parent.GetXattr(ctx, XattrOverlayWhiteout(name), 1) - return err == nil && s == "y" -} - -func overlayCreateWhiteout(ctx context.Context, parent *Inode, name string) error { - return parent.InodeOperations.SetXattr(ctx, parent, XattrOverlayWhiteout(name), "y", 0 /* flags */) -} - -func overlayWriteOut(ctx context.Context, o *overlayEntry) error { - // Hot path. Avoid defers. - var err error - o.copyMu.RLock() - if o.upper != nil { - err = o.upper.InodeOperations.WriteOut(ctx, o.upper) - } - o.copyMu.RUnlock() - return err -} - -// overlayLookup performs a lookup in parent. -// -// If name exists, it returns true if the Dirent is in the upper, false if the -// Dirent is in the lower. -func overlayLookup(ctx context.Context, parent *overlayEntry, inode *Inode, name string) (*Dirent, bool, error) { - // Hot path. Avoid defers. - parent.copyMu.RLock() - - // Assert that there is at least one upper or lower entry. - if parent.upper == nil && parent.lower == nil { - parent.copyMu.RUnlock() - panic("invalid overlayEntry, needs at least one Inode") - } - - var upperInode *Inode - var lowerInode *Inode - - // We must remember whether the upper fs returned a negative dirent, - // because it is only safe to return one if the upper did. - var negativeUpperChild bool - - // Does the parent directory exist in the upper file system? - if parent.upper != nil { - // First check if a file object exists in the upper file system. - // A file could have been created over a whiteout, so we need to - // check if something exists in the upper file system first. - child, err := parent.upper.Lookup(ctx, name) - if err != nil && !linuxerr.Equals(linuxerr.ENOENT, err) { - // We encountered an error that an overlay cannot handle, - // we must propagate it to the caller. - parent.copyMu.RUnlock() - return nil, false, err - } - if child != nil { - if child.IsNegative() { - negativeUpperChild = true - } else { - upperInode = child.Inode - upperInode.IncRef() - } - child.DecRef(ctx) - } - - // Are we done? - if overlayHasWhiteout(ctx, parent.upper, name) { - if upperInode == nil { - parent.copyMu.RUnlock() - if negativeUpperChild { - // If the upper fs returnd a negative - // Dirent, then the upper is OK with - // that negative Dirent being cached in - // the Dirent tree, so we can return - // one from the overlay. - return NewNegativeDirent(name), false, nil - } - // Upper fs is not OK with a negative Dirent - // being cached in the Dirent tree, so don't - // return one. - return nil, false, linuxerr.ENOENT - } - entry, err := newOverlayEntry(ctx, upperInode, nil, false) - if err != nil { - // Don't leak resources. - upperInode.DecRef(ctx) - parent.copyMu.RUnlock() - return nil, false, err - } - d, err := NewDirent(ctx, newOverlayInode(ctx, entry, inode.MountSource), name), nil - parent.copyMu.RUnlock() - return d, true, err - } - } - - // Check the lower file system. We do this unconditionally (even for - // non-directories) because we may need to use stable attributes from - // the lower filesystem (e.g. device number, inode number) that were - // visible before a copy up. - if parent.lower != nil { - // Check the lower file system. - child, err := parent.lower.Lookup(ctx, name) - // Same song and dance as above. - if err != nil && !linuxerr.Equals(linuxerr.ENOENT, err) { - // Don't leak resources. - if upperInode != nil { - upperInode.DecRef(ctx) - } - parent.copyMu.RUnlock() - return nil, false, err - } - if child != nil { - if !child.IsNegative() { - if upperInode == nil { - // If nothing was in the upper, use what we found in the lower. - lowerInode = child.Inode - lowerInode.IncRef() - } else { - // If we have something from the upper, we can only use it if the types - // match. - // NOTE(b/112312863): Allow SpecialDirectories and Directories to merge. - // This is needed to allow submounts in /proc and /sys. - if upperInode.StableAttr.Type == child.Inode.StableAttr.Type || - (IsDir(upperInode.StableAttr) && IsDir(child.Inode.StableAttr)) { - lowerInode = child.Inode - lowerInode.IncRef() - } - } - } - child.DecRef(ctx) - } - } - - // Was all of this for naught? - if upperInode == nil && lowerInode == nil { - parent.copyMu.RUnlock() - // We can only return a negative dirent if the upper returned - // one as well. See comments above regarding negativeUpperChild - // for more info. - if negativeUpperChild { - return NewNegativeDirent(name), false, nil - } - return nil, false, linuxerr.ENOENT - } - - // Did we find a lower Inode? Remember this because we may decide we don't - // actually need the lower Inode (see below). - lowerExists := lowerInode != nil - - // If we found something in the upper filesystem and the lower filesystem, - // use the stable attributes from the lower filesystem. If we don't do this, - // then it may appear that the file was magically recreated across copy up. - if upperInode != nil && lowerInode != nil { - // Steal attributes. - upperInode.StableAttr = lowerInode.StableAttr - - // For non-directories, the lower filesystem resource is strictly - // unnecessary because we don't need to copy-up and we will always - // operate (e.g. read/write) on the upper Inode. - if !IsDir(upperInode.StableAttr) { - lowerInode.DecRef(ctx) - lowerInode = nil - } - } - - // Phew, finally done. - entry, err := newOverlayEntry(ctx, upperInode, lowerInode, lowerExists) - if err != nil { - // Well, not quite, we failed at the last moment, how depressing. - // Be sure not to leak resources. - if upperInode != nil { - upperInode.DecRef(ctx) - } - if lowerInode != nil { - lowerInode.DecRef(ctx) - } - parent.copyMu.RUnlock() - return nil, false, err - } - d, err := NewDirent(ctx, newOverlayInode(ctx, entry, inode.MountSource), name), nil - parent.copyMu.RUnlock() - return d, upperInode != nil, err -} - -func overlayCreate(ctx context.Context, o *overlayEntry, parent *Dirent, name string, flags FileFlags, perm FilePermissions) (*File, error) { - // Sanity check. - if parent.Inode.overlay == nil { - panic(fmt.Sprintf("overlayCreate called with non-overlay parent inode (parent InodeOperations type is %T)", parent.Inode.InodeOperations)) - } - - // Dirent.Create takes renameMu if the Inode is an overlay Inode. - if err := copyUpLockedForRename(ctx, parent); err != nil { - return nil, err - } - - upperFile, err := o.upper.InodeOperations.Create(ctx, o.upper, name, flags, perm) - if err != nil { - return nil, err - } - - // We've added to the directory so we must drop the cache. - o.markDirectoryDirty() - - // Take another reference on the upper file's inode, which will be - // owned by the overlay entry. - upperFile.Dirent.Inode.IncRef() - entry, err := newOverlayEntry(ctx, upperFile.Dirent.Inode, nil, false) - if err != nil { - werr := fmt.Errorf("newOverlayEntry failed: %v", err) - cleanupUpper(ctx, o.upper, name, werr) - return nil, err - } - - // NOTE(b/71766861): Replace the Dirent with a transient Dirent, since - // we are about to create the real Dirent: an overlay Dirent. - // - // This ensures the *fs.File returned from overlayCreate is in the same - // state as the *fs.File returned by overlayGetFile, where the upper - // file has a transient Dirent. - // - // This is necessary for Save/Restore, as otherwise the upper Dirent - // (which has no path as it is unparented and never reachable by the - // user) will clobber the real path for the underlying Inode. - upperFile.Dirent.Inode.IncRef() - upperDirent := NewTransientDirent(upperFile.Dirent.Inode) - upperFile.Dirent.DecRef(ctx) - upperFile.Dirent = upperDirent - - // Create the overlay inode and dirent. We need this to construct the - // overlay file. - overlayInode := newOverlayInode(ctx, entry, parent.Inode.MountSource) - // d will own the inode reference. - overlayDirent := NewDirent(ctx, overlayInode, name) - // The overlay file created below with NewFile will take a reference on - // the overlayDirent, and it should be the only thing holding a - // reference at the time of creation, so we must drop this reference. - defer overlayDirent.DecRef(ctx) - - // Create a new overlay file that wraps the upper file. - flags.Pread = upperFile.Flags().Pread - flags.Pwrite = upperFile.Flags().Pwrite - overlayFile := NewFile(ctx, overlayDirent, flags, &overlayFileOperations{upper: upperFile}) - - return overlayFile, nil -} - -func overlayCreateDirectory(ctx context.Context, o *overlayEntry, parent *Dirent, name string, perm FilePermissions) error { - // Dirent.CreateDirectory takes renameMu if the Inode is an overlay - // Inode. - if err := copyUpLockedForRename(ctx, parent); err != nil { - return err - } - if err := o.upper.InodeOperations.CreateDirectory(ctx, o.upper, name, perm); err != nil { - return err - } - // We've added to the directory so we must drop the cache. - o.markDirectoryDirty() - return nil -} - -func overlayCreateLink(ctx context.Context, o *overlayEntry, parent *Dirent, oldname string, newname string) error { - // Dirent.CreateLink takes renameMu if the Inode is an overlay Inode. - if err := copyUpLockedForRename(ctx, parent); err != nil { - return err - } - if err := o.upper.InodeOperations.CreateLink(ctx, o.upper, oldname, newname); err != nil { - return err - } - // We've added to the directory so we must drop the cache. - o.markDirectoryDirty() - return nil -} - -func overlayCreateHardLink(ctx context.Context, o *overlayEntry, parent *Dirent, target *Dirent, name string) error { - // Dirent.CreateHardLink takes renameMu if the Inode is an overlay - // Inode. - if err := copyUpLockedForRename(ctx, parent); err != nil { - return err - } - if err := copyUpLockedForRename(ctx, target); err != nil { - return err - } - if err := o.upper.InodeOperations.CreateHardLink(ctx, o.upper, target.Inode.overlay.upper, name); err != nil { - return err - } - // We've added to the directory so we must drop the cache. - o.markDirectoryDirty() - return nil -} - -func overlayCreateFifo(ctx context.Context, o *overlayEntry, parent *Dirent, name string, perm FilePermissions) error { - // Dirent.CreateFifo takes renameMu if the Inode is an overlay Inode. - if err := copyUpLockedForRename(ctx, parent); err != nil { - return err - } - if err := o.upper.InodeOperations.CreateFifo(ctx, o.upper, name, perm); err != nil { - return err - } - // We've added to the directory so we must drop the cache. - o.markDirectoryDirty() - return nil -} - -func overlayRemove(ctx context.Context, o *overlayEntry, parent *Dirent, child *Dirent) error { - // Dirent.Remove and Dirent.RemoveDirectory take renameMu if the Inode - // is an overlay Inode. - if err := copyUpLockedForRename(ctx, parent); err != nil { - return err - } - child.Inode.overlay.copyMu.RLock() - defer child.Inode.overlay.copyMu.RUnlock() - if child.Inode.StableAttr.Type == Directory { - // RemoveDirectory requires that the directory is empty. - ser := &CollectEntriesSerializer{} - dirCtx := &DirCtx{ - Serializer: ser, - } - if _, err := overlayIterateDirLocked(ctx, child.Inode.overlay, child, dirCtx, 0); err != nil { - return err - } - if ser.Written() != 0 { - return linuxerr.ENOTEMPTY - } - } - if child.Inode.overlay.upper != nil { - if child.Inode.StableAttr.Type == Directory { - if err := o.upper.InodeOperations.RemoveDirectory(ctx, o.upper, child.name); err != nil { - return err - } - } else { - if err := o.upper.InodeOperations.Remove(ctx, o.upper, child.name); err != nil { - return err - } - } - } - if child.Inode.overlay.lowerExists { - if err := overlayCreateWhiteout(ctx, o.upper, child.name); err != nil { - return err - } - } - // We've removed from the directory so we must drop the cache. - o.markDirectoryDirty() - return nil -} - -func overlayRename(ctx context.Context, o *overlayEntry, oldParent *Dirent, renamed *Dirent, newParent *Dirent, newName string, replacement bool) error { - // To be able to copy these up below, they have to be part of an - // overlay file system. - // - // Maybe some day we can allow the more complicated case of - // non-overlay X overlay renames, but that's not necessary right now. - if renamed.Inode.overlay == nil || newParent.Inode.overlay == nil || oldParent.Inode.overlay == nil { - return linuxerr.EXDEV - } - - if replacement { - // Check here if the file to be replaced exists and is a - // non-empty directory. If we copy up first, we may end up - // copying the directory but none of its children, so the - // directory will appear empty in the upper fs, which will then - // allow the rename to proceed when it should return ENOTEMPTY. - // - // NOTE(b/111808347): Ideally, we'd just pass in the replaced - // Dirent from Rename, but we must drop the reference on - // replaced before we make the rename call, so Rename can't - // pass the Dirent to the Inode without significantly - // complicating the API. Thus we look it up again here. - // - // For the same reason we can't use defer here. - replaced, inUpper, err := overlayLookup(ctx, newParent.Inode.overlay, newParent.Inode, newName) - // If err == ENOENT or a negative Dirent is returned, then - // newName has been removed out from under us. That's fine; - // filesystems where that can happen must handle stale - // 'replaced'. - if err != nil && !linuxerr.Equals(linuxerr.ENOENT, err) { - return err - } - if err == nil { - if !inUpper { - // newName doesn't exist in - // newParent.Inode.overlay.upper, thus from - // that Inode's perspective this won't be a - // replacing rename. - replacement = false - } - - if !replaced.IsNegative() && IsDir(replaced.Inode.StableAttr) { - children, err := readdirOne(ctx, replaced) - if err != nil { - replaced.DecRef(ctx) - return err - } - - // readdirOne ensures that "." and ".." are not - // included among the returned children, so we don't - // need to bother checking for them. - if len(children) > 0 { - replaced.DecRef(ctx) - return linuxerr.ENOTEMPTY - } - } - - replaced.DecRef(ctx) - } - } - - if err := copyUpLockedForRename(ctx, renamed); err != nil { - return err - } - if err := copyUpLockedForRename(ctx, newParent); err != nil { - return err - } - oldName := renamed.name - if err := o.upper.InodeOperations.Rename(ctx, renamed.Inode.overlay.upper, oldParent.Inode.overlay.upper, oldName, newParent.Inode.overlay.upper, newName, replacement); err != nil { - return err - } - if renamed.Inode.overlay.lowerExists { - if err := overlayCreateWhiteout(ctx, oldParent.Inode.overlay.upper, oldName); err != nil { - return err - } - } - // We've changed the directory so we must drop the cache. - oldParent.Inode.overlay.markDirectoryDirty() - return nil -} - -func overlayBind(ctx context.Context, o *overlayEntry, parent *Dirent, name string, data transport.BoundEndpoint, perm FilePermissions) (*Dirent, error) { - if err := copyUpLockedForRename(ctx, parent); err != nil { - return nil, err - } - - o.copyMu.RLock() - defer o.copyMu.RUnlock() - - d, err := o.upper.InodeOperations.Bind(ctx, o.upper, name, data, perm) - if err != nil { - return nil, err - } - - // We've added to the directory so we must drop the cache. - o.markDirectoryDirty() - - // Grab the inode and drop the dirent, we don't need it. - inode := d.Inode - inode.IncRef() - d.DecRef(ctx) - - // Create a new overlay entry and dirent for the socket. - entry, err := newOverlayEntry(ctx, inode, nil, false) - if err != nil { - inode.DecRef(ctx) - return nil, err - } - // Use the parent's MountSource, since that corresponds to the overlay, - // and not the upper filesystem. - return NewDirent(ctx, newOverlayInode(ctx, entry, parent.Inode.MountSource), name), nil -} - -func overlayBoundEndpoint(o *overlayEntry, path string) transport.BoundEndpoint { - o.copyMu.RLock() - defer o.copyMu.RUnlock() - - if o.upper != nil { - return o.upper.InodeOperations.BoundEndpoint(o.upper, path) - } - - return o.lower.BoundEndpoint(path) -} - -func overlayGetFile(ctx context.Context, o *overlayEntry, d *Dirent, flags FileFlags) (*File, error) { - // Hot path. Avoid defers. - if flags.Write { - if err := copyUp(ctx, d); err != nil { - return nil, err - } - } - - o.copyMu.RLock() - - if o.upper != nil { - upper, err := overlayFile(ctx, o.upper, flags) - if err != nil { - o.copyMu.RUnlock() - return nil, err - } - flags.Pread = upper.Flags().Pread - flags.Pwrite = upper.Flags().Pwrite - f, err := NewFile(ctx, d, flags, &overlayFileOperations{upper: upper}), nil - o.copyMu.RUnlock() - return f, err - } - - lower, err := overlayFile(ctx, o.lower, flags) - if err != nil { - o.copyMu.RUnlock() - return nil, err - } - flags.Pread = lower.Flags().Pread - flags.Pwrite = lower.Flags().Pwrite - o.copyMu.RUnlock() - return NewFile(ctx, d, flags, &overlayFileOperations{lower: lower}), nil -} - -func overlayUnstableAttr(ctx context.Context, o *overlayEntry) (UnstableAttr, error) { - // Hot path. Avoid defers. - var ( - attr UnstableAttr - err error - ) - o.copyMu.RLock() - if o.upper != nil { - attr, err = o.upper.UnstableAttr(ctx) - } else { - attr, err = o.lower.UnstableAttr(ctx) - } - o.copyMu.RUnlock() - return attr, err -} - -func overlayGetXattr(ctx context.Context, o *overlayEntry, name string, size uint64) (string, error) { - // Hot path. This is how the overlay checks for whiteout files. - // Avoid defers. - var ( - s string - err error - ) - - // Don't forward the value of the extended attribute if it would - // unexpectedly change the behavior of a wrapping overlay layer. - if isXattrOverlay(name) { - return "", linuxerr.ENODATA - } - - o.copyMu.RLock() - if o.upper != nil { - s, err = o.upper.GetXattr(ctx, name, size) - } else { - s, err = o.lower.GetXattr(ctx, name, size) - } - o.copyMu.RUnlock() - return s, err -} - -func overlaySetXattr(ctx context.Context, o *overlayEntry, d *Dirent, name, value string, flags uint32) error { - // Don't allow changes to overlay xattrs through a setxattr syscall. - if isXattrOverlay(name) { - return linuxerr.EPERM - } - - if err := copyUp(ctx, d); err != nil { - return err - } - return o.upper.SetXattr(ctx, d, name, value, flags) -} - -func overlayListXattr(ctx context.Context, o *overlayEntry, size uint64) (map[string]struct{}, error) { - o.copyMu.RLock() - defer o.copyMu.RUnlock() - var names map[string]struct{} - var err error - if o.upper != nil { - names, err = o.upper.ListXattr(ctx, size) - } else { - names, err = o.lower.ListXattr(ctx, size) - } - for name := range names { - // Same as overlayGetXattr, we shouldn't forward along - // overlay attributes. - if isXattrOverlay(name) { - delete(names, name) - } - } - return names, err -} - -func overlayRemoveXattr(ctx context.Context, o *overlayEntry, d *Dirent, name string) error { - // Don't allow changes to overlay xattrs through a removexattr syscall. - if isXattrOverlay(name) { - return linuxerr.EPERM - } - - if err := copyUp(ctx, d); err != nil { - return err - } - return o.upper.RemoveXattr(ctx, d, name) -} - -func overlayCheck(ctx context.Context, o *overlayEntry, p PermMask) error { - o.copyMu.RLock() - // Hot path. Avoid defers. - var err error - if o.upper != nil { - err = o.upper.check(ctx, p) - } else { - err = o.lower.check(ctx, p) - } - o.copyMu.RUnlock() - return err -} - -func overlaySetPermissions(ctx context.Context, o *overlayEntry, d *Dirent, f FilePermissions) bool { - if err := copyUp(ctx, d); err != nil { - return false - } - return o.upper.InodeOperations.SetPermissions(ctx, o.upper, f) -} - -func overlaySetOwner(ctx context.Context, o *overlayEntry, d *Dirent, owner FileOwner) error { - if err := copyUp(ctx, d); err != nil { - return err - } - return o.upper.InodeOperations.SetOwner(ctx, o.upper, owner) -} - -func overlaySetTimestamps(ctx context.Context, o *overlayEntry, d *Dirent, ts TimeSpec) error { - if err := copyUp(ctx, d); err != nil { - return err - } - return o.upper.InodeOperations.SetTimestamps(ctx, o.upper, ts) -} - -func overlayTruncate(ctx context.Context, o *overlayEntry, d *Dirent, size int64) error { - if err := copyUp(ctx, d); err != nil { - return err - } - return o.upper.InodeOperations.Truncate(ctx, o.upper, size) -} - -func overlayAllocate(ctx context.Context, o *overlayEntry, d *Dirent, offset, length int64) error { - if err := copyUp(ctx, d); err != nil { - return err - } - return o.upper.InodeOperations.Allocate(ctx, o.upper, offset, length) -} - -func overlayReadlink(ctx context.Context, o *overlayEntry) (string, error) { - o.copyMu.RLock() - defer o.copyMu.RUnlock() - if o.upper != nil { - return o.upper.Readlink(ctx) - } - return o.lower.Readlink(ctx) -} - -func overlayGetlink(ctx context.Context, o *overlayEntry) (*Dirent, error) { - var dirent *Dirent - var err error - - o.copyMu.RLock() - defer o.copyMu.RUnlock() - - if o.upper != nil { - dirent, err = o.upper.Getlink(ctx) - } else { - dirent, err = o.lower.Getlink(ctx) - } - if dirent != nil { - // This dirent is likely bogus (its Inode likely doesn't contain - // the right overlayEntry). So we're forced to drop it on the - // ground and claim that jumping around the filesystem like this - // is not supported. - name, _ := dirent.FullName(nil) - dirent.DecRef(ctx) - - // Claim that the path is not accessible. - err = linuxerr.EACCES - log.Warningf("Getlink not supported in overlay for %q", name) - } - return nil, err -} - -func overlayStatFS(ctx context.Context, o *overlayEntry) (Info, error) { - o.copyMu.RLock() - defer o.copyMu.RUnlock() - - var i Info - var err error - if o.upper != nil { - i, err = o.upper.StatFS(ctx) - } else { - i, err = o.lower.StatFS(ctx) - } - if err != nil { - return Info{}, err - } - - i.Type = linux.OVERLAYFS_SUPER_MAGIC - - return i, nil -} - -// NewTestOverlayDir returns an overlay Inode for tests. -// -// If `revalidate` is true, then the upper filesystem will require -// revalidation. -func NewTestOverlayDir(ctx context.Context, upper, lower *Inode, revalidate bool) *Inode { - fs := &overlayFilesystem{} - var upperMsrc *MountSource - if revalidate { - upperMsrc = NewRevalidatingMountSource(ctx, fs, MountSourceFlags{}) - } else { - upperMsrc = NewNonCachingMountSource(ctx, fs, MountSourceFlags{}) - } - msrc := NewMountSource(ctx, &overlayMountSourceOperations{ - upper: upperMsrc, - lower: NewNonCachingMountSource(ctx, fs, MountSourceFlags{}), - }, fs, MountSourceFlags{}) - overlay := &overlayEntry{ - upper: upper, - lower: lower, - } - return newOverlayInode(ctx, overlay, msrc) -} - -// TestHasUpperFS returns true if i is an overlay Inode and it has a pointer -// to an Inode on an upper filesystem. -func (i *Inode) TestHasUpperFS() bool { - return i.overlay != nil && i.overlay.upper != nil -} - -// TestHasLowerFS returns true if i is an overlay Inode and it has a pointer -// to an Inode on a lower filesystem. -func (i *Inode) TestHasLowerFS() bool { - return i.overlay != nil && i.overlay.lower != nil -} diff --git a/pkg/sentry/fs/inotify.go b/pkg/sentry/fs/inotify.go deleted file mode 100644 index e37c3ba9e..000000000 --- a/pkg/sentry/fs/inotify.go +++ /dev/null @@ -1,359 +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 ( - "io" - - "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/hostarch" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/uniqueid" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// Inotify represents an inotify instance created by inotify_init(2) or -// inotify_init1(2). Inotify implements the FileOperations interface. -// -// Lock ordering: -// -// Inotify.mu -> Inode.Watches.mu -> Watch.mu -> Inotify.evMu -// -// +stateify savable -type Inotify struct { - // Unique identifier for this inotify instance. We don't just reuse the - // inotify fd because fds can be duped. These should not be exposed to the - // user, since we may aggressively reuse an id on S/R. - id uint64 - - waiter.Queue - - // evMu *only* protects the events list. We need a separate lock because - // while queuing events, a watch needs to lock the event queue, and using mu - // for that would violate lock ordering since at that point the calling - // goroutine already holds Watch.target.Watches.mu. - evMu inotifyEventMutex `state:"nosave"` - - // A list of pending events for this inotify instance. Protected by evMu. - events eventList - - // A scratch buffer, use to serialize inotify events. Use allocate this - // ahead of time and reuse performance. Protected by evMu. - scratch []byte - - // mu protects the fields below. - mu inotifyMutex `state:"nosave"` - - // The next watch descriptor number to use for this inotify instance. Note - // that Linux starts numbering watch descriptors from 1. - nextWatch int32 - - // Map from watch descriptors to watch objects. - watches map[int32]*Watch -} - -// NewInotify constructs a new Inotify instance. -func NewInotify(ctx context.Context) *Inotify { - return &Inotify{ - id: uniqueid.GlobalFromContext(ctx), - scratch: make([]byte, inotifyEventBaseSize), - nextWatch: 1, // Linux starts numbering watch descriptors from 1. - watches: make(map[int32]*Watch), - } -} - -// Release implements FileOperations.Release. Release removes all watches and -// frees all resources for an inotify instance. -func (i *Inotify) Release(ctx context.Context) { - // We need to hold i.mu to avoid a race with concurrent calls to - // Inotify.targetDestroyed from Watches. There's no risk of Watches - // accessing this Inotify after the destructor ends, because we remove all - // references to it below. - i.mu.Lock() - defer i.mu.Unlock() - for _, w := range i.watches { - // Remove references to the watch from the watch target. We don't need - // to worry about the references from the owner instance, since we're in - // the owner's destructor. - w.target.Watches.Remove(w.ID()) - // Don't leak any references to the target, held by pins in the watch. - w.destroy(ctx) - } -} - -// Readiness implements waiter.Waitable.Readiness. -// -// Readiness indicates whether there are pending events for an inotify instance. -func (i *Inotify) Readiness(mask waiter.EventMask) waiter.EventMask { - ready := waiter.EventMask(0) - - i.evMu.Lock() - defer i.evMu.Unlock() - - if !i.events.Empty() { - ready |= waiter.ReadableEvents - } - - return mask & ready -} - -// Seek implements FileOperations.Seek. -func (*Inotify) Seek(context.Context, *File, SeekWhence, int64) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// Readdir implements FileOperatons.Readdir. -func (*Inotify) Readdir(context.Context, *File, DentrySerializer) (int64, error) { - return 0, linuxerr.ENOTDIR -} - -// Write implements FileOperations.Write. -func (*Inotify) Write(context.Context, *File, usermem.IOSequence, int64) (int64, error) { - return 0, linuxerr.EBADF -} - -// Read implements FileOperations.Read. -func (i *Inotify) Read(ctx context.Context, _ *File, dst usermem.IOSequence, _ int64) (int64, error) { - if dst.NumBytes() < inotifyEventBaseSize { - return 0, linuxerr.EINVAL - } - - i.evMu.Lock() - defer i.evMu.Unlock() - - if i.events.Empty() { - // Nothing to read yet, tell caller to block. - return 0, linuxerr.ErrWouldBlock - } - - var writeLen int64 - for it := i.events.Front(); it != nil; { - event := it - it = it.Next() - - // Does the buffer have enough remaining space to hold the event we're - // about to write out? - if dst.NumBytes() < int64(event.sizeOf()) { - if writeLen > 0 { - // Buffer wasn't big enough for all pending events, but we did - // write some events out. - return writeLen, nil - } - return 0, linuxerr.EINVAL - } - - // Linux always dequeues an available event as long as there's enough - // buffer space to copy it out, even if the copy below fails. Emulate - // this behaviour. - i.events.Remove(event) - - // Buffer has enough space, copy event to the read buffer. - n, err := event.CopyTo(ctx, i.scratch, dst) - if err != nil { - return 0, err - } - - writeLen += n - dst = dst.DropFirst64(n) - } - return writeLen, nil -} - -// WriteTo implements FileOperations.WriteTo. -func (*Inotify) WriteTo(context.Context, *File, io.Writer, int64, bool) (int64, error) { - return 0, linuxerr.ENOSYS -} - -// Fsync implements FileOperations.Fsync. -func (*Inotify) Fsync(context.Context, *File, int64, int64, SyncType) error { - return linuxerr.EINVAL -} - -// ReadFrom implements FileOperations.ReadFrom. -func (*Inotify) ReadFrom(context.Context, *File, io.Reader, int64) (int64, error) { - return 0, linuxerr.ENOSYS -} - -// Flush implements FileOperations.Flush. -func (*Inotify) Flush(context.Context, *File) error { - return nil -} - -// ConfigureMMap implements FileOperations.ConfigureMMap. -func (*Inotify) ConfigureMMap(context.Context, *File, *memmap.MMapOpts) error { - return linuxerr.ENODEV -} - -// UnstableAttr implements FileOperations.UnstableAttr. -func (i *Inotify) UnstableAttr(ctx context.Context, file *File) (UnstableAttr, error) { - return file.Dirent.Inode.UnstableAttr(ctx) -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (i *Inotify) Ioctl(ctx context.Context, _ *File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - switch args[1].Int() { - case linux.FIONREAD: - i.evMu.Lock() - defer i.evMu.Unlock() - var n uint32 - for e := i.events.Front(); e != nil; e = e.Next() { - n += uint32(e.sizeOf()) - } - var buf [4]byte - hostarch.ByteOrder.PutUint32(buf[:], n) - _, err := io.CopyOut(ctx, args[2].Pointer(), buf[:], usermem.IOOpts{}) - return 0, err - - default: - return 0, linuxerr.ENOTTY - } -} - -func (i *Inotify) queueEvent(ev *Event) { - i.evMu.Lock() - - // Check if we should coalesce the event we're about to queue with the last - // one currently in the queue. Events are coalesced if they are identical. - if last := i.events.Back(); last != nil { - if ev.equals(last) { - // "Coalesce" the two events by simply not queuing the new one. We - // don't need to raise a waiter.EventIn notification because no new - // data is available for reading. - i.evMu.Unlock() - return - } - } - - i.events.PushBack(ev) - - // Release mutex before notifying waiters because we don't control what they - // can do. - i.evMu.Unlock() - - i.Queue.Notify(waiter.ReadableEvents) -} - -// newWatchLocked creates and adds a new watch to target. -func (i *Inotify) newWatchLocked(target *Dirent, mask uint32) *Watch { - wd := i.nextWatch - i.nextWatch++ - - watch := &Watch{ - owner: i, - wd: wd, - mask: atomicbitops.FromUint32(mask), - target: target.Inode, - pins: make(map[*Dirent]bool), - } - - i.watches[wd] = watch - - // Grab an extra reference to target to prevent it from being evicted from - // memory. This ref is dropped during either watch removal, target - // destruction, or inotify instance destruction. See callers of Watch.Unpin. - watch.Pin(target) - target.Inode.Watches.Add(watch) - - return watch -} - -// targetDestroyed is called by w to notify i that w's target is gone. This -// automatically generates a watch removal event. -func (i *Inotify) targetDestroyed(w *Watch) { - i.mu.Lock() - _, found := i.watches[w.wd] - delete(i.watches, w.wd) - i.mu.Unlock() - - if found { - i.queueEvent(newEvent(w.wd, "", linux.IN_IGNORED, 0)) - } -} - -// AddWatch constructs a new inotify watch and adds it to the target dirent. It -// returns the watch descriptor returned by inotify_add_watch(2). -func (i *Inotify) AddWatch(target *Dirent, mask uint32) int32 { - // Note: Locking this inotify instance protects the result returned by - // Lookup() below. With the lock held, we know for sure the lookup result - // won't become stale because it's impossible for *this* instance to - // add/remove watches on target. - i.mu.Lock() - defer i.mu.Unlock() - - // Does the target already have a watch from this inotify instance? - if existing := target.Inode.Watches.Lookup(i.id); existing != nil { - // This may be a watch on a different dirent pointing to the - // same inode. Obtain an extra reference if necessary. - existing.Pin(target) - - newmask := mask - if mergeMask := mask&linux.IN_MASK_ADD != 0; mergeMask { - // "Add (OR) events to watch mask for this pathname if it already - // exists (instead of replacing mask)." -- inotify(7) - newmask |= existing.mask.Load() - } - existing.mask.Store(newmask) - return existing.wd - } - - // No existing watch, create a new watch. - watch := i.newWatchLocked(target, mask) - return watch.wd -} - -// RmWatch implements watcher.Watchable.RmWatch. -// -// RmWatch looks up an inotify watch for the given 'wd' and configures the -// target dirent to stop sending events to this inotify instance. -func (i *Inotify) RmWatch(ctx context.Context, wd int32) error { - i.mu.Lock() - - // Find the watch we were asked to removed. - watch, ok := i.watches[wd] - if !ok { - i.mu.Unlock() - return linuxerr.EINVAL - } - - // Remove the watch from this instance. - delete(i.watches, wd) - - // Remove the watch from the watch target. - watch.target.Watches.Remove(watch.ID()) - - // The watch is now isolated and we can safely drop the instance lock. We - // need to do so because watch.destroy() acquires Watch.mu, which cannot be - // acquired with Inotify.mu held. - i.mu.Unlock() - - // Generate the event for the removal. - i.queueEvent(newEvent(watch.wd, "", linux.IN_IGNORED, 0)) - - // Remove all pins. - watch.destroy(ctx) - - return nil -} - -// EventRegister implements waiter.Waitable. -func (i *Inotify) EventRegister(e *waiter.Entry) error { - i.Queue.EventRegister(e) - return nil -} diff --git a/pkg/sentry/fs/inotify_event.go b/pkg/sentry/fs/inotify_event.go deleted file mode 100644 index 399aff1ed..000000000 --- a/pkg/sentry/fs/inotify_event.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 fs - -import ( - "bytes" - "fmt" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/usermem" -) - -// inotifyEventBaseSize is the base size of linux's struct inotify_event. This -// must be a power 2 for rounding below. -const inotifyEventBaseSize = 16 - -// Event represents a struct inotify_event from linux. -// -// +stateify savable -type Event struct { - eventEntry - - wd int32 - mask uint32 - cookie uint32 - - // len is computed based on the name field is set automatically by - // Event.setName. It should be 0 when no name is set; otherwise it is the - // length of the name slice. - len uint32 - - // The name field has special padding requirements and should only be set by - // calling Event.setName. - name []byte -} - -func newEvent(wd int32, name string, events, cookie uint32) *Event { - e := &Event{ - wd: wd, - mask: events, - cookie: cookie, - } - if name != "" { - e.setName(name) - } - return e -} - -// paddedBytes converts a go string to a null-terminated c-string, padded with -// null bytes to a total size of 'l'. 'l' must be large enough for all the bytes -// in the 's' plus at least one null byte. -func paddedBytes(s string, l uint32) []byte { - if l < uint32(len(s)+1) { - panic("Converting string to byte array results in truncation, this can lead to buffer-overflow due to the missing null-byte!") - } - b := make([]byte, l) - copy(b, s) - - // b was zero-value initialized during make(), so the rest of the slice is - // already filled with null bytes. - - return b -} - -// setName sets the optional name for this event. -func (e *Event) setName(name string) { - // We need to pad the name such that the entire event length ends up a - // multiple of inotifyEventBaseSize. - unpaddedLen := len(name) + 1 - // Round up to nearest multiple of inotifyEventBaseSize. - e.len = uint32((unpaddedLen + inotifyEventBaseSize - 1) & ^(inotifyEventBaseSize - 1)) - // Make sure we haven't overflowed and wrapped around when rounding. - if unpaddedLen > int(e.len) { - panic("Overflow when rounding inotify event size, the 'name' field was too big.") - } - e.name = paddedBytes(name, e.len) -} - -func (e *Event) sizeOf() int { - s := inotifyEventBaseSize + int(e.len) - if s < inotifyEventBaseSize { - panic("overflow") - } - return s -} - -// CopyTo serializes this event to dst. buf is used as a scratch buffer to -// construct the output. We use a buffer allocated ahead of time for -// performance. buf must be at least inotifyEventBaseSize bytes. -func (e *Event) CopyTo(ctx context.Context, buf []byte, dst usermem.IOSequence) (int64, error) { - hostarch.ByteOrder.PutUint32(buf[0:], uint32(e.wd)) - hostarch.ByteOrder.PutUint32(buf[4:], e.mask) - hostarch.ByteOrder.PutUint32(buf[8:], e.cookie) - hostarch.ByteOrder.PutUint32(buf[12:], e.len) - - writeLen := 0 - - n, err := dst.CopyOut(ctx, buf) - if err != nil { - return 0, err - } - writeLen += n - dst = dst.DropFirst(n) - - if e.len > 0 { - n, err = dst.CopyOut(ctx, e.name) - if err != nil { - return 0, err - } - writeLen += n - } - - // Santiy check. - if writeLen != e.sizeOf() { - panic(fmt.Sprintf("Serialized unexpected amount of data for an event, expected %v, wrote %v.", e.sizeOf(), writeLen)) - } - - return int64(writeLen), nil -} - -func (e *Event) equals(other *Event) bool { - return e.wd == other.wd && - e.mask == other.mask && - e.cookie == other.cookie && - e.len == other.len && - bytes.Equal(e.name, other.name) -} diff --git a/pkg/sentry/fs/inotify_watch.go b/pkg/sentry/fs/inotify_watch.go deleted file mode 100644 index fce1f791d..000000000 --- a/pkg/sentry/fs/inotify_watch.go +++ /dev/null @@ -1,135 +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 ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sync" -) - -// Watch represent a particular inotify watch created by inotify_add_watch. -// -// While a watch is active, it ensures the target inode is pinned in memory by -// holding an extra ref on each dirent known (by inotify) to point to the -// inode. These are known as pins. For a full discussion, see -// fs/g3doc/inotify.md. -// -// +stateify savable -type Watch struct { - // Inotify instance which owns this watch. - owner *Inotify - - // Descriptor for this watch. This is unique across an inotify instance. - wd int32 - - // The inode being watched. Note that we don't directly hold a reference on - // this inode. Instead we hold a reference on the dirent(s) containing the - // inode, which we record in pins. - target *Inode - - // unpinned indicates whether we have a hard reference on target. This field - // may only be modified through atomic ops. - unpinned uint32 - - // mu protects the fields below. - mu sync.Mutex `state:"nosave"` - - // Events being monitored via this watch. Must be accessed atomically, - // writes are protected by mu. - mask atomicbitops.Uint32 - - // pins is the set of dirents this watch is currently pinning in memory by - // holding a reference to them. See Pin()/Unpin(). - pins map[*Dirent]bool -} - -// ID returns the id of the inotify instance that owns this watch. -func (w *Watch) ID() uint64 { - return w.owner.id -} - -// NotifyParentAfterUnlink indicates whether the parent of the watched object -// should continue to be be notified of events after the target has been -// unlinked. -func (w *Watch) NotifyParentAfterUnlink() bool { - return w.mask.Load()&linux.IN_EXCL_UNLINK == 0 -} - -// isRenameEvent returns true if eventMask describes a rename event. -func isRenameEvent(eventMask uint32) bool { - return eventMask&(linux.IN_MOVED_FROM|linux.IN_MOVED_TO|linux.IN_MOVE_SELF) != 0 -} - -// Notify queues a new event on this watch. -func (w *Watch) Notify(name string, events uint32, cookie uint32) { - mask := w.mask.Load() - if mask&events == 0 { - // We weren't watching for this event. - return - } - - // Event mask should include bits matched from the watch plus all control - // event bits. - unmaskableBits := ^uint32(0) &^ linux.IN_ALL_EVENTS - effectiveMask := unmaskableBits | mask - matchedEvents := effectiveMask & events - w.owner.queueEvent(newEvent(w.wd, name, matchedEvents, cookie)) -} - -// Pin acquires a new ref on dirent, which pins the dirent in memory while -// the watch is active. Calling Pin for a second time on the same dirent for -// the same watch is a no-op. -func (w *Watch) Pin(d *Dirent) { - w.mu.Lock() - defer w.mu.Unlock() - if !w.pins[d] { - w.pins[d] = true - d.IncRef() - } -} - -// Unpin drops any extra refs held on dirent due to a previous Pin -// call. Calling Unpin multiple times for the same dirent, or on a dirent -// without a corresponding Pin call is a no-op. -func (w *Watch) Unpin(ctx context.Context, d *Dirent) { - w.mu.Lock() - defer w.mu.Unlock() - if w.pins[d] { - delete(w.pins, d) - d.DecRef(ctx) - } -} - -// TargetDestroyed notifies the owner of the watch that the watch target is -// gone. The owner should release its own references to the watcher upon -// receiving this notification. -func (w *Watch) TargetDestroyed() { - w.owner.targetDestroyed(w) -} - -// destroy prepares the watch for destruction. It unpins all dirents pinned by -// this watch. Destroy does not cause any new events to be generated. The caller -// is responsible for ensuring there are no outstanding references to this -// watch. -func (w *Watch) destroy(ctx context.Context) { - w.mu.Lock() - defer w.mu.Unlock() - for d := range w.pins { - d.DecRef(ctx) - } - w.pins = nil -} diff --git a/pkg/sentry/fs/mock.go b/pkg/sentry/fs/mock.go deleted file mode 100644 index fba7b961b..000000000 --- a/pkg/sentry/fs/mock.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 fs - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" -) - -// MockInodeOperations implements InodeOperations for testing Inodes. -type MockInodeOperations struct { - InodeOperations - - UAttr UnstableAttr - - createCalled bool - createDirectoryCalled bool - createLinkCalled bool - renameCalled bool - walkCalled bool -} - -// NewMockInode returns a mock *Inode using MockInodeOperations. -func NewMockInode(ctx context.Context, msrc *MountSource, sattr StableAttr) *Inode { - return NewInode(ctx, NewMockInodeOperations(ctx), msrc, sattr) -} - -// NewMockInodeOperations returns a *MockInodeOperations. -func NewMockInodeOperations(ctx context.Context) *MockInodeOperations { - return &MockInodeOperations{ - UAttr: WithCurrentTime(ctx, UnstableAttr{ - Perms: FilePermsFromMode(0777), - }), - } -} - -// MockMountSourceOps implements fs.MountSourceOperations. -type MockMountSourceOps struct { - MountSourceOperations - keep bool - revalidate bool -} - -// NewMockMountSource returns a new *MountSource using MockMountSourceOps. -func NewMockMountSource(cache *DirentCache) *MountSource { - var keep bool - if cache != nil { - keep = cache.maxSize > 0 - } - return &MountSource{ - MountSourceOperations: &MockMountSourceOps{keep: keep}, - fscache: cache, - } -} - -// Revalidate implements fs.MountSourceOperations.Revalidate. -func (n *MockMountSourceOps) Revalidate(context.Context, string, *Inode, *Inode) bool { - return n.revalidate -} - -// Keep implements fs.MountSourceOperations.Keep. -func (n *MockMountSourceOps) Keep(dirent *Dirent) bool { - return n.keep -} - -// CacheReaddir implements fs.MountSourceOperations.CacheReaddir. -func (n *MockMountSourceOps) CacheReaddir() bool { - // Common case: cache readdir results if there is a dirent cache. - return n.keep -} - -// WriteOut implements fs.InodeOperations.WriteOut. -func (n *MockInodeOperations) WriteOut(context.Context, *Inode) error { - return nil -} - -// UnstableAttr implements fs.InodeOperations.UnstableAttr. -func (n *MockInodeOperations) UnstableAttr(context.Context, *Inode) (UnstableAttr, error) { - return n.UAttr, nil -} - -// IsVirtual implements fs.InodeOperations.IsVirtual. -func (n *MockInodeOperations) IsVirtual() bool { - return false -} - -// Lookup implements fs.InodeOperations.Lookup. -func (n *MockInodeOperations) Lookup(ctx context.Context, dir *Inode, p string) (*Dirent, error) { - n.walkCalled = true - return NewDirent(ctx, NewInode(ctx, &MockInodeOperations{}, dir.MountSource, StableAttr{}), p), nil -} - -// SetPermissions implements fs.InodeOperations.SetPermissions. -func (n *MockInodeOperations) SetPermissions(context.Context, *Inode, FilePermissions) bool { - return false -} - -// SetOwner implements fs.InodeOperations.SetOwner. -func (*MockInodeOperations) SetOwner(context.Context, *Inode, FileOwner) error { - return linuxerr.EINVAL -} - -// SetTimestamps implements fs.InodeOperations.SetTimestamps. -func (n *MockInodeOperations) SetTimestamps(context.Context, *Inode, TimeSpec) error { - return nil -} - -// Create implements fs.InodeOperations.Create. -func (n *MockInodeOperations) Create(ctx context.Context, dir *Inode, p string, flags FileFlags, perms FilePermissions) (*File, error) { - n.createCalled = true - d := NewDirent(ctx, NewInode(ctx, &MockInodeOperations{}, dir.MountSource, StableAttr{}), p) - return &File{Dirent: d}, nil -} - -// CreateLink implements fs.InodeOperations.CreateLink. -func (n *MockInodeOperations) CreateLink(_ context.Context, dir *Inode, oldname string, newname string) error { - n.createLinkCalled = true - return nil -} - -// CreateDirectory implements fs.InodeOperations.CreateDirectory. -func (n *MockInodeOperations) CreateDirectory(context.Context, *Inode, string, FilePermissions) error { - n.createDirectoryCalled = true - return nil -} - -// Rename implements fs.InodeOperations.Rename. -func (n *MockInodeOperations) Rename(ctx context.Context, inode *Inode, oldParent *Inode, oldName string, newParent *Inode, newName string, replacement bool) error { - n.renameCalled = true - return nil -} - -// Check implements fs.InodeOperations.Check. -func (n *MockInodeOperations) Check(ctx context.Context, inode *Inode, p PermMask) bool { - return ContextCanAccessFile(ctx, inode, p) -} - -// Release implements fs.InodeOperations.Release. -func (n *MockInodeOperations) Release(context.Context) {} - -// Truncate implements fs.InodeOperations.Truncate. -func (n *MockInodeOperations) Truncate(ctx context.Context, inode *Inode, size int64) error { - return nil -} - -// Allocate implements fs.InodeOperations.Allocate. -func (n *MockInodeOperations) Allocate(ctx context.Context, inode *Inode, offset, length int64) error { - return nil -} - -// Remove implements fs.InodeOperations.Remove. -func (n *MockInodeOperations) Remove(context.Context, *Inode, string) error { - return nil -} - -// RemoveDirectory implements fs.InodeOperations.RemoveDirectory. -func (n *MockInodeOperations) RemoveDirectory(context.Context, *Inode, string) error { - return nil -} - -// Getlink implements fs.InodeOperations.Getlink. -func (n *MockInodeOperations) Getlink(context.Context, *Inode) (*Dirent, error) { - return nil, linuxerr.ENOLINK -} diff --git a/pkg/sentry/fs/mount.go b/pkg/sentry/fs/mount.go deleted file mode 100644 index 0f380e4e8..000000000 --- a/pkg/sentry/fs/mount.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 fs - -import ( - "bytes" - "fmt" - - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/refs" -) - -// DirentOperations provide file systems greater control over how long a Dirent -// stays pinned in core. Implementations must not take Dirent.mu. -type DirentOperations interface { - // Revalidate is called during lookup each time we encounter a Dirent - // in the cache. Implementations may update stale properties of the - // child Inode. If Revalidate returns true, then the entire Inode will - // be reloaded. - // - // Revalidate will never be called on a Inode that is mounted. - Revalidate(ctx context.Context, name string, parent, child *Inode) bool - - // Keep returns true if the Dirent should be kept in memory for as long - // as possible beyond any active references. - Keep(dirent *Dirent) bool - - // CacheReaddir returns true if directory entries returned by - // FileOperations.Readdir may be cached for future use. - // - // Postconditions: This method must always return the same value. - CacheReaddir() bool -} - -// MountSourceOperations contains filesystem specific operations. -type MountSourceOperations interface { - // DirentOperations provide optional extra management of Dirents. - DirentOperations - - // Destroy destroys the MountSource. - Destroy(ctx context.Context) - - // Below are MountSourceOperations that do not conform to Linux. - - // ResetInodeMappings clears all mappings of Inodes before SaveInodeMapping - // is called. - ResetInodeMappings() - - // SaveInodeMappings is called during saving to store, for each reachable - // Inode in the mounted filesystem, a mapping of Inode.StableAttr.InodeID - // to the Inode's path relative to its mount point. If an Inode is - // reachable at more than one path due to hard links, it is unspecified - // which path is mapped. Filesystems that do not use this information to - // restore inodes can make SaveInodeMappings a no-op. - SaveInodeMapping(inode *Inode, path string) -} - -// InodeMappings defines a fmt.Stringer MountSource Inode mappings. -type InodeMappings map[uint64]string - -// String implements fmt.Stringer.String. -func (i InodeMappings) String() string { - var mappingsBuf bytes.Buffer - mappingsBuf.WriteString("\n") - for ino, name := range i { - mappingsBuf.WriteString(fmt.Sprintf("\t%q\t\tinode number %d\n", name, ino)) - } - return mappingsBuf.String() -} - -// MountSource represents a source of file objects. -// -// MountSource corresponds to struct super_block in Linux. -// -// A mount source may represent a physical device (or a partition of a physical -// device) or a virtual source of files such as procfs for a specific PID -// namespace. There should be only one mount source per logical device. E.g. -// there should be only procfs mount source for a given PID namespace. -// -// A mount source represents files as inodes. Every inode belongs to exactly -// one mount source. Each file object may only be represented using one inode -// object in a sentry instance. -// -// TODO(b/63601033): Move Flags out of MountSource to Mount. -// -// +stateify savable -type MountSource struct { - refs.AtomicRefCount - - // MountSourceOperations defines filesystem specific behavior. - MountSourceOperations - - // FilesystemType is the type of the filesystem backing this mount. - FilesystemType string - - // Flags are the flags that this filesystem was mounted with. - Flags MountSourceFlags - - // fscache keeps Dirents pinned beyond application references to them. - // It must be flushed before kernel.SaveTo. - fscache *DirentCache - - // direntRefs is the sum of references on all Dirents in this MountSource. - // - // direntRefs is increased when a Dirent in MountSource is IncRef'd, and - // decreased when a Dirent in MountSource is DecRef'd. - // - // To cleanly unmount a MountSource, one must check that no direntRefs are - // held anymore. To check, one must hold root.parent.dirMu of the - // MountSource's root Dirent before reading direntRefs to prevent further - // walks to Dirents in this MountSource. - // - // direntRefs must be atomically changed. - direntRefs atomicbitops.Uint64 -} - -// DefaultDirentCacheSize is the number of Dirents that the VFS can hold an -// extra reference on. -const DefaultDirentCacheSize uint64 = 1000 - -// NewMountSource returns a new MountSource. Filesystem may be nil if there is no -// filesystem backing the mount. -func NewMountSource(ctx context.Context, mops MountSourceOperations, filesystem Filesystem, flags MountSourceFlags) *MountSource { - fsType := "none" - if filesystem != nil { - fsType = filesystem.Name() - } - msrc := MountSource{ - MountSourceOperations: mops, - Flags: flags, - FilesystemType: fsType, - fscache: NewDirentCache(DefaultDirentCacheSize), - } - msrc.EnableLeakCheck("fs.MountSource") - return &msrc -} - -// DirentRefs returns the current mount direntRefs. -func (msrc *MountSource) DirentRefs() uint64 { - return msrc.direntRefs.Load() -} - -// IncDirentRefs increases direntRefs. -func (msrc *MountSource) IncDirentRefs() { - msrc.direntRefs.Add(1) -} - -// DecDirentRefs decrements direntRefs. -func (msrc *MountSource) DecDirentRefs() { - if msrc.direntRefs.Add(^uint64(0)) == ^uint64(0) { - panic("Decremented zero mount reference direntRefs") - } -} - -func (msrc *MountSource) destroy(ctx context.Context) { - if c := msrc.DirentRefs(); c != 0 { - panic(fmt.Sprintf("MountSource with non-zero direntRefs is being destroyed: %d", c)) - } - msrc.MountSourceOperations.Destroy(ctx) -} - -// DecRef drops a reference on the MountSource. -func (msrc *MountSource) DecRef(ctx context.Context) { - msrc.DecRefWithDestructor(ctx, msrc.destroy) -} - -// FlushDirentRefs drops all references held by the MountSource on Dirents. -func (msrc *MountSource) FlushDirentRefs() { - msrc.fscache.Invalidate() -} - -// SetDirentCacheMaxSize sets the max size to the dirent cache associated with -// this mount source. -func (msrc *MountSource) SetDirentCacheMaxSize(max uint64) { - msrc.fscache.setMaxSize(max) -} - -// SetDirentCacheLimiter sets the limiter objcet to the dirent cache associated -// with this mount source. -func (msrc *MountSource) SetDirentCacheLimiter(l *DirentCacheLimiter) { - msrc.fscache.limit = l -} - -// NewCachingMountSource returns a generic mount that will cache dirents -// aggressively. -func NewCachingMountSource(ctx context.Context, filesystem Filesystem, flags MountSourceFlags) *MountSource { - return NewMountSource(ctx, &SimpleMountSourceOperations{ - keep: true, - revalidate: false, - cacheReaddir: true, - }, filesystem, flags) -} - -// NewNonCachingMountSource returns a generic mount that will never cache dirents. -func NewNonCachingMountSource(ctx context.Context, filesystem Filesystem, flags MountSourceFlags) *MountSource { - return NewMountSource(ctx, &SimpleMountSourceOperations{ - keep: false, - revalidate: false, - cacheReaddir: false, - }, filesystem, flags) -} - -// NewRevalidatingMountSource returns a generic mount that will cache dirents, -// but will revalidate them on each lookup and always perform uncached readdir. -func NewRevalidatingMountSource(ctx context.Context, filesystem Filesystem, flags MountSourceFlags) *MountSource { - return NewMountSource(ctx, &SimpleMountSourceOperations{ - keep: true, - revalidate: true, - cacheReaddir: false, - }, filesystem, flags) -} - -// NewPseudoMountSource returns a "pseudo" mount source that is not backed by -// an actual filesystem. It is always non-caching. -func NewPseudoMountSource(ctx context.Context) *MountSource { - return NewMountSource(ctx, &SimpleMountSourceOperations{ - keep: false, - revalidate: false, - cacheReaddir: false, - }, nil, MountSourceFlags{}) -} - -// SimpleMountSourceOperations implements MountSourceOperations. -// -// +stateify savable -type SimpleMountSourceOperations struct { - keep bool - revalidate bool - cacheReaddir bool -} - -// Revalidate implements MountSourceOperations.Revalidate. -func (smo *SimpleMountSourceOperations) Revalidate(context.Context, string, *Inode, *Inode) bool { - return smo.revalidate -} - -// Keep implements MountSourceOperations.Keep. -func (smo *SimpleMountSourceOperations) Keep(*Dirent) bool { - return smo.keep -} - -// CacheReaddir implements MountSourceOperations.CacheReaddir. -func (smo *SimpleMountSourceOperations) CacheReaddir() bool { - return smo.cacheReaddir -} - -// ResetInodeMappings implements MountSourceOperations.ResetInodeMappings. -func (*SimpleMountSourceOperations) ResetInodeMappings() {} - -// SaveInodeMapping implements MountSourceOperations.SaveInodeMapping. -func (*SimpleMountSourceOperations) SaveInodeMapping(*Inode, string) {} - -// Destroy implements MountSourceOperations.Destroy. -func (*SimpleMountSourceOperations) Destroy(context.Context) {} - -// Info defines attributes of a filesystem. -type Info struct { - // Type is the filesystem type magic value. - Type uint64 - - // TotalBlocks is the total data blocks in the filesystem. - TotalBlocks uint64 - - // FreeBlocks is the number of free blocks available. - FreeBlocks uint64 - - // TotalFiles is the total file nodes in the filesystem. - TotalFiles uint64 - - // FreeFiles is the number of free file nodes. - FreeFiles uint64 -} diff --git a/pkg/sentry/fs/mount_overlay.go b/pkg/sentry/fs/mount_overlay.go deleted file mode 100644 index cdc0d1024..000000000 --- a/pkg/sentry/fs/mount_overlay.go +++ /dev/null @@ -1,151 +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 ( - "gvisor.dev/gvisor/pkg/context" -) - -// overlayMountSourceOperations implements MountSourceOperations for an overlay -// mount point. The upper filesystem determines the caching behavior of the -// overlay. -// -// +stateify savable -type overlayMountSourceOperations struct { - upper *MountSource - lower *MountSource -} - -func newOverlayMountSource(ctx context.Context, upper, lower *MountSource, flags MountSourceFlags) *MountSource { - upper.IncRef() - lower.IncRef() - msrc := NewMountSource(ctx, &overlayMountSourceOperations{ - upper: upper, - lower: lower, - }, &overlayFilesystem{}, flags) - - // Use the minimum number to keep resource usage under limits. - size := lower.fscache.maxSize - if size > upper.fscache.maxSize { - size = upper.fscache.maxSize - } - msrc.fscache.setMaxSize(size) - - return msrc -} - -// Revalidate implements MountSourceOperations.Revalidate for an overlay by -// delegating to the upper filesystem's Revalidate method. We cannot reload -// files from the lower filesystem, so we panic if the lower filesystem's -// Revalidate method returns true. -func (o *overlayMountSourceOperations) Revalidate(ctx context.Context, name string, parent, child *Inode) bool { - if child.overlay == nil { - panic("overlay cannot revalidate inode that is not an overlay") - } - - // Revalidate is never called on a mount point, so parent and child - // must be from the same mount, and thus must both be overlay inodes. - if parent.overlay == nil { - panic("trying to revalidate an overlay inode but the parent is not an overlay") - } - - // We can't revalidate from the lower filesystem. - if child.overlay.lower != nil && o.lower.Revalidate(ctx, name, parent.overlay.lower, child.overlay.lower) { - panic("an overlay cannot revalidate file objects from the lower fs") - } - - var revalidate bool - child.overlay.copyMu.RLock() - if child.overlay.upper != nil { - // Does the upper require revalidation? - revalidate = o.upper.Revalidate(ctx, name, parent.overlay.upper, child.overlay.upper) - } else { - // Nothing to revalidate. - revalidate = false - } - child.overlay.copyMu.RUnlock() - return revalidate -} - -// Keep implements MountSourceOperations by delegating to the upper -// filesystem's Keep method. -func (o *overlayMountSourceOperations) Keep(dirent *Dirent) bool { - return o.upper.Keep(dirent) -} - -// CacheReaddir implements MountSourceOperations.CacheReaddir for an overlay by -// performing the logical AND of the upper and lower filesystems' CacheReaddir -// methods. -// -// N.B. This is fs-global instead of inode-specific because it must always -// return the same value. If it was inode-specific, we couldn't guarantee that -// property across copy up. -func (o *overlayMountSourceOperations) CacheReaddir() bool { - return o.lower.CacheReaddir() && o.upper.CacheReaddir() -} - -// ResetInodeMappings propagates the call to both upper and lower MountSource. -func (o *overlayMountSourceOperations) ResetInodeMappings() { - o.upper.ResetInodeMappings() - o.lower.ResetInodeMappings() -} - -// SaveInodeMapping propagates the call to both upper and lower MountSource. -func (o *overlayMountSourceOperations) SaveInodeMapping(inode *Inode, path string) { - inode.overlay.copyMu.RLock() - defer inode.overlay.copyMu.RUnlock() - if inode.overlay.upper != nil { - o.upper.SaveInodeMapping(inode.overlay.upper, path) - } - if inode.overlay.lower != nil { - o.lower.SaveInodeMapping(inode.overlay.lower, path) - } -} - -// Destroy drops references on the upper and lower MountSource. -func (o *overlayMountSourceOperations) Destroy(ctx context.Context) { - o.upper.DecRef(ctx) - o.lower.DecRef(ctx) -} - -// type overlayFilesystem is the filesystem for overlay mounts. -// -// +stateify savable -type overlayFilesystem struct{} - -// Name implements Filesystem.Name. -func (ofs *overlayFilesystem) Name() string { - return "overlayfs" -} - -// Flags implements Filesystem.Flags. -func (ofs *overlayFilesystem) Flags() FilesystemFlags { - return 0 -} - -// AllowUserMount implements Filesystem.AllowUserMount. -func (ofs *overlayFilesystem) AllowUserMount() bool { - return false -} - -// AllowUserList implements Filesystem.AllowUserList. -func (*overlayFilesystem) AllowUserList() bool { - return true -} - -// Mount implements Filesystem.Mount. -func (ofs *overlayFilesystem) Mount(ctx context.Context, device string, flags MountSourceFlags, data string, _ any) (*Inode, error) { - panic("overlayFilesystem.Mount should not be called!") -} diff --git a/pkg/sentry/fs/mounts.go b/pkg/sentry/fs/mounts.go deleted file mode 100644 index f1cc6a63b..000000000 --- a/pkg/sentry/fs/mounts.go +++ /dev/null @@ -1,622 +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" - "math" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/refs" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" -) - -// DefaultTraversalLimit provides a sensible default traversal limit that may -// be passed to FindInode and FindLink. You may want to provide other options in -// individual syscall implementations, but for internal functions this will be -// sane. -const DefaultTraversalLimit = 10 - -const invalidMountID = math.MaxUint64 - -// Mount represents a mount in the file system. It holds the root dirent for the -// mount. It also points back to the dirent or mount where it was mounted over, -// so that it can be restored when unmounted. The chained mount can be either: -// - Mount: when it's mounted on top of another mount point. -// - Dirent: when it's mounted on top of a dirent. In this case the mount is -// called an "undo" mount and only 'root' is set. All other fields are -// either invalid or nil. -// -// +stateify savable -type Mount struct { - // ID is a unique id for this mount. It may be invalidMountID if this is - // used to cache a dirent that was mounted over. - ID uint64 - - // ParentID is the parent's mount unique id. It may be invalidMountID if this - // is the root mount or if this is used to cache a dirent that was mounted - // over. - ParentID uint64 - - // root is the root Dirent of this mount. A reference on this Dirent must be - // held through the lifetime of the Mount which contains it. - root *Dirent - - // previous is the existing dirent or mount that this object was mounted over. - // It's nil for the root mount and for the last entry in the chain (always an - // "undo" mount). - previous *Mount -} - -// newMount creates a new mount, taking a reference on 'root'. Caller must -// release the reference when it's done with the mount. -func newMount(id, pid uint64, root *Dirent) *Mount { - root.IncRef() - return &Mount{ - ID: id, - ParentID: pid, - root: root, - } -} - -// newRootMount creates a new root mount (no parent), taking a reference on -// 'root'. Caller must release the reference when it's done with the mount. -func newRootMount(id uint64, root *Dirent) *Mount { - root.IncRef() - return &Mount{ - ID: id, - ParentID: invalidMountID, - root: root, - } -} - -// newUndoMount creates a new undo mount, taking a reference on 'd'. Caller must -// release the reference when it's done with the mount. -func newUndoMount(d *Dirent) *Mount { - d.IncRef() - return &Mount{ - ID: invalidMountID, - ParentID: invalidMountID, - root: d, - } -} - -// Root returns the root dirent of this mount. -// -// This may return nil if the mount has already been free. Callers must handle this -// case appropriately. If non-nil, callers must call DecRef on the returned *Dirent. -func (m *Mount) Root() *Dirent { - if !m.root.TryIncRef() { - return nil - } - return m.root -} - -// IsRoot returns true if the mount has no parent. -func (m *Mount) IsRoot() bool { - return !m.IsUndo() && m.ParentID == invalidMountID -} - -// IsUndo returns true if 'm' is an undo mount that should be used to restore -// the original dirent during unmount only and it's not a valid mount. -func (m *Mount) IsUndo() bool { - if m.ID == invalidMountID { - if m.ParentID != invalidMountID { - panic(fmt.Sprintf("Undo mount with valid parentID: %+v", m)) - } - return true - } - return false -} - -// MountNamespace defines a VFS root. It contains collection of Mounts that are -// mounted inside the Dirent tree rooted at the Root Dirent. It provides -// methods for traversing the Dirent, and for mounting/unmounting in the tree. -// -// Note that this does not correspond to a "mount namespace" in the Linux. It -// is more like a unique VFS instance. -// -// It's possible for different processes to have different MountNamespaces. In -// this case, the file systems exposed to the processes are completely -// distinct. -// -// +stateify savable -type MountNamespace struct { - refs.AtomicRefCount - - // userns is the user namespace associated with this mount namespace. - // - // All privileged operations on this mount namespace must have - // appropriate capabilities in this userns. - // - // userns is immutable. - userns *auth.UserNamespace - - // root is the root directory. - root *Dirent - - // mu protects mounts and mountID counter. - mu namespaceMutex `state:"nosave"` - - // mounts is a map of mounted Dirent -> Mount object. There are three - // possible cases: - // - Dirent is mounted over a mount point: the stored Mount object will be - // the Mount for that mount point. - // - Dirent is mounted over a regular (non-mount point) Dirent: the stored - // Mount object will be an "undo" mount containing the mounted-over - // Dirent. - // - Dirent is the root mount: the stored Mount object will be a root mount - // containing the Dirent itself. - mounts map[*Dirent]*Mount - - // mountID is the next mount id to assign. - mountID uint64 -} - -// NewMountNamespace returns a new MountNamespace, with the provided node at the -// root, and the given cache size. A root must always be provided. -func NewMountNamespace(ctx context.Context, root *Inode) (*MountNamespace, error) { - // Set the root dirent and id on the root mount. The reference returned from - // NewDirent will be donated to the MountNamespace constructed below. - d := NewDirent(ctx, root, "/") - - mnts := map[*Dirent]*Mount{ - d: newRootMount(1, d), - } - - creds := auth.CredentialsFromContext(ctx) - mns := MountNamespace{ - userns: creds.UserNamespace, - root: d, - mounts: mnts, - mountID: 2, - } - mns.EnableLeakCheck("fs.MountNamespace") - return &mns, nil -} - -// UserNamespace returns the user namespace associated with this mount manager. -func (mns *MountNamespace) UserNamespace() *auth.UserNamespace { - return mns.userns -} - -// Root returns the MountNamespace's root Dirent and increments its reference -// count. The caller must call DecRef when finished. -func (mns *MountNamespace) Root() *Dirent { - mns.root.IncRef() - return mns.root -} - -// FlushMountSourceRefs flushes extra references held by MountSources for all active mount points; -// see fs/mount.go:MountSource.FlushDirentRefs. -func (mns *MountNamespace) FlushMountSourceRefs() { - mns.mu.Lock() - defer mns.mu.Unlock() - mns.flushMountSourceRefsLocked() -} - -func (mns *MountNamespace) flushMountSourceRefsLocked() { - // Flush mounts' MountSource references. - for _, mp := range mns.mounts { - for ; mp != nil; mp = mp.previous { - mp.root.Inode.MountSource.FlushDirentRefs() - } - } - - if mns.root == nil { - // No root? This MountSource must have already been destroyed. - // This can happen when a Save is triggered while a process is - // exiting. There is nothing to flush. - return - } - - // Flush root's MountSource references. - mns.root.Inode.MountSource.FlushDirentRefs() -} - -// destroy drops root and mounts dirent references and closes any original nodes. -// -// After destroy is called, the MountNamespace may continue to be referenced (for -// example via /proc/mounts), but should free all resources and shouldn't have -// Find* methods called. -func (mns *MountNamespace) destroy(ctx context.Context) { - mns.mu.Lock() - defer mns.mu.Unlock() - - // Flush all mounts' MountSource references to Dirents. This allows for mount - // points to be torn down since there should be no remaining references after - // this and DecRef below. - mns.flushMountSourceRefsLocked() - - // Teardown mounts. - for _, mp := range mns.mounts { - // Drop the mount reference on all mounted dirents. - for ; mp != nil; mp = mp.previous { - mp.root.DecRef(ctx) - } - } - mns.mounts = nil - - // Drop reference on the root. - mns.root.DecRef(ctx) - - // Ensure that root cannot be accessed via this MountNamespace any - // more. - mns.root = nil - - // Wait for asynchronous work (queued by dropping Dirent references - // above) to complete before destroying this MountNamespace. - AsyncBarrier() -} - -// DecRef implements RefCounter.DecRef with destructor mns.destroy. -func (mns *MountNamespace) DecRef(ctx context.Context) { - mns.DecRefWithDestructor(ctx, mns.destroy) -} - -// withMountLocked prevents further walks to `node`, because `node` is about to -// be a mount point. -func (mns *MountNamespace) withMountLocked(node *Dirent, fn func() error) error { - mns.mu.Lock() - defer mns.mu.Unlock() - - renameMu.Lock() - defer renameMu.Unlock() - - // Linux allows mounting over the root (?). It comes with a strange set - // of semantics. We'll just not do this for now. - if node.parent == nil { - return linuxerr.EBUSY - } - - // For both mount and unmount, we take this lock so we can swap out the - // appropriate child in parent.children. - // - // For unmount, this also ensures that if `node` is a mount point, the - // underlying mount's MountSource.direntRefs cannot increase by preventing - // walks to node. - node.parent.dirMu.Lock() - defer node.parent.dirMu.Unlock() - - node.parent.mu.Lock() - defer node.parent.mu.Unlock() - - // We need not take node.dirMu since we have parent.dirMu. - - // We need to take node.mu, so that we can check for deletion. - node.mu.Lock() - defer node.mu.Unlock() - - return fn() -} - -// Mount mounts a `inode` over the subtree at `node`. -func (mns *MountNamespace) Mount(ctx context.Context, mountPoint *Dirent, inode *Inode) error { - return mns.withMountLocked(mountPoint, func() error { - replacement, err := mountPoint.mount(ctx, inode) - if err != nil { - return err - } - defer replacement.DecRef(ctx) - - // Set the mount's root dirent and id. - parentMnt := mns.findMountLocked(mountPoint) - childMnt := newMount(mns.mountID, parentMnt.ID, replacement) - mns.mountID++ - - // Drop mountPoint from its dirent cache. - mountPoint.dropExtendedReference() - - // If mountPoint is already a mount, push mountPoint on the stack so it can - // be recovered on unmount. - if prev := mns.mounts[mountPoint]; prev != nil { - childMnt.previous = prev - mns.mounts[replacement] = childMnt - delete(mns.mounts, mountPoint) - return nil - } - - // Was not already mounted, just add another mount point. - childMnt.previous = newUndoMount(mountPoint) - mns.mounts[replacement] = childMnt - return nil - }) -} - -// Unmount ensures no references to the MountSource remain and removes `node` from -// this subtree. The subtree formerly mounted in `node`'s place will be -// restored. node's MountSource will be destroyed as soon as the last reference to -// `node` is dropped, as no references to Dirents within will remain. -// -// If detachOnly is set, Unmount merely removes `node` from the subtree, but -// allows existing references to the MountSource remain. E.g. if an open file still -// refers to Dirents in MountSource, the Unmount will succeed anyway and MountSource will -// be destroyed at a later time when all references to Dirents within are -// dropped. -// -// The caller must hold a reference to node from walking to it. -func (mns *MountNamespace) Unmount(ctx context.Context, node *Dirent, detachOnly bool) error { - // This takes locks to prevent further walks to Dirents in this mount - // under the assumption that `node` is the root of the mount. - return mns.withMountLocked(node, func() error { - orig, ok := mns.mounts[node] - if !ok { - // node is not a mount point. - return linuxerr.EINVAL - } - - if orig.previous == nil { - panic("cannot unmount initial dirent") - } - - m := node.Inode.MountSource - if !detachOnly { - // Flush all references on the mounted node. - m.FlushDirentRefs() - - // At this point, exactly two references must be held - // to mount: one mount reference on node, and one due - // to walking to node. - // - // We must also be guaranteed that no more references - // can be taken on mount. This is why withMountLocked - // must be held at this point to prevent any walks to - // and from node. - if refs := m.DirentRefs(); refs < 2 { - panic(fmt.Sprintf("have %d refs on unmount, expect 2 or more", refs)) - } else if refs != 2 { - return linuxerr.EBUSY - } - } - - prev := orig.previous - if err := node.unmount(ctx, prev.root); err != nil { - return err - } - - if prev.previous == nil { - if !prev.IsUndo() { - panic(fmt.Sprintf("Last mount in the chain must be a undo mount: %+v", prev)) - } - // Drop mount reference taken at the end of MountNamespace.Mount. - prev.root.DecRef(ctx) - } else { - mns.mounts[prev.root] = prev - } - delete(mns.mounts, node) - - return nil - }) -} - -// FindMount returns the mount that 'd' belongs to. It walks the dirent back -// until a mount is found. It may return nil if no mount was found. -func (mns *MountNamespace) FindMount(d *Dirent) *Mount { - mns.mu.Lock() - defer mns.mu.Unlock() - renameMu.Lock() - defer renameMu.Unlock() - - return mns.findMountLocked(d) -} - -func (mns *MountNamespace) findMountLocked(d *Dirent) *Mount { - for { - if mnt := mns.mounts[d]; mnt != nil { - return mnt - } - if d.parent == nil { - return nil - } - d = d.parent - } -} - -// AllMountsUnder returns a slice of all mounts under the parent, including -// itself. -func (mns *MountNamespace) AllMountsUnder(parent *Mount) []*Mount { - mns.mu.Lock() - defer mns.mu.Unlock() - - var rv []*Mount - for _, mp := range mns.mounts { - if !mp.IsUndo() && mp.root.descendantOf(parent.root) { - rv = append(rv, mp) - } - } - return rv -} - -// FindLink returns an Dirent from a given node, which may be a symlink. -// -// The root argument is treated as the root directory, and FindLink will not -// return anything above that. The wd dirent provides the starting directory, -// and may be nil which indicates the root should be used. You must call DecRef -// on the resulting Dirent when you are no longer using the object. -// -// If wd is nil, then the root will be used as the working directory. If the -// path is absolute, this has no functional impact. -// -// Precondition: root must be non-nil. -// Precondition: the path must be non-empty. -func (mns *MountNamespace) FindLink(ctx context.Context, root, wd *Dirent, path string, remainingTraversals *uint) (*Dirent, error) { - if root == nil { - panic("MountNamespace.FindLink: root must not be nil") - } - if len(path) == 0 { - panic("MountNamespace.FindLink: path is empty") - } - - // Split the path. - first, remainder := SplitFirst(path) - - // Where does this walk originate? - current := wd - if current == nil { - current = root - } - for first == "/" { - // Special case: it's possible that we have nothing to walk at - // all. This is necessary since we're resplitting the path. - if remainder == "" { - root.IncRef() - return root, nil - } - - // Start at the root and advance the path component so that the - // walk below can proceed. Note at this point, it handles the - // no-op walk case perfectly fine. - current = root - first, remainder = SplitFirst(remainder) - } - - current.IncRef() // Transferred during walk. - - for { - // Check that the file is a directory and that we have - // permissions to walk. - // - // Note that we elide this check for the root directory as an - // optimization; a non-executable root may still be walked. A - // non-directory root is hopeless. - if current != root { - if !IsDir(current.Inode.StableAttr) { - current.DecRef(ctx) // Drop reference from above. - return nil, linuxerr.ENOTDIR - } - if err := current.Inode.CheckPermission(ctx, PermMask{Execute: true}); err != nil { - current.DecRef(ctx) // Drop reference from above. - return nil, err - } - } - - // Move to the next level. - next, err := current.Walk(ctx, root, first) - if err != nil { - // Allow failed walks to cache the dirent, because no - // children will acquire a reference at the end. - current.maybeExtendReference() - current.DecRef(ctx) - return nil, err - } - - // Drop old reference. - current.DecRef(ctx) - - if remainder != "" { - // Ensure it's resolved, unless it's the last level. - // - // See resolve for reference semantics; on err next - // will have one dropped. - current, err = mns.resolve(ctx, root, next, remainingTraversals) - if err != nil { - return nil, err - } - } else { - // Allow the file system to take an extra reference on the - // found child. This will hold a reference on the containing - // directory, so the whole tree will be implicitly cached. - next.maybeExtendReference() - return next, nil - } - - // Move to the next element. - first, remainder = SplitFirst(remainder) - } -} - -// FindInode is identical to FindLink except the return value is resolved. -// -//go:nosplit -func (mns *MountNamespace) FindInode(ctx context.Context, root, wd *Dirent, path string, remainingTraversals *uint) (*Dirent, error) { - d, err := mns.FindLink(ctx, root, wd, path, remainingTraversals) - if err != nil { - return nil, err - } - - // See resolve for reference semantics; on err d will have the - // reference dropped. - return mns.resolve(ctx, root, d, remainingTraversals) -} - -// resolve resolves the given link. -// -// If successful, a reference is dropped on node and one is acquired on the -// caller's behalf for the returned dirent. -// -// If not successful, a reference is _also_ dropped on the node and an error -// returned. This is for convenience in using resolve directly as a return -// value. -func (mns *MountNamespace) resolve(ctx context.Context, root, node *Dirent, remainingTraversals *uint) (*Dirent, error) { - // Resolve the path. - target, err := node.Inode.Getlink(ctx) - - switch { - case err == nil: - // Make sure we didn't exhaust the traversal budget. - if *remainingTraversals == 0 { - target.DecRef(ctx) - return nil, unix.ELOOP - } - - node.DecRef(ctx) // Drop the original reference. - return target, nil - - case linuxerr.Equals(linuxerr.ENOLINK, err): - // Not a symlink. - return node, nil - - case err == ErrResolveViaReadlink: - defer node.DecRef(ctx) // See above. - - // First, check if we should traverse. - if *remainingTraversals == 0 { - return nil, unix.ELOOP - } - - // Read the target path. - targetPath, err := node.Inode.Readlink(ctx) - if err != nil { - return nil, err - } - - // Find the node; we resolve relative to the current symlink's parent. - renameMu.RLock() - parent := node.parent - renameMu.RUnlock() - *remainingTraversals-- - d, err := mns.FindInode(ctx, root, parent, targetPath, remainingTraversals) - if err != nil { - return nil, err - } - - return d, err - - default: - node.DecRef(ctx) // Drop for err; see above. - - // Propagate the error. - return nil, err - } -} - -// SyncAll calls Dirent.SyncAll on the root. -func (mns *MountNamespace) SyncAll(ctx context.Context) { - mns.mu.Lock() - defer mns.mu.Unlock() - mns.root.SyncAll(ctx) -} diff --git a/pkg/sentry/fs/offset.go b/pkg/sentry/fs/offset.go deleted file mode 100644 index b7c97004b..000000000 --- a/pkg/sentry/fs/offset.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 fs - -import ( - "math" - - "gvisor.dev/gvisor/pkg/hostarch" -) - -// OffsetPageEnd returns the file offset rounded up to the nearest -// page boundary. OffsetPageEnd panics if rounding up causes overflow, -// which shouldn't be possible given that offset is an int64. -func OffsetPageEnd(offset int64) uint64 { - end, ok := hostarch.Addr(offset).RoundUp() - if !ok { - panic("impossible overflow") - } - return uint64(end) -} - -// ReadEndOffset returns an exclusive end offset for a read operation -// so that the read does not overflow an int64 nor size. -// -// Parameters: -// - offset: the starting offset of the read. -// - length: the number of bytes to read. -// - size: the size of the file. -// -// Postconditions: The returned offset is >= offset. -func ReadEndOffset(offset int64, length int64, size int64) int64 { - if offset >= size { - return offset - } - end := offset + length - // Don't overflow. - if end < offset || end > size { - end = size - } - return end -} - -// WriteEndOffset returns an exclusive end offset for a write operation -// so that the write does not overflow an int64. -// -// Parameters: -// - offset: the starting offset of the write. -// - length: the number of bytes to write. -// -// Postconditions: The returned offset is >= offset. -func WriteEndOffset(offset int64, length int64) int64 { - return ReadEndOffset(offset, length, math.MaxInt64) -} diff --git a/pkg/sentry/fs/overlay.go b/pkg/sentry/fs/overlay.go deleted file mode 100644 index 9090263eb..000000000 --- a/pkg/sentry/fs/overlay.go +++ /dev/null @@ -1,318 +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" - "strings" - - "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/memmap" - "gvisor.dev/gvisor/pkg/sync" -) - -// The virtual filesystem implements an overlay configuration. For a high-level -// description, see README.md. -// -// Note on whiteouts: -// -// This implementation does not use the "Docker-style" whiteouts (symlinks with -// ".wh." prefix). Instead upper filesystem directories support a set of extended -// attributes to encode whiteouts: "trusted.overlay.whiteout.". This -// gives flexibility to persist whiteouts independently of the filesystem layout -// while additionally preventing name conflicts with files prefixed with ".wh.". -// -// Known deficiencies: -// -// - The device number of two files under the same overlay mount point may be -// different. This can happen if a file is found in the lower filesystem (takes -// the lower filesystem device) and another file is created in the upper -// filesystem (takes the upper filesystem device). This may appear odd but -// should not break applications. -// -// - Registered events on files (i.e. for notification of read/write readiness) -// are not copied across copy up. This is fine in the common case of files that -// do not block. For files that do block, like pipes and sockets, copy up is not -// supported. -// -// - Hardlinks in a lower filesystem are broken by copy up. For this reason, no -// attempt is made to preserve link count across copy up. -// -// - The maximum length of an extended attribute name is the same as the maximum -// length of a file path in Linux (XATTR_NAME_MAX == NAME_MAX). This means that -// whiteout attributes, if set directly on the host, are limited additionally by -// the extra whiteout prefix length (file paths must be strictly shorter than -// NAME_MAX). This is not a problem for in-memory filesystems which don't enforce -// XATTR_NAME_MAX. - -const ( - // XattrOverlayPrefix is the prefix for extended attributes that affect - // the behavior of an overlay. - XattrOverlayPrefix = "trusted.overlay." - - // XattrOverlayWhiteoutPrefix is the prefix for extended attributes - // that indicate that a whiteout exists. - XattrOverlayWhiteoutPrefix = XattrOverlayPrefix + "whiteout." -) - -// XattrOverlayWhiteout returns an extended attribute that indicates a -// whiteout exists for name. It is supported by directories that wish to -// mask the existence of name. -func XattrOverlayWhiteout(name string) string { - return XattrOverlayWhiteoutPrefix + name -} - -// isXattrOverlay returns whether the given extended attribute configures the -// overlay. -func isXattrOverlay(name string) bool { - return strings.HasPrefix(name, XattrOverlayPrefix) -} - -// NewOverlayRoot produces the root of an overlay. -// -// Preconditions: -// - upper and lower must be non-nil. -// - upper must not be an overlay. -// - lower should not expose character devices, pipes, or sockets, because -// copying up these types of files is not supported. -// - lower must not require that file objects be revalidated. -// - lower must not have dynamic file/directory content. -func NewOverlayRoot(ctx context.Context, upper *Inode, lower *Inode, flags MountSourceFlags) (*Inode, error) { - if !IsDir(upper.StableAttr) { - return nil, fmt.Errorf("upper Inode is a %v, not a directory", upper.StableAttr.Type) - } - if !IsDir(lower.StableAttr) { - return nil, fmt.Errorf("lower Inode is a %v, not a directory", lower.StableAttr.Type) - } - if upper.overlay != nil { - return nil, fmt.Errorf("cannot nest overlay in upper file of another overlay") - } - - msrc := newOverlayMountSource(ctx, upper.MountSource, lower.MountSource, flags) - overlay, err := newOverlayEntry(ctx, upper, lower, true) - if err != nil { - msrc.DecRef(ctx) - return nil, err - } - - return newOverlayInode(ctx, overlay, msrc), nil -} - -// NewOverlayRootFile produces the root of an overlay that points to a file. -// -// Preconditions: -// - lower must be non-nil. -// - lower should not expose character devices, pipes, or sockets, because -// copying up these types of files is not supported. Neither it can be a dir. -// - lower must not require that file objects be revalidated. -// - lower must not have dynamic file/directory content. -func NewOverlayRootFile(ctx context.Context, upperMS *MountSource, lower *Inode, flags MountSourceFlags) (*Inode, error) { - if !IsRegular(lower.StableAttr) { - return nil, fmt.Errorf("lower Inode is not a regular file") - } - msrc := newOverlayMountSource(ctx, upperMS, lower.MountSource, flags) - overlay, err := newOverlayEntry(ctx, nil, lower, true) - if err != nil { - msrc.DecRef(ctx) - return nil, err - } - return newOverlayInode(ctx, overlay, msrc), nil -} - -// newOverlayInode creates a new Inode for an overlay. -func newOverlayInode(ctx context.Context, o *overlayEntry, msrc *MountSource) *Inode { - var inode *Inode - if o.upper != nil { - inode = NewInode(ctx, nil, msrc, o.upper.StableAttr) - } else { - inode = NewInode(ctx, nil, msrc, o.lower.StableAttr) - } - inode.overlay = o - return inode -} - -// overlayEntry is the overlay metadata of an Inode. It implements Mappable. -// -// +stateify savable -type overlayEntry struct { - // lowerExists is true if an Inode exists for this file in the lower - // filesystem. If lowerExists is true, then the overlay must create - // a whiteout entry when renaming and removing this entry to mask the - // lower Inode. - // - // Note that this is distinct from actually holding onto a non-nil - // lower Inode (below). The overlay does not need to keep a lower Inode - // around unless it needs to operate on it, but it always needs to know - // whether the lower Inode exists to correctly execute a rename or - // remove operation. - lowerExists bool - - // lower is an Inode from a lower filesystem. Modifications are - // never made on this Inode. - lower *Inode - - // copyMu serializes copy-up for operations above - // mm.MemoryManager.mappingMu in the lock order. - copyMu sync.RWMutex `state:"nosave"` - - // mapsMu serializes copy-up for operations between - // mm.MemoryManager.mappingMu and mm.MemoryManager.activeMu in the lock - // order. - mapsMu sync.Mutex `state:"nosave"` - - // mappings tracks memory mappings of this Mappable so they can be removed - // from the lower filesystem Mappable and added to the upper filesystem - // Mappable when copy up occurs. It is strictly unnecessary after copy-up. - // - // mappings is protected by mapsMu. - mappings memmap.MappingSet - - // dataMu serializes copy-up for operations below mm.MemoryManager.activeMu - // in the lock order. - dataMu sync.RWMutex `state:"nosave"` - - // upper is an Inode from an upper filesystem. It is non-nil if - // the file exists in the upper filesystem. It becomes non-nil - // when the Inode that owns this overlayEntry is modified. - // - // upper is protected by all of copyMu, mapsMu, and dataMu. Holding any of - // these locks is sufficient to read upper; holding all three for writing - // is required to mutate it. - upper *Inode - - // dirCacheMu protects dirCache. - dirCacheMu sync.RWMutex `state:"nosave"` - - // dirCache is cache of DentAttrs from upper and lower Inodes. - dirCache *SortedDentryMap -} - -// newOverlayEntry returns a new overlayEntry. -func newOverlayEntry(ctx context.Context, upper *Inode, lower *Inode, lowerExists bool) (*overlayEntry, error) { - if upper == nil && lower == nil { - panic("invalid overlayEntry, needs at least one Inode") - } - if upper != nil && upper.overlay != nil { - panic("nested writable layers are not supported") - } - // Check for supported lower filesystem types. - if lower != nil { - switch lower.StableAttr.Type { - case RegularFile, Directory, Symlink, Socket: - default: - // We don't support copying up from character devices, - // named pipes, or anything weird (like proc files). - log.Warningf("%s not supported in lower filesytem", lower.StableAttr.Type) - return nil, linuxerr.EINVAL - } - } - return &overlayEntry{ - lowerExists: lowerExists, - lower: lower, - upper: upper, - }, nil -} - -func (o *overlayEntry) release(ctx context.Context) { - // We drop a reference on upper and lower file system Inodes - // rather than releasing them, because in-memory filesystems - // may hold an extra reference to these Inodes so that they - // stay in memory. - if o.upper != nil { - o.upper.DecRef(ctx) - } - if o.lower != nil { - o.lower.DecRef(ctx) - } -} - -// overlayUpperMountSource gives the upper mount of an overlay mount. -// -// The caller may not use this MountSource past the lifetime of overlayMountSource and may -// not call DecRef on it. -func overlayUpperMountSource(overlayMountSource *MountSource) *MountSource { - return overlayMountSource.MountSourceOperations.(*overlayMountSourceOperations).upper -} - -// Preconditions: At least one of o.copyMu, o.mapsMu, or o.dataMu must be locked. -func (o *overlayEntry) inodeLocked() *Inode { - if o.upper != nil { - return o.upper - } - return o.lower -} - -// Preconditions: At least one of o.copyMu, o.mapsMu, or o.dataMu must be locked. -func (o *overlayEntry) isMappableLocked() bool { - return o.inodeLocked().Mappable() != nil -} - -// 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 (o *overlayEntry) markDirectoryDirty() { - o.dirCacheMu.Lock() - o.dirCache = nil - o.dirCacheMu.Unlock() -} - -// AddMapping implements memmap.Mappable.AddMapping. -func (o *overlayEntry) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error { - o.mapsMu.Lock() - defer o.mapsMu.Unlock() - if err := o.inodeLocked().Mappable().AddMapping(ctx, ms, ar, offset, writable); err != nil { - return err - } - o.mappings.AddMapping(ms, ar, offset, writable) - return nil -} - -// RemoveMapping implements memmap.Mappable.RemoveMapping. -func (o *overlayEntry) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) { - o.mapsMu.Lock() - defer o.mapsMu.Unlock() - o.inodeLocked().Mappable().RemoveMapping(ctx, ms, ar, offset, writable) - o.mappings.RemoveMapping(ms, ar, offset, writable) -} - -// CopyMapping implements memmap.Mappable.CopyMapping. -func (o *overlayEntry) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error { - o.mapsMu.Lock() - defer o.mapsMu.Unlock() - if err := o.inodeLocked().Mappable().CopyMapping(ctx, ms, srcAR, dstAR, offset, writable); err != nil { - return err - } - o.mappings.AddMapping(ms, dstAR, offset, writable) - return nil -} - -// Translate implements memmap.Mappable.Translate. -func (o *overlayEntry) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) { - o.dataMu.RLock() - defer o.dataMu.RUnlock() - return o.inodeLocked().Mappable().Translate(ctx, required, optional, at) -} - -// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable. -func (o *overlayEntry) InvalidateUnsavable(ctx context.Context) error { - o.mapsMu.Lock() - defer o.mapsMu.Unlock() - return o.inodeLocked().Mappable().InvalidateUnsavable(ctx) -} diff --git a/pkg/sentry/fs/path.go b/pkg/sentry/fs/path.go deleted file mode 100644 index e4dc02dbb..000000000 --- a/pkg/sentry/fs/path.go +++ /dev/null @@ -1,119 +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 ( - "path/filepath" - "strings" -) - -// TrimTrailingSlashes trims any trailing slashes. -// -// The returned boolean indicates whether any changes were made. -// -//go:nosplit -func TrimTrailingSlashes(dir string) (trimmed string, changed bool) { - // Trim the trailing slash, except for root. - for len(dir) > 1 && dir[len(dir)-1] == '/' { - dir = dir[:len(dir)-1] - changed = true - } - return dir, changed -} - -// SplitLast splits the given path into a directory and a file. -// -// The "absoluteness" of the path is preserved, but dir is always stripped of -// trailing slashes. -// -//go:nosplit -func SplitLast(path string) (dir, file string) { - path, _ = TrimTrailingSlashes(path) - if path == "" { - return ".", "." - } else if path == "/" { - return "/", "." - } - - var slash int // Last location of slash in path. - for slash = len(path) - 1; slash >= 0 && path[slash] != '/'; slash-- { - } - switch { - case slash < 0: - return ".", path - case slash == 0: - // Directory of the form "/foo", or just "/". We need to - // preserve the first slash here, since it indicates an - // absolute path. - return "/", path[1:] - default: - // Drop the trailing slash. - dir, _ = TrimTrailingSlashes(path[:slash]) - return dir, path[slash+1:] - } -} - -// SplitFirst splits the given path into a first directory and the remainder. -// -// If remainder is empty, then the path is a single element. -// -//go:nosplit -func SplitFirst(path string) (current, remainder string) { - path, _ = TrimTrailingSlashes(path) - if path == "" { - return ".", "" - } - - var slash int // First location of slash in path. - for slash = 0; slash < len(path) && path[slash] != '/'; slash++ { - } - switch { - case slash >= len(path): - return path, "" - case slash == 0: - // See above. - return "/", path[1:] - default: - current = path[:slash] - remainder = path[slash+1:] - // Strip redundant slashes. - for len(remainder) > 0 && remainder[0] == '/' { - remainder = remainder[1:] - } - return current, remainder - } -} - -// IsSubpath checks whether the first path is a (strict) descendent of the -// second. If it is a subpath, then true is returned along with a clean -// relative path from the second path to the first. Otherwise false is -// returned. -func IsSubpath(subpath, path string) (string, bool) { - cleanPath := filepath.Clean(path) - cleanSubpath := filepath.Clean(subpath) - - // Add a trailing slash to the path if it does not already have one. - if len(cleanPath) == 0 || cleanPath[len(cleanPath)-1] != '/' { - cleanPath += "/" - } - if cleanPath == cleanSubpath { - // Paths are equal, thus not a strict subpath. - return "", false - } - if strings.HasPrefix(cleanSubpath, cleanPath) { - return strings.TrimPrefix(cleanSubpath, cleanPath), true - } - return "", false -} diff --git a/pkg/sentry/fs/restore.go b/pkg/sentry/fs/restore.go deleted file mode 100644 index 5fb4d8279..000000000 --- a/pkg/sentry/fs/restore.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 fs - -import ( - "gvisor.dev/gvisor/pkg/sync" -) - -// RestoreEnvironment is the restore environment for file systems. It consists -// of things that change across save and restore and therefore cannot be saved -// in the object graph. -type RestoreEnvironment struct { - // MountSources maps Filesystem.Name() to mount arguments. - MountSources map[string][]MountArgs - - // ValidateFileSize indicates file size should not change across S/R. - ValidateFileSize bool - - // ValidateFileTimestamp indicates file modification timestamp should - // not change across S/R. - ValidateFileTimestamp bool -} - -// MountArgs holds arguments to Mount. -type MountArgs struct { - // Dev corresponds to the devname argumnent of Mount. - Dev string - - // Flags corresponds to the flags argument of Mount. - Flags MountSourceFlags - - // DataString corresponds to the data argument of Mount. - DataString string - - // DataObj corresponds to the data interface argument of Mount. - DataObj any -} - -// restoreEnv holds the fs package global RestoreEnvironment. -var restoreEnv = struct { - mu sync.Mutex - env RestoreEnvironment - set bool -}{} - -// SetRestoreEnvironment sets the RestoreEnvironment. Must be called before -// state.Load and only once. -func SetRestoreEnvironment(r RestoreEnvironment) { - restoreEnv.mu.Lock() - defer restoreEnv.mu.Unlock() - if restoreEnv.set { - panic("RestoreEnvironment may only be set once") - } - restoreEnv.env = r - restoreEnv.set = true -} - -// CurrentRestoreEnvironment returns the current, read-only RestoreEnvironment. -// If no RestoreEnvironment was ever set, returns (_, false). -func CurrentRestoreEnvironment() (RestoreEnvironment, bool) { - restoreEnv.mu.Lock() - defer restoreEnv.mu.Unlock() - e := restoreEnv.env - set := restoreEnv.set - return e, set -} diff --git a/pkg/sentry/fs/save.go b/pkg/sentry/fs/save.go deleted file mode 100644 index 1de9735dc..000000000 --- a/pkg/sentry/fs/save.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 fs - -import ( - "fmt" - - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/log" -) - -// SaveInodeMappings saves a mapping of path -> inode ID for every -// user-reachable Dirent. -// -// The entire kernel must be frozen to call this, and filesystem state must not -// change between SaveInodeMappings and state.Save, otherwise the saved state -// of any MountSource may be incoherent. -func SaveInodeMappings() { - mountsSeen := make(map[*MountSource]struct{}) - for dirent := range allDirents.dirents { - if _, ok := mountsSeen[dirent.Inode.MountSource]; !ok { - dirent.Inode.MountSource.ResetInodeMappings() - mountsSeen[dirent.Inode.MountSource] = struct{}{} - } - } - - for dirent := range allDirents.dirents { - if dirent.Inode != nil { - // We cannot trust the root provided in the mount due - // to the overlay. We can trust the overlay to delegate - // SaveInodeMappings to the right underlying - // filesystems, though. - root := dirent - for !root.mounted && root.parent != nil { - root = root.parent - } - - // Add the mapping. - n, reachable := dirent.FullName(root) - if !reachable { - // Something has gone seriously wrong if we can't reach our root. - panic(fmt.Sprintf("Unreachable root on dirent file %s", n)) - } - dirent.Inode.MountSource.SaveInodeMapping(dirent.Inode, n) - } - } -} - -// SaveFileFsyncError converts an fs.File.Fsync error to an error that -// indicates that the fs.File was not synced sufficiently to be saved. -func SaveFileFsyncError(err error) error { - switch err { - case nil: - // We succeeded, everything is great. - return nil - case unix.EBADF, unix.EINVAL, unix.EROFS, unix.ENOSYS, unix.EPERM: - // These errors mean that the underlying node might not be syncable, - // which we expect to be reported as such even from the gofer. - log.Infof("failed to sync during save: %v", err) - return nil - default: - // We failed in some way that indicates potential data loss. - return fmt.Errorf("failed to sync: %v, data loss may occur", err) - } -} diff --git a/pkg/sentry/fs/seek.go b/pkg/sentry/fs/seek.go deleted file mode 100644 index 0f43918ad..000000000 --- a/pkg/sentry/fs/seek.go +++ /dev/null @@ -1,43 +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 - -// SeekWhence determines seek direction. -type SeekWhence int - -const ( - // SeekSet sets the absolute offset. - SeekSet SeekWhence = iota - - // SeekCurrent sets relative to the current position. - SeekCurrent - - // SeekEnd sets relative to the end of the file. - SeekEnd -) - -// String returns a human readable string for whence. -func (s SeekWhence) String() string { - switch s { - case SeekSet: - return "Set" - case SeekCurrent: - return "Current" - case SeekEnd: - return "End" - default: - return "Unknown" - } -} diff --git a/pkg/sentry/fs/splice.go b/pkg/sentry/fs/splice.go deleted file mode 100644 index b6e1788b5..000000000 --- a/pkg/sentry/fs/splice.go +++ /dev/null @@ -1,169 +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 fs - -import ( - "io" - - "gvisor.dev/gvisor/pkg/atomicbitops" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" -) - -// Splice moves data to this file, directly from another. -// -// Offsets are updated only if DstOffset and SrcOffset are set. -// +checklocksignore -func Splice(ctx context.Context, dst *File, src *File, opts SpliceOpts) (int64, error) { - // Verify basic file flag permissions. - if !dst.Flags().Write || !src.Flags().Read { - return 0, linuxerr.EBADF - } - - // Check whether or not the objects being sliced are stream-oriented - // (i.e. pipes or sockets). For all stream-oriented files and files - // where a specific offiset is not request, we acquire the file mutex. - // This has two important side effects. First, it provides the standard - // protection against concurrent writes that would mutate the offset. - // Second, it prevents Splice deadlocks. Only internal anonymous files - // implement the ReadFrom and WriteTo methods directly, and since such - // anonymous files are referred to by a unique fs.File object, we know - // that the file mutex takes strict precedence over internal locks. - // Since we enforce lock ordering here, we can't deadlock by using - // using a file in two different splice operations simultaneously. - srcPipe := !IsRegular(src.Dirent.Inode.StableAttr) - dstPipe := !IsRegular(dst.Dirent.Inode.StableAttr) - dstAppend := !dstPipe && dst.Flags().Append - srcLock := srcPipe || !opts.SrcOffset - dstLock := dstPipe || !opts.DstOffset || dstAppend - - switch { - case srcLock && dstLock: - switch { - case dst.UniqueID < src.UniqueID: - // Acquire dst first. - dst.mu.Lock() - src.mu.Lock() - case dst.UniqueID > src.UniqueID: - // Acquire src first. - src.mu.Lock() - dst.mu.Lock() - case dst.UniqueID == src.UniqueID: - // Acquire only one lock; it's the same file. This is a - // bit of a edge case, but presumably it's possible. - dst.mu.Lock() - srcLock = false - } - // Use both offsets (locked). - opts.DstStart = dst.offset.RacyLoad() - opts.SrcStart = src.offset.RacyLoad() - case dstLock: - // Acquire only dst. - dst.mu.Lock() - opts.DstStart = dst.offset.RacyLoad() // Safe: locked. - case srcLock: - // Acquire only src. - src.mu.Lock() - opts.SrcStart = src.offset.RacyLoad() // Safe: locked. - } - - var err error - if dstAppend { - unlock := dst.Dirent.Inode.lockAppendMu(dst.Flags().Append) - defer unlock() - - // Figure out the appropriate offset to use. - - dstStart := atomicbitops.FromInt64(opts.DstStart) - err = dst.offsetForAppend(ctx, &dstStart) - opts.DstStart = dstStart.RacyLoad() - } - if err == nil && !dstPipe { - // Enforce file limits. - limit, ok := dst.checkLimit(ctx, opts.DstStart) - switch { - case ok && limit == 0: - err = linuxerr.ErrExceedsFileSizeLimit - case ok && limit < opts.Length: - opts.Length = limit // Cap the write. - } - } - if err != nil { - if dstLock { - dst.mu.Unlock() - } - if srcLock { - src.mu.Unlock() - } - return 0, err - } - - // Construct readers and writers for the splice. This is used to - // provide a safer locking path for the WriteTo/ReadFrom operations - // (since they will otherwise go through public interface methods which - // conflict with locking done above), and simplifies the fallback path. - w := &lockedWriter{ - Ctx: ctx, - File: dst, - Offset: opts.DstStart, - } - r := &lockedReader{ - Ctx: ctx, - File: src, - Offset: opts.SrcStart, - } - - // Attempt to do a WriteTo; this is likely the most efficient. - n, err := src.FileOperations.WriteTo(ctx, src, w, opts.Length, opts.Dup) - if n == 0 && linuxerr.Equals(linuxerr.ENOSYS, err) && !opts.Dup { - // Attempt as a ReadFrom. If a WriteTo, a ReadFrom may also be - // more efficient than a copy if buffers are cached or readily - // available. (It's unlikely that they can actually be donated). - n, err = dst.FileOperations.ReadFrom(ctx, dst, r, opts.Length) - } - - // Support one last fallback option, but only if at least one of - // the source and destination are regular files. This is because - // if we block at some point, we could lose data. If the source is - // not a pipe then reading is not destructive; if the destination - // is a regular file, then it is guaranteed not to block writing. - if n == 0 && linuxerr.Equals(linuxerr.ENOSYS, err) && !opts.Dup && (!dstPipe || !srcPipe) { - // Fallback to an in-kernel copy. - n, err = io.Copy(w, &io.LimitedReader{ - R: r, - N: opts.Length, - }) - } - - // Update offsets, if required. - if n > 0 { - if !dstPipe && !opts.DstOffset { - dst.offset.Add(n) - } - if !srcPipe && !opts.SrcOffset { - src.offset.Add(n) - } - } - - // Drop locks. - if dstLock { - dst.mu.Unlock() - } - if srcLock { - src.mu.Unlock() - } - - return n, err -} diff --git a/pkg/sentry/fs/sync.go b/pkg/sentry/fs/sync.go deleted file mode 100644 index 1fff8059c..000000000 --- a/pkg/sentry/fs/sync.go +++ /dev/null @@ -1,43 +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 - -// SyncType enumerates ways in which a File can be synced. -type SyncType int - -const ( - // SyncAll indicates that modified in-memory metadata and data should - // be written to backing storage. SyncAll implies SyncBackingStorage. - SyncAll SyncType = iota - - // SyncData indicates that along with modified in-memory data, only - // metadata needed to access that data needs to be written. - // - // For example, changes to access time or modification time do not - // need to be written because they are not necessary for a data read - // to be handled correctly, unlike the file size. - // - // The aim of SyncData is to reduce disk activity for applications - // that do not require all metadata to be synchronized with the disk, - // see fdatasync(2). File systems that implement SyncData as SyncAll - // do not support this optimization. - // - // SyncData implies SyncBackingStorage. - SyncData - - // SyncBackingStorage indicates that in-flight write operations to - // backing storage should be flushed. - SyncBackingStorage -) diff --git a/pkg/sentry/fsbridge/BUILD b/pkg/sentry/fsbridge/BUILD index 4631db2bb..e3526d3bc 100644 --- a/pkg/sentry/fsbridge/BUILD +++ b/pkg/sentry/fsbridge/BUILD @@ -6,16 +6,13 @@ go_library( name = "fsbridge", srcs = [ "bridge.go", - "fs.go", "vfs.go", ], visibility = ["//pkg/sentry:internal"], deps = [ "//pkg/abi/linux", "//pkg/context", - "//pkg/errors/linuxerr", "//pkg/fspath", - "//pkg/sentry/fs", "//pkg/sentry/kernel/auth", "//pkg/sentry/memmap", "//pkg/sentry/vfs", diff --git a/pkg/sentry/fsbridge/bridge.go b/pkg/sentry/fsbridge/bridge.go index 7e61209ee..83610ddee 100644 --- a/pkg/sentry/fsbridge/bridge.go +++ b/pkg/sentry/fsbridge/bridge.go @@ -14,6 +14,7 @@ // Package fsbridge provides common interfaces to bridge between VFS1 and VFS2 // files. +// TODO(gvisor.dev/issue/1624): Delete this package. package fsbridge import ( diff --git a/pkg/sentry/fsbridge/fs.go b/pkg/sentry/fsbridge/fs.go deleted file mode 100644 index 527bde181..000000000 --- a/pkg/sentry/fsbridge/fs.go +++ /dev/null @@ -1,181 +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 fsbridge - -import ( - "io" - "strings" - - "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/memmap" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/usermem" -) - -// fsFile implements File interface over fs.File. -// -// +stateify savable -type fsFile struct { - file *fs.File -} - -var _ File = (*fsFile)(nil) - -// NewFSFile creates a new File over fs.File. -func NewFSFile(file *fs.File) File { - return &fsFile{file: file} -} - -// PathnameWithDeleted implements File. -func (f *fsFile) PathnameWithDeleted(ctx context.Context) string { - root := fs.RootFromContext(ctx) - if root == nil { - // This doesn't correspond to anything in Linux because the vfs is - // global there. - return "" - } - defer root.DecRef(ctx) - - name, _ := f.file.Dirent.FullName(root) - return name -} - -// ReadFull implements File. -func (f *fsFile) ReadFull(ctx context.Context, dst usermem.IOSequence, offset int64) (int64, error) { - var total int64 - for dst.NumBytes() > 0 { - n, err := f.file.Preadv(ctx, dst, offset+total) - total += n - if err == io.EOF && total != 0 { - return total, io.ErrUnexpectedEOF - } else if err != nil { - return total, err - } - dst = dst.DropFirst64(n) - } - return total, nil -} - -// ConfigureMMap implements File. -func (f *fsFile) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error { - return f.file.ConfigureMMap(ctx, opts) -} - -// Type implements File. -func (f *fsFile) Type(context.Context) (linux.FileMode, error) { - return linux.FileMode(f.file.Dirent.Inode.StableAttr.Type.LinuxType()), nil -} - -// IncRef implements File. -func (f *fsFile) IncRef() { - f.file.IncRef() -} - -// DecRef implements File. -func (f *fsFile) DecRef(ctx context.Context) { - f.file.DecRef(ctx) -} - -// fsLookup implements Lookup interface using fs.File. -// -// +stateify savable -type fsLookup struct { - mntns *fs.MountNamespace - - root *fs.Dirent - workingDir *fs.Dirent -} - -var _ Lookup = (*fsLookup)(nil) - -// NewFSLookup creates a new Lookup using VFS1. -func NewFSLookup(mntns *fs.MountNamespace, root, workingDir *fs.Dirent) Lookup { - return &fsLookup{ - mntns: mntns, - root: root, - workingDir: workingDir, - } -} - -// OpenPath implements Lookup. -func (l *fsLookup) OpenPath(ctx context.Context, path string, opts vfs.OpenOptions, remainingTraversals *uint, resolveFinal bool) (File, error) { - var d *fs.Dirent - var err error - if resolveFinal { - d, err = l.mntns.FindInode(ctx, l.root, l.workingDir, path, remainingTraversals) - } else { - d, err = l.mntns.FindLink(ctx, l.root, l.workingDir, path, remainingTraversals) - } - if err != nil { - return nil, err - } - defer d.DecRef(ctx) - - if !resolveFinal && fs.IsSymlink(d.Inode.StableAttr) { - return nil, linuxerr.ELOOP - } - - fsPerm := openOptionsToPermMask(&opts) - if err := d.Inode.CheckPermission(ctx, fsPerm); err != nil { - return nil, err - } - - // If they claim it's a directory, then make sure. - if strings.HasSuffix(path, "/") { - if d.Inode.StableAttr.Type != fs.Directory { - return nil, linuxerr.ENOTDIR - } - } - - if opts.FileExec && d.Inode.StableAttr.Type != fs.RegularFile { - ctx.Infof("%q is not a regular file: %v", path, d.Inode.StableAttr.Type) - return nil, linuxerr.EACCES - } - - f, err := d.Inode.GetFile(ctx, d, flagsToFileFlags(opts.Flags)) - if err != nil { - return nil, err - } - - return &fsFile{file: f}, nil -} - -func openOptionsToPermMask(opts *vfs.OpenOptions) fs.PermMask { - mode := opts.Flags & linux.O_ACCMODE - return fs.PermMask{ - Read: mode == linux.O_RDONLY || mode == linux.O_RDWR, - Write: mode == linux.O_WRONLY || mode == linux.O_RDWR, - Execute: opts.FileExec, - } -} - -func flagsToFileFlags(flags uint32) fs.FileFlags { - return fs.FileFlags{ - Direct: flags&linux.O_DIRECT != 0, - DSync: flags&(linux.O_DSYNC|linux.O_SYNC) != 0, - Sync: flags&linux.O_SYNC != 0, - NonBlocking: flags&linux.O_NONBLOCK != 0, - Read: (flags & linux.O_ACCMODE) != linux.O_WRONLY, - Write: (flags & linux.O_ACCMODE) != linux.O_RDONLY, - Append: flags&linux.O_APPEND != 0, - Directory: flags&linux.O_DIRECTORY != 0, - Async: flags&linux.O_ASYNC != 0, - LargeFile: flags&linux.O_LARGEFILE != 0, - Truncate: flags&linux.O_TRUNC != 0, - } -} diff --git a/pkg/sentry/fsbridge/vfs.go b/pkg/sentry/fsbridge/vfs.go index be0900030..a7837f402 100644 --- a/pkg/sentry/fsbridge/vfs.go +++ b/pkg/sentry/fsbridge/vfs.go @@ -108,7 +108,7 @@ type vfsLookup struct { var _ Lookup = (*vfsLookup)(nil) -// NewVFSLookup creates a new Lookup using VFS2. +// NewVFSLookup creates a new Lookup. func NewVFSLookup(mntns *vfs.MountNamespace, root, workingDir vfs.VirtualDentry) Lookup { return &vfsLookup{ mntns: mntns, @@ -119,7 +119,7 @@ func NewVFSLookup(mntns *vfs.MountNamespace, root, workingDir vfs.VirtualDentry) // OpenPath implements Lookup. // -// remainingTraversals is not configurable in VFS2, all callers are using the +// remainingTraversals is not configurable, all callers are using the // default anyways. func (l *vfsLookup) OpenPath(ctx context.Context, pathname string, opts vfs.OpenOptions, _ *uint, resolveFinal bool) (File, error) { vfsObj := l.root.Mount().Filesystem().VirtualFilesystem() diff --git a/pkg/sentry/fsimpl/devpts/BUILD b/pkg/sentry/fsimpl/devpts/BUILD index c0c58839c..cbb71bcdf 100644 --- a/pkg/sentry/fsimpl/devpts/BUILD +++ b/pkg/sentry/fsimpl/devpts/BUILD @@ -38,7 +38,6 @@ go_library( "//pkg/refsvfs2", "//pkg/safemem", "//pkg/sentry/arch", - "//pkg/sentry/fs", "//pkg/sentry/fsimpl/kernfs", "//pkg/sentry/fsimpl/lock", "//pkg/sentry/kernel", diff --git a/pkg/sentry/fsimpl/fuse/dev_test.go b/pkg/sentry/fsimpl/fuse/dev_test.go index 8f39e12aa..91b01fe1c 100644 --- a/pkg/sentry/fsimpl/fuse/dev_test.go +++ b/pkg/sentry/fsimpl/fuse/dev_test.go @@ -238,7 +238,7 @@ func fuseClientRun(t *testing.T, s *testutil.System, k *kernel.Kernel, conn *con } }() - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) + tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) clientTask, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("fuse-client-%v", pid), tc, s.MntNs, s.Root, s.Root) if err != nil { t.Fatal(err) @@ -285,7 +285,7 @@ func fuseServerRun(t *testing.T, s *testutil.System, k *kernel.Kernel, fd *vfs.F }() // Create the tasks that the server will be using. - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) + tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) var readPayload primitive.Uint32 serverTask, err := testutil.CreateTask(s.Ctx, "fuse-server", tc, s.MntNs, s.Root, s.Root) diff --git a/pkg/sentry/fsimpl/fuse/fusefs.go b/pkg/sentry/fsimpl/fuse/fusefs.go index 9dc2ef6bd..31087d87a 100644 --- a/pkg/sentry/fsimpl/fuse/fusefs.go +++ b/pkg/sentry/fsimpl/fuse/fusefs.go @@ -133,7 +133,7 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt log.Warningf("%s.GetFilesystem: couldn't get kernel task from context", fsType.Name()) return nil, nil, linuxerr.EINVAL } - fuseFDGeneric := kernelTask.GetFileVFS2(int32(deviceDescriptor)) + fuseFDGeneric := kernelTask.GetFile(int32(deviceDescriptor)) if fuseFDGeneric == nil { return nil, nil, linuxerr.EINVAL } diff --git a/pkg/sentry/fsimpl/gofer/BUILD b/pkg/sentry/fsimpl/gofer/BUILD index 748abf918..c49ade1d3 100644 --- a/pkg/sentry/fsimpl/gofer/BUILD +++ b/pkg/sentry/fsimpl/gofer/BUILD @@ -77,10 +77,10 @@ go_library( "//pkg/refs", "//pkg/refsvfs2", "//pkg/safemem", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/host", "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsmetric", + "//pkg/sentry/fsutil", "//pkg/sentry/hostfd", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/fsimpl/gofer/gofer.go b/pkg/sentry/fsimpl/gofer/gofer.go index 0c565dfb6..8827615f5 100644 --- a/pkg/sentry/fsimpl/gofer/gofer.go +++ b/pkg/sentry/fsimpl/gofer/gofer.go @@ -55,8 +55,8 @@ import ( "gvisor.dev/gvisor/pkg/p9" 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/fsimpl/lock" + "gvisor.dev/gvisor/pkg/sentry/fsutil" "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/gofer/regular_file.go b/pkg/sentry/fsimpl/gofer/regular_file.go index 6117d8bf3..f88005cbf 100644 --- a/pkg/sentry/fsimpl/gofer/regular_file.go +++ b/pkg/sentry/fsimpl/gofer/regular_file.go @@ -27,8 +27,8 @@ import ( "gvisor.dev/gvisor/pkg/metric" "gvisor.dev/gvisor/pkg/p9" "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" "gvisor.dev/gvisor/pkg/sentry/fsmetric" + "gvisor.dev/gvisor/pkg/sentry/fsutil" "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sentry/pgalloc" "gvisor.dev/gvisor/pkg/sentry/usage" diff --git a/pkg/sentry/fsimpl/gofer/special_file.go b/pkg/sentry/fsimpl/gofer/special_file.go index f10050cdc..1e50ebe83 100644 --- a/pkg/sentry/fsimpl/gofer/special_file.go +++ b/pkg/sentry/fsimpl/gofer/special_file.go @@ -27,8 +27,8 @@ import ( "gvisor.dev/gvisor/pkg/metric" "gvisor.dev/gvisor/pkg/p9" "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" "gvisor.dev/gvisor/pkg/sentry/fsmetric" + "gvisor.dev/gvisor/pkg/sentry/fsutil" "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" diff --git a/pkg/sentry/fsimpl/host/BUILD b/pkg/sentry/fsimpl/host/BUILD index 3387b1a2a..e85b66e53 100644 --- a/pkg/sentry/fsimpl/host/BUILD +++ b/pkg/sentry/fsimpl/host/BUILD @@ -41,9 +41,9 @@ go_library( "//pkg/refsvfs2", "//pkg/safemem", "//pkg/sentry/arch", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/kernfs", "//pkg/sentry/fsimpl/lock", + "//pkg/sentry/fsutil", "//pkg/sentry/hostfd", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/fsimpl/iouringfs/iouringfs.go b/pkg/sentry/fsimpl/iouringfs/iouringfs.go index 2588b56a8..0e69d1ab6 100644 --- a/pkg/sentry/fsimpl/iouringfs/iouringfs.go +++ b/pkg/sentry/fsimpl/iouringfs/iouringfs.go @@ -419,7 +419,7 @@ func (fd *FileDescription) handleReadv(t *kernel.Task, sqe *linux.IOUringSqe, fl if err != nil { return 0, err } - file := t.GetFileVFS2(sqe.Fd) + file := t.GetFile(sqe.Fd) if file == nil { return 0, linuxerr.EBADF } diff --git a/pkg/sentry/fsimpl/kernfs/BUILD b/pkg/sentry/fsimpl/kernfs/BUILD index 3f051a600..cc8efe8fc 100644 --- a/pkg/sentry/fsimpl/kernfs/BUILD +++ b/pkg/sentry/fsimpl/kernfs/BUILD @@ -129,8 +129,8 @@ go_library( "//pkg/refs", "//pkg/refsvfs2", "//pkg/safemem", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/lock", + "//pkg/sentry/fsutil", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/time", "//pkg/sentry/memmap", diff --git a/pkg/sentry/fsimpl/kernfs/kernfs.go b/pkg/sentry/fsimpl/kernfs/kernfs.go index 37c0bf108..de846d00a 100644 --- a/pkg/sentry/fsimpl/kernfs/kernfs.go +++ b/pkg/sentry/fsimpl/kernfs/kernfs.go @@ -15,7 +15,7 @@ // Package kernfs provides the tools to implement inode-based filesystems. // Kernfs has two main features: // -// 1. The Inode interface, which maps VFS2's path-based filesystem operations to +// 1. The Inode interface, which maps VFS's path-based filesystem operations to // specific filesystem nodes. Kernfs uses the Inode interface to provide a // blanket implementation for the vfs.FilesystemImpl. Kernfs also serves as // the synchronization mechanism for all filesystem operations by holding a diff --git a/pkg/sentry/fsimpl/kernfs/mmap_util.go b/pkg/sentry/fsimpl/kernfs/mmap_util.go index d1539d904..269f9fb9e 100644 --- a/pkg/sentry/fsimpl/kernfs/mmap_util.go +++ b/pkg/sentry/fsimpl/kernfs/mmap_util.go @@ -18,7 +18,7 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" + "gvisor.dev/gvisor/pkg/sentry/fsutil" "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sync" ) diff --git a/pkg/sentry/fsimpl/mqfs/BUILD b/pkg/sentry/fsimpl/mqfs/BUILD index 9125a118d..0c7189942 100644 --- a/pkg/sentry/fsimpl/mqfs/BUILD +++ b/pkg/sentry/fsimpl/mqfs/BUILD @@ -30,7 +30,6 @@ go_library( "//pkg/context", "//pkg/errors/linuxerr", "//pkg/refsvfs2", - "//pkg/sentry/fs", "//pkg/sentry/fsimpl/kernfs", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/ipc", diff --git a/pkg/sentry/fsimpl/mqfs/registry.go b/pkg/sentry/fsimpl/mqfs/registry.go index 28c18b2cd..4b547b3c0 100644 --- a/pkg/sentry/fsimpl/mqfs/registry.go +++ b/pkg/sentry/fsimpl/mqfs/registry.go @@ -18,7 +18,6 @@ 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/fsimpl/kernfs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/mq" @@ -157,15 +156,15 @@ func (r *RegistryImpl) newFD(q *mq.Queue, inode *queueInode, access mq.AccessTyp } // perm returns a permission mask created using given flags. -func perm(access mq.AccessType) fs.PermMask { +func perm(access mq.AccessType) vfs.AccessTypes { switch access { case mq.ReadWrite: - return fs.PermMask{Read: true, Write: true} + return vfs.MayRead | vfs.MayWrite case mq.WriteOnly: - return fs.PermMask{Write: true} + return vfs.MayWrite case mq.ReadOnly: - return fs.PermMask{Read: true} + return vfs.MayRead default: - return fs.PermMask{} // Can't happen, see NewView. + return 0 // Can't happen, see NewView. } } diff --git a/pkg/sentry/fsimpl/proc/task_fds.go b/pkg/sentry/fsimpl/proc/task_fds.go index 0c636aa5e..8289b2ef6 100644 --- a/pkg/sentry/fsimpl/proc/task_fds.go +++ b/pkg/sentry/fsimpl/proc/task_fds.go @@ -36,7 +36,7 @@ func getTaskFD(t *kernel.Task, fd int32) (*vfs.FileDescription, kernel.FDFlags) ) t.WithMuLocked(func(t *kernel.Task) { if fdt := t.FDTable(); fdt != nil { - file, flags = fdt.GetVFS2(fd) + file, flags = fdt.Get(fd) } }) return file, flags diff --git a/pkg/sentry/fsimpl/proc/task_files.go b/pkg/sentry/fsimpl/proc/task_files.go index aec45540e..8a51b7e14 100644 --- a/pkg/sentry/fsimpl/proc/task_files.go +++ b/pkg/sentry/fsimpl/proc/task_files.go @@ -1024,7 +1024,7 @@ func (s *cwdSymlink) Getlink(ctx context.Context, _ *vfs.Mount) (vfs.VirtualDent if err := checkTaskState(s.task); err != nil { return vfs.VirtualDentry{}, "", err } - cwd := s.task.FSContext().WorkingDirectoryVFS2() + cwd := s.task.FSContext().WorkingDirectory() if !cwd.Ok() { // It could have raced with process deletion. return vfs.VirtualDentry{}, "", linuxerr.ESRCH @@ -1085,7 +1085,7 @@ func (s *rootSymlink) Getlink(ctx context.Context, _ *vfs.Mount) (vfs.VirtualDen if err := checkTaskState(s.task); err != nil { return vfs.VirtualDentry{}, "", err } - root := s.task.FSContext().RootDirectoryVFS2() + root := s.task.FSContext().RootDirectory() if !root.Ok() { // It could have raced with process deletion. return vfs.VirtualDentry{}, "", linuxerr.ESRCH @@ -1116,7 +1116,7 @@ func (i *mountInfoData) Generate(ctx context.Context, buf *bytes.Buffer) error { // The task has been destroyed. Nothing to show here. return nil } - rootDir := fsctx.RootDirectoryVFS2() + rootDir := fsctx.RootDirectory() if !rootDir.Ok() { // Root has been destroyed. Don't try to read mounts. return nil @@ -1148,7 +1148,7 @@ func (i *mountsData) Generate(ctx context.Context, buf *bytes.Buffer) error { // The task has been destroyed. Nothing to show here. return nil } - rootDir := fsctx.RootDirectoryVFS2() + rootDir := fsctx.RootDirectory() if !rootDir.Ok() { // Root has been destroyed. Don't try to read mounts. return nil diff --git a/pkg/sentry/fsimpl/proc/task_net.go b/pkg/sentry/fsimpl/proc/task_net.go index 29a9b1516..1caf76a1b 100644 --- a/pkg/sentry/fsimpl/proc/task_net.go +++ b/pkg/sentry/fsimpl/proc/task_net.go @@ -206,17 +206,17 @@ var _ dynamicInode = (*netUnixData)(nil) func (n *netUnixData) Generate(ctx context.Context, buf *bytes.Buffer) error { buf.WriteString("Num RefCount Protocol Flags Type St Inode Path\n") for _, se := range n.kernel.ListSockets() { - s := se.SockVFS2 + s := se.Sock if !s.TryIncRef() { // Racing with socket destruction, this is ok. continue } - if family, _, _ := s.Impl().(socket.SocketVFS2).Type(); family != linux.AF_UNIX { + if family, _, _ := s.Impl().(socket.Socket).Type(); family != linux.AF_UNIX { s.DecRef(ctx) // Not a unix socket. continue } - sops := s.Impl().(*unix.SocketVFS2) + sops := s.Impl().(*unix.Socket) addr, err := sops.Endpoint().GetLocalAddress() if err != nil { @@ -263,13 +263,13 @@ func (n *netUnixData) Generate(ctx context.Context, buf *bytes.Buffer) error { // // For now, we always redact this pointer. fmt.Fprintf(buf, "%#016p: %08X %08X %08X %04X %02X %8d", - (*unix.SocketOperations)(nil), // Num, pointer to kernel socket struct. - s.ReadRefs()-1, // RefCount, don't count our own ref. - 0, // Protocol, always 0 for UDS. - sockFlags, // Flags. - sops.Endpoint().Type(), // Type. - sops.State(), // State. - ino, // Inode. + (*unix.Socket)(nil), // Num, pointer to kernel socket struct. + s.ReadRefs()-1, // RefCount, don't count our own ref. + 0, // Protocol, always 0 for UDS. + sockFlags, // Flags. + sops.Endpoint().Type(), // Type. + sops.State(), // State. + ino, // Inode. ) // Path @@ -351,12 +351,12 @@ func commonGenerateTCP(ctx context.Context, buf *bytes.Buffer, k *kernel.Kernel, t := kernel.TaskFromContext(ctx) for _, se := range k.ListSockets() { - s := se.SockVFS2 + s := se.Sock if !s.TryIncRef() { // Racing with socket destruction, this is ok. continue } - sops, ok := s.Impl().(socket.SocketVFS2) + sops, ok := s.Impl().(socket.Socket) if !ok { panic(fmt.Sprintf("Found non-socket file in socket table: %+v", s)) } @@ -516,12 +516,12 @@ func (d *netUDPData) Generate(ctx context.Context, buf *bytes.Buffer) error { buf.WriteString(" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops \n") for _, se := range d.kernel.ListSockets() { - s := se.SockVFS2 + s := se.Sock if !s.TryIncRef() { // Racing with socket destruction, this is ok. continue } - sops, ok := s.Impl().(socket.SocketVFS2) + sops, ok := s.Impl().(socket.Socket) if !ok { panic(fmt.Sprintf("Found non-socket file in socket table: %+v", s)) } diff --git a/pkg/sentry/fsimpl/proc/tasks_test.go b/pkg/sentry/fsimpl/proc/tasks_test.go index 5db02a0cd..d4cc2c507 100644 --- a/pkg/sentry/fsimpl/proc/tasks_test.go +++ b/pkg/sentry/fsimpl/proc/tasks_test.go @@ -167,7 +167,7 @@ func TestTasks(t *testing.T) { k := kernel.KernelFromContext(s.Ctx) var tasks []*kernel.Task for i := 0; i < 5; i++ { - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) + tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root) if err != nil { t.Fatalf("CreateTask(): %v", err) @@ -250,7 +250,7 @@ func TestTasksOffset(t *testing.T) { k := kernel.KernelFromContext(s.Ctx) for i := 0; i < 3; i++ { - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) + tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) if _, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root); err != nil { t.Fatalf("CreateTask(): %v", err) } @@ -375,7 +375,7 @@ func TestTask(t *testing.T) { defer s.Destroy() k := kernel.KernelFromContext(s.Ctx) - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) + tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) _, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root) if err != nil { t.Fatalf("CreateTask(): %v", err) @@ -390,7 +390,7 @@ func TestProcSelf(t *testing.T) { defer s.Destroy() k := kernel.KernelFromContext(s.Ctx) - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) + tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) task, err := testutil.CreateTask(s.Ctx, "name", tc, s.MntNs, s.Root, s.Root) if err != nil { t.Fatalf("CreateTask(): %v", err) @@ -488,13 +488,13 @@ func TestTree(t *testing.T) { var tasks []*kernel.Task for i := 0; i < 5; i++ { - tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) + tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits()) task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc, s.MntNs, s.Root, s.Root) if err != nil { t.Fatalf("CreateTask(): %v", err) } // Add file to populate /proc/[pid]/fd and fdinfo directories. - task.FDTable().NewFDVFS2(task.AsyncContext(), 0, file, kernel.FDFlags{}) + task.FDTable().NewFD(task.AsyncContext(), 0, file, kernel.FDFlags{}) tasks = append(tasks, task) } diff --git a/pkg/sentry/fsimpl/sys/sys_test.go b/pkg/sentry/fsimpl/sys/sys_test.go index 0c46a3a13..343843d79 100644 --- a/pkg/sentry/fsimpl/sys/sys_test.go +++ b/pkg/sentry/fsimpl/sys/sys_test.go @@ -89,9 +89,7 @@ func TestSysRootContainsExpectedEntries(t *testing.T) { } func TestCgroupMountpointExists(t *testing.T) { - // Note: The mountpoint is only created if cgroups are available. This is - // the VFS2 implementation of sysfs and the test runs with VFS2 enabled, so - // we expect to see the mount point unconditionally. + // Note: The mountpoint is only created if cgroups are available. s := newTestSystem(t) defer s.Destroy() pop := s.PathOpAtRoot("/fs") diff --git a/pkg/sentry/fsimpl/testutil/kernel.go b/pkg/sentry/fsimpl/testutil/kernel.go index 1644abeea..f89a7e7f4 100644 --- a/pkg/sentry/fsimpl/testutil/kernel.go +++ b/pkg/sentry/fsimpl/testutil/kernel.go @@ -110,7 +110,7 @@ func Boot() (*kernel.Kernel, error) { if err != nil { return nil, err } - tg := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, ls) + tg := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, ls) k.TestOnlySetGlobalInit(tg) return k, nil diff --git a/pkg/sentry/fsimpl/tmpfs/BUILD b/pkg/sentry/fsimpl/tmpfs/BUILD index 8fd0192c7..f91d6cd5e 100644 --- a/pkg/sentry/fsimpl/tmpfs/BUILD +++ b/pkg/sentry/fsimpl/tmpfs/BUILD @@ -99,10 +99,9 @@ go_library( "//pkg/refsvfs2", "//pkg/safemem", "//pkg/sentry/arch", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsmetric", + "//pkg/sentry/fsutil", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/pipe", "//pkg/sentry/kernel/time", @@ -127,12 +126,9 @@ go_test( deps = [ ":tmpfs", "//pkg/abi/linux", - "//pkg/context", - "//pkg/errors/linuxerr", "//pkg/fspath", "//pkg/refs", "//pkg/sentry/contexttest", - "//pkg/sentry/fs", "//pkg/sentry/kernel/auth", "//pkg/sentry/vfs", ], diff --git a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go index 00a2ef513..8399caf36 100644 --- a/pkg/sentry/fsimpl/tmpfs/benchmark_test.go +++ b/pkg/sentry/fsimpl/tmpfs/benchmark_test.go @@ -21,12 +21,9 @@ import ( "testing" "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" @@ -51,43 +48,7 @@ const ( filename = "gvisor_test_temp_0_1557494568" ) -// This is copied from syscalls/linux/sys_file.go, with the dependency on -// kernel.Task stripped out. -func fileOpOn(ctx context.Context, mntns *fs.MountNamespace, root, wd *fs.Dirent, dirFD int32, path string, resolve bool, fn func(root *fs.Dirent, d *fs.Dirent) error) error { - var ( - d *fs.Dirent // The file. - rel *fs.Dirent // The relative directory for search (if required.) - err error - ) - - // Extract the working directory (maybe). - if len(path) > 0 && path[0] == '/' { - // Absolute path; rel can be nil. - } else if dirFD == linux.AT_FDCWD { - // Need to reference the working directory. - rel = wd - } else { - // Need to extract the given FD. - return linuxerr.EBADF - } - - // Lookup the node. - remainingTraversals := uint(linux.MaxSymlinkTraversals) - if resolve { - d, err = mntns.FindInode(ctx, root, rel, path, &remainingTraversals) - } else { - d, err = mntns.FindLink(ctx, root, rel, path, &remainingTraversals) - } - if err != nil { - return err - } - - err = fn(root, d) - d.DecRef(ctx) - return err -} - -func BenchmarkVFS2TmpfsStat(b *testing.B) { +func BenchmarkTmpfsStat(b *testing.B) { for _, depth := range depths { b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { ctx := contexttest.Context(b) @@ -179,7 +140,7 @@ func BenchmarkVFS2TmpfsStat(b *testing.B) { } } -func BenchmarkVFS2TmpfsMountStat(b *testing.B) { +func BenchmarkTmpfsMountStat(b *testing.B) { for _, depth := range depths { b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { ctx := contexttest.Context(b) @@ -298,7 +259,6 @@ func BenchmarkVFS2TmpfsMountStat(b *testing.B) { } func init() { - // Turn off reference leak checking for a fair comparison between vfs1 and - // vfs2. + // Turn off reference leak checking for a benchmarking. refs.SetLeakMode(refs.NoLeakChecking) } diff --git a/pkg/sentry/fsimpl/tmpfs/regular_file.go b/pkg/sentry/fsimpl/tmpfs/regular_file.go index 2efb57102..6daec5bdd 100644 --- a/pkg/sentry/fsimpl/tmpfs/regular_file.go +++ b/pkg/sentry/fsimpl/tmpfs/regular_file.go @@ -25,9 +25,8 @@ import ( "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/fsutil" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sentry/pgalloc" @@ -210,8 +209,8 @@ func (rf *regularFile) truncateLocked(newSize uint64) (bool, error) { rf.dataMu.Unlock() // Invalidate past translations of truncated pages. - oldpgend := fs.OffsetPageEnd(int64(oldSize)) - newpgend := fs.OffsetPageEnd(int64(newSize)) + oldpgend := offsetPageEnd(int64(oldSize)) + newpgend := offsetPageEnd(int64(newSize)) if newpgend < oldpgend { rf.mapsMu.Lock() rf.mappings.Invalidate(memmap.MappableRange{newpgend, oldpgend}, memmap.InvalidateOpts{ @@ -289,7 +288,7 @@ func (rf *regularFile) Translate(ctx context.Context, required, optional memmap. // Constrain translations to f.attr.Size (rounded up) to prevent // translation to pages that may be concurrently truncated. - pgend := fs.OffsetPageEnd(int64(rf.size.RacyLoad())) + pgend := offsetPageEnd(int64(rf.size.RacyLoad())) var beyondEOF bool if required.End > pgend { if required.Start >= pgend { @@ -558,6 +557,17 @@ func (fd *regularFileFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpt return vfs.GenericConfigureMMap(&fd.vfsfd, file, opts) } +// offsetPageEnd returns the file offset rounded up to the nearest +// page boundary. offsetPageEnd panics if rounding up causes overflow, +// which shouldn't be possible given that offset is an int64. +func offsetPageEnd(offset int64) uint64 { + end, ok := hostarch.Addr(offset).RoundUp() + if !ok { + panic("impossible overflow") + } + return uint64(end) +} + // regularFileReadWriter implements safemem.Reader and Safemem.Writer. type regularFileReadWriter struct { file *regularFile diff --git a/pkg/sentry/fsmetric/fsmetric.go b/pkg/sentry/fsmetric/fsmetric.go index 30248a8fa..3855ef7e3 100644 --- a/pkg/sentry/fsmetric/fsmetric.go +++ b/pkg/sentry/fsmetric/fsmetric.go @@ -12,11 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package fsmetric defines filesystem metrics that are used by both VFS1 and -// VFS2. -// -// TODO(gvisor.dev/issue/1624): Once VFS1 is deleted, inline these metrics into -// VFS2. +// Package fsmetric defines filesystem metrics. package fsmetric import ( diff --git a/pkg/sentry/fs/fsutil/BUILD b/pkg/sentry/fsutil/BUILD similarity index 87% rename from pkg/sentry/fs/fsutil/BUILD rename to pkg/sentry/fsutil/BUILD index 1a59800ea..62c37ec3e 100644 --- a/pkg/sentry/fs/fsutil/BUILD +++ b/pkg/sentry/fsutil/BUILD @@ -59,7 +59,6 @@ go_library( srcs = [ "dirty_set.go", "dirty_set_impl.go", - "file.go", "file_range_set.go", "file_range_set_impl.go", "frame_ref_set.go", @@ -68,9 +67,6 @@ go_library( "host_file_mapper.go", "host_file_mapper_state.go", "host_file_mapper_unsafe.go", - "host_mappable.go", - "inode.go", - "inode_cached.go", ], visibility = ["//pkg/sentry:internal"], deps = [ @@ -82,7 +78,6 @@ go_library( "//pkg/safemem", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs", "//pkg/sentry/kernel/time", "//pkg/sentry/memmap", "//pkg/sentry/pgalloc", @@ -101,18 +96,10 @@ go_test( size = "small", srcs = [ "dirty_set_test.go", - "inode_cached_test.go", ], library = ":fsutil", deps = [ - "//pkg/context", - "//pkg/errors/linuxerr", "//pkg/hostarch", - "//pkg/safemem", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - "//pkg/sentry/kernel/time", "//pkg/sentry/memmap", - "//pkg/usermem", ], ) diff --git a/pkg/sentry/fs/fsutil/README.md b/pkg/sentry/fsutil/README.md similarity index 100% rename from pkg/sentry/fs/fsutil/README.md rename to pkg/sentry/fsutil/README.md diff --git a/pkg/sentry/fs/fsutil/dirty_set.go b/pkg/sentry/fsutil/dirty_set.go similarity index 100% rename from pkg/sentry/fs/fsutil/dirty_set.go rename to pkg/sentry/fsutil/dirty_set.go diff --git a/pkg/sentry/fs/fsutil/dirty_set_test.go b/pkg/sentry/fsutil/dirty_set_test.go similarity index 100% rename from pkg/sentry/fs/fsutil/dirty_set_test.go rename to pkg/sentry/fsutil/dirty_set_test.go diff --git a/pkg/sentry/fs/fsutil/file_range_set.go b/pkg/sentry/fsutil/file_range_set.go similarity index 100% rename from pkg/sentry/fs/fsutil/file_range_set.go rename to pkg/sentry/fsutil/file_range_set.go diff --git a/pkg/sentry/fs/fsutil/frame_ref_set.go b/pkg/sentry/fsutil/frame_ref_set.go similarity index 100% rename from pkg/sentry/fs/fsutil/frame_ref_set.go rename to pkg/sentry/fsutil/frame_ref_set.go diff --git a/pkg/sentry/socket/hostinet/save_restore.go b/pkg/sentry/fsutil/fsutil.go similarity index 80% rename from pkg/sentry/socket/hostinet/save_restore.go rename to pkg/sentry/fsutil/fsutil.go index 1dec33897..2ed0c4d5f 100644 --- a/pkg/sentry/socket/hostinet/save_restore.go +++ b/pkg/sentry/fsutil/fsutil.go @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -package hostinet - -// beforeSave is invoked by stateify. -func (*socketOperations) beforeSave() { - panic("host.socketOperations is not savable") -} +// Package fsutil provides utilities for implementing vfs.FileDescriptionImpl +// and vfs.FilesystemImpl. +package fsutil diff --git a/pkg/sentry/fs/fsutil/host_file_mapper.go b/pkg/sentry/fsutil/host_file_mapper.go similarity index 100% rename from pkg/sentry/fs/fsutil/host_file_mapper.go rename to pkg/sentry/fsutil/host_file_mapper.go diff --git a/pkg/sentry/fs/fsutil/host_file_mapper_state.go b/pkg/sentry/fsutil/host_file_mapper_state.go similarity index 100% rename from pkg/sentry/fs/fsutil/host_file_mapper_state.go rename to pkg/sentry/fsutil/host_file_mapper_state.go diff --git a/pkg/sentry/fs/fsutil/host_file_mapper_unsafe.go b/pkg/sentry/fsutil/host_file_mapper_unsafe.go similarity index 100% rename from pkg/sentry/fs/fsutil/host_file_mapper_unsafe.go rename to pkg/sentry/fsutil/host_file_mapper_unsafe.go diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index 05c42e2ac..c4e3701da 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -126,18 +126,6 @@ go_template_instance( }, ) -go_template_instance( - name = "socket_list", - out = "socket_list.go", - package = "kernel", - prefix = "socket", - template = "//pkg/ilist:generic_list", - types = { - "Element": "*SocketRecordVFS1", - "Linker": "*SocketRecordVFS1", - }, -) - go_template_instance( name = "fd_table_refs", out = "fd_table_refs.go", @@ -242,7 +230,6 @@ go_library( "signal.go", "signal_handlers.go", "signal_handlers_mutex.go", - "socket_list.go", "syscalls.go", "syscalls_state.go", "syslog.go", @@ -318,7 +305,6 @@ go_library( "//pkg/secio", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs", "//pkg/sentry/fsbridge", "//pkg/sentry/fsimpl/kernfs", "//pkg/sentry/fsimpl/lock", diff --git a/pkg/sentry/kernel/fasync/BUILD b/pkg/sentry/kernel/fasync/BUILD index 6b2dd09da..5c0a86c2e 100644 --- a/pkg/sentry/kernel/fasync/BUILD +++ b/pkg/sentry/kernel/fasync/BUILD @@ -9,7 +9,6 @@ go_library( deps = [ "//pkg/abi/linux", "//pkg/errors/linuxerr", - "//pkg/sentry/fs", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", "//pkg/sentry/vfs", diff --git a/pkg/sentry/kernel/fasync/fasync.go b/pkg/sentry/kernel/fasync/fasync.go index d595ce077..b682f5898 100644 --- a/pkg/sentry/kernel/fasync/fasync.go +++ b/pkg/sentry/kernel/fasync/fasync.go @@ -18,7 +18,6 @@ package fasync import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" @@ -41,17 +40,9 @@ var bandTable = map[waiter.EventMask]int64{ waiter.EventHUp: linux.EPOLLHUP | linux.EPOLLERR, } -// New returns a function that creates a new fs.FileAsync with the given file -// descriptor. -func New(fd int) func() fs.FileAsync { - return func() fs.FileAsync { - return &FileAsync{fd: fd} - } -} - -// NewVFS2 returns a function that creates a new vfs.FileAsync with the given +// New returns a function that creates a new vfs.FileAsync with the given // file descriptor. -func NewVFS2(fd int) func() vfs.FileAsync { +func New(fd int) func() vfs.FileAsync { return func() vfs.FileAsync { return &FileAsync{fd: fd} } diff --git a/pkg/sentry/kernel/fd_table.go b/pkg/sentry/kernel/fd_table.go index c724f8ecf..f9e31a9c1 100644 --- a/pkg/sentry/kernel/fd_table.go +++ b/pkg/sentry/kernel/fd_table.go @@ -24,7 +24,6 @@ import ( "gvisor.dev/gvisor/pkg/bitmap" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/lock" "gvisor.dev/gvisor/pkg/sentry/limits" "gvisor.dev/gvisor/pkg/sentry/vfs" @@ -62,14 +61,10 @@ func (f FDFlags) ToLinuxFDFlags() (mask uint) { // Note that this is immutable and can only be changed via operations on the // descriptorTable. // -// It contains both VFS1 and VFS2 file types, but only one of them can be set. -// // +stateify savable type descriptor struct { - // TODO(gvisor.dev/issue/1624): Remove fs.File. - file *fs.File - fileVFS2 *vfs.FileDescription - flags FDFlags + file *vfs.FileDescription + flags FDFlags } // MaxFdLimit defines the upper limit on the integer value of file descriptors. @@ -97,11 +92,10 @@ func (f *FDTable) saveDescriptorTable() map[int32]descriptor { m := make(map[int32]descriptor) f.mu.Lock() defer f.mu.Unlock() - f.forEach(context.Background(), func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) { + f.forEach(context.Background(), func(fd int32, file *vfs.FileDescription, flags FDFlags) { m[fd] = descriptor{ - file: file, - fileVFS2: fileVFS2, - flags: flags, + file: file, + flags: flags, } }) return m @@ -116,46 +110,21 @@ func (f *FDTable) loadDescriptorTable(m map[int32]descriptor) { panic(fmt.Sprintf("FD is not supposed to be negative. FD: %d", fd)) } - if file, fileVFS2 := f.setAll(ctx, fd, d.file, d.fileVFS2, d.flags); file != nil || fileVFS2 != nil { - panic("VFS1 or VFS2 files set") + if file := f.set(ctx, fd, d.file, d.flags); file != nil { + panic("file set") } f.fdBitmap.Add(uint32(fd)) // Note that we do _not_ need to acquire a extra table reference here. The // table reference will already be accounted for in the file, so we drop the // reference taken by set above. - switch { - case d.file != nil: + if d.file != nil { d.file.DecRef(ctx) - case d.fileVFS2 != nil: - d.fileVFS2.DecRef(ctx) } } } // drop drops the table reference. -func (f *FDTable) drop(ctx context.Context, file *fs.File) { - // Release locks. - file.Dirent.Inode.LockCtx.Posix.UnlockRegion(f, lock.LockRange{0, lock.LockEOF}) - - // Send inotify events. - d := file.Dirent - var ev uint32 - if fs.IsDir(d.Inode.StableAttr) { - ev |= linux.IN_ISDIR - } - if file.Flags().Write { - ev |= linux.IN_CLOSE_WRITE - } else { - ev |= linux.IN_CLOSE_NOWRITE - } - d.InotifyEvent(ev, 0) - - // Drop the table reference. - file.DecRef(ctx) -} - -// dropVFS2 drops the table reference. -func (f *FDTable) dropVFS2(ctx context.Context, file *vfs.FileDescription) { +func (f *FDTable) drop(ctx context.Context, file *vfs.FileDescription) { // Release any POSIX lock possibly held by the FDTable. if file.SupportsLocks() { err := file.UnlockPOSIX(ctx, f, lock.LockRange{0, lock.LockEOF}) @@ -180,7 +149,7 @@ func (k *Kernel) NewFDTable() *FDTable { // If f reaches zero references, all of its file descriptors are removed. func (f *FDTable) DecRef(ctx context.Context) { f.FDTableRefs.DecRef(func() { - f.RemoveIf(ctx, func(*fs.File, *vfs.FileDescription, FDFlags) bool { + f.RemoveIf(ctx, func(*vfs.FileDescription, FDFlags) bool { return true }) }) @@ -189,7 +158,7 @@ func (f *FDTable) DecRef(ctx context.Context) { // forEachUpTo iterates over all non-nil files upto maxFds (non-inclusive) in sorted order. // // It is the caller's responsibility to acquire an appropriate lock. -func (f *FDTable) forEachUpTo(ctx context.Context, maxFds int32, fn func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags)) { +func (f *FDTable) forEachUpTo(ctx context.Context, maxFds int32, fn func(fd int32, file *vfs.FileDescription, flags FDFlags)) { // retries tracks the number of failed TryIncRef attempts for the same FD. retries := 0 fds := f.fdBitmap.ToSlice() @@ -199,31 +168,20 @@ func (f *FDTable) forEachUpTo(ctx context.Context, maxFds int32, fn func(fd int3 if fd >= maxFds { break } - file, fileVFS2, flags, ok := f.getAll(fd) + file, flags, ok := f.get(fd) if !ok { break } - switch { - case file != nil: + if file != nil { if !file.TryIncRef() { retries++ if retries > 1000 { - panic(fmt.Sprintf("File in FD table has been destroyed. FD: %d, File: %+v, FileOps: %+v", fd, file, file.FileOperations)) + panic(fmt.Sprintf("File in FD table has been destroyed. FD: %d, File: %+v, Impl: %+v", fd, file, file.Impl())) } continue // Race caught. } - fn(fd, file, nil, flags) + fn(fd, file, flags) file.DecRef(ctx) - case fileVFS2 != nil: - if !fileVFS2.TryIncRef() { - retries++ - if retries > 1000 { - panic(fmt.Sprintf("File in FD table has been destroyed. FD: %d, File: %+v, Impl: %+v", fd, fileVFS2, fileVFS2.Impl())) - } - continue // Race caught. - } - fn(fd, nil, fileVFS2, flags) - fileVFS2.DecRef(ctx) } retries = 0 } @@ -232,7 +190,7 @@ func (f *FDTable) forEachUpTo(ctx context.Context, maxFds int32, fn func(fd int3 // forEach iterates over all non-nil files upto maxFd in sorted order. // // It is the caller's responsibility to acquire an appropriate lock. -func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags)) { +func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *vfs.FileDescription, flags FDFlags)) { f.forEachUpTo(ctx, MaxFdLimit, fn) } @@ -240,20 +198,14 @@ func (f *FDTable) forEach(ctx context.Context, fn func(fd int32, file *fs.File, func (f *FDTable) String() string { var buf strings.Builder ctx := context.Background() - files := make(map[int32]*fs.File) - filesVFS2 := make(map[int32]*vfs.FileDescription) + files := make(map[int32]*vfs.FileDescription) f.mu.Lock() // Can't release f.mu from defer, because vfsObj.PathnameWithDeleted // should not be called under the fdtable mutex. - f.forEach(ctx, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) { - switch { - case file != nil: + f.forEach(ctx, func(fd int32, file *vfs.FileDescription, flags FDFlags) { + if file != nil { file.IncRef() files[fd] = file - - case fileVFS2 != nil: - fileVFS2.IncRef() - filesVFS2[fd] = fileVFS2 } }) f.mu.Unlock() @@ -261,22 +213,15 @@ func (f *FDTable) String() string { for _, f := range files { f.DecRef(ctx) } - for _, f := range filesVFS2 { - f.DecRef(ctx) - } }() - for fd, file := range files { - n, _ := file.Dirent.FullName(nil /* root */) - fmt.Fprintf(&buf, "\tfd:%d => name %s\n", fd, n) - } - for fd, fileVFS2 := range filesVFS2 { - vfsObj := fileVFS2.Mount().Filesystem().VirtualFilesystem() - vd := fileVFS2.VirtualDentry() + for fd, file := range files { + vfsObj := file.Mount().Filesystem().VirtualFilesystem() + vd := file.VirtualDentry() if vd.Dentry() == nil { - panic(fmt.Sprintf("fd %d (type %T) has nil dentry: %#v", fd, fileVFS2.Impl(), fileVFS2)) + panic(fmt.Sprintf("fd %d (type %T) has nil dentry: %#v", fd, file.Impl(), file)) } - name, err := vfsObj.PathnameWithDeleted(ctx, vfs.VirtualDentry{}, fileVFS2.VirtualDentry()) + name, err := vfsObj.PathnameWithDeleted(ctx, vfs.VirtualDentry{}, file.VirtualDentry()) if err != nil { fmt.Fprintf(&buf, "\n", err) continue @@ -289,7 +234,7 @@ func (f *FDTable) String() string { // NewFDs allocates new FDs guaranteed to be the lowest number available // greater than or equal to the minFD parameter. All files will share the set // flags. Success is guaranteed to be all or none. -func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*fs.File, flags FDFlags) (fds []int32, err error) { +func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*vfs.FileDescription, flags FDFlags) (fds []int32, err error) { if minFD < 0 { // Don't accept negative FDs. return nil, unix.EINVAL @@ -363,87 +308,11 @@ func (f *FDTable) NewFDs(ctx context.Context, minFD int32, files []*fs.File, fla return fds, nil } -// NewFDsVFS2 allocates new FDs guaranteed to be the lowest number available -// greater than or equal to the minFD parameter. All files will share the set -// flags. Success is guaranteed to be all or none. -func (f *FDTable) NewFDsVFS2(ctx context.Context, minFD int32, files []*vfs.FileDescription, flags FDFlags) (fds []int32, err error) { - if minFD < 0 { - // Don't accept negative FDs. - return nil, unix.EINVAL - } - - // Default limit. - end := MaxFdLimit - - // Ensure we don't get past the provided limit. - if limitSet := limits.FromContext(ctx); limitSet != nil { - lim := limitSet.Get(limits.NumberOfFiles) - if lim.Cur != limits.Infinity { - end = int32(lim.Cur) - } - if minFD+int32(len(files)) > end { - return nil, unix.EMFILE - } - } - - f.mu.Lock() - - // max is used as the largest number in fdBitmap + 1. - max := int32(0) - - if !f.fdBitmap.IsEmpty() { - max = int32(f.fdBitmap.Maximum()) - max++ - } - - // Adjust max in case it is less than minFD. - if max < minFD { - max = minFD - } - - for len(fds) < len(files) { - // Try to use free bit in fdBitmap. - // If all bits in fdBitmap are used, expand fd to the max. - fd, err := f.fdBitmap.FirstZero(uint32(minFD)) - if err != nil { - fd = uint32(max) - max++ - } - if fd >= uint32(end) { - break - } - f.fdBitmap.Add(fd) - f.setVFS2(ctx, int32(fd), files[len(fds)], flags) - fds = append(fds, int32(fd)) - minFD = int32(fd) - } - // Failure? Unwind existing FDs. - if len(fds) < len(files) { - for _, i := range fds { - f.setVFS2(ctx, i, nil, FDFlags{}) - f.fdBitmap.Remove(uint32(i)) - } - f.mu.Unlock() - - // Drop the reference taken by the call to f.setVFS2() that - // originally installed the file. Don't call f.dropVFS2() - // (generating inotify events, etc.) since the file should - // appear to have never been inserted into f. - for _, file := range files[:len(fds)] { - file.DecRef(ctx) - } - return nil, unix.EMFILE - } - - f.mu.Unlock() - return fds, nil -} - -// NewFDVFS2 allocates a file descriptor greater than or equal to minFD for +// NewFD allocates a file descriptor greater than or equal to minFD for // the given file description. If it succeeds, it takes a reference on file. -func (f *FDTable) NewFDVFS2(ctx context.Context, minFD int32, file *vfs.FileDescription, flags FDFlags) (int32, error) { +func (f *FDTable) NewFD(ctx context.Context, minFD int32, file *vfs.FileDescription, flags FDFlags) (int32, error) { files := []*vfs.FileDescription{file} - fileSlice, error := f.NewFDsVFS2(ctx, minFD, files, flags) + fileSlice, error := f.NewFDs(ctx, minFD, files, flags) if error != nil { return -1, error } @@ -453,8 +322,8 @@ func (f *FDTable) NewFDVFS2(ctx context.Context, minFD int32, file *vfs.FileDesc // NewFDAt sets the file reference for the given FD. If there is an active // reference for that FD, the ref count for that existing reference is // decremented. -func (f *FDTable) NewFDAt(ctx context.Context, fd int32, file *fs.File, flags FDFlags) error { - df, _, err := f.newFDAt(ctx, fd, file, nil, flags) +func (f *FDTable) NewFDAt(ctx context.Context, fd int32, file *vfs.FileDescription, flags FDFlags) error { + df, err := f.newFDAt(ctx, fd, file, flags) if err != nil { return err } @@ -464,30 +333,16 @@ func (f *FDTable) NewFDAt(ctx context.Context, fd int32, file *fs.File, flags FD return nil } -// NewFDAtVFS2 sets the file reference for the given FD. If there is an active -// reference for that FD, the ref count for that existing reference is -// decremented. -func (f *FDTable) NewFDAtVFS2(ctx context.Context, fd int32, file *vfs.FileDescription, flags FDFlags) error { - _, dfVFS2, err := f.newFDAt(ctx, fd, nil, file, flags) - if err != nil { - return err - } - if dfVFS2 != nil { - f.dropVFS2(ctx, dfVFS2) - } - return nil -} - -func (f *FDTable) newFDAt(ctx context.Context, fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) (*fs.File, *vfs.FileDescription, error) { +func (f *FDTable) newFDAt(ctx context.Context, fd int32, file *vfs.FileDescription, flags FDFlags) (*vfs.FileDescription, error) { if fd < 0 { // Don't accept negative FDs. - return nil, nil, unix.EBADF + return nil, unix.EBADF } // Check the limit for the provided file. if limitSet := limits.FromContext(ctx); limitSet != nil { if lim := limitSet.Get(limits.NumberOfFiles); lim.Cur != limits.Infinity && uint64(fd) >= lim.Cur { - return nil, nil, unix.EMFILE + return nil, unix.EMFILE } } @@ -495,13 +350,13 @@ func (f *FDTable) newFDAt(ctx context.Context, fd int32, file *fs.File, fileVFS2 f.mu.Lock() defer f.mu.Unlock() - df, dfVFS2 := f.setAll(ctx, fd, file, fileVFS2, flags) + df := f.set(ctx, fd, file, flags) // Add fd to fdBitmap. - if file != nil || fileVFS2 != nil { + if file != nil { f.fdBitmap.Add(uint32(fd)) } - return df, dfVFS2, nil + return df, nil } // SetFlags sets the flags for the given file descriptor. @@ -539,50 +394,8 @@ func (f *FDTable) SetFlagsForRange(ctx context.Context, startFd int32, endFd int for fd, err := f.fdBitmap.FirstOne(uint32(startFd)); err == nil && fd <= uint32(endFd); fd, err = f.fdBitmap.FirstOne(fd + 1) { fdI32 := int32(fd) - fd, _, _ := f.getVFS2(fdI32) - f.setVFS2(ctx, fdI32, fd, flags) - } - - return nil -} - -// SetFlagsVFS2 sets the flags for the given file descriptor. -// -// True is returned iff flags were changed. -func (f *FDTable) SetFlagsVFS2(ctx context.Context, fd int32, flags FDFlags) error { - if fd < 0 { - // Don't accept negative FDs. - return unix.EBADF - } - - f.mu.Lock() - defer f.mu.Unlock() - - file, _, _ := f.getVFS2(fd) - if file == nil { - // No file found. - return unix.EBADF - } - - // Update the flags. - f.setVFS2(ctx, fd, file, flags) - return nil -} - -// SetFlagsForRangeVFS2 sets the flags for the given range of file descriptors -// (inclusive: [startFd, endFd]). -func (f *FDTable) SetFlagsForRangeVFS2(ctx context.Context, startFd int32, endFd int32, flags FDFlags) error { - if startFd < 0 || startFd > endFd { - return unix.EBADF - } - - f.mu.Lock() - defer f.mu.Unlock() - - for fd, err := f.fdBitmap.FirstOne(uint32(startFd)); err == nil && fd <= uint32(endFd); fd, err = f.fdBitmap.FirstOne(fd + 1) { - fdI32 := int32(fd) - file, _, _ := f.getVFS2(fdI32) - f.setVFS2(ctx, fdI32, file, flags) + file, _, _ := f.get(fdI32) + f.set(ctx, fdI32, file, flags) } return nil @@ -594,7 +407,7 @@ func (f *FDTable) SetFlagsForRangeVFS2(ctx context.Context, startFd int32, endFd // N.B. Callers are required to use DecRef when they are done. // //go:nosplit -func (f *FDTable) Get(fd int32) (*fs.File, FDFlags) { +func (f *FDTable) Get(fd int32) (*vfs.FileDescription, FDFlags) { if fd < 0 { return nil, FDFlags{} } @@ -613,31 +426,6 @@ func (f *FDTable) Get(fd int32) (*fs.File, FDFlags) { } } -// GetVFS2 returns a reference to the file and the flags for the FD or nil if no -// file is defined for the given fd. -// -// N.B. Callers are required to use DecRef when they are done. -// -//go:nosplit -func (f *FDTable) GetVFS2(fd int32) (*vfs.FileDescription, FDFlags) { - if fd < 0 { - return nil, FDFlags{} - } - - for { - file, flags, _ := f.getVFS2(fd) - if file != nil { - if !file.TryIncRef() { - continue // Race caught. - } - // Reference acquired. - return file, flags - } - // No file available. - return nil, FDFlags{} - } -} - // GetFDs returns a sorted list of valid fds. // // Precondition: The caller must be running on the task goroutine, or Task.mu @@ -646,7 +434,7 @@ func (f *FDTable) GetFDs(ctx context.Context) []int32 { f.mu.Lock() defer f.mu.Unlock() fds := make([]int32, 0, int(f.fdBitmap.GetNumOnes())) - f.forEach(ctx, func(fd int32, _ *fs.File, _ *vfs.FileDescription, _ FDFlags) { + f.forEach(ctx, func(fd int32, _ *vfs.FileDescription, _ FDFlags) { fds = append(fds, fd) }) return fds @@ -657,11 +445,11 @@ func (f *FDTable) Fork(ctx context.Context, maxFds int32) *FDTable { clone := f.k.NewFDTable() f.mu.Lock() defer f.mu.Unlock() - f.forEachUpTo(ctx, maxFds, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) { + f.forEachUpTo(ctx, maxFds, func(fd int32, file *vfs.FileDescription, flags FDFlags) { // The set function here will acquire an appropriate table // reference for the clone. We don't need anything else. - if df, dfVFS2 := clone.setAll(ctx, fd, file, fileVFS2, flags); df != nil || dfVFS2 != nil { - panic("VFS1 or VFS2 files set") + if df := clone.set(ctx, fd, file, flags); df != nil { + panic("file set") } clone.fdBitmap.Add(uint32(fd)) }) @@ -672,25 +460,17 @@ func (f *FDTable) Fork(ctx context.Context, maxFds int32) *FDTable { // iff successful. // // N.B. Callers are required to use DecRef on the returned file when they are done. -func (f *FDTable) Remove(ctx context.Context, fd int32) (*fs.File, *vfs.FileDescription) { +func (f *FDTable) Remove(ctx context.Context, fd int32) *vfs.FileDescription { if fd < 0 { - return nil, nil + return nil } f.mu.Lock() - - file, fileVFS2, _, _ := f.getAll(fd) - - // Add reference for caller. - switch { - case file != nil: + file, _, _ := f.get(fd) + if file != nil { + // Add reference for caller. file.IncRef() - case fileVFS2 != nil: - fileVFS2.IncRef() - } - - if file != nil || fileVFS2 != nil { - file, fileVFS2 = f.setAll(ctx, fd, nil, nil, FDFlags{}) // Zap entry. + file = f.set(ctx, fd, nil, FDFlags{}) // Zap entry. f.fdBitmap.Remove(uint32(fd)) } f.mu.Unlock() @@ -698,30 +478,21 @@ func (f *FDTable) Remove(ctx context.Context, fd int32) (*fs.File, *vfs.FileDesc if file != nil { f.drop(ctx, file) } - if fileVFS2 != nil { - f.dropVFS2(ctx, fileVFS2) - } - - return file, fileVFS2 + return file } // RemoveIf removes all FDs where cond is true. -func (f *FDTable) RemoveIf(ctx context.Context, cond func(*fs.File, *vfs.FileDescription, FDFlags) bool) { - // TODO(gvisor.dev/issue/1624): Remove fs.File slice. - var files []*fs.File - var filesVFS2 []*vfs.FileDescription +func (f *FDTable) RemoveIf(ctx context.Context, cond func(*vfs.FileDescription, FDFlags) bool) { + var files []*vfs.FileDescription f.mu.Lock() - f.forEach(ctx, func(fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) { - if cond(file, fileVFS2, flags) { - df, dfVFS2 := f.setAll(ctx, fd, nil, nil, FDFlags{}) // Clear from table. + f.forEach(ctx, func(fd int32, file *vfs.FileDescription, flags FDFlags) { + if cond(file, flags) { + df := f.set(ctx, fd, nil, FDFlags{}) // Clear from table. f.fdBitmap.Remove(uint32(fd)) if df != nil { files = append(files, df) } - if dfVFS2 != nil { - filesVFS2 = append(filesVFS2, dfVFS2) - } } }) f.mu.Unlock() @@ -729,19 +500,15 @@ func (f *FDTable) RemoveIf(ctx context.Context, cond func(*fs.File, *vfs.FileDes for _, file := range files { f.drop(ctx, file) } - - for _, file := range filesVFS2 { - f.dropVFS2(ctx, file) - } } // RemoveNextInRange removes the next FD that falls within the given range, // and returns a tuple where one of the files is non-nil iff successful. // // N.B. Callers are required to use DecRef on the returned file when they are done. -func (f *FDTable) RemoveNextInRange(ctx context.Context, startFd int32, endFd int32) (int32, *fs.File, *vfs.FileDescription) { +func (f *FDTable) RemoveNextInRange(ctx context.Context, startFd int32, endFd int32) (int32, *vfs.FileDescription) { if startFd < 0 || startFd > endFd { - return MaxFdLimit, nil, nil + return MaxFdLimit, nil } f.mu.Lock() @@ -750,20 +517,14 @@ func (f *FDTable) RemoveNextInRange(ctx context.Context, startFd int32, endFd in fd := int32(fdUint) if err != nil || fd > endFd { f.mu.Unlock() - return MaxFdLimit, nil, nil + return MaxFdLimit, nil } - file, fileVFS2, _, _ := f.getAll(fd) + file, _, _ := f.get(fd) - // Add reference for caller. - switch { - case file != nil: + if file != nil { + // Add reference for caller. file.IncRef() - case fileVFS2 != nil: - fileVFS2.IncRef() - } - - if file != nil || fileVFS2 != nil { - file, fileVFS2 = f.setAll(ctx, fd, nil, nil, FDFlags{}) // Zap entry. + file = f.set(ctx, fd, nil, FDFlags{}) // Zap entry. f.fdBitmap.Remove(uint32(fd)) } f.mu.Unlock() @@ -771,11 +532,8 @@ func (f *FDTable) RemoveNextInRange(ctx context.Context, startFd int32, endFd in if file != nil { f.drop(ctx, file) } - if fileVFS2 != nil { - f.dropVFS2(ctx, fileVFS2) - } - return fd, file, fileVFS2 + return fd, file } // GetLastFd returns the last set FD in the FDTable bitmap. diff --git a/pkg/sentry/kernel/fd_table_test.go b/pkg/sentry/kernel/fd_table_test.go index 0fb5e5723..a8827b036 100644 --- a/pkg/sentry/kernel/fd_table_test.go +++ b/pkg/sentry/kernel/fd_table_test.go @@ -81,22 +81,22 @@ func runTest(t testing.TB, fn func(ctx context.Context, fdTable *FDTable, fd *vf func TestFDTableMany(t *testing.T) { runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { for i := 0; i < maxFD; i++ { - if _, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { + if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { t.Fatalf("Allocated %v FDs but wanted to allocate %v", i, maxFD) } } - if _, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { + if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { t.Fatalf("fdTable.NewFDs(0, r) in full map: got nil, wanted error") } - if err := fdTable.NewFDAtVFS2(ctx, 1, fd, FDFlags{}); err != nil { + if err := fdTable.NewFDAt(ctx, 1, fd, FDFlags{}); err != nil { t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) } i := int32(2) fdTable.Remove(ctx, i) - if fds, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil || fds[0] != i { + if fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil || fds[0] != i { t.Fatalf("Allocated %v FDs but wanted to allocate %v: %v", i, maxFD, err) } }) @@ -104,15 +104,15 @@ func TestFDTableMany(t *testing.T) { func TestFDTableOverLimit(t *testing.T) { runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { - if _, err := fdTable.NewFDsVFS2(ctx, maxFD, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { + if _, err := fdTable.NewFDs(ctx, maxFD, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { t.Fatalf("fdTable.NewFDs(maxFD, f): got nil, wanted error") } - if _, err := fdTable.NewFDsVFS2(ctx, maxFD-2, []*vfs.FileDescription{fd, fd, fd}, FDFlags{}); err == nil { + if _, err := fdTable.NewFDs(ctx, maxFD-2, []*vfs.FileDescription{fd, fd, fd}, FDFlags{}); err == nil { t.Fatalf("fdTable.NewFDs(maxFD-2, {f,f,f}): got nil, wanted error") } - if fds, err := fdTable.NewFDsVFS2(ctx, maxFD-3, []*vfs.FileDescription{fd, fd, fd}, FDFlags{}); err != nil { + if fds, err := fdTable.NewFDs(ctx, maxFD-3, []*vfs.FileDescription{fd, fd, fd}, FDFlags{}); err != nil { t.Fatalf("fdTable.NewFDs(maxFD-3, {f,f,f}): got %v, wanted nil", err) } else { for _, fd := range fds { @@ -120,11 +120,11 @@ func TestFDTableOverLimit(t *testing.T) { } } - if fds, err := fdTable.NewFDsVFS2(ctx, maxFD-1, []*vfs.FileDescription{fd}, FDFlags{}); err != nil || fds[0] != maxFD-1 { + if fds, err := fdTable.NewFDs(ctx, maxFD-1, []*vfs.FileDescription{fd}, FDFlags{}); err != nil || fds[0] != maxFD-1 { t.Fatalf("fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) } - if fds, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { + if fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { t.Fatalf("Adding an FD to a resized map: got %v, want nil", err) } else if len(fds) != 1 || fds[0] != 0 { t.Fatalf("Added an FD to a resized map: got %v, want {1}", fds) @@ -140,46 +140,46 @@ func TestFDTable(t *testing.T) { // Cap the limit at one. limitSet.Set(limits.NumberOfFiles, limits.Limit{1, maxFD}, true) - if _, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { + if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { t.Fatalf("Adding an FD to an empty 1-size map: got %v, want nil", err) } - if _, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { + if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err == nil { t.Fatalf("Adding an FD to a filled 1-size map: got nil, wanted an error") } // Remove the previous limit. limitSet.Set(limits.NumberOfFiles, limits.Limit{maxFD, maxFD}, true) - if fds, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { + if fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { t.Fatalf("Adding an FD to a resized map: got %v, want nil", err) } else if len(fds) != 1 || fds[0] != 1 { t.Fatalf("Added an FD to a resized map: got %v, want {1}", fds) } - if err := fdTable.NewFDAtVFS2(ctx, 1, fd, FDFlags{}); err != nil { + if err := fdTable.NewFDAt(ctx, 1, fd, FDFlags{}); err != nil { t.Fatalf("Replacing FD 1 via fdTable.NewFDAt(1, r, FDFlags{}): got %v, wanted nil", err) } - if err := fdTable.NewFDAtVFS2(ctx, maxFD+1, fd, FDFlags{}); err == nil { + if err := fdTable.NewFDAt(ctx, maxFD+1, fd, FDFlags{}); err == nil { t.Fatalf("Using an FD that was too large via fdTable.NewFDAt(%v, r, FDFlags{}): got nil, wanted an error", maxFD+1) } - if ref, _ := fdTable.GetVFS2(1); ref == nil { - t.Fatalf("fdTable.GetVFS2(1): got nil, wanted %v", fd) + if ref, _ := fdTable.Get(1); ref == nil { + t.Fatalf("fdTable.Get(1): got nil, wanted %v", fd) } - if ref, _ := fdTable.GetVFS2(2); ref != nil { - t.Fatalf("fdTable.GetVFS2(2): got a %v, wanted nil", ref) + if ref, _ := fdTable.Get(2); ref != nil { + t.Fatalf("fdTable.Get(2): got a %v, wanted nil", ref) } - _, ref := fdTable.Remove(ctx, 1) + ref := fdTable.Remove(ctx, 1) if ref == nil { t.Fatalf("fdTable.Remove(1) for an existing FD: failed, want success") } ref.DecRef(ctx) - if _, ref := fdTable.Remove(ctx, 1); ref != nil { + if ref := fdTable.Remove(ctx, 1); ref != nil { t.Fatalf("r.Remove(1) for a removed FD: got success, want failure") } }) @@ -187,13 +187,13 @@ func TestFDTable(t *testing.T) { func TestDescriptorFlags(t *testing.T) { runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { - if err := fdTable.NewFDAtVFS2(ctx, 2, fd, FDFlags{CloseOnExec: true}); err != nil { + if err := fdTable.NewFDAt(ctx, 2, fd, FDFlags{CloseOnExec: true}); err != nil { t.Fatalf("fdTable.NewFDAt(2, r, FDFlags{}): got %v, wanted nil", err) } - newFile, flags := fdTable.GetVFS2(2) + newFile, flags := fdTable.Get(2) if newFile == nil { - t.Fatalf("fdTable.GetVFS2(2): got a %v, wanted nil", newFile) + t.Fatalf("fdTable.Get(2): got a %v, wanted nil", newFile) } if !flags.CloseOnExec { @@ -206,14 +206,14 @@ func BenchmarkFDLookupAndDecRef(b *testing.B) { b.StopTimer() // Setup. runTest(b, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { - fds, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd, fd, fd, fd, fd}, FDFlags{}) + fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd, fd, fd, fd, fd}, FDFlags{}) if err != nil { b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) } b.StartTimer() // Benchmark. for i := 0; i < b.N; i++ { - tf, _ := fdTable.GetVFS2(fds[i%len(fds)]) + tf, _ := fdTable.Get(fds[i%len(fds)]) tf.DecRef(ctx) } }) @@ -223,7 +223,7 @@ func BenchmarkFDLookupAndDecRefConcurrent(b *testing.B) { b.StopTimer() // Setup. runTest(b, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { - fds, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd, fd, fd, fd, fd}, FDFlags{}) + fds, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd, fd, fd, fd, fd}, FDFlags{}) if err != nil { b.Fatalf("fdTable.NewFDs: got %v, wanted nil", err) } @@ -241,7 +241,7 @@ func BenchmarkFDLookupAndDecRefConcurrent(b *testing.B) { go func() { defer wg.Done() for i := 0; i < each; i++ { - tf, _ := fdTable.GetVFS2(fds[i%len(fds)]) + tf, _ := fdTable.Get(fds[i%len(fds)]) tf.DecRef(ctx) } }() @@ -266,7 +266,7 @@ func TestSetFlagsForRange(t *testing.T) { for _, test := range testCases { runTest(t, func(ctx context.Context, fdTable *FDTable, fd *vfs.FileDescription, _ *limits.LimitSet) { for i := 0; i < maxFD; i++ { - if _, err := fdTable.NewFDsVFS2(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { + if _, err := fdTable.NewFDs(ctx, 0, []*vfs.FileDescription{fd}, FDFlags{}); err != nil { t.Fatalf("testCase: %v\nfdTable.NewFDs(_, 0, %+v, FDFlags{}): %d, want: nil", test, []*vfs.FileDescription{fd}, err) } } @@ -282,9 +282,9 @@ func TestSetFlagsForRange(t *testing.T) { testRangeFlags := func(start int32, end int32, expected FDFlags) { for i := start; i <= end; i++ { - file, flags := fdTable.GetVFS2(i) + file, flags := fdTable.Get(i) if file == nil || flags != expected { - t.Fatalf("testCase: %v\nfdTable.GetVFS2(%d): (%v, %v), wanted (non-nil, %v)", test, i, file, flags, expected) + t.Fatalf("testCase: %v\nfdTable.Get(%d): (%v, %v), wanted (non-nil, %v)", test, i, file, flags, expected) } } } diff --git a/pkg/sentry/kernel/fd_table_unsafe.go b/pkg/sentry/kernel/fd_table_unsafe.go index 2b3e6ef71..549c1f073 100644 --- a/pkg/sentry/kernel/fd_table_unsafe.go +++ b/pkg/sentry/kernel/fd_table_unsafe.go @@ -21,7 +21,6 @@ import ( "gvisor.dev/gvisor/pkg/bitmap" "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/vfs" ) @@ -54,39 +53,16 @@ func (f *FDTable) init() { // The boolean indicates whether this was in range. // //go:nosplit -func (f *FDTable) get(fd int32) (*fs.File, FDFlags, bool) { - file, _, flags, ok := f.getAll(fd) - return file, flags, ok -} - -// getVFS2 gets a file entry. -// -// The boolean indicates whether this was in range. -// -//go:nosplit -func (f *FDTable) getVFS2(fd int32) (*vfs.FileDescription, FDFlags, bool) { - _, file, flags, ok := f.getAll(fd) - return file, flags, ok -} - -// getAll gets a file entry. -// -// The boolean indicates whether this was in range. -// -//go:nosplit -func (f *FDTable) getAll(fd int32) (*fs.File, *vfs.FileDescription, FDFlags, bool) { +func (f *FDTable) get(fd int32) (*vfs.FileDescription, FDFlags, bool) { slice := *(*[]unsafe.Pointer)(atomic.LoadPointer(&f.slice)) if fd >= int32(len(slice)) { - return nil, nil, FDFlags{}, false + return nil, FDFlags{}, false } d := (*descriptor)(atomic.LoadPointer(&slice[fd])) if d == nil { - return nil, nil, FDFlags{}, true + return nil, FDFlags{}, true } - if d.file != nil && d.fileVFS2 != nil { - panic("VFS1 and VFS2 files set") - } - return d.file, d.fileVFS2, d.flags, true + return d.file, d.flags, true } // CurrentMaxFDs returns the number of file descriptors that may be stored in f @@ -96,34 +72,14 @@ func (f *FDTable) CurrentMaxFDs() int { return len(slice) } -// set sets an entry for VFS1, refer to setAll(). -// -// Precondition: mu must be held. -func (f *FDTable) set(ctx context.Context, fd int32, file *fs.File, flags FDFlags) *fs.File { - dropFile, _ := f.setAll(ctx, fd, file, nil, flags) - return dropFile -} - -// setVFS2 sets an entry for VFS2, refer to setAll(). -// -// Precondition: mu must be held. -func (f *FDTable) setVFS2(ctx context.Context, fd int32, file *vfs.FileDescription, flags FDFlags) *vfs.FileDescription { - _, dropFile := f.setAll(ctx, fd, nil, file, flags) - return dropFile -} - -// setAll sets the file description referred to by fd to file/fileVFS2. If -// file/fileVFS2 are non-nil, it takes a reference on them. If setAll replaces +// set sets the file description referred to by fd to file. If +// file is non-nil, it takes a reference on them. If setAll replaces // an existing file description, it returns it with the FDTable's reference -// transferred to the caller, which must call f.drop/dropVFS2() on the returned +// transferred to the caller, which must call f.drop on the returned // file after unlocking f.mu. // // Precondition: mu must be held. -func (f *FDTable) setAll(ctx context.Context, fd int32, file *fs.File, fileVFS2 *vfs.FileDescription, flags FDFlags) (*fs.File, *vfs.FileDescription) { - if file != nil && fileVFS2 != nil { - panic("VFS1 and VFS2 files set") - } - +func (f *FDTable) set(ctx context.Context, fd int32, file *vfs.FileDescription, flags FDFlags) *vfs.FileDescription { slicePtr := (*[]unsafe.Pointer)(atomic.LoadPointer(&f.slice)) // Grow the table as required. @@ -140,11 +96,10 @@ func (f *FDTable) setAll(ctx context.Context, fd int32, file *fs.File, fileVFS2 slice := *slicePtr var desc *descriptor - if file != nil || fileVFS2 != nil { + if file != nil { desc = &descriptor{ - file: file, - fileVFS2: fileVFS2, - flags: flags, + file: file, + flags: flags, } } @@ -152,30 +107,16 @@ func (f *FDTable) setAll(ctx context.Context, fd int32, file *fs.File, fileVFS2 orig := (*descriptor)(atomic.SwapPointer(&slice[fd], unsafe.Pointer(desc))) // Acquire a table reference. - if desc != nil { - switch { - case desc.file != nil: - if orig == nil || desc.file != orig.file { - desc.file.IncRef() - } - case desc.fileVFS2 != nil: - if orig == nil || desc.fileVFS2 != orig.fileVFS2 { - desc.fileVFS2.IncRef() - } + if desc != nil && desc.file != nil { + if orig == nil || desc.file != orig.file { + desc.file.IncRef() } } - if orig != nil { - switch { - case orig.file != nil: - if desc == nil || desc.file != orig.file { - return orig.file, nil - } - case orig.fileVFS2 != nil: - if desc == nil || desc.fileVFS2 != orig.fileVFS2 { - return nil, orig.fileVFS2 - } + if orig != nil && orig.file != nil { + if desc == nil || desc.file != orig.file { + return orig.file } } - return nil, nil + return nil } diff --git a/pkg/sentry/kernel/fs_context.go b/pkg/sentry/kernel/fs_context.go index 2606f8db4..80a1ff7aa 100644 --- a/pkg/sentry/kernel/fs_context.go +++ b/pkg/sentry/kernel/fs_context.go @@ -18,7 +18,6 @@ import ( "fmt" "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" ) @@ -34,19 +33,11 @@ type FSContext struct { // mu protects below. mu sync.Mutex `state:"nosave"` - // root is the filesystem root. Will be nil iff the FSContext has been - // destroyed. - root *fs.Dirent + // root is the filesystem root. + root vfs.VirtualDentry - // rootVFS2 is the filesystem root. - rootVFS2 vfs.VirtualDentry - - // cwd is the current working directory. Will be nil iff the FSContext - // has been destroyed. - cwd *fs.Dirent - - // cwdVFS2 is the current working directory. - cwdVFS2 vfs.VirtualDentry + // cwd is the current working directory. + cwd vfs.VirtualDentry // umask is the current file mode creation mask. When a thread using this // context invokes a syscall that creates a file, bits set in umask are @@ -59,9 +50,9 @@ func NewFSContext(root, cwd vfs.VirtualDentry, umask uint) *FSContext { root.IncRef() cwd.IncRef() f := FSContext{ - rootVFS2: root, - cwdVFS2: cwd, - umask: umask, + root: root, + cwd: cwd, + umask: umask, } f.InitRefs() return &f @@ -82,10 +73,10 @@ func (f *FSContext) DecRef(ctx context.Context) { f.mu.Lock() defer f.mu.Unlock() - f.rootVFS2.DecRef(ctx) - f.rootVFS2 = vfs.VirtualDentry{} - f.cwdVFS2.DecRef(ctx) - f.cwdVFS2 = vfs.VirtualDentry{} + f.root.DecRef(ctx) + f.root = vfs.VirtualDentry{} + f.cwd.DecRef(ctx) + f.cwd = vfs.VirtualDentry{} }) } @@ -96,18 +87,16 @@ func (f *FSContext) Fork() *FSContext { f.mu.Lock() defer f.mu.Unlock() - if !f.cwdVFS2.Ok() { + if !f.cwd.Ok() { panic("FSContext.Fork() called after destroy") } - f.cwdVFS2.IncRef() - f.rootVFS2.IncRef() + f.cwd.IncRef() + f.root.IncRef() ctx := &FSContext{ - cwd: f.cwd, - root: f.root, - cwdVFS2: f.cwdVFS2, - rootVFS2: f.rootVFS2, - umask: f.umask, + cwd: f.cwd, + root: f.root, + umask: f.umask, } ctx.InitRefs() return ctx @@ -115,45 +104,27 @@ func (f *FSContext) Fork() *FSContext { // WorkingDirectory returns the current working directory. // -// This will return nil if called after f is destroyed, otherwise it will return -// a Dirent with a reference taken. -func (f *FSContext) WorkingDirectory() *fs.Dirent { +// This will return an empty vfs.VirtualDentry if called after f is +// destroyed, otherwise it will return a Dirent with a reference taken. +func (f *FSContext) WorkingDirectory() vfs.VirtualDentry { f.mu.Lock() defer f.mu.Unlock() - if f.cwd != nil { + if f.cwd.Ok() { f.cwd.IncRef() } return f.cwd } -// WorkingDirectoryVFS2 returns the current working directory. -// -// This will return an empty vfs.VirtualDentry if called after f is -// destroyed, otherwise it will return a Dirent with a reference taken. -func (f *FSContext) WorkingDirectoryVFS2() vfs.VirtualDentry { - f.mu.Lock() - defer f.mu.Unlock() - - if f.cwdVFS2.Ok() { - f.cwdVFS2.IncRef() - } - return f.cwdVFS2 -} - // SetWorkingDirectory sets the current working directory. -// This will take an extra reference on the Dirent. +// This will take an extra reference on the VirtualDentry. // // This is not a valid call after f is destroyed. -func (f *FSContext) SetWorkingDirectory(ctx context.Context, d *fs.Dirent) { - if d == nil { - panic("FSContext.SetWorkingDirectory called with nil dirent") - } - +func (f *FSContext) SetWorkingDirectory(ctx context.Context, d vfs.VirtualDentry) { f.mu.Lock() defer f.mu.Unlock() - if f.cwd == nil { + if !f.cwd.Ok() { panic(fmt.Sprintf("FSContext.SetWorkingDirectory(%v)) called after destroy", d)) } @@ -163,91 +134,38 @@ func (f *FSContext) SetWorkingDirectory(ctx context.Context, d *fs.Dirent) { old.DecRef(ctx) } -// SetWorkingDirectoryVFS2 sets the current working directory. -// This will take an extra reference on the VirtualDentry. -// -// This is not a valid call after f is destroyed. -func (f *FSContext) SetWorkingDirectoryVFS2(ctx context.Context, d vfs.VirtualDentry) { - f.mu.Lock() - defer f.mu.Unlock() - - if !f.cwdVFS2.Ok() { - panic(fmt.Sprintf("FSContext.SetWorkingDirectoryVFS2(%v)) called after destroy", d)) - } - - old := f.cwdVFS2 - f.cwdVFS2 = d - d.IncRef() - old.DecRef(ctx) -} - // RootDirectory returns the current filesystem root. // -// This will return nil if called after f is destroyed, otherwise it will return -// a Dirent with a reference taken. -func (f *FSContext) RootDirectory() *fs.Dirent { +// This will return an empty vfs.VirtualDentry if called after f is +// destroyed, otherwise it will return a Dirent with a reference taken. +func (f *FSContext) RootDirectory() vfs.VirtualDentry { f.mu.Lock() defer f.mu.Unlock() - if f.root != nil { + + if f.root.Ok() { f.root.IncRef() } return f.root } -// RootDirectoryVFS2 returns the current filesystem root. -// -// This will return an empty vfs.VirtualDentry if called after f is -// destroyed, otherwise it will return a Dirent with a reference taken. -func (f *FSContext) RootDirectoryVFS2() vfs.VirtualDentry { - f.mu.Lock() - defer f.mu.Unlock() - - if f.rootVFS2.Ok() { - f.rootVFS2.IncRef() - } - return f.rootVFS2 -} - -// SetRootDirectory sets the root directory. -// This will take an extra reference on the Dirent. +// SetRootDirectory sets the root directory. It takes a reference on vd. // // This is not a valid call after f is destroyed. -func (f *FSContext) SetRootDirectory(ctx context.Context, d *fs.Dirent) { - if d == nil { - panic("FSContext.SetRootDirectory called with nil dirent") +func (f *FSContext) SetRootDirectory(ctx context.Context, vd vfs.VirtualDentry) { + if !vd.Ok() { + panic("FSContext.SetRootDirectory called with zero-value VirtualDentry") } f.mu.Lock() - defer f.mu.Unlock() - if f.root == nil { - panic(fmt.Sprintf("FSContext.SetRootDirectory(%v)) called after destroy", d)) + if !f.root.Ok() { + f.mu.Unlock() + panic(fmt.Sprintf("FSContext.SetRootDirectory(%v)) called after destroy", vd)) } old := f.root - f.root = d - d.IncRef() - old.DecRef(ctx) -} - -// SetRootDirectoryVFS2 sets the root directory. It takes a reference on vd. -// -// This is not a valid call after f is destroyed. -func (f *FSContext) SetRootDirectoryVFS2(ctx context.Context, vd vfs.VirtualDentry) { - if !vd.Ok() { - panic("FSContext.SetRootDirectoryVFS2 called with zero-value VirtualDentry") - } - - f.mu.Lock() - - if !f.rootVFS2.Ok() { - f.mu.Unlock() - panic(fmt.Sprintf("FSContext.SetRootDirectoryVFS2(%v)) called after destroy", vd)) - } - - old := f.rootVFS2 vd.IncRef() - f.rootVFS2 = vd + f.root = vd f.mu.Unlock() old.DecRef(ctx) } diff --git a/pkg/sentry/kernel/ipc/BUILD b/pkg/sentry/kernel/ipc/BUILD index bb5cf1c17..b1f760e40 100644 --- a/pkg/sentry/kernel/ipc/BUILD +++ b/pkg/sentry/kernel/ipc/BUILD @@ -15,7 +15,7 @@ go_library( "//pkg/context", "//pkg/errors/linuxerr", "//pkg/log", - "//pkg/sentry/fs", "//pkg/sentry/kernel/auth", + "//pkg/sentry/vfs", ], ) diff --git a/pkg/sentry/kernel/ipc/object.go b/pkg/sentry/kernel/ipc/object.go index facd157c7..f6d4da74d 100644 --- a/pkg/sentry/kernel/ipc/object.go +++ b/pkg/sentry/kernel/ipc/object.go @@ -21,8 +21,8 @@ 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/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) // Key is a user-provided identifier for IPC objects. @@ -46,14 +46,20 @@ type Object struct { // Key is a user-provided identifier for the IPC object. Immutable. Key Key - // Creator is the user who created the IPC object. Immutable. - Creator fs.FileOwner + // CreatorUID is the UID of user who created the IPC object. Immutable. + CreatorUID auth.KUID - // Owner is the current owner of the IPC object. - Owner fs.FileOwner + // CreatorGID is the GID of user who created the IPC object. Immutable. + CreatorGID auth.KGID - // Perms is the access permissions the IPC object. - Perms fs.FilePermissions + // OwnerUID is the UID of the current owner of the IPC object. Immutable. + OwnerUID auth.KUID + + // OwnerGID is the GID of the current owner of the IPC object. Immutable. + OwnerGID auth.KGID + + // Mode is the access permissions the IPC object. + Mode linux.FileMode } // Mechanism represents a SysV mechanism that holds an IPC object. It can also @@ -77,20 +83,22 @@ type Mechanism interface { // NewObject returns a new, initialized ipc.Object. The newly returned object // doesn't have a valid ID. When the object is registered, the registry assigns // it a new unique ID. -func NewObject(un *auth.UserNamespace, key Key, creator, owner fs.FileOwner, perms fs.FilePermissions) *Object { +func NewObject(un *auth.UserNamespace, key Key, creator, owner *auth.Credentials, mode linux.FileMode) *Object { return &Object{ - UserNS: un, - Key: key, - Creator: creator, - Owner: owner, - Perms: perms, + UserNS: un, + Key: key, + CreatorUID: creator.EffectiveKUID, + CreatorGID: creator.EffectiveKGID, + OwnerUID: owner.EffectiveKUID, + OwnerGID: owner.EffectiveKGID, + Mode: mode, } } // CheckOwnership verifies whether an IPC object may be accessed using creds as // an owner. See ipc/util.c:ipcctl_obtain_check() in Linux. func (o *Object) CheckOwnership(creds *auth.Credentials) bool { - if o.Owner.UID == creds.EffectiveKUID || o.Creator.UID == creds.EffectiveKUID { + if o.OwnerUID == creds.EffectiveKUID || o.CreatorUID == creds.EffectiveKUID { return true } @@ -102,15 +110,15 @@ func (o *Object) CheckOwnership(creds *auth.Credentials) bool { // CheckPermissions verifies whether an IPC object is accessible using creds for // access described by req. See ipc/util.c:ipcperms() in Linux. -func (o *Object) CheckPermissions(creds *auth.Credentials, req fs.PermMask) bool { - p := o.Perms.Other - if o.Owner.UID == creds.EffectiveKUID { - p = o.Perms.User - } else if creds.InGroup(o.Owner.GID) { - p = o.Perms.Group +func (o *Object) CheckPermissions(creds *auth.Credentials, req vfs.AccessTypes) bool { + perms := uint16(o.Mode.Permissions()) + if o.OwnerUID == creds.EffectiveKUID { + perms >>= 6 + } else if creds.InGroup(o.OwnerGID) { + perms >>= 3 } - if p.SupersetOf(req) { + if uint16(req)&perms == uint16(req) { return true } return creds.HasCapabilityIn(linux.CAP_IPC_OWNER, o.UserNS) @@ -142,9 +150,9 @@ func (o *Object) Set(ctx context.Context, perm *linux.IPCPerm) error { // always 0 for the underlying inode. mode := linux.FileMode(perm.Mode & 0x1ff) - o.Perms = fs.FilePermsFromMode(mode) - o.Owner.UID = uid - o.Owner.GID = gid + o.Mode = mode + o.OwnerUID = uid + o.OwnerGID = gid return nil } diff --git a/pkg/sentry/kernel/ipc/registry.go b/pkg/sentry/kernel/ipc/registry.go index 91de19070..9f25c968d 100644 --- a/pkg/sentry/kernel/ipc/registry.go +++ b/pkg/sentry/kernel/ipc/registry.go @@ -19,8 +19,8 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) // Registry is similar to Object, but for registries. It represent an abstract @@ -63,7 +63,7 @@ func (r *Registry) Find(ctx context.Context, key Key, mode linux.FileMode, creat obj := mech.Object() creds := auth.CredentialsFromContext(ctx) - if !obj.CheckPermissions(creds, fs.PermsFromMode(mode)) { + if !obj.CheckPermissions(creds, vfs.AccessTypes(mode&linux.ModeOtherAll)) { // The [calling process / user] does not have permission to access // the set, and does not have the CAP_IPC_OWNER capability in the // user namespace that governs its IPC namespace. diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 34ef07fcd..99ae8a4c9 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -46,9 +46,7 @@ import ( "gvisor.dev/gvisor/pkg/eventchannel" "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsbridge" "gvisor.dev/gvisor/pkg/sentry/fsimpl/pipefs" "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" @@ -253,14 +251,8 @@ type Kernel struct { // danglingEndpoints is used to save / restore tcpip.DanglingEndpoints. danglingEndpoints struct{} `state:".([]tcpip.Endpoint)"` - // sockets is the list of all network sockets in the system. - // Protected by extMu. - // TODO(gvisor.dev/issue/1624): Only used by VFS1. - sockets socketList - - // socketsVFS2 records all network sockets in the system. Protected by - // extMu. - socketsVFS2 map[*vfs.FileDescription]*SocketRecord + // sockets records all network sockets in the system. Protected by extMu. + sockets map[*vfs.FileDescription]*SocketRecord // nextSocketRecord is the next entry number to use in sockets. Protected // by extMu. @@ -269,11 +261,6 @@ type Kernel struct { // deviceRegistry is used to save/restore device.SimpleDevices. deviceRegistry struct{} `state:".(*device.Registry)"` - // DirentCacheLimiter controls the number of total dirent entries can be in - // caches. Not all caches use it, only the caches that use host resources use - // the limiter. It may be nil if disabled. - DirentCacheLimiter *fs.DirentCacheLimiter - // unimplementedSyscallEmitterOnce is used in the initialization of // unimplementedSyscallEmitter. unimplementedSyscallEmitterOnce sync.Once `state:"nosave"` @@ -468,7 +455,7 @@ func (k *Kernel) Init(args InitKernelArgs) error { defer socketFilesystem.DecRef(ctx) k.socketMount = k.vfs.NewDisconnectedMount(socketFilesystem, nil, &vfs.MountOptions{}) - k.socketsVFS2 = make(map[*vfs.FileDescription]*SocketRecord) + k.sockets = make(map[*vfs.FileDescription]*SocketRecord) k.cgroupRegistry = newCgroupRegistry() return nil @@ -547,77 +534,6 @@ func (k *Kernel) SaveTo(ctx context.Context, w wire.Writer) error { return nil } -// flushMountSourceRefs flushes the MountSources for all mounted filesystems -// and open FDs. -// -// Preconditions: !VFS2Enabled. -func (k *Kernel) flushMountSourceRefs(ctx context.Context) error { - // Flush all mount sources for currently mounted filesystems in each task. - flushed := make(map[*fs.MountNamespace]struct{}) - k.tasks.mu.RLock() - k.tasks.forEachThreadGroupLocked(func(tg *ThreadGroup) { - if _, ok := flushed[tg.mounts]; ok { - // Already flushed. - return - } - tg.mounts.FlushMountSourceRefs() - flushed[tg.mounts] = struct{}{} - }) - k.tasks.mu.RUnlock() - - // There may be some open FDs whose filesystems have been unmounted. We - // must flush those as well. - return k.tasks.forEachFDPaused(ctx, func(file *fs.File, _ *vfs.FileDescription) error { - file.Dirent.Inode.MountSource.FlushDirentRefs() - return nil - }) -} - -// forEachFDPaused applies the given function to each open file descriptor in -// each task. -// -// Precondition: Must be called with the kernel paused. -func (ts *TaskSet) forEachFDPaused(ctx context.Context, f func(*fs.File, *vfs.FileDescription) error) (err error) { - ts.mu.RLock() - defer ts.mu.RUnlock() - for t := range ts.Root.tids { - // We can skip locking Task.mu here since the kernel is paused. - if t.fdTable == nil { - continue - } - t.fdTable.forEach(ctx, func(_ int32, file *fs.File, fileVFS2 *vfs.FileDescription, _ FDFlags) { - if lastErr := f(file, fileVFS2); lastErr != nil && err == nil { - err = lastErr - } - }) - } - return err -} - -// Preconditions: !VFS2Enabled. -func (k *Kernel) flushWritesToFiles(ctx context.Context) error { - return k.tasks.forEachFDPaused(ctx, func(file *fs.File, _ *vfs.FileDescription) error { - if flags := file.Flags(); !flags.Write { - return nil - } - if sattr := file.Dirent.Inode.StableAttr; !fs.IsFile(sattr) && !fs.IsDir(sattr) { - return nil - } - // Here we need all metadata synced. - syncErr := file.Fsync(ctx, 0, fs.FileMaxOffset, fs.SyncAll) - if err := fs.SaveFileFsyncError(syncErr); err != nil { - name, _ := file.Dirent.FullName(nil /* root */) - // Wrap this error in ErrSaveRejection so that it will trigger a save - // error, rather than a panic. This also allows us to distinguish Fsync - // errors from state file errors in state.Save. - return &fs.ErrSaveRejection{ - Err: fmt.Errorf("%q was not sufficiently synced: %w", name, err), - } - } - return nil - }) -} - // Preconditions: The kernel must be paused. func (k *Kernel) invalidateUnsavableMappings(ctx context.Context) error { invalidated := make(map[*mm.MemoryManager]struct{}) @@ -841,11 +757,9 @@ func (ctx *createProcessContext) Value(key any) any { if ctx.kernel.globalInit == nil { return nil } - mntns := ctx.kernel.GlobalInit().Leader().MountNamespaceVFS2() + mntns := ctx.kernel.GlobalInit().Leader().MountNamespace() mntns.IncRef() return mntns - case fs.CtxDirentCacheLimiter: - return ctx.kernel.DirentCacheLimiter case inet.CtxStack: return ctx.kernel.RootNetworkNamespace().Stack() case ktime.CtxRealtimeClock: @@ -894,7 +808,7 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, return nil, 0, fmt.Errorf("mount namespace is nil") } // Add a reference to the namespace, which is transferred to the new process. - mntns = k.globalInit.Leader().MountNamespaceVFS2() + mntns = k.globalInit.Leader().MountNamespace() mntns.IncRef() } // Get the root directory from the MountNamespace. @@ -928,7 +842,7 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, opener := fsbridge.NewVFSLookup(mntns, root, wd) fsContext := NewFSContext(root, wd, args.Umask) - tg := k.NewThreadGroup(nil, args.PIDNamespace, NewSignalHandlers(), linux.SIGCHLD, args.Limits) + tg := k.NewThreadGroup(args.PIDNamespace, NewSignalHandlers(), linux.SIGCHLD, args.Limits) cu := cleanup.Make(func() { tg.Release(ctx) }) @@ -1088,7 +1002,7 @@ func (k *Kernel) pauseTimeLocked(ctx context.Context) { // This means we'll iterate FDTables shared by multiple tasks repeatedly, // but ktime.Timer.Pause is idempotent so this is harmless. if t.fdTable != nil { - t.fdTable.forEach(ctx, func(_ int32, file *fs.File, fd *vfs.FileDescription, _ FDFlags) { + t.fdTable.forEach(ctx, func(_ int32, fd *vfs.FileDescription, _ FDFlags) { if tfd, ok := fd.Impl().(*timerfd.TimerFileDescription); ok { tfd.PauseTimer() } @@ -1118,7 +1032,7 @@ func (k *Kernel) resumeTimeLocked(ctx context.Context) { } } if t.fdTable != nil { - t.fdTable.forEach(ctx, func(_ int32, file *fs.File, fd *vfs.FileDescription, _ FDFlags) { + t.fdTable.forEach(ctx, func(_ int32, fd *vfs.FileDescription, _ FDFlags) { if tfd, ok := fd.Impl().(*timerfd.TimerFileDescription); ok { tfd.ResumeTimer() } @@ -1481,88 +1395,53 @@ func (k *Kernel) SupervisorContext() context.Context { } } -// SocketRecord represents a socket recorded in Kernel.socketsVFS2. +// SocketRecord represents a socket recorded in Kernel.sockets. // // +stateify savable type SocketRecord struct { - k *Kernel - Sock *refs.WeakRef // TODO(gvisor.dev/issue/1624): Only used by VFS1. - SockVFS2 *vfs.FileDescription // Only used by VFS2. - ID uint64 // Socket table entry number. + k *Kernel + Sock *vfs.FileDescription + ID uint64 // Socket table entry number. } -// SocketRecordVFS1 represents a socket recorded in Kernel.sockets. It implements -// refs.WeakRefUser for sockets stored in the socket table. -// -// +stateify savable -type SocketRecordVFS1 struct { - socketEntry - SocketRecord -} - -// WeakRefGone implements refs.WeakRefUser.WeakRefGone. -func (s *SocketRecordVFS1) WeakRefGone(context.Context) { - s.k.extMu.Lock() - s.k.sockets.Remove(s) - s.k.extMu.Unlock() -} - -// RecordSocket adds a socket to the system-wide socket table for tracking. -// -// Precondition: Caller must hold a reference to sock. -func (k *Kernel) RecordSocket(sock *fs.File) { - k.extMu.Lock() - id := k.nextSocketRecord - k.nextSocketRecord++ - s := &SocketRecordVFS1{ - SocketRecord: SocketRecord{ - k: k, - ID: id, - }, - } - s.Sock = refs.NewWeakRef(sock, s) - k.sockets.PushBack(s) - k.extMu.Unlock() -} - -// RecordSocketVFS2 adds a VFS2 socket to the system-wide socket table for +// RecordSocket adds a socket to the system-wide socket table for // tracking. // // Precondition: Caller must hold a reference to sock. // // Note that the socket table will not hold a reference on the // vfs.FileDescription. -func (k *Kernel) RecordSocketVFS2(sock *vfs.FileDescription) { +func (k *Kernel) RecordSocket(sock *vfs.FileDescription) { k.extMu.Lock() - if _, ok := k.socketsVFS2[sock]; ok { + if _, ok := k.sockets[sock]; ok { panic(fmt.Sprintf("Socket %p added twice", sock)) } id := k.nextSocketRecord k.nextSocketRecord++ s := &SocketRecord{ - k: k, - ID: id, - SockVFS2: sock, + k: k, + ID: id, + Sock: sock, } - k.socketsVFS2[sock] = s + k.sockets[sock] = s k.extMu.Unlock() } -// DeleteSocketVFS2 removes a VFS2 socket from the system-wide socket table. -func (k *Kernel) DeleteSocketVFS2(sock *vfs.FileDescription) { +// DeleteSocket removes a socket from the system-wide socket table. +func (k *Kernel) DeleteSocket(sock *vfs.FileDescription) { k.extMu.Lock() - delete(k.socketsVFS2, sock) + delete(k.sockets, sock) k.extMu.Unlock() } // ListSockets returns a snapshot of all sockets. // -// Callers of ListSockets() in VFS2 should use SocketRecord.SockVFS2.TryIncRef() +// Callers of ListSockets() should use SocketRecord.Sock.TryIncRef() // to get a reference on a socket in the table. func (k *Kernel) ListSockets() []*SocketRecord { k.extMu.Lock() var socks []*SocketRecord - for _, s := range k.socketsVFS2 { + for _, s := range k.sockets { socks = append(socks, s) } k.extMu.Unlock() @@ -1612,27 +1491,20 @@ func (ctx *supervisorContext) Value(key any) any { case auth.CtxCredentials: // The supervisor context is global root. return auth.NewRootCredentials(ctx.Kernel.rootUserNamespace) - case fs.CtxRoot: - if ctx.Kernel.globalInit != nil { - return ctx.Kernel.globalInit.mounts.Root() - } - return nil case vfs.CtxRoot: if ctx.Kernel.globalInit == nil { return vfs.VirtualDentry{} } - root := ctx.Kernel.GlobalInit().Leader().MountNamespaceVFS2().Root() + root := ctx.Kernel.GlobalInit().Leader().MountNamespace().Root() root.IncRef() return root case vfs.CtxMountNamespace: if ctx.Kernel.globalInit == nil { return nil } - mntns := ctx.Kernel.GlobalInit().Leader().MountNamespaceVFS2() + mntns := ctx.Kernel.GlobalInit().Leader().MountNamespace() mntns.IncRef() return mntns - case fs.CtxDirentCacheLimiter: - return ctx.Kernel.DirentCacheLimiter case inet.CtxStack: return ctx.Kernel.RootNetworkNamespace().Stack() case ktime.CtxRealtimeClock: @@ -1800,15 +1672,15 @@ func (k *Kernel) ReplaceFSContextRoots(ctx context.Context, oldRoot vfs.VirtualD if fsc := t.fsContext; fsc != nil { fsc.mu.Lock() defer fsc.mu.Unlock() - if fsc.rootVFS2 == oldRoot { + if fsc.root == oldRoot { newRoot.IncRef() oldRootDecRefs++ - fsc.rootVFS2 = newRoot + fsc.root = newRoot } - if fsc.cwdVFS2 == oldRoot { + if fsc.cwd == oldRoot { newRoot.IncRef() oldRootDecRefs++ - fsc.cwdVFS2 = newRoot + fsc.cwd = newRoot } } }) diff --git a/pkg/sentry/kernel/mq/BUILD b/pkg/sentry/kernel/mq/BUILD index fefac3ba5..b2aceeb56 100644 --- a/pkg/sentry/kernel/mq/BUILD +++ b/pkg/sentry/kernel/mq/BUILD @@ -26,7 +26,6 @@ go_library( "//pkg/abi/linux", "//pkg/context", "//pkg/errors/linuxerr", - "//pkg/sentry/fs", "//pkg/sentry/kernel/auth", "//pkg/sentry/vfs", "//pkg/sync", diff --git a/pkg/sentry/kernel/mq/mq.go b/pkg/sentry/kernel/mq/mq.go index 586700127..597b11a83 100644 --- a/pkg/sentry/kernel/mq/mq.go +++ b/pkg/sentry/kernel/mq/mq.go @@ -23,7 +23,6 @@ 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/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" @@ -182,7 +181,7 @@ func (r *Registry) FindOrCreate(ctx context.Context, opts OpenOpts, mode linux.F return nil, linuxerr.ENOENT } - q, err := r.newQueueLocked(auth.CredentialsFromContext(ctx), fs.FileOwnerFromContext(ctx), fs.FilePermsFromMode(mode), attr) + q, err := r.newQueueLocked(auth.CredentialsFromContext(ctx), mode, attr) if err != nil { return nil, err } @@ -192,11 +191,12 @@ func (r *Registry) FindOrCreate(ctx context.Context, opts OpenOpts, mode linux.F // newQueueLocked creates a new queue using the given attributes. If attr is nil // return a queue with default values, otherwise use attr to create a new queue, // and return an error if attributes are invalid. -func (r *Registry) newQueueLocked(creds *auth.Credentials, owner fs.FileOwner, perms fs.FilePermissions, attr *linux.MqAttr) (*Queue, error) { +func (r *Registry) newQueueLocked(creds *auth.Credentials, mode linux.FileMode, attr *linux.MqAttr) (*Queue, error) { if attr == nil { return &Queue{ - owner: owner, - perms: perms, + ownerUID: creds.EffectiveKUID, + ownerGID: creds.EffectiveKGID, + mode: mode, maxMessageCount: int64(maxMsgDefault), maxMessageSize: uint64(msgSizeDefault), }, nil @@ -219,8 +219,9 @@ func (r *Registry) newQueueLocked(creds *auth.Credentials, owner fs.FileOwner, p } return &Queue{ - owner: owner, - perms: perms, + ownerUID: creds.EffectiveKUID, + ownerGID: creds.EffectiveKGID, + mode: mode, maxMessageCount: attr.MqMaxmsg, maxMessageSize: uint64(attr.MqMsgsize), }, nil @@ -254,11 +255,14 @@ func (r *Registry) Impl() RegistryImpl { // // +stateify savable type Queue struct { - // owner is the registry's owner. Immutable. - owner fs.FileOwner + // ownerUID is the registry's owner's UID. Immutable. + ownerUID auth.KUID - // perms is the registry's access permissions. Immutable. - perms fs.FilePermissions + // ownerGID is the registry's owner's GID. Immutable. + ownerGID auth.KGID + + // mode is the registry's access permissions. Immutable. + mode linux.FileMode // mu protects all the fields below. mu sync.Mutex `state:"nosave"` @@ -434,12 +438,13 @@ func (q *Queue) EventUnregister(e *waiter.Entry) { // HasPermissions returns true if the given credentials meet the access // permissions required by the queue. -func (q *Queue) HasPermissions(creds *auth.Credentials, req fs.PermMask) bool { - p := q.perms.Other - if q.owner.UID == creds.EffectiveKUID { - p = q.perms.User - } else if creds.InGroup(q.owner.GID) { - p = q.perms.Group +func (q *Queue) HasPermissions(creds *auth.Credentials, req vfs.AccessTypes) bool { + perms := uint16(q.mode.Permissions()) + if q.ownerUID == creds.EffectiveKUID { + perms >>= 6 + } else if creds.InGroup(q.ownerGID) { + perms >>= 3 } - return p.SupersetOf(req) + + return uint16(req)&perms == uint16(req) } diff --git a/pkg/sentry/kernel/msgqueue/BUILD b/pkg/sentry/kernel/msgqueue/BUILD index 5ec11e1f6..7751d197d 100644 --- a/pkg/sentry/kernel/msgqueue/BUILD +++ b/pkg/sentry/kernel/msgqueue/BUILD @@ -26,10 +26,10 @@ go_library( "//pkg/abi/linux", "//pkg/context", "//pkg/errors/linuxerr", - "//pkg/sentry/fs", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/ipc", "//pkg/sentry/kernel/time", + "//pkg/sentry/vfs", "//pkg/sync", "//pkg/waiter", ], diff --git a/pkg/sentry/kernel/msgqueue/msgqueue.go b/pkg/sentry/kernel/msgqueue/msgqueue.go index f891ac9eb..705057dde 100644 --- a/pkg/sentry/kernel/msgqueue/msgqueue.go +++ b/pkg/sentry/kernel/msgqueue/msgqueue.go @@ -19,10 +19,10 @@ 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/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/ipc" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/waiter" ) @@ -168,17 +168,17 @@ func (r *Registry) FindOrCreate(ctx context.Context, key ipc.Key, mode linux.Fil return nil, linuxerr.ENOSPC } - return r.newQueueLocked(ctx, key, fs.FileOwnerFromContext(ctx), fs.FilePermsFromMode(mode)) + return r.newQueueLocked(ctx, key, auth.CredentialsFromContext(ctx), mode) } // newQueueLocked creates a new queue using the given fields. An error is // returned if there're no more available identifiers. // // Precondition: r.mu must be held. -func (r *Registry) newQueueLocked(ctx context.Context, key ipc.Key, creator fs.FileOwner, perms fs.FilePermissions) (*Queue, error) { +func (r *Registry) newQueueLocked(ctx context.Context, key ipc.Key, creds *auth.Credentials, mode linux.FileMode) (*Queue, error) { q := &Queue{ registry: r, - obj: ipc.NewObject(r.reg.UserNS, key, creator, creator, perms), + obj: ipc.NewObject(r.reg.UserNS, key, creds, creds, mode), sendTime: ktime.ZeroTime, receiveTime: ktime.ZeroTime, changeTime: ktime.NowFromContext(ctx), @@ -305,7 +305,7 @@ func (q *Queue) push(ctx context.Context, m Message, creds *auth.Credentials, pi q.mu.Lock() defer q.mu.Unlock() - if !q.obj.CheckPermissions(creds, fs.PermMask{Write: true}) { + if !q.obj.CheckPermissions(creds, vfs.MayWrite) { // The calling process does not have write permission on the message // queue, and does not have the CAP_IPC_OWNER capability in the user // namespace that governs its IPC namespace. @@ -397,7 +397,7 @@ func (q *Queue) pop(ctx context.Context, creds *auth.Credentials, mType int64, m q.mu.Lock() defer q.mu.Unlock() - if !q.obj.CheckPermissions(creds, fs.PermMask{Read: true}) { + if !q.obj.CheckPermissions(creds, vfs.MayRead) { // The calling process does not have read permission on the message // queue, and does not have the CAP_IPC_OWNER capability in the user // namespace that governs its IPC namespace. @@ -542,23 +542,23 @@ func (q *Queue) Set(ctx context.Context, ds *linux.MsqidDS) error { // Stat returns a MsqidDS object filled with information about the queue. See // msgctl(IPC_STAT) and msgctl(MSG_STAT). func (q *Queue) Stat(ctx context.Context) (*linux.MsqidDS, error) { - return q.stat(ctx, fs.PermMask{Read: true}) + return q.stat(ctx, vfs.MayRead) } // StatAny is similar to Queue.Stat, but doesn't require read permission. See // msgctl(MSG_STAT_ANY). func (q *Queue) StatAny(ctx context.Context) (*linux.MsqidDS, error) { - return q.stat(ctx, fs.PermMask{}) + return q.stat(ctx, 0) } // stat returns a MsqidDS object filled with information about the queue. An // error is returned if the user doesn't have the specified permissions. -func (q *Queue) stat(ctx context.Context, mask fs.PermMask) (*linux.MsqidDS, error) { +func (q *Queue) stat(ctx context.Context, ats vfs.AccessTypes) (*linux.MsqidDS, error) { q.mu.Lock() defer q.mu.Unlock() creds := auth.CredentialsFromContext(ctx) - if !q.obj.CheckPermissions(creds, mask) { + if !q.obj.CheckPermissions(creds, ats) { // "The caller must have read permission on the message queue." return nil, linuxerr.EACCES } @@ -566,11 +566,11 @@ func (q *Queue) stat(ctx context.Context, mask fs.PermMask) (*linux.MsqidDS, err return &linux.MsqidDS{ MsgPerm: linux.IPCPerm{ Key: uint32(q.obj.Key), - UID: uint32(creds.UserNamespace.MapFromKUID(q.obj.Owner.UID)), - GID: uint32(creds.UserNamespace.MapFromKGID(q.obj.Owner.GID)), - CUID: uint32(creds.UserNamespace.MapFromKUID(q.obj.Creator.UID)), - CGID: uint32(creds.UserNamespace.MapFromKGID(q.obj.Creator.GID)), - Mode: uint16(q.obj.Perms.LinuxMode()), + UID: uint32(creds.UserNamespace.MapFromKUID(q.obj.OwnerUID)), + GID: uint32(creds.UserNamespace.MapFromKGID(q.obj.OwnerGID)), + CUID: uint32(creds.UserNamespace.MapFromKUID(q.obj.CreatorUID)), + CGID: uint32(creds.UserNamespace.MapFromKGID(q.obj.CreatorGID)), + Mode: uint16(q.obj.Mode), Seq: 0, // IPC sequences not supported. }, MsgStime: q.sendTime.TimeT(), diff --git a/pkg/sentry/kernel/pipe/BUILD b/pkg/sentry/kernel/pipe/BUILD index bb6787673..430c85a88 100644 --- a/pkg/sentry/kernel/pipe/BUILD +++ b/pkg/sentry/kernel/pipe/BUILD @@ -41,16 +41,12 @@ go_library( srcs = [ "device.go", "inode_mutex.go", - "node.go", "pipe.go", "pipe_mutex.go", "pipe_unsafe.go", "pipe_util.go", - "reader.go", - "reader_writer.go", "save_restore.go", "vfs.go", - "writer.go", ], visibility = ["//pkg/sentry:internal"], deps = [ @@ -63,8 +59,7 @@ go_library( "//pkg/safemem", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", + "//pkg/sentry/fsutil", "//pkg/sentry/vfs", "//pkg/sync", "//pkg/sync/locking", @@ -78,7 +73,6 @@ go_test( name = "pipe_test", size = "small", srcs = [ - "node_test.go", "pipe_test.go", ], library = ":pipe", @@ -86,7 +80,7 @@ go_test( "//pkg/context", "//pkg/errors/linuxerr", "//pkg/sentry/contexttest", - "//pkg/sentry/fs", + "//pkg/sentry/vfs", "//pkg/usermem", "//pkg/waiter", ], diff --git a/pkg/sentry/kernel/pipe/node.go b/pkg/sentry/kernel/pipe/node.go deleted file mode 100644 index 682d2258e..000000000 --- a/pkg/sentry/kernel/pipe/node.go +++ /dev/null @@ -1,121 +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 pipe - -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/waiter" -) - -// inodeOperations implements fs.InodeOperations for pipes. -// -// +stateify savable -type inodeOperations struct { - fsutil.InodeGenericChecker `state:"nosave"` - fsutil.InodeNoExtendedAttributes `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"` - - // Marking pipe inodes as virtual allows them to be saved and restored - // even if they have been unlinked. We can get away with this because - // their state exists entirely within the sentry. - fsutil.InodeVirtual `state:"nosave"` - fsutil.InodeSimpleAttributes - - // p is the underlying Pipe object representing this fifo. This field - // may have methods called on it, but the pointer is immutable. - p *Pipe -} - -var _ fs.InodeOperations = (*inodeOperations)(nil) - -// NewInodeOperations returns a new fs.InodeOperations for a given pipe. -func NewInodeOperations(ctx context.Context, perms fs.FilePermissions, p *Pipe) *inodeOperations { - i := &inodeOperations{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.FileOwnerFromContext(ctx), perms, linux.PIPEFS_MAGIC), - p: p, - } - return i -} - -// GetFile implements fs.InodeOperations.GetFile. Named pipes have special blocking -// semantics during open: -// -// "Normally, opening the FIFO blocks until the other end is opened also. A -// process can open a FIFO in nonblocking mode. In this case, opening for -// read-only will succeed even if no-one has opened on the write side yet, -// opening for write-only will fail with ENXIO (no such device or address) -// unless the other end has already been opened. Under Linux, opening a FIFO -// for read and write will succeed both in blocking and nonblocking mode. POSIX -// leaves this behavior undefined. This can be used to open a FIFO for writing -// while there are no readers available." - fifo(7) -func (i *inodeOperations) GetFile(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) (*fs.File, error) { - switch { - case flags.Read && !flags.Write: // O_RDONLY. - tWriters := i.p.totalWriters.Load() - r := i.p.Open(ctx, d, flags) - for i.p.isNamed && !flags.NonBlocking && !i.p.HasWriters() && - tWriters == i.p.totalWriters.Load() { - if !ctx.BlockOn((*waitWriters)(i.p), waiter.EventInternal) { - r.DecRef(ctx) - return nil, linuxerr.ErrInterrupted - } - } - - // By now, either we're doing a nonblocking open or we have a writer. On - // a nonblocking read-only open, the open succeeds even if no-one has - // opened the write side yet. - return r, nil - - case flags.Write && !flags.Read: // O_WRONLY. - tReaders := i.p.totalReaders.Load() - w := i.p.Open(ctx, d, flags) - for i.p.isNamed && !i.p.HasReaders() && - tReaders == i.p.totalReaders.Load() { - // On a nonblocking, write-only open, the open fails with ENXIO if the - // read side isn't open yet. - if flags.NonBlocking { - w.DecRef(ctx) - return nil, linuxerr.ENXIO - } - if !ctx.BlockOn((*waitReaders)(i.p), waiter.EventInternal) { - w.DecRef(ctx) - return nil, linuxerr.ErrInterrupted - } - } - return w, nil - - case flags.Read && flags.Write: // O_RDWR. - // Pipes opened for read-write always succeeds without blocking. - rw := i.p.Open(ctx, d, flags) - return rw, nil - - default: - return nil, linuxerr.EINVAL - } -} - -func (*inodeOperations) Allocate(_ context.Context, _ *fs.Inode, _, _ int64) error { - return linuxerr.EPIPE -} diff --git a/pkg/sentry/kernel/pipe/node_test.go b/pkg/sentry/kernel/pipe/node_test.go deleted file mode 100644 index 59dbe8dd7..000000000 --- a/pkg/sentry/kernel/pipe/node_test.go +++ /dev/null @@ -1,295 +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 pipe - -import ( - "testing" - "time" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -type openResult struct { - *fs.File - error -} - -var perms fs.FilePermissions = fs.FilePermissions{ - User: fs.PermMask{Read: true, Write: true}, -} - -func testOpenOrDie(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, doneChan chan<- struct{}) (*fs.File, error) { - inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe}) - d := fs.NewDirent(ctx, inode, "pipe") - file, err := n.GetFile(ctx, d, flags) - if err != nil { - t.Errorf("open with flags %+v failed: %v", flags, err) - return nil, err - } - if doneChan != nil { - doneChan <- struct{}{} - } - return file, err -} - -func testOpen(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, resChan chan<- openResult) (*fs.File, error) { - inode := fs.NewMockInode(ctx, fs.NewMockMountSource(nil), fs.StableAttr{Type: fs.Pipe}) - d := fs.NewDirent(ctx, inode, "pipe") - file, err := n.GetFile(ctx, d, flags) - if resChan != nil { - resChan <- openResult{file, err} - } - return file, err -} - -func newNamedPipe(t *testing.T) *Pipe { - return NewPipe(true, DefaultPipeSize) -} - -func newAnonPipe(t *testing.T) *Pipe { - return NewPipe(false, DefaultPipeSize) -} - -// assertRecvBlocks ensures that a recv attempt on c blocks for at least -// blockDuration. This is useful for checking that a goroutine that is supposed -// to be executing a blocking operation is actually blocking. -func assertRecvBlocks(t *testing.T, c <-chan struct{}, blockDuration time.Duration, failMsg string) { - t.Helper() - select { - case <-c: - t.Fatalf(failMsg) - case <-time.After(blockDuration): - // Ok, blocked for the required duration. - } -} - -func TestReadOpenBlocksForWriteOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - rDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) - - // Verify that the open for read is blocking. - assertRecvBlocks(t, rDone, time.Millisecond*100, - "open for read not blocking with no writers") - - wDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) - - <-wDone - <-rDone -} - -func TestWriteOpenBlocksForReadOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - wDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) - - // Verify that the open for write is blocking - assertRecvBlocks(t, wDone, time.Millisecond*100, - "open for write not blocking with no readers") - - rDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) - - <-rDone - <-wDone -} - -func TestMultipleWriteOpenDoesntCountAsReadOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - rDone1 := make(chan struct{}) - rDone2 := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone1) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone2) - - assertRecvBlocks(t, rDone1, time.Millisecond*100, - "open for read didn't block with no writers") - assertRecvBlocks(t, rDone2, time.Millisecond*100, - "open for read didn't block with no writers") - - wDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) - - <-wDone - <-rDone2 - <-rDone1 -} - -func TestClosedReaderBlocksWriteOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - rFile, _ := testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil) - rFile.DecRef(ctx) - - wDone := make(chan struct{}) - // This open for write should block because the reader is now gone. - go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) - assertRecvBlocks(t, wDone, time.Millisecond*100, - "open for write didn't block with no concurrent readers") - - // Open for read again. This should unblock the open for write. - rDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) - - <-rDone - <-wDone -} - -func TestReadWriteOpenNeverBlocks(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - rwDone := make(chan struct{}) - // Open for read-write never wait for a reader or writer, even if the - // nonblocking flag is not set. - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true, NonBlocking: false}, rwDone) - <-rwDone -} - -func TestReadWriteOpenUnblocksReadOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - rDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) - - rwDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone) - - <-rwDone - <-rDone -} - -func TestReadWriteOpenUnblocksWriteOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - wDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) - - rwDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone) - - <-rwDone - <-wDone -} - -func TestBlockedOpenIsCancellable(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - done := make(chan openResult) - go testOpen(ctx, t, f, fs.FileFlags{Read: true}, done) - select { - case <-done: - t.Fatalf("open for read didn't block with no writers") - case <-time.After(time.Millisecond * 100): - // Ok. - } - - ctx.Interrupt() - - // If the cancel on the sleeper didn't work, the open for read would never - // return. - res := <-done - if res.error != linuxerr.ErrInterrupted { - t.Fatalf("Cancellation didn't cause GetFile to return fs.ErrInterrupted, got %v.", - res.error) - } -} - -func TestNonblockingReadOpenFileNoWriters(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil { - t.Fatalf("Nonblocking open for read failed with error %v.", err) - } -} - -func TestNonblockingWriteOpenFileNoReaders(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); !linuxerr.Equals(linuxerr.ENXIO, err) { - t.Fatalf("Nonblocking open for write failed unexpected error %v.", err) - } -} - -func TestNonBlockingReadOpenWithWriter(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - wDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone) - - // Open for write blocks since there are no readers yet. - assertRecvBlocks(t, wDone, time.Millisecond*100, - "Open for write didn't block with no reader.") - - if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil { - t.Fatalf("Nonblocking open for read failed with error %v.", err) - } - - // Open for write should now be unblocked. - <-wDone -} - -func TestNonBlockingWriteOpenWithReader(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newNamedPipe(t)) - - rDone := make(chan struct{}) - go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone) - - // Open for write blocked, since no reader yet. - assertRecvBlocks(t, rDone, time.Millisecond*100, - "Open for reader didn't block with no writer.") - - if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); err != nil { - t.Fatalf("Nonblocking open for write failed with error %v.", err) - } - - // Open for write should now be unblocked. - <-rDone -} - -func TestAnonReadOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newAnonPipe(t)) - - if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true}, nil); err != nil { - t.Fatalf("open anon pipe for read failed: %v", err) - } -} - -func TestAnonWriteOpen(t *testing.T) { - ctx := contexttest.Context(t) - f := NewInodeOperations(ctx, perms, newAnonPipe(t)) - - if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true}, nil); err != nil { - t.Fatalf("open anon pipe for write failed: %v", err) - } -} diff --git a/pkg/sentry/kernel/pipe/pipe.go b/pkg/sentry/kernel/pipe/pipe.go index 8ae324ab0..794b854cd 100644 --- a/pkg/sentry/kernel/pipe/pipe.go +++ b/pkg/sentry/kernel/pipe/pipe.go @@ -21,11 +21,9 @@ import ( "golang.org/x/sys/unix" "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/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/waiter" ) @@ -181,60 +179,6 @@ func initPipe(pipe *Pipe, isNamed bool, sizeBytes int64) { pipe.max = sizeBytes } -// NewConnectedPipe initializes a pipe and returns a pair of objects -// representing the read and write ends of the pipe. -func NewConnectedPipe(ctx context.Context, sizeBytes int64) (*fs.File, *fs.File) { - p := NewPipe(false /* isNamed */, sizeBytes) - - // Build an fs.Dirent for the pipe which will be shared by both - // returned files. - perms := fs.FilePermissions{ - User: fs.PermMask{Read: true, Write: true}, - } - iops := NewInodeOperations(ctx, perms, p) - ino := pipeDevice.NextIno() - sattr := fs.StableAttr{ - Type: fs.Pipe, - DeviceID: pipeDevice.DeviceID(), - InodeID: ino, - BlockSize: int64(atomicIOBytes), - } - ms := fs.NewPseudoMountSource(ctx) - d := fs.NewDirent(ctx, fs.NewInode(ctx, iops, ms, sattr), fmt.Sprintf("pipe:[%d]", ino)) - // The p.Open calls below will each take a reference on the Dirent. We - // must drop the one we already have. - defer d.DecRef(ctx) - return p.Open(ctx, d, fs.FileFlags{Read: true}), p.Open(ctx, d, fs.FileFlags{Write: true}) -} - -// Open opens the pipe and returns a new file. -// -// Precondition: at least one of flags.Read or flags.Write must be set. -func (p *Pipe) Open(ctx context.Context, d *fs.Dirent, flags fs.FileFlags) *fs.File { - flags.NonSeekable = true - switch { - case flags.Read && flags.Write: - p.rOpen() - p.wOpen() - return fs.NewFile(ctx, d, flags, &ReaderWriter{ - Pipe: p, - }) - case flags.Read: - p.rOpen() - return fs.NewFile(ctx, d, flags, &Reader{ - ReaderWriter: ReaderWriter{Pipe: p}, - }) - case flags.Write: - p.wOpen() - return fs.NewFile(ctx, d, flags, &Writer{ - ReaderWriter: ReaderWriter{Pipe: p}, - }) - default: - // Precondition violated. - panic("invalid pipe flags") - } -} - // peekLocked passes the first count bytes in the pipe to f and returns its // result. If fewer than count bytes are available, the safemem.BlockSeq passed // to f will be less than count bytes in length. @@ -494,13 +438,6 @@ func (p *Pipe) queuedLocked() int64 { return p.size } -// FifoSize implements fs.FifoSizer.FifoSize. -func (p *Pipe) FifoSize(context.Context, *fs.File) (int64, error) { - p.mu.Lock() - defer p.mu.Unlock() - return p.max, nil -} - // SetFifoSize implements fs.FifoSizer.SetFifoSize. func (p *Pipe) SetFifoSize(size int64) (int64, error) { if size < 0 { diff --git a/pkg/sentry/kernel/pipe/pipe_test.go b/pkg/sentry/kernel/pipe/pipe_test.go index d76033195..bbeba4f66 100644 --- a/pkg/sentry/kernel/pipe/pipe_test.go +++ b/pkg/sentry/kernel/pipe/pipe_test.go @@ -18,123 +18,132 @@ import ( "bytes" "testing" + "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/sentry/contexttest" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/usermem" "gvisor.dev/gvisor/pkg/waiter" ) -func TestPipeRW(t *testing.T) { +func runTest(t *testing.T, sizeBytes int64, tester func(ctx context.Context, r, w *vfs.FileDescription)) { ctx := contexttest.Context(t) - r, w := NewConnectedPipe(ctx, 65536) + vfsObj := &vfs.VirtualFilesystem{} + if err := vfsObj.Init(ctx); err != nil { + t.Fatalf("VFS init: %v", err) + } + vd := vfsObj.NewAnonVirtualDentry("pipe") + defer vd.DecRef(ctx) + + vp := NewVFSPipe(false /* isNamed */, sizeBytes) + r, w, err := vp.ReaderWriterPair(ctx, vd.Mount(), vd.Dentry(), 0) + if err != nil { + t.Fatalf("ReaderWriterPair failed: %v", err) + } defer r.DecRef(ctx) defer w.DecRef(ctx) - msg := []byte("here's some bytes") - wantN := int64(len(msg)) - n, err := w.Writev(ctx, usermem.BytesIOSequence(msg)) - if n != wantN || err != nil { - t.Fatalf("Writev: got (%d, %v), wanted (%d, nil)", n, err, wantN) - } + tester(ctx, r, w) +} - buf := make([]byte, len(msg)) - n, err = r.Readv(ctx, usermem.BytesIOSequence(buf)) - if n != wantN || err != nil || !bytes.Equal(buf, msg) { - t.Fatalf("Readv: got (%d, %v) %q, wanted (%d, nil) %q", n, err, buf, wantN, msg) - } +func TestPipeRW(t *testing.T) { + runTest(t, 65536, func(ctx context.Context, r *vfs.FileDescription, w *vfs.FileDescription) { + msg := []byte("here's some bytes") + wantN := int64(len(msg)) + n, err := w.Write(ctx, usermem.BytesIOSequence(msg), vfs.WriteOptions{}) + if n != wantN || err != nil { + t.Fatalf("Writev: got (%d, %v), wanted (%d, nil)", n, err, wantN) + } + + buf := make([]byte, len(msg)) + n, err = r.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{}) + if n != wantN || err != nil || !bytes.Equal(buf, msg) { + t.Fatalf("Readv: got (%d, %v) %q, wanted (%d, nil) %q", n, err, buf, wantN, msg) + } + }) } func TestPipeReadBlock(t *testing.T) { - ctx := contexttest.Context(t) - r, w := NewConnectedPipe(ctx, 65536) - defer r.DecRef(ctx) - defer w.DecRef(ctx) - - n, err := r.Readv(ctx, usermem.BytesIOSequence(make([]byte, 1))) - if n != 0 || err != linuxerr.ErrWouldBlock { - t.Fatalf("Readv: got (%d, %v), wanted (0, %v)", n, err, linuxerr.ErrWouldBlock) - } + runTest(t, 65536, func(ctx context.Context, r *vfs.FileDescription, w *vfs.FileDescription) { + n, err := r.Read(ctx, usermem.BytesIOSequence(make([]byte, 1)), vfs.ReadOptions{}) + if n != 0 || err != linuxerr.ErrWouldBlock { + t.Fatalf("Readv: got (%d, %v), wanted (0, %v)", n, err, linuxerr.ErrWouldBlock) + } + }) } func TestPipeWriteBlock(t *testing.T) { const atomicIOBytes = 2 const capacity = MinimumPipeSize - ctx := contexttest.Context(t) - r, w := NewConnectedPipe(ctx, capacity) - defer r.DecRef(ctx) - defer w.DecRef(ctx) - - msg := make([]byte, capacity+1) - n, err := w.Writev(ctx, usermem.BytesIOSequence(msg)) - if wantN, wantErr := int64(capacity), linuxerr.ErrWouldBlock; n != wantN || err != wantErr { - t.Fatalf("Writev: got (%d, %v), wanted (%d, %v)", n, err, wantN, wantErr) - } + runTest(t, capacity, func(ctx context.Context, r *vfs.FileDescription, w *vfs.FileDescription) { + msg := make([]byte, capacity+1) + n, err := w.Write(ctx, usermem.BytesIOSequence(msg), vfs.WriteOptions{}) + if wantN, wantErr := int64(capacity), linuxerr.ErrWouldBlock; n != wantN || err != wantErr { + t.Fatalf("Writev: got (%d, %v), wanted (%d, %v)", n, err, wantN, wantErr) + } + }) } func TestPipeWriteUntilEnd(t *testing.T) { const atomicIOBytes = 2 + runTest(t, atomicIOBytes, func(ctx context.Context, r *vfs.FileDescription, w *vfs.FileDescription) { + msg := []byte("here's some bytes") - ctx := contexttest.Context(t) - r, w := NewConnectedPipe(ctx, atomicIOBytes) - defer r.DecRef(ctx) - defer w.DecRef(ctx) + wDone := make(chan struct{}, 0) + rDone := make(chan struct{}, 0) + defer func() { + // Signal the reader to stop and wait until it does so. + close(wDone) + <-rDone + }() - msg := []byte("here's some bytes") - - wDone := make(chan struct{}, 0) - rDone := make(chan struct{}, 0) - defer func() { - // Signal the reader to stop and wait until it does so. - close(wDone) - <-rDone - }() - - go func() { - defer close(rDone) - // Read from r until done is closed. - ctx := contexttest.Context(t) - buf := make([]byte, len(msg)+1) - dst := usermem.BytesIOSequence(buf) - e, ch := waiter.NewChannelEntry(waiter.ReadableEvents) - r.EventRegister(&e) - defer r.EventUnregister(&e) - for { - n, err := r.Readv(ctx, dst) - dst = dst.DropFirst64(n) - if err == linuxerr.ErrWouldBlock { - select { - case <-ch: - continue - case <-wDone: - // We expect to have 1 byte left in dst since len(buf) == - // len(msg)+1. - if dst.NumBytes() != 1 || !bytes.Equal(buf[:len(msg)], msg) { - t.Errorf("Reader: got %q (%d bytes remaining), wanted %q", buf, dst.NumBytes(), msg) + go func() { + defer close(rDone) + // Read from r until done is closed. + ctx := contexttest.Context(t) + buf := make([]byte, len(msg)+1) + dst := usermem.BytesIOSequence(buf) + e, ch := waiter.NewChannelEntry(waiter.ReadableEvents) + r.EventRegister(&e) + defer r.EventUnregister(&e) + for { + n, err := r.Read(ctx, dst, vfs.ReadOptions{}) + dst = dst.DropFirst64(n) + if err == linuxerr.ErrWouldBlock { + select { + case <-ch: + continue + case <-wDone: + // We expect to have 1 byte left in dst since len(buf) == + // len(msg)+1. + if dst.NumBytes() != 1 || !bytes.Equal(buf[:len(msg)], msg) { + t.Errorf("Reader: got %q (%d bytes remaining), wanted %q", buf, dst.NumBytes(), msg) + } + return } + } + if err != nil { + t.Errorf("Readv: got unexpected error %v", err) return } } + }() + + src := usermem.BytesIOSequence(msg) + e, ch := waiter.NewChannelEntry(waiter.WritableEvents) + w.EventRegister(&e) + defer w.EventUnregister(&e) + for src.NumBytes() != 0 { + n, err := w.Write(ctx, src, vfs.WriteOptions{}) + src = src.DropFirst64(n) + if err == linuxerr.ErrWouldBlock { + <-ch + continue + } if err != nil { - t.Errorf("Readv: got unexpected error %v", err) - return + t.Fatalf("Writev: got (%d, %v)", n, err) } } - }() - - src := usermem.BytesIOSequence(msg) - e, ch := waiter.NewChannelEntry(waiter.WritableEvents) - w.EventRegister(&e) - defer w.EventUnregister(&e) - for src.NumBytes() != 0 { - n, err := w.Writev(ctx, src) - src = src.DropFirst64(n) - if err == linuxerr.ErrWouldBlock { - <-ch - continue - } - if err != nil { - t.Fatalf("Writev: got (%d, %v)", n, err) - } - } + }) } diff --git a/pkg/sentry/kernel/pipe/reader.go b/pkg/sentry/kernel/pipe/reader.go deleted file mode 100644 index 1348150a2..000000000 --- a/pkg/sentry/kernel/pipe/reader.go +++ /dev/null @@ -1,43 +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 pipe - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/waiter" -) - -// Reader satisfies the fs.FileOperations interface for read-only pipes. -// Reader should be used with !fs.FileFlags.Write to reject writes. -// -// +stateify savable -type Reader struct { - ReaderWriter -} - -// Release implements fs.FileOperations.Release. -// -// This overrides ReaderWriter.Release. -func (r *Reader) Release(context.Context) { - r.Pipe.rClose() - - // Wake up writers. - r.Pipe.queue.Notify(waiter.EventOut) -} - -// Readiness returns the ready events in the underlying pipe. -func (r *Reader) Readiness(mask waiter.EventMask) waiter.EventMask { - return r.Pipe.rReadiness() & mask -} diff --git a/pkg/sentry/kernel/pipe/reader_writer.go b/pkg/sentry/kernel/pipe/reader_writer.go deleted file mode 100644 index b2b5691ee..000000000 --- a/pkg/sentry/kernel/pipe/reader_writer.go +++ /dev/null @@ -1,67 +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 pipe - -import ( - "io" - - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/usermem" -) - -// ReaderWriter satisfies the FileOperations interface and services both -// read and write requests. This should only be used directly for named pipes. -// pipe(2) and pipe2(2) only support unidirectional pipes and should use -// either pipe.Reader or pipe.Writer. -// -// +stateify savable -type ReaderWriter 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"` - *Pipe -} - -// Read implements fs.FileOperations.Read. -func (rw *ReaderWriter) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - return rw.Pipe.Read(ctx, dst) -} - -// WriteTo implements fs.FileOperations.WriteTo. -func (rw *ReaderWriter) WriteTo(ctx context.Context, _ *fs.File, w io.Writer, count int64, dup bool) (int64, error) { - return rw.Pipe.WriteTo(ctx, w, count, dup) -} - -// Write implements fs.FileOperations.Write. -func (rw *ReaderWriter) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - return rw.Pipe.Write(ctx, src) -} - -// ReadFrom implements fs.FileOperations.WriteTo. -func (rw *ReaderWriter) ReadFrom(ctx context.Context, _ *fs.File, r io.Reader, count int64) (int64, error) { - return rw.Pipe.ReadFrom(ctx, r, count) -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (rw *ReaderWriter) Ioctl(ctx context.Context, _ *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - return rw.Pipe.Ioctl(ctx, io, args) -} diff --git a/pkg/sentry/kernel/pipe/writer.go b/pkg/sentry/kernel/pipe/writer.go deleted file mode 100644 index 3ae4a7c20..000000000 --- a/pkg/sentry/kernel/pipe/writer.go +++ /dev/null @@ -1,43 +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 pipe - -import ( - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/waiter" -) - -// Writer satisfies the fs.FileOperations interface for write-only pipes. -// Writer should be used with !fs.FileFlags.Read to reject reads. -// -// +stateify savable -type Writer struct { - ReaderWriter -} - -// Release implements fs.FileOperations.Release. -// -// This overrides ReaderWriter.Release. -func (w *Writer) Release(context.Context) { - w.Pipe.wClose() - - // Wake up readers. - w.Pipe.queue.Notify(waiter.EventHUp) -} - -// Readiness returns the ready events in the underlying pipe. -func (w *Writer) Readiness(mask waiter.EventMask) waiter.EventMask { - return w.Pipe.wReadiness() & mask -} diff --git a/pkg/sentry/kernel/seccheck.go b/pkg/sentry/kernel/seccheck.go index 9d680235e..2e2a86c60 100644 --- a/pkg/sentry/kernel/seccheck.go +++ b/pkg/sentry/kernel/seccheck.go @@ -49,9 +49,9 @@ func LoadSeccheckDataLocked(t *Task, mask seccheck.FieldMask, info *pb.ContextDa info.ContainerId = t.tg.leader.ContainerID() } if mask.Contains(seccheck.FieldCtxtCwd) { - if root := t.FSContext().RootDirectoryVFS2(); root.Ok() { + if root := t.FSContext().RootDirectory(); root.Ok() { defer root.DecRef(t) - if wd := t.FSContext().WorkingDirectoryVFS2(); wd.Ok() { + if wd := t.FSContext().WorkingDirectory(); wd.Ok() { defer wd.DecRef(t) vfsObj := root.Mount().Filesystem().VirtualFilesystem() info.Cwd, _ = vfsObj.PathnameWithDeleted(t, root, wd) diff --git a/pkg/sentry/kernel/semaphore/BUILD b/pkg/sentry/kernel/semaphore/BUILD index 6aa74219e..063acca35 100644 --- a/pkg/sentry/kernel/semaphore/BUILD +++ b/pkg/sentry/kernel/semaphore/BUILD @@ -26,10 +26,10 @@ go_library( "//pkg/abi/linux", "//pkg/context", "//pkg/errors/linuxerr", - "//pkg/sentry/fs", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/ipc", "//pkg/sentry/kernel/time", + "//pkg/sentry/vfs", "//pkg/sync", ], ) diff --git a/pkg/sentry/kernel/semaphore/semaphore.go b/pkg/sentry/kernel/semaphore/semaphore.go index 28e466948..f5e873409 100644 --- a/pkg/sentry/kernel/semaphore/semaphore.go +++ b/pkg/sentry/kernel/semaphore/semaphore.go @@ -21,10 +21,10 @@ 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/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/ipc" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" ) @@ -157,7 +157,7 @@ func (r *Registry) FindOrCreate(ctx context.Context, key ipc.Key, nsems int32, m } // Finally create a new set. - return r.newSetLocked(ctx, key, fs.FileOwnerFromContext(ctx), fs.FilePermsFromMode(mode), nsems) + return r.newSetLocked(ctx, key, auth.CredentialsFromContext(ctx), mode, nsems) } // IPCInfo returns information about system-wide semaphore limits and parameters. @@ -228,10 +228,10 @@ func (r *Registry) Remove(id ipc.ID, creds *auth.Credentials) error { // are no more available identifiers. // // Precondition: r.mu must be held. -func (r *Registry) newSetLocked(ctx context.Context, key ipc.Key, creator fs.FileOwner, perms fs.FilePermissions, nsems int32) (*Set, error) { +func (r *Registry) newSetLocked(ctx context.Context, key ipc.Key, creator *auth.Credentials, mode linux.FileMode, nsems int32) (*Set, error) { set := &Set{ registry: r, - obj: ipc.NewObject(r.reg.UserNS, ipc.Key(key), creator, creator, perms), + obj: ipc.NewObject(r.reg.UserNS, ipc.Key(key), creator, creator, mode), changeTime: ktime.NowFromContext(ctx), sems: make([]sem, nsems), } @@ -352,30 +352,30 @@ func (s *Set) Set(ctx context.Context, ds *linux.SemidDS) error { // GetStat extracts semid_ds information from the set. func (s *Set) GetStat(creds *auth.Credentials) (*linux.SemidDS, error) { // "The calling process must have read permission on the semaphore set." - return s.semStat(creds, fs.PermMask{Read: true}) + return s.semStat(creds, vfs.MayRead) } // GetStatAny extracts semid_ds information from the set without requiring read access. func (s *Set) GetStatAny(creds *auth.Credentials) (*linux.SemidDS, error) { - return s.semStat(creds, fs.PermMask{}) + return s.semStat(creds, 0) } -func (s *Set) semStat(creds *auth.Credentials, permMask fs.PermMask) (*linux.SemidDS, error) { +func (s *Set) semStat(creds *auth.Credentials, ats vfs.AccessTypes) (*linux.SemidDS, error) { s.mu.Lock() defer s.mu.Unlock() - if !s.obj.CheckPermissions(creds, permMask) { + if !s.obj.CheckPermissions(creds, ats) { return nil, linuxerr.EACCES } return &linux.SemidDS{ SemPerm: linux.IPCPerm{ Key: uint32(s.obj.Key), - UID: uint32(creds.UserNamespace.MapFromKUID(s.obj.Owner.UID)), - GID: uint32(creds.UserNamespace.MapFromKGID(s.obj.Owner.GID)), - CUID: uint32(creds.UserNamespace.MapFromKUID(s.obj.Creator.UID)), - CGID: uint32(creds.UserNamespace.MapFromKGID(s.obj.Creator.GID)), - Mode: uint16(s.obj.Perms.LinuxMode()), + UID: uint32(creds.UserNamespace.MapFromKUID(s.obj.OwnerUID)), + GID: uint32(creds.UserNamespace.MapFromKGID(s.obj.OwnerGID)), + CUID: uint32(creds.UserNamespace.MapFromKUID(s.obj.CreatorUID)), + CGID: uint32(creds.UserNamespace.MapFromKGID(s.obj.CreatorGID)), + Mode: uint16(s.obj.Mode), Seq: 0, // IPC sequence not supported. }, SemOTime: s.opTime.TimeT(), @@ -394,7 +394,7 @@ func (s *Set) SetVal(ctx context.Context, num int32, val int16, creds *auth.Cred defer s.mu.Unlock() // "The calling process must have alter permission on the semaphore set." - if !s.obj.CheckPermissions(creds, fs.PermMask{Write: true}) { + if !s.obj.CheckPermissions(creds, vfs.MayWrite) { return linuxerr.EACCES } @@ -430,7 +430,7 @@ func (s *Set) SetValAll(ctx context.Context, vals []uint16, creds *auth.Credenti defer s.mu.Unlock() // "The calling process must have alter permission on the semaphore set." - if !s.obj.CheckPermissions(creds, fs.PermMask{Write: true}) { + if !s.obj.CheckPermissions(creds, vfs.MayWrite) { return linuxerr.EACCES } @@ -452,7 +452,7 @@ func (s *Set) GetVal(num int32, creds *auth.Credentials) (int16, error) { defer s.mu.Unlock() // "The calling process must have read permission on the semaphore set." - if !s.obj.CheckPermissions(creds, fs.PermMask{Read: true}) { + if !s.obj.CheckPermissions(creds, vfs.MayRead) { return 0, linuxerr.EACCES } @@ -469,7 +469,7 @@ func (s *Set) GetValAll(creds *auth.Credentials) ([]uint16, error) { defer s.mu.Unlock() // "The calling process must have read permission on the semaphore set." - if !s.obj.CheckPermissions(creds, fs.PermMask{Read: true}) { + if !s.obj.CheckPermissions(creds, vfs.MayRead) { return nil, linuxerr.EACCES } @@ -486,7 +486,7 @@ func (s *Set) GetPID(num int32, creds *auth.Credentials) (int32, error) { defer s.mu.Unlock() // "The calling process must have read permission on the semaphore set." - if !s.obj.CheckPermissions(creds, fs.PermMask{Read: true}) { + if !s.obj.CheckPermissions(creds, vfs.MayRead) { return 0, linuxerr.EACCES } @@ -502,7 +502,7 @@ func (s *Set) countWaiters(num int32, creds *auth.Credentials, pred func(w *wait defer s.mu.Unlock() // The calling process must have read permission on the semaphore set. - if !s.obj.CheckPermissions(creds, fs.PermMask{Read: true}) { + if !s.obj.CheckPermissions(creds, vfs.MayRead) { return 0, linuxerr.EACCES } @@ -558,7 +558,11 @@ func (s *Set) ExecuteOps(ctx context.Context, ops []linux.Sembuf, creds *auth.Cr } } - if !s.obj.CheckPermissions(creds, fs.PermMask{Read: readOnly, Write: !readOnly}) { + ats := vfs.MayRead + if !readOnly { + ats = vfs.MayWrite + } + if !s.obj.CheckPermissions(creds, ats) { return nil, 0, linuxerr.EACCES } diff --git a/pkg/sentry/kernel/shm/BUILD b/pkg/sentry/kernel/shm/BUILD index 17e3ea4b9..245a58e9d 100644 --- a/pkg/sentry/kernel/shm/BUILD +++ b/pkg/sentry/kernel/shm/BUILD @@ -35,13 +35,13 @@ go_library( "//pkg/refs", "//pkg/refsvfs2", "//pkg/sentry/device", - "//pkg/sentry/fs", "//pkg/sentry/kernel/auth", "//pkg/sentry/kernel/ipc", "//pkg/sentry/kernel/time", "//pkg/sentry/memmap", "//pkg/sentry/pgalloc", "//pkg/sentry/usage", + "//pkg/sentry/vfs", "//pkg/sync", "//pkg/usermem", ], diff --git a/pkg/sentry/kernel/shm/shm.go b/pkg/sentry/kernel/shm/shm.go index e70727b4c..af5674dbb 100644 --- a/pkg/sentry/kernel/shm/shm.go +++ b/pkg/sentry/kernel/shm/shm.go @@ -41,13 +41,13 @@ import ( "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/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/ipc" 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/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" ) @@ -187,7 +187,7 @@ func (r *Registry) FindOrCreate(ctx context.Context, pid int32, key ipc.Key, siz } // Need to create a new segment. - s, err := r.newShmLocked(ctx, pid, key, fs.FileOwnerFromContext(ctx), fs.FilePermsFromMode(mode), size) + s, err := r.newShmLocked(ctx, pid, key, auth.CredentialsFromContext(ctx), mode, size) if err != nil { return nil, err } @@ -200,7 +200,7 @@ func (r *Registry) FindOrCreate(ctx context.Context, pid int32, key ipc.Key, siz // newShmLocked creates a new segment in the registry. // // Precondition: Caller must hold r.mu. -func (r *Registry) newShmLocked(ctx context.Context, pid int32, key ipc.Key, creator fs.FileOwner, perms fs.FilePermissions, size uint64) (*Shm, error) { +func (r *Registry) newShmLocked(ctx context.Context, pid int32, key ipc.Key, creator *auth.Credentials, mode linux.FileMode, size uint64) (*Shm, error) { mfp := pgalloc.MemoryFileProviderFromContext(ctx) if mfp == nil { panic(fmt.Sprintf("context.Context %T lacks non-nil value for key %T", ctx, pgalloc.CtxMemoryFileProvider)) @@ -217,7 +217,7 @@ func (r *Registry) newShmLocked(ctx context.Context, pid int32, key ipc.Key, cre registry: r, size: size, effectiveSize: effectiveSize, - obj: ipc.NewObject(r.reg.UserNS, ipc.Key(key), creator, creator, perms), + obj: ipc.NewObject(r.reg.UserNS, ipc.Key(key), creator, creator, mode), fr: fr, creatorPID: pid, changeTime: ktime.NowFromContext(ctx), @@ -524,11 +524,14 @@ func (s *Shm) ConfigureAttach(ctx context.Context, addr hostarch.Addr, opts Atta } creds := auth.CredentialsFromContext(ctx) - if !s.obj.CheckPermissions(creds, fs.PermMask{ - Read: true, - Write: !opts.Readonly, - Execute: opts.Execute, - }) { + ats := vfs.MayRead + if !opts.Readonly { + ats |= vfs.MayWrite + } + if opts.Execute { + ats |= vfs.MayExec + } + if !s.obj.CheckPermissions(creds, ats) { // "The calling process does not have the required permissions for the // requested attach type, and does not have the CAP_IPC_OWNER capability // in the user namespace that governs its IPC namespace." - man shmat(2) @@ -565,7 +568,7 @@ func (s *Shm) IPCStat(ctx context.Context) (*linux.ShmidDS, error) { // "The caller must have read permission on the shared memory segment." // - man shmctl(2) creds := auth.CredentialsFromContext(ctx) - if !s.obj.CheckPermissions(creds, fs.PermMask{Read: true}) { + if !s.obj.CheckPermissions(creds, vfs.MayRead) { // "IPC_STAT or SHM_STAT is requested and shm_perm.mode does not allow // read access for shmid, and the calling process does not have the // CAP_IPC_OWNER capability in the user namespace that governs its IPC @@ -594,11 +597,11 @@ func (s *Shm) IPCStat(ctx context.Context) (*linux.ShmidDS, error) { ds := &linux.ShmidDS{ ShmPerm: linux.IPCPerm{ Key: uint32(s.obj.Key), - UID: uint32(creds.UserNamespace.MapFromKUID(s.obj.Owner.UID)), - GID: uint32(creds.UserNamespace.MapFromKGID(s.obj.Owner.GID)), - CUID: uint32(creds.UserNamespace.MapFromKUID(s.obj.Creator.UID)), - CGID: uint32(creds.UserNamespace.MapFromKGID(s.obj.Creator.GID)), - Mode: mode | uint16(s.obj.Perms.LinuxMode()), + UID: uint32(creds.UserNamespace.MapFromKUID(s.obj.OwnerUID)), + GID: uint32(creds.UserNamespace.MapFromKGID(s.obj.OwnerGID)), + CUID: uint32(creds.UserNamespace.MapFromKUID(s.obj.CreatorUID)), + CGID: uint32(creds.UserNamespace.MapFromKGID(s.obj.CreatorGID)), + Mode: mode | uint16(s.obj.Mode), Seq: 0, // IPC sequences not supported. }, ShmSegsz: s.size, diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index 5ac4106b1..35b72e54c 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -25,7 +25,6 @@ import ( "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/metric" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/futex" @@ -706,7 +705,7 @@ func (t *Task) SyscallRestartBlock() SyscallRestartBlock { // must be locked. func (t *Task) IsChrooted() bool { realRoot := t.mountNamespace.Root() - root := t.fsContext.RootDirectoryVFS2() + root := t.fsContext.RootDirectory() defer root.DecRef(t) return root != realRoot } @@ -740,57 +739,27 @@ func (t *Task) FDTable() *FDTable { // GetFile is a convenience wrapper for t.FDTable().Get. // // Precondition: same as FDTable.Get. -func (t *Task) GetFile(fd int32) *fs.File { +func (t *Task) GetFile(fd int32) *vfs.FileDescription { f, _ := t.fdTable.Get(fd) return f } -// GetFileVFS2 is a convenience wrapper for t.FDTable().GetVFS2. -// -// Precondition: same as FDTable.Get. -func (t *Task) GetFileVFS2(fd int32) *vfs.FileDescription { - f, _ := t.fdTable.GetVFS2(fd) - return f -} - // NewFDs is a convenience wrapper for t.FDTable().NewFDs. // // This automatically passes the task as the context. // // Precondition: same as FDTable. -func (t *Task) NewFDs(fd int32, files []*fs.File, flags FDFlags) ([]int32, error) { +func (t *Task) NewFDs(fd int32, files []*vfs.FileDescription, flags FDFlags) ([]int32, error) { return t.fdTable.NewFDs(t, fd, files, flags) } -// NewFDsVFS2 is a convenience wrapper for t.FDTable().NewFDsVFS2. -// -// This automatically passes the task as the context. -// -// Precondition: same as FDTable. -func (t *Task) NewFDsVFS2(fd int32, files []*vfs.FileDescription, flags FDFlags) ([]int32, error) { - return t.fdTable.NewFDsVFS2(t, fd, files, flags) -} - -// NewFDFrom is a convenience wrapper for t.FDTable().NewFDs with a single file. -// -// This automatically passes the task as the context. -// -// Precondition: same as FDTable. -func (t *Task) NewFDFrom(fd int32, file *fs.File, flags FDFlags) (int32, error) { - fds, err := t.fdTable.NewFDs(t, fd, []*fs.File{file}, flags) - if err != nil { - return 0, err - } - return fds[0], nil -} - -// NewFDFromVFS2 is a convenience wrapper for t.FDTable().NewFDVFS2. +// NewFDFrom is a convenience wrapper for t.FDTable().NewFD. // // This automatically passes the task as the context. // // Precondition: same as FDTable.Get. -func (t *Task) NewFDFromVFS2(fd int32, file *vfs.FileDescription, flags FDFlags) (int32, error) { - return t.fdTable.NewFDVFS2(t, fd, file, flags) +func (t *Task) NewFDFrom(fd int32, file *vfs.FileDescription, flags FDFlags) (int32, error) { + return t.fdTable.NewFD(t, fd, file, flags) } // NewFDAt is a convenience wrapper for t.FDTable().NewFDAt. @@ -798,19 +767,10 @@ func (t *Task) NewFDFromVFS2(fd int32, file *vfs.FileDescription, flags FDFlags) // This automatically passes the task as the context. // // Precondition: same as FDTable. -func (t *Task) NewFDAt(fd int32, file *fs.File, flags FDFlags) error { +func (t *Task) NewFDAt(fd int32, file *vfs.FileDescription, flags FDFlags) error { return t.fdTable.NewFDAt(t, fd, file, flags) } -// NewFDAtVFS2 is a convenience wrapper for t.FDTable().NewFDAtVFS2. -// -// This automatically passes the task as the context. -// -// Precondition: same as FDTable. -func (t *Task) NewFDAtVFS2(fd int32, file *vfs.FileDescription, flags FDFlags) error { - return t.fdTable.NewFDAtVFS2(t, fd, file, flags) -} - // WithMuLocked executes f with t.mu locked. func (t *Task) WithMuLocked(f func(*Task)) { t.mu.Lock() @@ -818,15 +778,9 @@ func (t *Task) WithMuLocked(f func(*Task)) { t.mu.Unlock() } -// MountNamespace returns t's MountNamespace. MountNamespace does not take an -// additional reference on the returned MountNamespace. -func (t *Task) MountNamespace() *fs.MountNamespace { - return t.tg.mounts -} - -// MountNamespaceVFS2 returns t's MountNamespace. A reference is taken on the +// MountNamespace returns t's MountNamespace. A reference is taken on the // returned mount namespace. -func (t *Task) MountNamespaceVFS2() *vfs.MountNamespace { +func (t *Task) MountNamespace() *vfs.MountNamespace { t.mu.Lock() defer t.mu.Unlock() return t.mountNamespace diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index a3eccdc5c..5b33e75d1 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -176,14 +176,11 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { rseqAddr := hostarch.Addr(0) rseqSignature := uint32(0) if args.Flags&linux.CLONE_THREAD == 0 { - if tg.mounts != nil { - tg.mounts.IncRef() - } sh := t.tg.signalHandlers if args.Flags&linux.CLONE_SIGHAND == 0 { sh = sh.Fork() } - tg = t.k.NewThreadGroup(tg.mounts, pidns, sh, linux.Signal(args.ExitSignal), tg.limits.GetCopy()) + tg = t.k.NewThreadGroup(pidns, sh, linux.Signal(args.ExitSignal), tg.limits.GetCopy()) tg.oomScoreAdj = atomicbitops.FromInt32(t.tg.oomScoreAdj.Load()) rseqAddr = t.rseqAddr rseqSignature = t.rseqSignature diff --git a/pkg/sentry/kernel/task_context.go b/pkg/sentry/kernel/task_context.go index d4c25352f..cb3acea4c 100644 --- a/pkg/sentry/kernel/task_context.go +++ b/pkg/sentry/kernel/task_context.go @@ -20,7 +20,6 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/cpuid" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/kernel/ipc" @@ -88,18 +87,12 @@ func (t *Task) contextValue(key any, isTaskGoroutine bool) any { return t.creds.Load() case auth.CtxThreadGroupID: return int32(t.tg.ID()) - case fs.CtxRoot: - if !isTaskGoroutine { - t.mu.Lock() - defer t.mu.Unlock() - } - return t.fsContext.RootDirectory() case vfs.CtxRoot: if !isTaskGoroutine { t.mu.Lock() defer t.mu.Unlock() } - return t.fsContext.RootDirectoryVFS2() + return t.fsContext.RootDirectory() case vfs.CtxMountNamespace: if !isTaskGoroutine { t.mu.Lock() @@ -107,8 +100,6 @@ func (t *Task) contextValue(key any, isTaskGoroutine bool) any { } t.mountNamespace.IncRef() return t.mountNamespace - case fs.CtxDirentCacheLimiter: - return t.k.DirentCacheLimiter case inet.CtxStack: return t.NetworkContext() case ktime.CtxRealtimeClock: diff --git a/pkg/sentry/kernel/task_exec.go b/pkg/sentry/kernel/task_exec.go index f77c9c8b9..97dd7f2f8 100644 --- a/pkg/sentry/kernel/task_exec.go +++ b/pkg/sentry/kernel/task_exec.go @@ -67,7 +67,6 @@ package kernel import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/fsbridge" "gvisor.dev/gvisor/pkg/sentry/mm" "gvisor.dev/gvisor/pkg/sentry/seccheck" @@ -220,7 +219,7 @@ func (r *runSyscallAfterExecStop) execute(t *Task) taskRunState { oldFDTable.DecRef(t) // Remove FDs with the CloseOnExec flag set. - t.fdTable.RemoveIf(t, func(_ *fs.File, _ *vfs.FileDescription, flags FDFlags) bool { + t.fdTable.RemoveIf(t, func(_ *vfs.FileDescription, flags FDFlags) bool { return flags.CloseOnExec }) diff --git a/pkg/sentry/kernel/thread_group.go b/pkg/sentry/kernel/thread_group.go index 5475ff66b..218a808dd 100644 --- a/pkg/sentry/kernel/thread_group.go +++ b/pkg/sentry/kernel/thread_group.go @@ -21,7 +21,6 @@ import ( "gvisor.dev/gvisor/pkg/atomicbitops" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/limits" @@ -241,14 +240,6 @@ type ThreadGroup struct { // oldRSeqCritical is the thread group's old rseq critical region. oldRSeqCritical atomic.Value `state:".(*OldRSeqCriticalRegion)"` - // mounts is the thread group's mount namespace. This does not really - // correspond to a "mount namespace" in Linux, but is more like a - // complete VFS that need not be shared between processes. See the - // comment in mounts.go for more information. - // - // mounts is immutable. - mounts *fs.MountNamespace - // tty is the thread group's controlling terminal. If nil, there is no // controlling terminal. // @@ -265,7 +256,7 @@ type ThreadGroup struct { // thread group leader will send its parent terminationSignal when it exits. // The new thread group isn't visible to the system until a task has been // created inside of it by a successful call to TaskSet.NewTask. -func (k *Kernel) NewThreadGroup(mntns *fs.MountNamespace, pidns *PIDNamespace, sh *SignalHandlers, terminationSignal linux.Signal, limits *limits.LimitSet) *ThreadGroup { +func (k *Kernel) NewThreadGroup(pidns *PIDNamespace, sh *SignalHandlers, terminationSignal linux.Signal, limits *limits.LimitSet) *ThreadGroup { tg := &ThreadGroup{ threadGroupNode: threadGroupNode{ pidns: pidns, @@ -274,7 +265,6 @@ func (k *Kernel) NewThreadGroup(mntns *fs.MountNamespace, pidns *PIDNamespace, s terminationSignal: terminationSignal, ioUsage: &usage.IO{}, limits: limits, - mounts: mntns, } tg.itimerRealTimer = ktime.NewTimer(k.timekeeper.monotonicClock, &itimerRealListener{tg: tg}) tg.timers = make(map[linux.TimerID]*IntervalTimer) @@ -322,9 +312,6 @@ func (tg *ThreadGroup) Release(ctx context.Context) { for _, it := range its { it.DestroyTimer() } - if tg.mounts != nil { - tg.mounts.DecRef(ctx) - } } // forEachChildThreadGroupLocked indicates over all child ThreadGroups. diff --git a/pkg/sentry/mm/special_mappable.go b/pkg/sentry/mm/special_mappable.go index 727284fb2..d0270aba2 100644 --- a/pkg/sentry/mm/special_mappable.go +++ b/pkg/sentry/mm/special_mappable.go @@ -20,7 +20,6 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/sentry/memmap" "gvisor.dev/gvisor/pkg/sentry/pgalloc" - "gvisor.dev/gvisor/pkg/sentry/usage" ) // SpecialMappable implements memmap.MappingIdentity and memmap.Mappable with @@ -132,27 +131,3 @@ func (m *SpecialMappable) FileRange() memmap.FileRange { func (m *SpecialMappable) Length() uint64 { return m.fr.Length() } - -// NewSharedAnonMappable returns a SpecialMappable that implements the -// semantics of mmap(MAP_SHARED|MAP_ANONYMOUS) and mappings of /dev/zero. -// -// TODO(gvisor.dev/issue/1624): Linux uses an ephemeral file created by -// mm/shmem.c:shmem_zero_setup(), and VFS2 does something analogous. VFS1 uses -// a SpecialMappable instead, incorrectly getting device and inode IDs of zero -// and causing memory for shared anonymous mappings to be allocated up-front -// instead of on first touch; this is to avoid exacerbating the fs.MountSource -// leak (b/143656263). Delete this function along with VFS1. -func NewSharedAnonMappable(length uint64, mfp pgalloc.MemoryFileProvider) (*SpecialMappable, error) { - if length == 0 { - return nil, linuxerr.EINVAL - } - alignedLen, ok := hostarch.Addr(length).RoundUp() - if !ok { - return nil, linuxerr.EINVAL - } - fr, err := mfp.MemoryFile().Allocate(uint64(alignedLen), pgalloc.AllocOpts{Kind: usage.Anonymous}) - if err != nil { - return nil, err - } - return NewSpecialMappable("/dev/zero (deleted)", mfp, fr), nil -} diff --git a/pkg/sentry/socket/BUILD b/pkg/sentry/socket/BUILD index a64c0b63c..a61732358 100644 --- a/pkg/sentry/socket/BUILD +++ b/pkg/sentry/socket/BUILD @@ -15,9 +15,6 @@ go_library( "//pkg/context", "//pkg/hostarch", "//pkg/marshal", - "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/kernel", "//pkg/sentry/kernel/time", "//pkg/sentry/socket/unix/transport", diff --git a/pkg/sentry/socket/control/BUILD b/pkg/sentry/socket/control/BUILD index 9753e733a..59cf5521d 100644 --- a/pkg/sentry/socket/control/BUILD +++ b/pkg/sentry/socket/control/BUILD @@ -6,7 +6,6 @@ go_library( name = "control", srcs = [ "control.go", - "control_vfs2.go", ], imports = [ "gvisor.dev/gvisor/pkg/sentry/fs", @@ -20,7 +19,6 @@ go_library( "//pkg/hostarch", "//pkg/marshal", "//pkg/marshal/primitive", - "//pkg/sentry/fs", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", "//pkg/sentry/socket", diff --git a/pkg/sentry/socket/control/control.go b/pkg/sentry/socket/control/control.go index fd9db941c..7af94f4a7 100644 --- a/pkg/sentry/socket/control/control.go +++ b/pkg/sentry/socket/control/control.go @@ -27,11 +27,11 @@ import ( "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal" "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/fs" "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/transport" + "gvisor.dev/gvisor/pkg/sentry/vfs" ) // SCMCredentials represents a SCM_CREDENTIALS socket control message. @@ -43,111 +43,6 @@ type SCMCredentials interface { Credentials(t *kernel.Task) (kernel.ThreadID, auth.UID, auth.GID) } -// LINT.IfChange - -// SCMRights represents a SCM_RIGHTS socket control message. -type SCMRights interface { - transport.RightsControlMessage - - // Files returns up to max RightsFiles. - // - // Returned files are consumed and ownership is transferred to the caller. - // Subsequent calls to Files will return the next files. - Files(ctx context.Context, max int) (rf RightsFiles, truncated bool) -} - -// RightsFiles represents a SCM_RIGHTS socket control message. A reference is -// maintained for each fs.File and is release either when an FD is created or -// when the Release method is called. -// -// +stateify savable -type RightsFiles []*fs.File - -// NewSCMRights creates a new SCM_RIGHTS socket control message representation -// using local sentry FDs. -func NewSCMRights(t *kernel.Task, fds []primitive.Int32) (SCMRights, error) { - files := make(RightsFiles, 0, len(fds)) - for _, fd := range fds { - file := t.GetFile(int32(fd)) - if file == nil { - files.Release(t) - return nil, linuxerr.EBADF - } - files = append(files, file) - } - return &files, nil -} - -// Files implements SCMRights.Files. -func (fs *RightsFiles) Files(_ context.Context, max int) (RightsFiles, bool) { - n := max - var trunc bool - if l := len(*fs); n > l { - n = l - } else if n < l { - trunc = true - } - rf := (*fs)[:n] - *fs = (*fs)[n:] - return rf, trunc -} - -// Clone implements transport.RightsControlMessage.Clone. -func (fs *RightsFiles) Clone() transport.RightsControlMessage { - nfs := append(RightsFiles(nil), *fs...) - for _, nf := range nfs { - nf.IncRef() - } - return &nfs -} - -// Release implements transport.RightsControlMessage.Release. -func (fs *RightsFiles) Release(ctx context.Context) { - for _, f := range *fs { - f.DecRef(ctx) - } - *fs = nil -} - -// rightsFDs gets up to the specified maximum number of FDs. -func rightsFDs(t *kernel.Task, rights SCMRights, cloexec bool, max int) ([]int32, bool) { - files, trunc := rights.Files(t, max) - fds := make([]int32, 0, len(files)) - for i := 0; i < max && len(files) > 0; i++ { - fd, err := t.NewFDFrom(0, files[0], kernel.FDFlags{ - CloseOnExec: cloexec, - }) - files[0].DecRef(t) - files = files[1:] - if err != nil { - t.Warningf("Error inserting FD: %v", err) - // This is what Linux does. - break - } - - fds = append(fds, fd) - } - return fds, trunc -} - -// PackRights packs as many FDs as will fit into the unused capacity of buf. -func PackRights(t *kernel.Task, rights SCMRights, cloexec bool, buf []byte, flags int) ([]byte, int) { - maxFDs := (cap(buf) - len(buf) - linux.SizeOfControlMessageHeader) / 4 - // Linux does not return any FDs if none fit. - if maxFDs <= 0 { - flags |= linux.MSG_CTRUNC - return buf, flags - } - fds, trunc := rightsFDs(t, rights, cloexec, maxFDs) - if trunc { - flags |= linux.MSG_CTRUNC - } - align := t.Arch().Width() - return putCmsg(buf, flags, linux.SCM_RIGHTS, align, fds) -} - -// LINT.ThenChange(./control_vfs2.go) - // scmCredentials represents an SCM_CREDENTIALS socket control message. // // +stateify savable @@ -712,7 +607,7 @@ func Parse(t *kernel.Task, socketOrEndpoint any, buf []byte, width uint) (socket } if len(fds) > 0 { - rights, err := NewSCMRightsVFS2(t, fds) + rights, err := NewSCMRights(t, fds) if err != nil { return socket.ControlMessages{}, err } @@ -741,14 +636,112 @@ func MakeCreds(t *kernel.Task) SCMCredentials { return &scmCredentials{t, tcred.EffectiveKUID, tcred.EffectiveKGID} } -// LINT.IfChange - // New creates default control messages if needed. -func New(t *kernel.Task, socketOrEndpoint any, rights SCMRights) transport.ControlMessages { +func New(t *kernel.Task, socketOrEndpoint any) transport.ControlMessages { return transport.ControlMessages{ Credentials: makeCreds(t, socketOrEndpoint), - Rights: rights, } } -// LINT.ThenChange(./control_vfs2.go) +// SCMRights represents a SCM_RIGHTS socket control message. +// +// +stateify savable +type SCMRights interface { + transport.RightsControlMessage + + // Files returns up to max RightsFiles. + // + // Returned files are consumed and ownership is transferred to the caller. + // Subsequent calls to Files will return the next files. + Files(ctx context.Context, max int) (rf RightsFiles, truncated bool) +} + +// RightsFiles represents a SCM_RIGHTS socket control message. A reference +// is maintained for each vfs.FileDescription and is release either when an FD +// is created or when the Release method is called. +// +// +stateify savable +type RightsFiles []*vfs.FileDescription + +// NewSCMRights creates a new SCM_RIGHTS socket control message +// representation using local sentry FDs. +func NewSCMRights(t *kernel.Task, fds []primitive.Int32) (SCMRights, error) { + files := make(RightsFiles, 0, len(fds)) + for _, fd := range fds { + file := t.GetFile(int32(fd)) + if file == nil { + files.Release(t) + return nil, linuxerr.EBADF + } + files = append(files, file) + } + return &files, nil +} + +// Files implements SCMRights.Files. +func (fs *RightsFiles) Files(ctx context.Context, max int) (RightsFiles, bool) { + n := max + var trunc bool + if l := len(*fs); n > l { + n = l + } else if n < l { + trunc = true + } + rf := (*fs)[:n] + *fs = (*fs)[n:] + return rf, trunc +} + +// Clone implements transport.RightsControlMessage.Clone. +func (fs *RightsFiles) Clone() transport.RightsControlMessage { + nfs := append(RightsFiles(nil), *fs...) + for _, nf := range nfs { + nf.IncRef() + } + return &nfs +} + +// Release implements transport.RightsControlMessage.Release. +func (fs *RightsFiles) Release(ctx context.Context) { + for _, f := range *fs { + f.DecRef(ctx) + } + *fs = nil +} + +// rightsFDs gets up to the specified maximum number of FDs. +func rightsFDs(t *kernel.Task, rights SCMRights, cloexec bool, max int) ([]int32, bool) { + files, trunc := rights.Files(t, max) + fds := make([]int32, 0, len(files)) + for i := 0; i < max && len(files) > 0; i++ { + fd, err := t.NewFDFrom(0, files[0], kernel.FDFlags{ + CloseOnExec: cloexec, + }) + files[0].DecRef(t) + files = files[1:] + if err != nil { + t.Warningf("Error inserting FD: %v", err) + // This is what Linux does. + break + } + + fds = append(fds, int32(fd)) + } + return fds, trunc +} + +// PackRights packs as many FDs as will fit into the unused capacity of buf. +func PackRights(t *kernel.Task, rights SCMRights, cloexec bool, buf []byte, flags int) ([]byte, int) { + maxFDs := (cap(buf) - len(buf) - linux.SizeOfControlMessageHeader) / 4 + // Linux does not return any FDs if none fit. + if maxFDs <= 0 { + flags |= linux.MSG_CTRUNC + return buf, flags + } + fds, trunc := rightsFDs(t, rights, cloexec, maxFDs) + if trunc { + flags |= linux.MSG_CTRUNC + } + align := t.Arch().Width() + return putCmsg(buf, flags, linux.SCM_RIGHTS, align, fds) +} diff --git a/pkg/sentry/socket/control/control_vfs2.go b/pkg/sentry/socket/control/control_vfs2.go deleted file mode 100644 index dcc2cdda8..000000000 --- a/pkg/sentry/socket/control/control_vfs2.go +++ /dev/null @@ -1,128 +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 control - -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/kernel" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sentry/vfs" -) - -// SCMRightsVFS2 represents a SCM_RIGHTS socket control message. -// -// +stateify savable -type SCMRightsVFS2 interface { - transport.RightsControlMessage - - // Files returns up to max RightsFiles. - // - // Returned files are consumed and ownership is transferred to the caller. - // Subsequent calls to Files will return the next files. - Files(ctx context.Context, max int) (rf RightsFilesVFS2, truncated bool) -} - -// RightsFilesVFS2 represents a SCM_RIGHTS socket control message. A reference -// is maintained for each vfs.FileDescription and is release either when an FD -// is created or when the Release method is called. -// -// +stateify savable -type RightsFilesVFS2 []*vfs.FileDescription - -// NewSCMRightsVFS2 creates a new SCM_RIGHTS socket control message -// representation using local sentry FDs. -func NewSCMRightsVFS2(t *kernel.Task, fds []primitive.Int32) (SCMRightsVFS2, error) { - files := make(RightsFilesVFS2, 0, len(fds)) - for _, fd := range fds { - file := t.GetFileVFS2(int32(fd)) - if file == nil { - files.Release(t) - return nil, linuxerr.EBADF - } - files = append(files, file) - } - return &files, nil -} - -// Files implements SCMRights.Files. -func (fs *RightsFilesVFS2) Files(ctx context.Context, max int) (RightsFilesVFS2, bool) { - n := max - var trunc bool - if l := len(*fs); n > l { - n = l - } else if n < l { - trunc = true - } - rf := (*fs)[:n] - *fs = (*fs)[n:] - return rf, trunc -} - -// Clone implements transport.RightsControlMessage.Clone. -func (fs *RightsFilesVFS2) Clone() transport.RightsControlMessage { - nfs := append(RightsFilesVFS2(nil), *fs...) - for _, nf := range nfs { - nf.IncRef() - } - return &nfs -} - -// Release implements transport.RightsControlMessage.Release. -func (fs *RightsFilesVFS2) Release(ctx context.Context) { - for _, f := range *fs { - f.DecRef(ctx) - } - *fs = nil -} - -// rightsFDsVFS2 gets up to the specified maximum number of FDs. -func rightsFDsVFS2(t *kernel.Task, rights SCMRightsVFS2, cloexec bool, max int) ([]int32, bool) { - files, trunc := rights.Files(t, max) - fds := make([]int32, 0, len(files)) - for i := 0; i < max && len(files) > 0; i++ { - fd, err := t.NewFDFromVFS2(0, files[0], kernel.FDFlags{ - CloseOnExec: cloexec, - }) - files[0].DecRef(t) - files = files[1:] - if err != nil { - t.Warningf("Error inserting FD: %v", err) - // This is what Linux does. - break - } - - fds = append(fds, int32(fd)) - } - return fds, trunc -} - -// PackRightsVFS2 packs as many FDs as will fit into the unused capacity of buf. -func PackRightsVFS2(t *kernel.Task, rights SCMRightsVFS2, cloexec bool, buf []byte, flags int) ([]byte, int) { - maxFDs := (cap(buf) - len(buf) - linux.SizeOfControlMessageHeader) / 4 - // Linux does not return any FDs if none fit. - if maxFDs <= 0 { - flags |= linux.MSG_CTRUNC - return buf, flags - } - fds, trunc := rightsFDsVFS2(t, rights, cloexec, maxFDs) - if trunc { - flags |= linux.MSG_CTRUNC - } - align := t.Arch().Width() - return putCmsg(buf, flags, linux.SCM_RIGHTS, align, fds) -} diff --git a/pkg/sentry/socket/hostinet/BUILD b/pkg/sentry/socket/hostinet/BUILD index e53321d82..c61d9b6aa 100644 --- a/pkg/sentry/socket/hostinet/BUILD +++ b/pkg/sentry/socket/hostinet/BUILD @@ -7,10 +7,8 @@ go_library( srcs = [ "device.go", "hostinet.go", - "save_restore.go", "socket.go", "socket_unsafe.go", - "socket_vfs2.go", "sockopt_impl.go", "stack.go", "stack_unsafe.go", @@ -28,8 +26,6 @@ go_library( "//pkg/safemem", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/sockfs", "//pkg/sentry/hostfd", "//pkg/sentry/inet", diff --git a/pkg/sentry/socket/hostinet/socket.go b/pkg/sentry/socket/hostinet/socket.go index 93ad0ef3c..9e96e4f6b 100644 --- a/pkg/sentry/socket/hostinet/socket.go +++ b/pkg/sentry/socket/hostinet/socket.go @@ -28,12 +28,13 @@ import ( "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/safemem" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" + "gvisor.dev/gvisor/pkg/sentry/hostfd" "gvisor.dev/gvisor/pkg/sentry/kernel" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/socket" "gvisor.dev/gvisor/pkg/sentry/socket/control" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/syserr" "gvisor.dev/gvisor/pkg/usermem" "gvisor.dev/gvisor/pkg/waiter" @@ -51,94 +52,122 @@ const ( maxControlLen = 1024 ) -// LINT.IfChange +// Socket implements socket.Socket (and by extension, vfs.FileDescriptionImpl) +// for host sockets. +// +// +stateify savable +type Socket struct { + vfsfd vfs.FileDescription + vfs.FileDescriptionDefaultImpl + vfs.LockFD + // We store metadata for hostinet sockets internally. Technically, we should + // access metadata (e.g. through stat, chmod) on the host for correctness, + // but this is not very useful for inet socket fds, which do not belong to a + // concrete file anyway. + vfs.DentryMetadataFileDescriptionImpl + socket.SendReceiveTimeout -// socketOperations implements fs.FileOperations and socket.Socket for a socket -// implemented using a host socket. -type socketOperations 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"` + family int // Read-only. + stype linux.SockType // Read-only. + protocol int // Read-only. + queue waiter.Queue - socketOpsCommon + // fd is the host socket fd. It must have O_NONBLOCK, so that operations + // will return EWOULDBLOCK instead of blocking on the host. This allows us to + // handle blocking behavior independently in the sentry. + fd int } -var _ = socket.Socket(&socketOperations{}) +var _ = socket.Socket(&Socket{}) -func newSocketFile(ctx context.Context, family int, stype linux.SockType, protocol int, fd int, nonblock bool) (*fs.File, *syserr.Error) { - s := &socketOperations{ - socketOpsCommon: socketOpsCommon{ - family: family, - stype: stype, - protocol: protocol, - fd: fd, - }, +func newSocket(t *kernel.Task, family int, stype linux.SockType, protocol int, fd int, flags uint32) (*vfs.FileDescription, *syserr.Error) { + mnt := t.Kernel().SocketMount() + d := sockfs.NewDentry(t, mnt) + defer d.DecRef(t) + + s := &Socket{ + family: family, + stype: stype, + protocol: protocol, + fd: fd, } + s.LockFD.Init(&vfs.FileLocks{}) if err := fdnotifier.AddFD(int32(fd), &s.queue); err != nil { return nil, syserr.FromError(err) } - dirent := socket.NewDirent(ctx, socketDevice) - defer dirent.DecRef(ctx) - return fs.NewFile(ctx, dirent, fs.FileFlags{NonBlocking: nonblock, Read: true, Write: true, NonSeekable: true}, s), nil + vfsfd := &s.vfsfd + if err := vfsfd.Init(s, linux.O_RDWR|(flags&linux.O_NONBLOCK), mnt, d, &vfs.FileDescriptionOptions{ + DenyPRead: true, + DenyPWrite: true, + UseDentryMetadata: true, + }); err != nil { + fdnotifier.RemoveFD(int32(s.fd)) + return nil, syserr.FromError(err) + } + return vfsfd, nil } -// Ioctl implements fs.FileOperations.Ioctl. -func (s *socketOperations) Ioctl(ctx context.Context, _ *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - return ioctl(ctx, s.fd, io, args) +// Release implements vfs.FileDescriptionImpl.Release. +func (s *Socket) Release(ctx context.Context) { + kernel.KernelFromContext(ctx).DeleteSocket(&s.vfsfd) + fdnotifier.RemoveFD(int32(s.fd)) + _ = unix.Close(s.fd) } -// Read implements fs.FileOperations.Read. -func (s *socketOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - n, err := dst.CopyOutFrom(ctx, safemem.ReaderFunc(func(dsts safemem.BlockSeq) (uint64, error) { - // Refuse to do anything if any part of dst.Addrs was unusable. - if uint64(dst.NumBytes()) != dsts.NumBytes() { - return 0, nil - } - if dsts.IsEmpty() { - return 0, nil - } - if dsts.NumBlocks() == 1 { - // Skip allocating []unix.Iovec. - n, err := unix.Read(s.fd, dsts.Head().ToSlice()) - if err != nil { - return 0, translateIOSyscallError(err) - } - return uint64(n), nil - } - return readv(s.fd, safemem.IovecsFromBlockSeq(dsts)) - })) - return n, err +// Epollable implements FileDescriptionImpl.Epollable. +func (s *Socket) Epollable() bool { + return true } -// Write implements fs.FileOperations.Write. -func (s *socketOperations) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - n, err := src.CopyInTo(ctx, safemem.WriterFunc(func(srcs safemem.BlockSeq) (uint64, error) { - // Refuse to do anything if any part of src.Addrs was unusable. - if uint64(src.NumBytes()) != srcs.NumBytes() { - return 0, nil - } - if srcs.IsEmpty() { - return 0, nil - } - if srcs.NumBlocks() == 1 { - // Skip allocating []unix.Iovec. - n, err := unix.Write(s.fd, srcs.Head().ToSlice()) - if err != nil { - return 0, translateIOSyscallError(err) - } - return uint64(n), nil - } - return writev(s.fd, safemem.IovecsFromBlockSeq(srcs)) - })) - return n, err +// Ioctl implements vfs.FileDescriptionImpl. +func (s *Socket) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { + return ioctl(ctx, s.fd, uio, args) +} + +// PRead implements vfs.FileDescriptionImpl.PRead. +func (s *Socket) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { + return 0, linuxerr.ESPIPE +} + +// Read implements vfs.FileDescriptionImpl. +func (s *Socket) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP + } + + reader := hostfd.GetReadWriterAt(int32(s.fd), -1, opts.Flags) + n, err := dst.CopyOutFrom(ctx, reader) + hostfd.PutReadWriterAt(reader) + return int64(n), err +} + +// PWrite implements vfs.FileDescriptionImpl. +func (s *Socket) PWrite(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { + return 0, linuxerr.ESPIPE +} + +// Write implements vfs.FileDescriptionImpl. +func (s *Socket) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP + } + + writer := hostfd.GetReadWriterAt(int32(s.fd), -1, opts.Flags) + n, err := src.CopyInTo(ctx, writer) + hostfd.PutReadWriterAt(writer) + return int64(n), err +} + +type socketProvider struct { + family int } // Socket implements socket.Provider.Socket. -func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, protocol int) (*fs.File, *syserr.Error) { +func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { // Check that we are using the host network stack. stack := t.NetworkContext() if stack == nil { @@ -176,47 +205,22 @@ func (p *socketProvider) Socket(t *kernel.Task, stypeflags linux.SockType, proto if err != nil { return nil, syserr.FromError(err) } - return newSocketFile(t, p.family, stype, protocol, fd, stypeflags&unix.SOCK_NONBLOCK != 0) + return newSocket(t, p.family, stype, protocol, fd, uint32(stypeflags&unix.SOCK_NONBLOCK)) } // Pair implements socket.Provider.Pair. -func (p *socketProvider) Pair(*kernel.Task, linux.SockType, int) (*fs.File, *fs.File, *syserr.Error) { +func (p *socketProvider) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { // Not supported by AF_INET/AF_INET6. return nil, nil, nil } -// LINT.ThenChange(./socket_vfs2.go) - -// socketOpsCommon contains the socket operations common to VFS1 and VFS2. -// -// +stateify savable -type socketOpsCommon struct { - socket.SendReceiveTimeout - - family int // Read-only. - stype linux.SockType // Read-only. - protocol int // Read-only. - queue waiter.Queue - - // fd is the host socket fd. It must have O_NONBLOCK, so that operations - // will return EWOULDBLOCK instead of blocking on the host. This allows us to - // handle blocking behavior independently in the sentry. - fd int -} - -// Release implements fs.FileOperations.Release. -func (s *socketOpsCommon) Release(context.Context) { - fdnotifier.RemoveFD(int32(s.fd)) - _ = unix.Close(s.fd) -} - // Readiness implements waiter.Waitable.Readiness. -func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask { +func (s *Socket) Readiness(mask waiter.EventMask) waiter.EventMask { return fdnotifier.NonBlockingPoll(int32(s.fd), mask) } // EventRegister implements waiter.Waitable.EventRegister. -func (s *socketOpsCommon) EventRegister(e *waiter.Entry) error { +func (s *Socket) EventRegister(e *waiter.Entry) error { s.queue.EventRegister(e) if err := fdnotifier.UpdateFD(int32(s.fd)); err != nil { s.queue.EventUnregister(e) @@ -226,7 +230,7 @@ func (s *socketOpsCommon) EventRegister(e *waiter.Entry) error { } // EventUnregister implements waiter.Waitable.EventUnregister. -func (s *socketOpsCommon) EventUnregister(e *waiter.Entry) { +func (s *Socket) EventUnregister(e *waiter.Entry) { s.queue.EventUnregister(e) if err := fdnotifier.UpdateFD(int32(s.fd)); err != nil { panic(err) @@ -234,7 +238,7 @@ func (s *socketOpsCommon) EventUnregister(e *waiter.Entry) { } // Connect implements socket.Socket.Connect. -func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { +func (s *Socket) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { if len(sockaddr) > sizeofSockaddr { sockaddr = sockaddr[:sizeofSockaddr] } @@ -275,7 +279,7 @@ func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool } // Accept implements socket.Socket.Accept. -func (s *socketOpsCommon) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { var peerAddr linux.SockAddr var peerAddrBuf []byte var peerAddrlen uint32 @@ -319,23 +323,23 @@ func (s *socketOpsCommon) Accept(t *kernel.Task, peerRequested bool, flags int, kfd int32 kerr error ) - f, err := newVFS2Socket(t, s.family, s.stype, s.protocol, fd, uint32(flags&unix.SOCK_NONBLOCK)) + f, err := newSocket(t, s.family, s.stype, s.protocol, fd, uint32(flags&unix.SOCK_NONBLOCK)) if err != nil { _ = unix.Close(fd) return 0, nil, 0, err } defer f.DecRef(t) - kfd, kerr = t.NewFDFromVFS2(0, f, kernel.FDFlags{ + kfd, kerr = t.NewFDFrom(0, f, kernel.FDFlags{ CloseOnExec: flags&unix.SOCK_CLOEXEC != 0, }) - t.Kernel().RecordSocketVFS2(f) + t.Kernel().RecordSocket(f) return kfd, peerAddr, peerAddrlen, syserr.FromError(kerr) } // Bind implements socket.Socket.Bind. -func (s *socketOpsCommon) Bind(_ *kernel.Task, sockaddr []byte) *syserr.Error { +func (s *Socket) Bind(_ *kernel.Task, sockaddr []byte) *syserr.Error { if len(sockaddr) > sizeofSockaddr { sockaddr = sockaddr[:sizeofSockaddr] } @@ -348,12 +352,12 @@ func (s *socketOpsCommon) Bind(_ *kernel.Task, sockaddr []byte) *syserr.Error { } // Listen implements socket.Socket.Listen. -func (s *socketOpsCommon) Listen(_ *kernel.Task, backlog int) *syserr.Error { +func (s *Socket) Listen(_ *kernel.Task, backlog int) *syserr.Error { return syserr.FromError(unix.Listen(s.fd, backlog)) } // Shutdown implements socket.Socket.Shutdown. -func (s *socketOpsCommon) Shutdown(_ *kernel.Task, how int) *syserr.Error { +func (s *Socket) Shutdown(_ *kernel.Task, how int) *syserr.Error { switch how { case unix.SHUT_RD, unix.SHUT_WR, unix.SHUT_RDWR: return syserr.FromError(unix.Shutdown(s.fd, how)) @@ -363,7 +367,7 @@ func (s *socketOpsCommon) Shutdown(_ *kernel.Task, how int) *syserr.Error { } // GetSockOpt implements socket.Socket.GetSockOpt. -func (s *socketOpsCommon) GetSockOpt(t *kernel.Task, level int, name int, optValAddr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { +func (s *Socket) GetSockOpt(t *kernel.Task, level int, name int, optValAddr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { if outLen < 0 { return nil, syserr.ErrInvalidArgument } @@ -432,7 +436,7 @@ func (s *socketOpsCommon) GetSockOpt(t *kernel.Task, level int, name int, optVal } // SetSockOpt implements socket.Socket.SetSockOpt. -func (s *socketOpsCommon) SetSockOpt(t *kernel.Task, level int, name int, opt []byte) *syserr.Error { +func (s *Socket) SetSockOpt(t *kernel.Task, level int, name int, opt []byte) *syserr.Error { // Only allow known and safe options. optlen := setSockOptLen(t, level, name) switch level { @@ -477,7 +481,7 @@ func (s *socketOpsCommon) SetSockOpt(t *kernel.Task, level int, name int, opt [] return nil } -func (s *socketOpsCommon) recvMsgFromHost(iovs []unix.Iovec, flags int, senderRequested bool, controlLen uint64) (uint64, int, []byte, []byte, error) { +func (s *Socket) recvMsgFromHost(iovs []unix.Iovec, flags int, senderRequested bool, controlLen uint64) (uint64, int, []byte, []byte, error) { // We always do a non-blocking recv*(). sysflags := flags | unix.MSG_DONTWAIT @@ -509,7 +513,7 @@ func (s *socketOpsCommon) recvMsgFromHost(iovs []unix.Iovec, flags int, senderRe } // RecvMsg implements socket.Socket.RecvMsg. -func (s *socketOpsCommon) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, controlLen uint64) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { +func (s *Socket) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, controlLen uint64) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { // Only allow known and safe flags. if flags&^(unix.MSG_DONTWAIT|unix.MSG_PEEK|unix.MSG_TRUNC|unix.MSG_ERRQUEUE) != 0 { return 0, 0, nil, 0, socket.ControlMessages{}, syserr.ErrInvalidArgument @@ -671,7 +675,7 @@ func parseUnixControlMessages(unixControlMessages []unix.SocketControlMessage) s } // SendMsg implements socket.Socket.SendMsg. -func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { +func (s *Socket) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { // Only allow known and safe flags. if flags&^(unix.MSG_DONTWAIT|unix.MSG_EOR|unix.MSG_FASTOPEN|unix.MSG_MORE|unix.MSG_NOSIGNAL) != 0 { return 0, syserr.ErrInvalidArgument @@ -771,7 +775,7 @@ func translateIOSyscallError(err error) error { } // State implements socket.Socket.State. -func (s *socketOpsCommon) State() uint32 { +func (s *Socket) State() uint32 { info := linux.TCPInfo{} buf := make([]byte, linux.SizeOfTCPInfo) var err error @@ -795,17 +799,12 @@ func (s *socketOpsCommon) State() uint32 { } // Type implements socket.Socket.Type. -func (s *socketOpsCommon) Type() (family int, skType linux.SockType, protocol int) { +func (s *Socket) Type() (family int, skType linux.SockType, protocol int) { return s.family, s.stype, s.protocol } -type socketProvider struct { - family int -} - func init() { for _, family := range []int{unix.AF_INET, unix.AF_INET6} { socket.RegisterProvider(family, &socketProvider{family}) - socket.RegisterProviderVFS2(family, &socketProviderVFS2{family}) } } diff --git a/pkg/sentry/socket/hostinet/socket_unsafe.go b/pkg/sentry/socket/hostinet/socket_unsafe.go index 77162992a..4e62571d4 100644 --- a/pkg/sentry/socket/hostinet/socket_unsafe.go +++ b/pkg/sentry/socket/hostinet/socket_unsafe.go @@ -230,7 +230,7 @@ func getsockopt(fd int, level, name int, opt []byte) ([]byte, error) { } // GetSockName implements socket.Socket.GetSockName. -func (s *socketOpsCommon) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { addr := make([]byte, sizeofSockaddr) addrlen := uint32(len(addr)) _, _, errno := unix.Syscall(unix.SYS_GETSOCKNAME, uintptr(s.fd), uintptr(unsafe.Pointer(&addr[0])), uintptr(unsafe.Pointer(&addrlen))) @@ -241,7 +241,7 @@ func (s *socketOpsCommon) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, * } // GetPeerName implements socket.Socket.GetPeerName. -func (s *socketOpsCommon) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { addr := make([]byte, sizeofSockaddr) addrlen := uint32(len(addr)) _, _, errno := unix.Syscall(unix.SYS_GETPEERNAME, uintptr(s.fd), uintptr(unsafe.Pointer(&addr[0])), uintptr(unsafe.Pointer(&addrlen))) diff --git a/pkg/sentry/socket/hostinet/socket_vfs2.go b/pkg/sentry/socket/hostinet/socket_vfs2.go deleted file mode 100644 index 6c2635264..000000000 --- a/pkg/sentry/socket/hostinet/socket_vfs2.go +++ /dev/null @@ -1,199 +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 hostinet - -import ( - "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/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" - "gvisor.dev/gvisor/pkg/sentry/hostfd" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// +stateify savable -type socketVFS2 struct { - vfsfd vfs.FileDescription - vfs.FileDescriptionDefaultImpl - vfs.LockFD - - // We store metadata for hostinet sockets internally. Technically, we should - // access metadata (e.g. through stat, chmod) on the host for correctness, - // but this is not very useful for inet socket fds, which do not belong to a - // concrete file anyway. - vfs.DentryMetadataFileDescriptionImpl - - socketOpsCommon -} - -var _ = socket.SocketVFS2(&socketVFS2{}) - -func newVFS2Socket(t *kernel.Task, family int, stype linux.SockType, protocol int, fd int, flags uint32) (*vfs.FileDescription, *syserr.Error) { - mnt := t.Kernel().SocketMount() - d := sockfs.NewDentry(t, mnt) - defer d.DecRef(t) - - s := &socketVFS2{ - socketOpsCommon: socketOpsCommon{ - family: family, - stype: stype, - protocol: protocol, - fd: fd, - }, - } - s.LockFD.Init(&vfs.FileLocks{}) - if err := fdnotifier.AddFD(int32(fd), &s.queue); err != nil { - return nil, syserr.FromError(err) - } - vfsfd := &s.vfsfd - if err := vfsfd.Init(s, linux.O_RDWR|(flags&linux.O_NONBLOCK), mnt, d, &vfs.FileDescriptionOptions{ - DenyPRead: true, - DenyPWrite: true, - UseDentryMetadata: true, - }); err != nil { - fdnotifier.RemoveFD(int32(s.fd)) - return nil, syserr.FromError(err) - } - return vfsfd, nil -} - -// Release implements vfs.FileDescriptionImpl.Release. -func (s *socketVFS2) Release(ctx context.Context) { - kernel.KernelFromContext(ctx).DeleteSocketVFS2(&s.vfsfd) - s.socketOpsCommon.Release(ctx) -} - -// Readiness implements waiter.Waitable.Readiness. -func (s *socketVFS2) Readiness(mask waiter.EventMask) waiter.EventMask { - return s.socketOpsCommon.Readiness(mask) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (s *socketVFS2) EventRegister(e *waiter.Entry) error { - return s.socketOpsCommon.EventRegister(e) -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (s *socketVFS2) EventUnregister(e *waiter.Entry) { - s.socketOpsCommon.EventUnregister(e) -} - -// Epollable implements FileDescriptionImpl.Epollable. -func (s *socketVFS2) Epollable() bool { - return true -} - -// Ioctl implements vfs.FileDescriptionImpl. -func (s *socketVFS2) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { - return ioctl(ctx, s.fd, uio, args) -} - -// PRead implements vfs.FileDescriptionImpl.PRead. -func (s *socketVFS2) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// Read implements vfs.FileDescriptionImpl. -func (s *socketVFS2) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - reader := hostfd.GetReadWriterAt(int32(s.fd), -1, opts.Flags) - n, err := dst.CopyOutFrom(ctx, reader) - hostfd.PutReadWriterAt(reader) - return int64(n), err -} - -// PWrite implements vfs.FileDescriptionImpl. -func (s *socketVFS2) PWrite(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// Write implements vfs.FileDescriptionImpl. -func (s *socketVFS2) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - writer := hostfd.GetReadWriterAt(int32(s.fd), -1, opts.Flags) - n, err := src.CopyInTo(ctx, writer) - hostfd.PutReadWriterAt(writer) - return int64(n), err -} - -type socketProviderVFS2 struct { - family int -} - -// Socket implements socket.ProviderVFS2.Socket. -func (p *socketProviderVFS2) Socket(t *kernel.Task, stypeflags linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { - // Check that we are using the host network stack. - stack := t.NetworkContext() - if stack == nil { - return nil, nil - } - if _, ok := stack.(*Stack); !ok { - return nil, nil - } - - // Only accept TCP and UDP. - stype := stypeflags & linux.SOCK_TYPE_MASK - switch stype { - case unix.SOCK_STREAM: - switch protocol { - case 0, unix.IPPROTO_TCP: - // ok - default: - return nil, nil - } - case unix.SOCK_DGRAM: - switch protocol { - case 0, unix.IPPROTO_UDP: - // ok - default: - return nil, nil - } - default: - return nil, nil - } - - // Conservatively ignore all flags specified by the application and add - // SOCK_NONBLOCK since socketOperations requires it. Pass a protocol of 0 - // to simplify the syscall filters, since 0 and IPPROTO_* are equivalent. - fd, err := unix.Socket(p.family, int(stype)|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, 0) - if err != nil { - return nil, syserr.FromError(err) - } - return newVFS2Socket(t, p.family, stype, protocol, fd, uint32(stypeflags&unix.SOCK_NONBLOCK)) -} - -// Pair implements socket.Provider.Pair. -func (p *socketProviderVFS2) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { - // Not supported by AF_INET/AF_INET6. - return nil, nil, nil -} diff --git a/pkg/sentry/socket/netlink/BUILD b/pkg/sentry/socket/netlink/BUILD index 9710a15ee..130715ad7 100644 --- a/pkg/sentry/socket/netlink/BUILD +++ b/pkg/sentry/socket/netlink/BUILD @@ -7,9 +7,7 @@ go_library( srcs = [ "message.go", "provider.go", - "provider_vfs2.go", "socket.go", - "socket_vfs2.go", ], visibility = ["//pkg/sentry:internal"], deps = [ @@ -23,8 +21,6 @@ go_library( "//pkg/marshal/primitive", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/sockfs", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/socket/netlink/provider.go b/pkg/sentry/socket/netlink/provider.go index 31e374833..0c01180e6 100644 --- a/pkg/sentry/socket/netlink/provider.go +++ b/pkg/sentry/socket/netlink/provider.go @@ -19,9 +19,10 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/sentry/fs" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/socket" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/syserr" ) @@ -67,14 +68,12 @@ func RegisterProvider(protocol int, provider Provider) { protocols[protocol] = provider } -// LINT.IfChange - // socketProvider implements socket.Provider. type socketProvider struct { } // Socket implements socket.Provider.Socket. -func (*socketProvider) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *syserr.Error) { +func (*socketProvider) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { // Netlink sockets must be specified as datagram or raw, but they // behave the same regardless of type. if stype != linux.SOCK_DGRAM && stype != linux.SOCK_RAW { @@ -91,26 +90,32 @@ func (*socketProvider) Socket(t *kernel.Task, stype linux.SockType, protocol int return nil, err } - s, err := NewSocket(t, stype, p) + s, err := New(t, stype, p) if err != nil { return nil, err } - d := socket.NewDirent(t, netlinkSocketDevice) + vfsfd := &s.vfsfd + mnt := t.Kernel().SocketMount() + d := sockfs.NewDentry(t, mnt) defer d.DecRef(t) - return fs.NewFile(t, d, fs.FileFlags{Read: true, Write: true, NonSeekable: true}, s), nil + if err := vfsfd.Init(s, linux.O_RDWR, mnt, d, &vfs.FileDescriptionOptions{ + DenyPRead: true, + DenyPWrite: true, + UseDentryMetadata: true, + }); err != nil { + return nil, syserr.FromError(err) + } + return vfsfd, nil } // Pair implements socket.Provider.Pair by returning an error. -func (*socketProvider) Pair(*kernel.Task, linux.SockType, int) (*fs.File, *fs.File, *syserr.Error) { +func (*socketProvider) Pair(*kernel.Task, linux.SockType, int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { // Netlink sockets never supports creating socket pairs. return nil, nil, syserr.ErrNotSupported } -// LINT.ThenChange(./provider_vfs2.go) - // init registers the socket provider. func init() { socket.RegisterProvider(linux.AF_NETLINK, &socketProvider{}) - socket.RegisterProviderVFS2(linux.AF_NETLINK, &socketProviderVFS2{}) } diff --git a/pkg/sentry/socket/netlink/provider_vfs2.go b/pkg/sentry/socket/netlink/provider_vfs2.go deleted file mode 100644 index f061c5d62..000000000 --- a/pkg/sentry/socket/netlink/provider_vfs2.go +++ /dev/null @@ -1,70 +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 netlink - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserr" -) - -// socketProviderVFS2 implements socket.Provider. -type socketProviderVFS2 struct { -} - -// Socket implements socket.Provider.Socket. -func (*socketProviderVFS2) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { - // Netlink sockets must be specified as datagram or raw, but they - // behave the same regardless of type. - if stype != linux.SOCK_DGRAM && stype != linux.SOCK_RAW { - return nil, syserr.ErrSocketNotSupported - } - - provider, ok := protocols[protocol] - if !ok { - return nil, syserr.ErrProtocolNotSupported - } - - p, err := provider(t) - if err != nil { - return nil, err - } - - s, err := NewVFS2(t, stype, p) - if err != nil { - return nil, err - } - - vfsfd := &s.vfsfd - mnt := t.Kernel().SocketMount() - d := sockfs.NewDentry(t, mnt) - defer d.DecRef(t) - if err := vfsfd.Init(s, linux.O_RDWR, mnt, d, &vfs.FileDescriptionOptions{ - DenyPRead: true, - DenyPWrite: true, - UseDentryMetadata: true, - }); err != nil { - return nil, syserr.FromError(err) - } - return vfsfd, nil -} - -// Pair implements socket.Provider.Pair by returning an error. -func (*socketProviderVFS2) Pair(*kernel.Task, linux.SockType, int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { - // Netlink sockets never supports creating socket pairs. - return nil, nil, syserr.ErrNotSupported -} diff --git a/pkg/sentry/socket/netlink/socket.go b/pkg/sentry/socket/netlink/socket.go index fe7f150c4..9eb5c5a3c 100644 --- a/pkg/sentry/socket/netlink/socket.go +++ b/pkg/sentry/socket/netlink/socket.go @@ -28,8 +28,6 @@ import ( "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/sentry/arch" "gvisor.dev/gvisor/pkg/sentry/device" - "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" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" @@ -37,6 +35,7 @@ import ( "gvisor.dev/gvisor/pkg/sentry/socket/netlink/port" "gvisor.dev/gvisor/pkg/sentry/socket/unix" "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/syserr" "gvisor.dev/gvisor/pkg/tcpip" @@ -62,8 +61,6 @@ var errNoFilter = syserr.New("no filter attached", errno.ENOENT) // netlinkSocketDevice is the netlink socket virtual device. var netlinkSocketDevice = device.NewAnonDevice() -// LINT.IfChange - // Socket is the base socket type for netlink sockets. // // This implementation only supports userspace sending and receiving messages @@ -73,21 +70,10 @@ var netlinkSocketDevice = device.NewAnonDevice() // // +stateify savable type Socket 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"` - - socketOpsCommon -} - -// socketOpsCommon contains the socket operations common to VFS1 and VFS2. -// -// +stateify savable -type socketOpsCommon struct { + vfsfd vfs.FileDescription + vfs.FileDescriptionDefaultImpl + vfs.DentryMetadataFileDescriptionImpl + vfs.LockFD socket.SendReceiveTimeout // ports provides netlink port allocation. @@ -131,8 +117,8 @@ type socketOpsCommon struct { var _ socket.Socket = (*Socket)(nil) var _ transport.Credentialer = (*Socket)(nil) -// NewSocket creates a new Socket. -func NewSocket(t *kernel.Task, skType linux.SockType, protocol Protocol) (*Socket, *syserr.Error) { +// New creates a new Socket. +func New(t *kernel.Task, skType linux.SockType, protocol Protocol) (*Socket, *syserr.Error) { // Datagram endpoint used to buffer kernel -> user messages. ep := transport.NewConnectionless(t) @@ -150,20 +136,22 @@ func NewSocket(t *kernel.Task, skType linux.SockType, protocol Protocol) (*Socke return nil, err } - return &Socket{ - socketOpsCommon: socketOpsCommon{ - ports: t.Kernel().NetlinkPorts(), - protocol: protocol, - skType: skType, - ep: ep, - connection: connection, - sendBufferSize: defaultSendBufferSize, - }, - }, nil + fd := &Socket{ + ports: t.Kernel().NetlinkPorts(), + protocol: protocol, + skType: skType, + ep: ep, + connection: connection, + sendBufferSize: defaultSendBufferSize, + } + fd.LockFD.Init(&vfs.FileLocks{}) + return fd, nil } -// Release implements fs.FileOperations.Release. -func (s *socketOpsCommon) Release(ctx context.Context) { +// Release implements vfs.FileDescriptionImpl.Release. +func (s *Socket) Release(ctx context.Context) { + t := kernel.TaskFromContext(ctx) + t.Kernel().DeleteSocket(&s.vfsfd) s.connection.Release(ctx) s.ep.Close(ctx) @@ -172,8 +160,62 @@ func (s *socketOpsCommon) Release(ctx context.Context) { } } +// Epollable implements FileDescriptionImpl.Epollable. +func (s *Socket) Epollable() bool { + return true +} + +// Ioctl implements vfs.FileDescriptionImpl. +func (*Socket) Ioctl(context.Context, usermem.IO, arch.SyscallArguments) (uintptr, error) { + // TODO(b/68878065): no ioctls supported. + return 0, linuxerr.ENOTTY +} + +// PRead implements vfs.FileDescriptionImpl. +func (s *Socket) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { + return 0, linuxerr.ESPIPE +} + +// Read implements vfs.FileDescriptionImpl. +func (s *Socket) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP + } + + if dst.NumBytes() == 0 { + return 0, nil + } + r := unix.EndpointReader{ + Endpoint: s.ep, + } + n, err := dst.CopyOutFrom(ctx, &r) + if r.Notify != nil { + r.Notify() + } + return n, err +} + +// PWrite implements vfs.FileDescriptionImpl. +func (s *Socket) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { + return 0, linuxerr.ESPIPE +} + +// Write implements vfs.FileDescriptionImpl. +func (s *Socket) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP + } + + n, err := s.sendMsg(ctx, src, nil, 0, socket.ControlMessages{}) + return int64(n), err.ToError() +} + // Readiness implements waiter.Waitable.Readiness. -func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask { +func (s *Socket) Readiness(mask waiter.EventMask) waiter.EventMask { // ep holds messages to be read and thus handles EventIn readiness. ready := s.ep.Readiness(mask) @@ -187,35 +229,29 @@ func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask { } // EventRegister implements waiter.Waitable.EventRegister. -func (s *socketOpsCommon) EventRegister(e *waiter.Entry) error { +func (s *Socket) EventRegister(e *waiter.Entry) error { return s.ep.EventRegister(e) // Writable readiness never changes, so no registration is needed. } // EventUnregister implements waiter.Waitable.EventUnregister. -func (s *socketOpsCommon) EventUnregister(e *waiter.Entry) { +func (s *Socket) EventUnregister(e *waiter.Entry) { s.ep.EventUnregister(e) } // Passcred implements transport.Credentialer.Passcred. -func (s *socketOpsCommon) Passcred() bool { +func (s *Socket) Passcred() bool { return s.ep.SocketOptions().GetPassCred() } // ConnectedPasscred implements transport.Credentialer.ConnectedPasscred. -func (s *socketOpsCommon) ConnectedPasscred() bool { +func (s *Socket) ConnectedPasscred() bool { // This socket is connected to the kernel, which doesn't need creds. // // This is arbitrary, as ConnectedPasscred on this type has no callers. return false } -// Ioctl implements fs.FileOperations.Ioctl. -func (*Socket) Ioctl(context.Context, *fs.File, usermem.IO, arch.SyscallArguments) (uintptr, error) { - // TODO(b/68878065): no ioctls supported. - return 0, linuxerr.ENOTTY -} - // ExtractSockAddr extracts the SockAddrNetlink from b. func ExtractSockAddr(b []byte) (*linux.SockAddrNetlink, *syserr.Error) { if len(b) < linux.SockAddrNetlinkSize { @@ -237,7 +273,7 @@ func ExtractSockAddr(b []byte) (*linux.SockAddrNetlink, *syserr.Error) { // port of 0 defaults to the ThreadGroup ID. // // Preconditions: mu is held. -func (s *socketOpsCommon) bindPort(t *kernel.Task, port int32) *syserr.Error { +func (s *Socket) bindPort(t *kernel.Task, port int32) *syserr.Error { if s.bound { // Re-binding is only allowed if the port doesn't change. if port != s.portID { @@ -261,7 +297,7 @@ func (s *socketOpsCommon) bindPort(t *kernel.Task, port int32) *syserr.Error { } // Bind implements socket.Socket.Bind. -func (s *socketOpsCommon) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error { +func (s *Socket) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error { a, err := ExtractSockAddr(sockaddr) if err != nil { return err @@ -279,7 +315,7 @@ func (s *socketOpsCommon) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error { } // Connect implements socket.Socket.Connect. -func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { +func (s *Socket) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { a, err := ExtractSockAddr(sockaddr) if err != nil { return err @@ -310,25 +346,25 @@ func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool } // Accept implements socket.Socket.Accept. -func (s *socketOpsCommon) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { // Netlink sockets never support accept. return 0, nil, 0, syserr.ErrNotSupported } // Listen implements socket.Socket.Listen. -func (s *socketOpsCommon) Listen(t *kernel.Task, backlog int) *syserr.Error { +func (s *Socket) Listen(t *kernel.Task, backlog int) *syserr.Error { // Netlink sockets never support listen. return syserr.ErrNotSupported } // Shutdown implements socket.Socket.Shutdown. -func (s *socketOpsCommon) Shutdown(t *kernel.Task, how int) *syserr.Error { +func (s *Socket) Shutdown(t *kernel.Task, how int) *syserr.Error { // Netlink sockets never support shutdown. return syserr.ErrNotSupported } // GetSockOpt implements socket.Socket.GetSockOpt. -func (s *socketOpsCommon) GetSockOpt(t *kernel.Task, level int, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { +func (s *Socket) GetSockOpt(t *kernel.Task, level int, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { switch level { case linux.SOL_SOCKET: switch name { @@ -374,7 +410,7 @@ func (s *socketOpsCommon) GetSockOpt(t *kernel.Task, level int, name int, outPtr } // SetSockOpt implements socket.Socket.SetSockOpt. -func (s *socketOpsCommon) SetSockOpt(t *kernel.Task, level int, name int, opt []byte) *syserr.Error { +func (s *Socket) SetSockOpt(t *kernel.Task, level int, name int, opt []byte) *syserr.Error { switch level { case linux.SOL_SOCKET: switch name { @@ -462,7 +498,7 @@ func (s *socketOpsCommon) SetSockOpt(t *kernel.Task, level int, name int, opt [] } // GetSockName implements socket.Socket.GetSockName. -func (s *socketOpsCommon) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { s.mu.Lock() defer s.mu.Unlock() @@ -474,7 +510,7 @@ func (s *socketOpsCommon) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, * } // GetPeerName implements socket.Socket.GetPeerName. -func (s *socketOpsCommon) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { sa := &linux.SockAddrNetlink{ Family: linux.AF_NETLINK, // TODO(b/68878065): Support non-kernel peers. For now the peer @@ -485,7 +521,7 @@ func (s *socketOpsCommon) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, * } // RecvMsg implements socket.Socket.RecvMsg. -func (s *socketOpsCommon) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, controlDataLen uint64) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { +func (s *Socket) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, controlDataLen uint64) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { from := &linux.SockAddrNetlink{ Family: linux.AF_NETLINK, PortID: 0, @@ -557,16 +593,6 @@ func (s *socketOpsCommon) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags } } -// Read implements fs.FileOperations.Read. -func (s *Socket) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - if dst.NumBytes() == 0 { - return 0, nil - } - return dst.CopyOutFrom(ctx, &unix.EndpointReader{ - Endpoint: s.ep, - }) -} - // kernelSCM implements control.SCMCredentials with credentials that represent // the kernel itself rather than a Task. // @@ -588,7 +614,7 @@ func (kernelSCM) Credentials(*kernel.Task) (kernel.ThreadID, auth.UID, auth.GID) var kernelCreds = &kernelSCM{} // sendResponse sends the response messages in ms back to userspace. -func (s *socketOpsCommon) sendResponse(ctx context.Context, ms *MessageSet) *syserr.Error { +func (s *Socket) sendResponse(ctx context.Context, ms *MessageSet) *syserr.Error { // Linux combines multiple netlink messages into a single datagram. bufs := make([][]byte, 0, len(ms.Messages)) for _, m := range ms.Messages { @@ -664,7 +690,7 @@ func dumpAckMesage(hdr linux.NetlinkMessageHeader, ms *MessageSet) { // processMessages handles each message in buf, passing it to the protocol // handler for final handling. -func (s *socketOpsCommon) processMessages(ctx context.Context, buf []byte) *syserr.Error { +func (s *Socket) processMessages(ctx context.Context, buf []byte) *syserr.Error { for len(buf) > 0 { msg, rest, ok := ParseMessage(buf) if !ok { @@ -696,7 +722,7 @@ func (s *socketOpsCommon) processMessages(ctx context.Context, buf []byte) *syse } // sendMsg is the core of message send, used for SendMsg and Write. -func (s *socketOpsCommon) sendMsg(ctx context.Context, src usermem.IOSequence, to []byte, flags int, controlMessages socket.ControlMessages) (int, *syserr.Error) { +func (s *Socket) sendMsg(ctx context.Context, src usermem.IOSequence, to []byte, flags int, controlMessages socket.ControlMessages) (int, *syserr.Error) { dstPort := int32(0) if len(to) != 0 { @@ -749,24 +775,16 @@ func (s *socketOpsCommon) sendMsg(ctx context.Context, src usermem.IOSequence, t } // SendMsg implements socket.Socket.SendMsg. -func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { +func (s *Socket) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { return s.sendMsg(t, src, to, flags, controlMessages) } -// Write implements fs.FileOperations.Write. -func (s *Socket) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - n, err := s.sendMsg(ctx, src, nil, 0, socket.ControlMessages{}) - return int64(n), err.ToError() -} - // State implements socket.Socket.State. -func (s *socketOpsCommon) State() uint32 { +func (s *Socket) State() uint32 { return s.ep.State() } // Type implements socket.Socket.Type. -func (s *socketOpsCommon) Type() (family int, skType linux.SockType, protocol int) { +func (s *Socket) Type() (family int, skType linux.SockType, protocol int) { return linux.AF_NETLINK, s.skType, s.protocol.Protocol() } - -// LINT.ThenChange(./socket_vfs2.go) diff --git a/pkg/sentry/socket/netlink/socket_vfs2.go b/pkg/sentry/socket/netlink/socket_vfs2.go deleted file mode 100644 index 59583ec33..000000000 --- a/pkg/sentry/socket/netlink/socket_vfs2.go +++ /dev/null @@ -1,160 +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 netlink - -import ( - "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/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/socket/unix" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// SocketVFS2 is the base VFS2 socket type for netlink sockets. -// -// This implementation only supports userspace sending and receiving messages -// to/from the kernel. -// -// SocketVFS2 implements socket.SocketVFS2 and transport.Credentialer. -// -// +stateify savable -type SocketVFS2 struct { - vfsfd vfs.FileDescription - vfs.FileDescriptionDefaultImpl - vfs.DentryMetadataFileDescriptionImpl - vfs.LockFD - - socketOpsCommon -} - -var _ socket.SocketVFS2 = (*SocketVFS2)(nil) -var _ transport.Credentialer = (*SocketVFS2)(nil) - -// NewVFS2 creates a new SocketVFS2. -func NewVFS2(t *kernel.Task, skType linux.SockType, protocol Protocol) (*SocketVFS2, *syserr.Error) { - // Datagram endpoint used to buffer kernel -> user messages. - ep := transport.NewConnectionless(t) - - // Bind the endpoint for good measure so we can connect to it. The - // bound address will never be exposed. - if err := ep.Bind(tcpip.FullAddress{Addr: "dummy"}); err != nil { - ep.Close(t) - return nil, err - } - - // Create a connection from which the kernel can write messages. - connection, err := ep.(transport.BoundEndpoint).UnidirectionalConnect(t) - if err != nil { - ep.Close(t) - return nil, err - } - - fd := &SocketVFS2{ - socketOpsCommon: socketOpsCommon{ - ports: t.Kernel().NetlinkPorts(), - protocol: protocol, - skType: skType, - ep: ep, - connection: connection, - sendBufferSize: defaultSendBufferSize, - }, - } - fd.LockFD.Init(&vfs.FileLocks{}) - return fd, nil -} - -// Release implements vfs.FileDescriptionImpl.Release. -func (s *SocketVFS2) Release(ctx context.Context) { - t := kernel.TaskFromContext(ctx) - t.Kernel().DeleteSocketVFS2(&s.vfsfd) - s.socketOpsCommon.Release(ctx) -} - -// Readiness implements waiter.Waitable.Readiness. -func (s *SocketVFS2) Readiness(mask waiter.EventMask) waiter.EventMask { - return s.socketOpsCommon.Readiness(mask) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (s *SocketVFS2) EventRegister(e *waiter.Entry) error { - return s.socketOpsCommon.EventRegister(e) -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (s *SocketVFS2) EventUnregister(e *waiter.Entry) { - s.socketOpsCommon.EventUnregister(e) -} - -// Epollable implements FileDescriptionImpl.Epollable. -func (s *SocketVFS2) Epollable() bool { - return true -} - -// Ioctl implements vfs.FileDescriptionImpl. -func (*SocketVFS2) Ioctl(context.Context, usermem.IO, arch.SyscallArguments) (uintptr, error) { - // TODO(b/68878065): no ioctls supported. - return 0, linuxerr.ENOTTY -} - -// PRead implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// Read implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - if dst.NumBytes() == 0 { - return 0, nil - } - r := unix.EndpointReader{ - Endpoint: s.ep, - } - n, err := dst.CopyOutFrom(ctx, &r) - if r.Notify != nil { - r.Notify() - } - return n, err -} - -// PWrite implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// Write implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - n, err := s.sendMsg(ctx, src, nil, 0, socket.ControlMessages{}) - return int64(n), err.ToError() -} diff --git a/pkg/sentry/socket/netstack/BUILD b/pkg/sentry/socket/netstack/BUILD index 01ebe2d00..f772ebdc7 100644 --- a/pkg/sentry/socket/netstack/BUILD +++ b/pkg/sentry/socket/netstack/BUILD @@ -8,9 +8,7 @@ go_library( "device.go", "netstack.go", "netstack_state.go", - "netstack_vfs2.go", "provider.go", - "provider_vfs2.go", "save_restore.go", "stack.go", "tun.go", @@ -30,8 +28,6 @@ go_library( "//pkg/metric", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/sockfs", "//pkg/sentry/inet", "//pkg/sentry/kernel", diff --git a/pkg/sentry/socket/netstack/netstack.go b/pkg/sentry/socket/netstack/netstack.go index aa41d2e85..6db78c8c4 100644 --- a/pkg/sentry/socket/netstack/netstack.go +++ b/pkg/sentry/socket/netstack/netstack.go @@ -45,14 +45,14 @@ import ( "gvisor.dev/gvisor/pkg/marshal/primitive" "gvisor.dev/gvisor/pkg/metric" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" "gvisor.dev/gvisor/pkg/sentry/inet" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/socket" "gvisor.dev/gvisor/pkg/sentry/socket/netfilter" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/syserr" "gvisor.dev/gvisor/pkg/tcpip" @@ -342,27 +342,15 @@ type commonEndpoint interface { SocketOptions() *tcpip.SocketOptions } -// LINT.IfChange - -// SocketOperations encapsulates all the state needed to represent a network stack +// Socket encapsulates all the state needed to represent a network stack // endpoint in the kernel context. // // +stateify savable -type SocketOperations struct { - fsutil.FilePipeSeek `state:"nosave"` - fsutil.FileNotDirReaddir `state:"nosave"` - fsutil.FileNoopFlush `state:"nosave"` - fsutil.FileNoFsync `state:"nosave"` - fsutil.FileNoMMap `state:"nosave"` - fsutil.FileUseInodeUnstableAttr `state:"nosave"` - - socketOpsCommon -} - -// socketOpsCommon contains the socket operations common to VFS1 and VFS2. -// -// +stateify savable -type socketOpsCommon struct { +type Socket struct { + vfsfd vfs.FileDescription + vfs.FileDescriptionDefaultImpl + vfs.DentryMetadataFileDescriptionImpl + vfs.LockFD socket.SendReceiveTimeout *waiter.Queue @@ -391,56 +379,40 @@ type socketOpsCommon struct { sockOptInq bool } +var _ = socket.Socket(&Socket{}) + // New creates a new endpoint socket. -func New(t *kernel.Task, family int, skType linux.SockType, protocol int, queue *waiter.Queue, endpoint tcpip.Endpoint) (*fs.File, *syserr.Error) { +func New(t *kernel.Task, family int, skType linux.SockType, protocol int, queue *waiter.Queue, endpoint tcpip.Endpoint) (*vfs.FileDescription, *syserr.Error) { if skType == linux.SOCK_STREAM { endpoint.SocketOptions().SetDelayOption(true) } - dirent := socket.NewDirent(t, netstackDevice) - defer dirent.DecRef(t) - return fs.NewFile(t, dirent, fs.FileFlags{Read: true, Write: true, NonSeekable: true}, &SocketOperations{ - socketOpsCommon: socketOpsCommon{ - Queue: queue, - family: family, - Endpoint: endpoint, - skType: skType, - protocol: protocol, - }, - }), nil -} + mnt := t.Kernel().SocketMount() + d := sockfs.NewDentry(t, mnt) + defer d.DecRef(t) -var sockAddrInetSize = (*linux.SockAddrInet)(nil).SizeBytes() -var sockAddrInet6Size = (*linux.SockAddrInet6)(nil).SizeBytes() -var sockAddrLinkSize = (*linux.SockAddrLink)(nil).SizeBytes() - -// minSockAddrLen returns the minimum length in bytes of a socket address for -// the socket's family. -func (s *socketOpsCommon) minSockAddrLen() int { - const addressFamilySize = 2 - - switch s.family { - case linux.AF_UNIX: - return addressFamilySize - case linux.AF_INET: - return sockAddrInetSize - case linux.AF_INET6: - return sockAddrInet6Size - case linux.AF_PACKET: - return sockAddrLinkSize - case linux.AF_UNSPEC: - return addressFamilySize - default: - panic(fmt.Sprintf("s.family unrecognized = %d", s.family)) + s := &Socket{ + Queue: queue, + family: family, + Endpoint: endpoint, + skType: skType, + protocol: protocol, } + s.LockFD.Init(&vfs.FileLocks{}) + vfsfd := &s.vfsfd + if err := vfsfd.Init(s, linux.O_RDWR, mnt, d, &vfs.FileDescriptionOptions{ + DenyPRead: true, + DenyPWrite: true, + UseDentryMetadata: true, + }); err != nil { + return nil, syserr.FromError(err) + } + return vfsfd, nil } -func (s *socketOpsCommon) isPacketBased() bool { - return s.skType == linux.SOCK_DGRAM || s.skType == linux.SOCK_SEQPACKET || s.skType == linux.SOCK_RDM || s.skType == linux.SOCK_RAW -} - -// Release implements fs.FileOperations.Release. -func (s *socketOpsCommon) Release(ctx context.Context) { +// Release implements vfs.FileDescriptionImpl.Release. +func (s *Socket) Release(ctx context.Context) { + kernel.KernelFromContext(ctx).DeleteSocket(&s.vfsfd) e, ch := waiter.NewChannelEntry(waiter.EventHUp | waiter.EventErr) s.EventRegister(&e) defer s.EventUnregister(&e) @@ -467,8 +439,19 @@ func (s *socketOpsCommon) Release(ctx context.Context) { } } -// Read implements fs.FileOperations.Read. -func (s *SocketOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { +// Epollable implements FileDescriptionImpl.Epollable. +func (s *Socket) Epollable() bool { + return true +} + +// Read implements vfs.FileDescriptionImpl. +func (s *Socket) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP + } + if dst.NumBytes() == 0 { return 0, nil } @@ -482,28 +465,14 @@ func (s *SocketOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOS return int64(n), nil } -// WriteTo implements fs.FileOperations.WriteTo. -func (s *SocketOperations) WriteTo(_ context.Context, _ *fs.File, dst io.Writer, count int64, dup bool) (int64, error) { - s.readMu.Lock() - defer s.readMu.Unlock() - - w := tcpip.LimitedWriter{ - W: dst, - N: count, +// Write implements vfs.FileDescriptionImpl. +func (s *Socket) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP } - // This may return a blocking error. - res, err := s.Endpoint.Read(&w, tcpip.ReadOptions{ - Peek: dup, - }) - if err != nil { - return 0, syserr.TranslateNetstackError(err).ToError() - } - return int64(res.Count), nil -} - -// Write implements fs.FileOperations.Write. -func (s *SocketOperations) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { r := src.Reader(ctx) n, err := s.Endpoint.Write(r, tcpip.WriteOptions{}) if _, ok := err.(*tcpip.ErrWouldBlock); ok { @@ -520,44 +489,150 @@ func (s *SocketOperations) Write(ctx context.Context, _ *fs.File, src usermem.IO return n, nil } -var _ tcpip.Payloader = (*limitedPayloader)(nil) - -type limitedPayloader struct { - inner io.LimitedReader - err error -} - -func (l *limitedPayloader) Read(p []byte) (int, error) { - n, err := l.inner.Read(p) - l.err = err - return n, err -} - -func (l *limitedPayloader) Len() int { - return int(l.inner.N) -} - -// ReadFrom implements fs.FileOperations.ReadFrom. -func (s *SocketOperations) ReadFrom(_ context.Context, _ *fs.File, r io.Reader, count int64) (int64, error) { - f := limitedPayloader{ - inner: io.LimitedReader{ - R: r, - N: count, - }, +// Accept implements the linux syscall accept(2) for sockets backed by +// tcpip.Endpoint. +func (s *Socket) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { + // Issue the accept request to get the new endpoint. + var peerAddr *tcpip.FullAddress + if peerRequested { + peerAddr = &tcpip.FullAddress{} } - n, err := s.Endpoint.Write(&f, tcpip.WriteOptions{ - // Reads may be destructive but should be very fast, - // so we can't release the lock while copying data. - Atomic: true, + ep, wq, terr := s.Endpoint.Accept(peerAddr) + if terr != nil { + if _, ok := terr.(*tcpip.ErrWouldBlock); !ok || !blocking { + return 0, nil, 0, syserr.TranslateNetstackError(terr) + } + + var err *syserr.Error + ep, wq, err = s.blockingAccept(t, peerAddr) + if err != nil { + return 0, nil, 0, err + } + } + + ns, err := New(t, s.family, s.skType, s.protocol, wq, ep) + if err != nil { + return 0, nil, 0, err + } + defer ns.DecRef(t) + + if err := ns.SetStatusFlags(t, t.Credentials(), uint32(flags&linux.SOCK_NONBLOCK)); err != nil { + return 0, nil, 0, syserr.FromError(err) + } + + var addr linux.SockAddr + var addrLen uint32 + if peerAddr != nil { + // Get address of the peer and write it to peer slice. + addr, addrLen = socket.ConvertAddress(s.family, *peerAddr) + } + + fd, e := t.NewFDFrom(0, ns, kernel.FDFlags{ + CloseOnExec: flags&linux.SOCK_CLOEXEC != 0, }) - if _, ok := err.(*tcpip.ErrBadBuffer); ok { - return n, f.err + + t.Kernel().RecordSocket(ns) + + return fd, addr, addrLen, syserr.FromError(e) +} + +// GetSockOpt implements the linux syscall getsockopt(2) for sockets backed by +// tcpip.Endpoint. +func (s *Socket) GetSockOpt(t *kernel.Task, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { + // TODO(b/78348848): Unlike other socket options, SO_TIMESTAMP is + // implemented specifically for netstack.Socket rather than + // commonEndpoint. commonEndpoint should be extended to support socket + // options where the implementation is not shared, as unix sockets need + // their own support for SO_TIMESTAMP. + if level == linux.SOL_SOCKET && name == linux.SO_TIMESTAMP { + if outLen < sizeOfInt32 { + return nil, syserr.ErrInvalidArgument + } + val := primitive.Int32(0) + s.readMu.Lock() + defer s.readMu.Unlock() + if s.sockOptTimestamp { + val = 1 + } + return &val, nil } - return n, syserr.TranslateNetstackError(err).ToError() + if level == linux.SOL_TCP && name == linux.TCP_INQ { + if outLen < sizeOfInt32 { + return nil, syserr.ErrInvalidArgument + } + val := primitive.Int32(0) + s.readMu.Lock() + defer s.readMu.Unlock() + if s.sockOptInq { + val = 1 + } + return &val, nil + } + + return GetSockOpt(t, s, s.Endpoint, s.family, s.skType, level, name, outPtr, outLen) +} + +// SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by +// tcpip.Endpoint. +func (s *Socket) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error { + // TODO(b/78348848): Unlike other socket options, SO_TIMESTAMP is + // implemented specifically for netstack.Socket rather than + // commonEndpoint. commonEndpoint should be extended to support socket + // options where the implementation is not shared, as unix sockets need + // their own support for SO_TIMESTAMP. + if level == linux.SOL_SOCKET && name == linux.SO_TIMESTAMP { + if len(optVal) < sizeOfInt32 { + return syserr.ErrInvalidArgument + } + s.readMu.Lock() + defer s.readMu.Unlock() + s.sockOptTimestamp = hostarch.ByteOrder.Uint32(optVal) != 0 + return nil + } + if level == linux.SOL_TCP && name == linux.TCP_INQ { + if len(optVal) < sizeOfInt32 { + return syserr.ErrInvalidArgument + } + s.readMu.Lock() + defer s.readMu.Unlock() + s.sockOptInq = hostarch.ByteOrder.Uint32(optVal) != 0 + return nil + } + + return SetSockOpt(t, s, s.Endpoint, level, name, optVal) +} + +var sockAddrInetSize = (*linux.SockAddrInet)(nil).SizeBytes() +var sockAddrInet6Size = (*linux.SockAddrInet6)(nil).SizeBytes() +var sockAddrLinkSize = (*linux.SockAddrLink)(nil).SizeBytes() + +// minSockAddrLen returns the minimum length in bytes of a socket address for +// the socket's family. +func (s *Socket) minSockAddrLen() int { + const addressFamilySize = 2 + + switch s.family { + case linux.AF_UNIX: + return addressFamilySize + case linux.AF_INET: + return sockAddrInetSize + case linux.AF_INET6: + return sockAddrInet6Size + case linux.AF_PACKET: + return sockAddrLinkSize + case linux.AF_UNSPEC: + return addressFamilySize + default: + panic(fmt.Sprintf("s.family unrecognized = %d", s.family)) + } +} + +func (s *Socket) isPacketBased() bool { + return s.skType == linux.SOCK_DGRAM || s.skType == linux.SOCK_SEQPACKET || s.skType == linux.SOCK_RDM || s.skType == linux.SOCK_RAW } // Readiness returns a mask of ready events for socket s. -func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask { +func (s *Socket) Readiness(mask waiter.EventMask) waiter.EventMask { return s.Endpoint.Readiness(mask) } @@ -566,7 +641,7 @@ func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask { // // If exact is true, then the specified address family must be an exact match // with the socket's family. -func (s *socketOpsCommon) checkFamily(family uint16, exact bool) bool { +func (s *Socket) checkFamily(family uint16, exact bool) bool { if family == uint16(s.family) { return true } @@ -585,7 +660,7 @@ func (s *socketOpsCommon) checkFamily(family uint16, exact bool) bool { // represented by the empty string. // // TODO(gvisor.dev/issue/1556): remove this function. -func (s *socketOpsCommon) mapFamily(addr tcpip.FullAddress, family uint16) tcpip.FullAddress { +func (s *Socket) mapFamily(addr tcpip.FullAddress, family uint16) tcpip.FullAddress { if len(addr.Addr) == 0 && s.family == linux.AF_INET6 && family == linux.AF_INET { addr.Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00" } @@ -594,7 +669,7 @@ func (s *socketOpsCommon) mapFamily(addr tcpip.FullAddress, family uint16) tcpip // Connect implements the linux syscall connect(2) for sockets backed by // tpcip.Endpoint. -func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { +func (s *Socket) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { addr, family, err := socket.AddressAndFamily(sockaddr) if err != nil { return err @@ -649,7 +724,7 @@ func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool // Bind implements the linux syscall bind(2) for sockets backed by // tcpip.Endpoint. -func (s *socketOpsCommon) Bind(_ *kernel.Task, sockaddr []byte) *syserr.Error { +func (s *Socket) Bind(_ *kernel.Task, sockaddr []byte) *syserr.Error { if len(sockaddr) < 2 { return syserr.ErrInvalidArgument } @@ -709,13 +784,13 @@ func (s *socketOpsCommon) Bind(_ *kernel.Task, sockaddr []byte) *syserr.Error { // Listen implements the linux syscall listen(2) for sockets backed by // tcpip.Endpoint. -func (s *socketOpsCommon) Listen(_ *kernel.Task, backlog int) *syserr.Error { +func (s *Socket) Listen(_ *kernel.Task, backlog int) *syserr.Error { return syserr.TranslateNetstackError(s.Endpoint.Listen(backlog)) } // blockingAccept implements a blocking version of accept(2), that is, if no // connections are ready to be accept, it will block until one becomes ready. -func (s *socketOpsCommon) blockingAccept(t *kernel.Task, peerAddr *tcpip.FullAddress) (tcpip.Endpoint, *waiter.Queue, *syserr.Error) { +func (s *Socket) blockingAccept(t *kernel.Task, peerAddr *tcpip.FullAddress) (tcpip.Endpoint, *waiter.Queue, *syserr.Error) { // Register for notifications. e, ch := waiter.NewChannelEntry(waiter.ReadableEvents) s.EventRegister(&e) @@ -735,53 +810,6 @@ func (s *socketOpsCommon) blockingAccept(t *kernel.Task, peerAddr *tcpip.FullAdd } } -// Accept implements the linux syscall accept(2) for sockets backed by -// tcpip.Endpoint. -func (s *SocketOperations) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { - var peerAddr *tcpip.FullAddress - if peerRequested { - peerAddr = &tcpip.FullAddress{} - } - ep, wq, terr := s.Endpoint.Accept(peerAddr) - if terr != nil { - if _, ok := terr.(*tcpip.ErrWouldBlock); !ok || !blocking { - return 0, nil, 0, syserr.TranslateNetstackError(terr) - } - - var err *syserr.Error - ep, wq, err = s.blockingAccept(t, peerAddr) - if err != nil { - return 0, nil, 0, err - } - } - - ns, err := New(t, s.family, s.skType, s.protocol, wq, ep) - if err != nil { - return 0, nil, 0, err - } - defer ns.DecRef(t) - - if flags&linux.SOCK_NONBLOCK != 0 { - flags := ns.Flags() - flags.NonBlocking = true - ns.SetFlags(flags.Settable()) - } - - var addr linux.SockAddr - var addrLen uint32 - if peerAddr != nil { - addr, addrLen = socket.ConvertAddress(s.family, *peerAddr) - } - - fd, e := t.NewFDFrom(0, ns, kernel.FDFlags{ - CloseOnExec: flags&linux.SOCK_CLOEXEC != 0, - }) - - t.Kernel().RecordSocket(ns) - - return fd, addr, addrLen, syserr.FromError(e) -} - // ConvertShutdown converts Linux shutdown flags into tcpip shutdown flags. func ConvertShutdown(how int) (tcpip.ShutdownFlags, *syserr.Error) { var f tcpip.ShutdownFlags @@ -800,7 +828,7 @@ func ConvertShutdown(how int) (tcpip.ShutdownFlags, *syserr.Error) { // Shutdown implements the linux syscall shutdown(2) for sockets backed by // tcpip.Endpoint. -func (s *socketOpsCommon) Shutdown(_ *kernel.Task, how int) *syserr.Error { +func (s *Socket) Shutdown(_ *kernel.Task, how int) *syserr.Error { f, err := ConvertShutdown(how) if err != nil { return err @@ -810,45 +838,9 @@ func (s *socketOpsCommon) Shutdown(_ *kernel.Task, how int) *syserr.Error { return syserr.TranslateNetstackError(s.Endpoint.Shutdown(f)) } -// GetSockOpt implements the linux syscall getsockopt(2) for sockets backed by -// tcpip.Endpoint. -func (s *SocketOperations) GetSockOpt(t *kernel.Task, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { - // TODO(b/78348848): Unlike other socket options, SO_TIMESTAMP is - // implemented specifically for netstack.SocketOperations rather than - // commonEndpoint. commonEndpoint should be extended to support socket - // options where the implementation is not shared, as unix sockets need - // their own support for SO_TIMESTAMP. - if level == linux.SOL_SOCKET && name == linux.SO_TIMESTAMP { - if outLen < sizeOfInt32 { - return nil, syserr.ErrInvalidArgument - } - val := primitive.Int32(0) - s.readMu.Lock() - defer s.readMu.Unlock() - if s.sockOptTimestamp { - val = 1 - } - return &val, nil - } - if level == linux.SOL_TCP && name == linux.TCP_INQ { - if outLen < sizeOfInt32 { - return nil, syserr.ErrInvalidArgument - } - val := primitive.Int32(0) - s.readMu.Lock() - defer s.readMu.Unlock() - if s.sockOptInq { - val = 1 - } - return &val, nil - } - - return GetSockOpt(t, s, s.Endpoint, s.family, s.skType, level, name, outPtr, outLen) -} - // GetSockOpt can be used to implement the linux syscall getsockopt(2) for // sockets backed by a commonEndpoint. -func GetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, family int, skType linux.SockType, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { +func GetSockOpt(t *kernel.Task, s socket.Socket, ep commonEndpoint, family int, skType linux.SockType, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { switch level { case linux.SOL_SOCKET: return getSockOptSocket(t, s, ep, family, skType, name, outLen) @@ -882,7 +874,7 @@ func boolToInt32(v bool) int32 { } // getSockOptSocket implements GetSockOpt when level is SOL_SOCKET. -func getSockOptSocket(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, family int, _ linux.SockType, name, outLen int) (marshal.Marshallable, *syserr.Error) { +func getSockOptSocket(t *kernel.Task, s socket.Socket, ep commonEndpoint, family int, _ linux.SockType, name, outLen int) (marshal.Marshallable, *syserr.Error) { // TODO(b/124056281): Stop rejecting short optLen values in getsockopt. switch name { case linux.SO_ERROR: @@ -1077,7 +1069,7 @@ func getSockOptSocket(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, fam } // getSockOptTCP implements GetSockOpt when level is SOL_TCP. -func getSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name, outLen int) (marshal.Marshallable, *syserr.Error) { +func getSockOptTCP(t *kernel.Task, s socket.Socket, ep commonEndpoint, name, outLen int) (marshal.Marshallable, *syserr.Error) { if _, skType, skProto := s.Type(); !isTCPSocket(skType, skProto) { log.Warningf("SOL_TCP options are only supported on TCP sockets: skType, skProto = %v, %d", skType, skProto) return nil, syserr.ErrUnknownProtocolOption @@ -1301,7 +1293,7 @@ func getSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name, return nil, syserr.ErrProtocolNotAvailable } -func getSockOptICMPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, outLen int) (marshal.Marshallable, *syserr.Error) { +func getSockOptICMPv6(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, outLen int) (marshal.Marshallable, *syserr.Error) { if _, ok := ep.(tcpip.Endpoint); !ok { log.Warningf("SOL_ICMPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name) return nil, syserr.ErrUnknownProtocolOption @@ -1342,7 +1334,7 @@ func defaultTTL(t *kernel.Task, network tcpip.NetworkProtocolNumber) (primitive. } // getSockOptIPv6 implements GetSockOpt when level is SOL_IPV6. -func getSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { +func getSockOptIPv6(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { if _, ok := ep.(tcpip.Endpoint); !ok { log.Warningf("SOL_IPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name) return nil, syserr.ErrUnknownProtocolOption @@ -1537,7 +1529,7 @@ func getSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name } // getSockOptIP implements GetSockOpt when level is SOL_IP. -func getSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, outPtr hostarch.Addr, outLen int, _ int) (marshal.Marshallable, *syserr.Error) { +func getSockOptIP(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, outPtr hostarch.Addr, outLen int, _ int) (marshal.Marshallable, *syserr.Error) { if _, ok := ep.(tcpip.Endpoint); !ok { log.Warningf("SOL_IP options not supported on endpoints other than tcpip.Endpoint: option = %d", name) return nil, syserr.ErrUnknownProtocolOption @@ -1741,39 +1733,9 @@ func getSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in return nil, syserr.ErrProtocolNotAvailable } -// SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by -// tcpip.Endpoint. -func (s *SocketOperations) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error { - // TODO(b/78348848): Unlike other socket options, SO_TIMESTAMP is - // implemented specifically for netstack.SocketOperations rather than - // commonEndpoint. commonEndpoint should be extended to support socket - // options where the implementation is not shared, as unix sockets need - // their own support for SO_TIMESTAMP. - if level == linux.SOL_SOCKET && name == linux.SO_TIMESTAMP { - if len(optVal) < sizeOfInt32 { - return syserr.ErrInvalidArgument - } - s.readMu.Lock() - defer s.readMu.Unlock() - s.sockOptTimestamp = hostarch.ByteOrder.Uint32(optVal) != 0 - return nil - } - if level == linux.SOL_TCP && name == linux.TCP_INQ { - if len(optVal) < sizeOfInt32 { - return syserr.ErrInvalidArgument - } - s.readMu.Lock() - defer s.readMu.Unlock() - s.sockOptInq = hostarch.ByteOrder.Uint32(optVal) != 0 - return nil - } - - return SetSockOpt(t, s, s.Endpoint, level, name, optVal) -} - // SetSockOpt can be used to implement the linux syscall setsockopt(2) for // sockets backed by a commonEndpoint. -func SetSockOpt(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, level int, name int, optVal []byte) *syserr.Error { +func SetSockOpt(t *kernel.Task, s socket.Socket, ep commonEndpoint, level int, name int, optVal []byte) *syserr.Error { switch level { case linux.SOL_SOCKET: return setSockOptSocket(t, s, ep, name, optVal) @@ -1825,7 +1787,7 @@ func clampBufSize(newSz, min, max int64, ignoreMax bool) int64 { } // setSockOptSocket implements SetSockOpt when level is SOL_SOCKET. -func setSockOptSocket(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error { +func setSockOptSocket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, optVal []byte) *syserr.Error { switch name { case linux.SO_SNDBUF: if len(optVal) < sizeOfInt32 { @@ -2008,7 +1970,7 @@ func setSockOptSocket(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, nam } // setSockOptTCP implements SetSockOpt when level is SOL_TCP. -func setSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error { +func setSockOptTCP(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, optVal []byte) *syserr.Error { if _, skType, skProto := s.Type(); !isTCPSocket(skType, skProto) { log.Warningf("SOL_TCP options are only supported on TCP sockets: skType, skProto = %v, %d", skType, skProto) return syserr.ErrUnknownProtocolOption @@ -2147,7 +2109,7 @@ func setSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name i return nil } -func setSockOptICMPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error { +func setSockOptICMPv6(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, optVal []byte) *syserr.Error { if _, ok := ep.(tcpip.Endpoint); !ok { log.Warningf("SOL_ICMPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name) return syserr.ErrUnknownProtocolOption @@ -2172,7 +2134,7 @@ func setSockOptICMPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, nam } // setSockOptIPv6 implements SetSockOpt when level is SOL_IPV6. -func setSockOptIPv6(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error { +func setSockOptIPv6(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, optVal []byte) *syserr.Error { if _, ok := ep.(tcpip.Endpoint); !ok { log.Warningf("SOL_IPV6 options not supported on endpoints other than tcpip.Endpoint: option = %d", name) return syserr.ErrUnknownProtocolOption @@ -2402,7 +2364,7 @@ func parseIntOrChar(buf []byte) (int32, *syserr.Error) { } // setSockOptIP implements SetSockOpt when level is SOL_IP. -func setSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name int, optVal []byte) *syserr.Error { +func setSockOptIP(t *kernel.Task, s socket.Socket, ep commonEndpoint, name int, optVal []byte) *syserr.Error { if _, ok := ep.(tcpip.Endpoint); !ok { log.Warningf("SOL_IP options not supported on endpoints other than tcpip.Endpoint: option = %d", name) return syserr.ErrUnknownProtocolOption @@ -2617,7 +2579,7 @@ func setSockOptIP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name in // GetSockName implements the linux syscall getsockname(2) for sockets backed by // tcpip.Endpoint. -func (s *socketOpsCommon) GetSockName(*kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) GetSockName(*kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { addr, err := s.Endpoint.GetLocalAddress() if err != nil { return nil, 0, syserr.TranslateNetstackError(err) @@ -2629,7 +2591,7 @@ func (s *socketOpsCommon) GetSockName(*kernel.Task) (linux.SockAddr, uint32, *sy // GetPeerName implements the linux syscall getpeername(2) for sockets backed by // tcpip.Endpoint. -func (s *socketOpsCommon) GetPeerName(*kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) GetPeerName(*kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { addr, err := s.Endpoint.GetRemoteAddress() if err != nil { return nil, 0, syserr.TranslateNetstackError(err) @@ -2639,7 +2601,7 @@ func (s *socketOpsCommon) GetPeerName(*kernel.Task) (linux.SockAddr, uint32, *sy return a, l, nil } -func (s *socketOpsCommon) fillCmsgInq(cmsg *socket.ControlMessages) { +func (s *Socket) fillCmsgInq(cmsg *socket.ControlMessages) { if !s.sockOptInq { return } @@ -2671,7 +2633,7 @@ func toLinuxPacketType(pktType tcpip.PacketType) uint8 { // nonBlockingRead issues a non-blocking read. // // TODO(b/78348848): Support timestamps for stream sockets. -func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSequence, peek, trunc, senderRequested bool) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { +func (s *Socket) nonBlockingRead(ctx context.Context, dst usermem.IOSequence, peek, trunc, senderRequested bool) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { isPacket := s.isPacketBased() readOptions := tcpip.ReadOptions{ @@ -2760,7 +2722,7 @@ func (s *socketOpsCommon) nonBlockingRead(ctx context.Context, dst usermem.IOSeq return res.Count, 0, nil, 0, cmsg, syserr.TranslateNetstackError(err) } -func (s *socketOpsCommon) netstackToLinuxControlMessages(cm tcpip.ReceivableControlMessages) socket.ControlMessages { +func (s *Socket) netstackToLinuxControlMessages(cm tcpip.ReceivableControlMessages) socket.ControlMessages { readCM := socket.NewIPControlMessages(s.family, cm) return socket.ControlMessages{ IP: socket.IPControlMessages{ @@ -2786,7 +2748,7 @@ func (s *socketOpsCommon) netstackToLinuxControlMessages(cm tcpip.ReceivableCont } } -func (s *socketOpsCommon) linuxToNetstackControlMessages(cm socket.ControlMessages) tcpip.SendableControlMessages { +func (s *Socket) linuxToNetstackControlMessages(cm socket.ControlMessages) tcpip.SendableControlMessages { return tcpip.SendableControlMessages{ HasTTL: cm.IP.HasTTL, TTL: uint8(cm.IP.TTL), @@ -2799,7 +2761,7 @@ func (s *socketOpsCommon) linuxToNetstackControlMessages(cm socket.ControlMessag // successfully writing packet data out to userspace. // // Precondition: s.readMu must be locked. -func (s *socketOpsCommon) updateTimestamp(cm tcpip.ReceivableControlMessages) { +func (s *Socket) updateTimestamp(cm tcpip.ReceivableControlMessages) { // Save the SIOCGSTAMP timestamp only if SO_TIMESTAMP is disabled. if !s.sockOptTimestamp { s.timestampValid = true @@ -2808,7 +2770,7 @@ func (s *socketOpsCommon) updateTimestamp(cm tcpip.ReceivableControlMessages) { } // dequeueErr is analogous to net/core/skbuff.c:sock_dequeue_err_skb(). -func (s *socketOpsCommon) dequeueErr() *tcpip.SockError { +func (s *Socket) dequeueErr() *tcpip.SockError { so := s.Endpoint.SocketOptions() err := so.DequeueErr() if err == nil { @@ -2839,7 +2801,7 @@ func addrFamilyFromNetProto(net tcpip.NetworkProtocolNumber) int { // recvErr handles MSG_ERRQUEUE for recvmsg(2). // This is analogous to net/ipv4/ip_sockglue.c:ip_recv_error(). -func (s *socketOpsCommon) recvErr(t *kernel.Task, dst usermem.IOSequence) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { +func (s *Socket) recvErr(t *kernel.Task, dst usermem.IOSequence) (int, int, linux.SockAddr, uint32, socket.ControlMessages, *syserr.Error) { sockErr := s.dequeueErr() if sockErr == nil { return 0, 0, nil, 0, socket.ControlMessages{}, syserr.ErrTryAgain @@ -2865,7 +2827,7 @@ func (s *socketOpsCommon) recvErr(t *kernel.Task, dst usermem.IOSequence) (int, // RecvMsg implements the linux syscall recvmsg(2) for sockets backed by // tcpip.Endpoint. -func (s *socketOpsCommon) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, _ uint64) (n int, msgFlags int, senderAddr linux.SockAddr, senderAddrLen uint32, controlMessages socket.ControlMessages, err *syserr.Error) { +func (s *Socket) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, _ uint64) (n int, msgFlags int, senderAddr linux.SockAddr, senderAddrLen uint32, controlMessages socket.ControlMessages, err *syserr.Error) { if flags&linux.MSG_ERRQUEUE != 0 { return s.recvErr(t, dst) } @@ -2937,7 +2899,7 @@ func (s *socketOpsCommon) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags // SendMsg implements the linux syscall sendmsg(2) for sockets backed by // tcpip.Endpoint. -func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { +func (s *Socket) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { // Reject Unix control messages. if !controlMessages.Unix.Empty() { return 0, syserr.ErrInvalidArgument @@ -3009,12 +2971,8 @@ func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []b } } -// Ioctl implements fs.FileOperations.Ioctl. -func (s *SocketOperations) Ioctl(ctx context.Context, _ *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - return s.socketOpsCommon.ioctl(ctx, io, args) -} - -func (s *socketOpsCommon) ioctl(ctx context.Context, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { +// Ioctl implements vfs.FileDescriptionImpl. +func (s *Socket) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { t := kernel.TaskFromContext(ctx) if t == nil { panic("ioctl(2) may only be called from a task goroutine") @@ -3051,7 +3009,7 @@ func (s *socketOpsCommon) ioctl(ctx context.Context, io usermem.IO, args arch.Sy return 0, err } - return Ioctl(ctx, s.Endpoint, io, args) + return Ioctl(ctx, s.Endpoint, uio, args) } // Ioctl performs a socket ioctl. @@ -3371,7 +3329,7 @@ func isICMPSocket(skType linux.SockType, skProto int) bool { // State implements socket.Socket.State. State translates the internal state // returned by netstack to values defined by Linux. -func (s *socketOpsCommon) State() uint32 { +func (s *Socket) State() uint32 { if s.family != linux.AF_INET && s.family != linux.AF_INET6 { // States not implemented for this socket's family. return 0 @@ -3431,14 +3389,17 @@ func (s *socketOpsCommon) State() uint32 { } // Type implements socket.Socket.Type. -func (s *socketOpsCommon) Type() (family int, skType linux.SockType, protocol int) { +func (s *Socket) Type() (family int, skType linux.SockType, protocol int) { return s.family, s.skType, s.protocol } // EventRegister implements waiter.Waitable. -func (s *socketOpsCommon) EventRegister(e *waiter.Entry) error { +func (s *Socket) EventRegister(e *waiter.Entry) error { s.Queue.EventRegister(e) return nil } -// LINT.ThenChange(./netstack_vfs2.go) +// EventUnregister implements waiter.Waitable.EventUnregister. +func (s *Socket) EventUnregister(e *waiter.Entry) { + s.Queue.EventUnregister(e) +} diff --git a/pkg/sentry/socket/netstack/netstack_state.go b/pkg/sentry/socket/netstack/netstack_state.go index 591e00d42..a3cf1fa92 100644 --- a/pkg/sentry/socket/netstack/netstack_state.go +++ b/pkg/sentry/socket/netstack/netstack_state.go @@ -18,13 +18,13 @@ import ( "time" ) -func (s *socketOpsCommon) saveTimestamp() int64 { +func (s *Socket) saveTimestamp() int64 { s.readMu.Lock() defer s.readMu.Unlock() return s.timestamp.UnixNano() } -func (s *socketOpsCommon) loadTimestamp(nsec int64) { +func (s *Socket) loadTimestamp(nsec int64) { s.readMu.Lock() defer s.readMu.Unlock() s.timestamp = time.Unix(0, nsec) diff --git a/pkg/sentry/socket/netstack/netstack_vfs2.go b/pkg/sentry/socket/netstack/netstack_vfs2.go deleted file mode 100644 index cbe23ce65..000000000 --- a/pkg/sentry/socket/netstack/netstack_vfs2.go +++ /dev/null @@ -1,269 +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 netstack - -import ( - "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/marshal" - "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// SocketVFS2 encapsulates all the state needed to represent a network stack -// endpoint in the kernel context. -// -// +stateify savable -type SocketVFS2 struct { - vfsfd vfs.FileDescription - vfs.FileDescriptionDefaultImpl - vfs.DentryMetadataFileDescriptionImpl - vfs.LockFD - - socketOpsCommon -} - -var _ = socket.SocketVFS2(&SocketVFS2{}) - -// NewVFS2 creates a new endpoint socket. -func NewVFS2(t *kernel.Task, family int, skType linux.SockType, protocol int, queue *waiter.Queue, endpoint tcpip.Endpoint) (*vfs.FileDescription, *syserr.Error) { - if skType == linux.SOCK_STREAM { - endpoint.SocketOptions().SetDelayOption(true) - } - - mnt := t.Kernel().SocketMount() - d := sockfs.NewDentry(t, mnt) - defer d.DecRef(t) - - s := &SocketVFS2{ - socketOpsCommon: socketOpsCommon{ - Queue: queue, - family: family, - Endpoint: endpoint, - skType: skType, - protocol: protocol, - }, - } - s.LockFD.Init(&vfs.FileLocks{}) - vfsfd := &s.vfsfd - if err := vfsfd.Init(s, linux.O_RDWR, mnt, d, &vfs.FileDescriptionOptions{ - DenyPRead: true, - DenyPWrite: true, - UseDentryMetadata: true, - }); err != nil { - return nil, syserr.FromError(err) - } - return vfsfd, nil -} - -// Release implements vfs.FileDescriptionImpl.Release. -func (s *SocketVFS2) Release(ctx context.Context) { - kernel.KernelFromContext(ctx).DeleteSocketVFS2(&s.vfsfd) - s.socketOpsCommon.Release(ctx) -} - -// Readiness implements waiter.Waitable.Readiness. -func (s *SocketVFS2) Readiness(mask waiter.EventMask) waiter.EventMask { - return s.socketOpsCommon.Readiness(mask) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (s *SocketVFS2) EventRegister(e *waiter.Entry) error { - s.socketOpsCommon.EventRegister(e) - return nil -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (s *SocketVFS2) EventUnregister(e *waiter.Entry) { - s.socketOpsCommon.EventUnregister(e) -} - -// Epollable implements FileDescriptionImpl.Epollable. -func (s *SocketVFS2) Epollable() bool { - return true -} - -// Read implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - if dst.NumBytes() == 0 { - return 0, nil - } - n, _, _, _, _, err := s.nonBlockingRead(ctx, dst, false, false, false) - if err == syserr.ErrWouldBlock { - return int64(n), linuxerr.ErrWouldBlock - } - if err != nil { - return 0, err.ToError() - } - return int64(n), nil -} - -// Write implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - r := src.Reader(ctx) - n, err := s.Endpoint.Write(r, tcpip.WriteOptions{}) - if _, ok := err.(*tcpip.ErrWouldBlock); ok { - return 0, linuxerr.ErrWouldBlock - } - if err != nil { - return 0, syserr.TranslateNetstackError(err).ToError() - } - - if n < src.NumBytes() { - return n, linuxerr.ErrWouldBlock - } - - return n, nil -} - -// Accept implements the linux syscall accept(2) for sockets backed by -// tcpip.Endpoint. -func (s *SocketVFS2) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { - // Issue the accept request to get the new endpoint. - var peerAddr *tcpip.FullAddress - if peerRequested { - peerAddr = &tcpip.FullAddress{} - } - ep, wq, terr := s.Endpoint.Accept(peerAddr) - if terr != nil { - if _, ok := terr.(*tcpip.ErrWouldBlock); !ok || !blocking { - return 0, nil, 0, syserr.TranslateNetstackError(terr) - } - - var err *syserr.Error - ep, wq, err = s.blockingAccept(t, peerAddr) - if err != nil { - return 0, nil, 0, err - } - } - - ns, err := NewVFS2(t, s.family, s.skType, s.protocol, wq, ep) - if err != nil { - return 0, nil, 0, err - } - defer ns.DecRef(t) - - if err := ns.SetStatusFlags(t, t.Credentials(), uint32(flags&linux.SOCK_NONBLOCK)); err != nil { - return 0, nil, 0, syserr.FromError(err) - } - - var addr linux.SockAddr - var addrLen uint32 - if peerAddr != nil { - // Get address of the peer and write it to peer slice. - addr, addrLen = socket.ConvertAddress(s.family, *peerAddr) - } - - fd, e := t.NewFDFromVFS2(0, ns, kernel.FDFlags{ - CloseOnExec: flags&linux.SOCK_CLOEXEC != 0, - }) - - t.Kernel().RecordSocketVFS2(ns) - - return fd, addr, addrLen, syserr.FromError(e) -} - -// Ioctl implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { - return s.socketOpsCommon.ioctl(ctx, uio, args) -} - -// GetSockOpt implements the linux syscall getsockopt(2) for sockets backed by -// tcpip.Endpoint. -func (s *SocketVFS2) GetSockOpt(t *kernel.Task, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { - // TODO(b/78348848): Unlike other socket options, SO_TIMESTAMP is - // implemented specifically for netstack.SocketVFS2 rather than - // commonEndpoint. commonEndpoint should be extended to support socket - // options where the implementation is not shared, as unix sockets need - // their own support for SO_TIMESTAMP. - if level == linux.SOL_SOCKET && name == linux.SO_TIMESTAMP { - if outLen < sizeOfInt32 { - return nil, syserr.ErrInvalidArgument - } - val := primitive.Int32(0) - s.readMu.Lock() - defer s.readMu.Unlock() - if s.sockOptTimestamp { - val = 1 - } - return &val, nil - } - if level == linux.SOL_TCP && name == linux.TCP_INQ { - if outLen < sizeOfInt32 { - return nil, syserr.ErrInvalidArgument - } - val := primitive.Int32(0) - s.readMu.Lock() - defer s.readMu.Unlock() - if s.sockOptInq { - val = 1 - } - return &val, nil - } - - return GetSockOpt(t, s, s.Endpoint, s.family, s.skType, level, name, outPtr, outLen) -} - -// SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by -// tcpip.Endpoint. -func (s *SocketVFS2) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error { - // TODO(b/78348848): Unlike other socket options, SO_TIMESTAMP is - // implemented specifically for netstack.SocketVFS2 rather than - // commonEndpoint. commonEndpoint should be extended to support socket - // options where the implementation is not shared, as unix sockets need - // their own support for SO_TIMESTAMP. - if level == linux.SOL_SOCKET && name == linux.SO_TIMESTAMP { - if len(optVal) < sizeOfInt32 { - return syserr.ErrInvalidArgument - } - s.readMu.Lock() - defer s.readMu.Unlock() - s.sockOptTimestamp = hostarch.ByteOrder.Uint32(optVal) != 0 - return nil - } - if level == linux.SOL_TCP && name == linux.TCP_INQ { - if len(optVal) < sizeOfInt32 { - return syserr.ErrInvalidArgument - } - s.readMu.Lock() - defer s.readMu.Unlock() - s.sockOptInq = hostarch.ByteOrder.Uint32(optVal) != 0 - return nil - } - - return SetSockOpt(t, s, s.Endpoint, level, name, optVal) -} diff --git a/pkg/sentry/socket/netstack/provider.go b/pkg/sentry/socket/netstack/provider.go index e675db199..46fa9dc69 100644 --- a/pkg/sentry/socket/netstack/provider.go +++ b/pkg/sentry/socket/netstack/provider.go @@ -1,4 +1,4 @@ -// Copyright 2018 The gVisor Authors. +// 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. @@ -21,10 +21,10 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/socket" + "gvisor.dev/gvisor/pkg/sentry/vfs" "gvisor.dev/gvisor/pkg/syserr" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" @@ -35,8 +35,6 @@ import ( "gvisor.dev/gvisor/pkg/waiter" ) -// LINT.IfChange - // provider is an inet socket provider. type provider struct { family int @@ -96,7 +94,7 @@ func getTransportProtocol(ctx context.Context, stype linux.SockType, protocol in // Socket creates a new socket object for the AF_INET, AF_INET6, or AF_PACKET // family. -func (p *provider) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *syserr.Error) { +func (p *provider) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { // Fail right away if we don't have a stack. stack := t.NetworkContext() if stack == nil { @@ -143,10 +141,11 @@ func (p *provider) Socket(t *kernel.Task, stype linux.SockType, protocol int) (* return New(t, p.family, stype, int(transProto), wq, ep) } -func packetSocket(t *kernel.Task, epStack *Stack, stype linux.SockType, protocol int) (*fs.File, *syserr.Error) { +func packetSocket(t *kernel.Task, epStack *Stack, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { // Packet sockets require CAP_NET_RAW. creds := auth.CredentialsFromContext(t) if !creds.HasCapability(linux.CAP_NET_RAW) { + rawMissingLogger.Infof("A process tried to create a raw socket without CAP_NET_RAW. Should the container config enable CAP_NET_RAW?") return nil, syserr.ErrNotPermitted } @@ -174,10 +173,8 @@ func packetSocket(t *kernel.Task, epStack *Stack, stype linux.SockType, protocol return New(t, linux.AF_PACKET, stype, protocol, wq, ep) } -// LINT.ThenChange(./provider_vfs2.go) - // Pair just returns nil sockets (not supported). -func (*provider) Pair(*kernel.Task, linux.SockType, int) (*fs.File, *fs.File, *syserr.Error) { +func (*provider) Pair(*kernel.Task, linux.SockType, int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { return nil, nil, nil } diff --git a/pkg/sentry/socket/netstack/provider_vfs2.go b/pkg/sentry/socket/netstack/provider_vfs2.go deleted file mode 100644 index f2ebb233f..000000000 --- a/pkg/sentry/socket/netstack/provider_vfs2.go +++ /dev/null @@ -1,142 +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 netstack - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" - "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" - "gvisor.dev/gvisor/pkg/waiter" -) - -// providerVFS2 is an inet socket provider. -type providerVFS2 struct { - family int - netProto tcpip.NetworkProtocolNumber -} - -// Socket creates a new socket object for the AF_INET, AF_INET6, or AF_PACKET -// family. -func (p *providerVFS2) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { - // Fail right away if we don't have a stack. - stack := t.NetworkContext() - if stack == nil { - // Don't propagate an error here. Instead, allow the socket - // code to continue searching for another provider. - return nil, nil - } - eps, ok := stack.(*Stack) - if !ok { - return nil, nil - } - - // Packet sockets are handled separately, since they are neither INET - // nor INET6 specific. - if p.family == linux.AF_PACKET { - return packetSocketVFS2(t, eps, stype, protocol) - } - - // Figure out the transport protocol. - transProto, associated, err := getTransportProtocol(t, stype, protocol) - if err != nil { - return nil, err - } - - // Create the endpoint. - var ep tcpip.Endpoint - var e tcpip.Error - wq := &waiter.Queue{} - if stype == linux.SOCK_RAW { - ep, e = eps.Stack.NewRawEndpoint(transProto, p.netProto, wq, associated) - } else { - ep, e = eps.Stack.NewEndpoint(transProto, p.netProto, wq) - - // Assign task to PacketOwner interface to get the UID and GID for - // iptables owner matching. - if e == nil { - ep.SetOwner(t) - } - } - if e != nil { - return nil, syserr.TranslateNetstackError(e) - } - - return NewVFS2(t, p.family, stype, int(transProto), wq, ep) -} - -func packetSocketVFS2(t *kernel.Task, epStack *Stack, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { - // Packet sockets require CAP_NET_RAW. - creds := auth.CredentialsFromContext(t) - if !creds.HasCapability(linux.CAP_NET_RAW) { - rawMissingLogger.Infof("A process tried to create a raw socket without CAP_NET_RAW. Should the container config enable CAP_NET_RAW?") - return nil, syserr.ErrNotPermitted - } - - // "cooked" packets don't contain link layer information. - var cooked bool - switch stype { - case linux.SOCK_DGRAM: - cooked = true - case linux.SOCK_RAW: - cooked = false - default: - return nil, syserr.ErrProtocolNotSupported - } - - // protocol is passed in network byte order, but netstack wants it in - // host order. - netProto := tcpip.NetworkProtocolNumber(socket.Ntohs(uint16(protocol))) - - wq := &waiter.Queue{} - ep, err := epStack.Stack.NewPacketEndpoint(cooked, netProto, wq) - if err != nil { - return nil, syserr.TranslateNetstackError(err) - } - - return NewVFS2(t, linux.AF_PACKET, stype, protocol, wq, ep) -} - -// Pair just returns nil sockets (not supported). -func (*providerVFS2) Pair(*kernel.Task, linux.SockType, int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { - return nil, nil, nil -} - -// init registers socket providers for AF_INET, AF_INET6, and AF_PACKET. -func init() { - // Providers backed by netstack. - p := []providerVFS2{ - { - family: linux.AF_INET, - netProto: ipv4.ProtocolNumber, - }, - { - family: linux.AF_INET6, - netProto: ipv6.ProtocolNumber, - }, - { - family: linux.AF_PACKET, - }, - } - - for i := range p { - socket.RegisterProviderVFS2(p[i].family, &p[i]) - } -} diff --git a/pkg/sentry/socket/socket.go b/pkg/sentry/socket/socket.go index 5d843bc61..ae4cb1d84 100644 --- a/pkg/sentry/socket/socket.go +++ b/pkg/sentry/socket/socket.go @@ -28,9 +28,6 @@ import ( "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal" - "gvisor.dev/gvisor/pkg/sentry/device" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" "gvisor.dev/gvisor/pkg/sentry/kernel" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" @@ -220,25 +217,11 @@ func (c *ControlMessages) Release(ctx context.Context) { c.Unix.Release(ctx) } -// Socket is an interface combining fs.FileOperations and SocketOps, -// representing a VFS1 socket file. -type Socket interface { - fs.FileOperations - SocketOps -} - -// SocketVFS2 is an interface combining vfs.FileDescription and SocketOps, -// representing a VFS2 socket file. -type SocketVFS2 interface { - vfs.FileDescriptionImpl - SocketOps -} - -// SocketOps is the interface containing socket syscalls used by the syscall +// Socket is an interface containing socket syscalls used by the syscall // layer to redirect them to the appropriate implementation. -// -// It is implemented by both Socket and SocketVFS2. -type SocketOps interface { +type Socket interface { + vfs.FileDescriptionImpl + // Connect implements the connect(2) linux unix. Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error @@ -317,20 +300,20 @@ type SocketOps interface { Type() (family int, skType linux.SockType, protocol int) } -// Provider is the interface implemented by providers of sockets for specific -// address families (e.g., AF_INET). +// Provider is the interface implemented by providers of sockets for +// specific address families (e.g., AF_INET). type Provider interface { // Socket creates a new socket. // // If a nil Socket _and_ a nil error is returned, it means that the // protocol is not supported. A non-nil error should only be returned // if the protocol is supported, but an error occurs during creation. - Socket(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *syserr.Error) + Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) // Pair creates a pair of connected sockets. // // See Socket for error information. - Pair(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *fs.File, *syserr.Error) + Pair(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) } // families holds a map of all known address families and their providers. @@ -346,7 +329,7 @@ func RegisterProvider(family int, provider Provider) { } // New creates a new socket with the given family, type and protocol. -func New(t *kernel.Task, family int, stype linux.SockType, protocol int) (*fs.File, *syserr.Error) { +func New(t *kernel.Task, family int, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { for _, p := range families[family] { s, err := p.Socket(t, stype, protocol) if err != nil { @@ -363,7 +346,7 @@ func New(t *kernel.Task, family int, stype linux.SockType, protocol int) (*fs.Fi // Pair creates a new connected socket pair with the given family, type and // protocol. -func Pair(t *kernel.Task, family int, stype linux.SockType, protocol int) (*fs.File, *fs.File, *syserr.Error) { +func Pair(t *kernel.Task, family int, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { providers, ok := families[family] if !ok { return nil, nil, syserr.ErrAddressFamilyNotSupported @@ -385,93 +368,6 @@ func Pair(t *kernel.Task, family int, stype linux.SockType, protocol int) (*fs.F return nil, nil, syserr.ErrSocketNotSupported } -// NewDirent returns a sockfs fs.Dirent that resides on device d. -func NewDirent(ctx context.Context, d *device.Device) *fs.Dirent { - ino := d.NextIno() - iops := &fsutil.SimpleFileInode{ - InodeSimpleAttributes: fsutil.NewInodeSimpleAttributes(ctx, fs.FileOwnerFromContext(ctx), fs.FilePermissions{ - User: fs.PermMask{Read: true, Write: true}, - }, linux.SOCKFS_MAGIC), - } - inode := fs.NewInode(ctx, iops, fs.NewPseudoMountSource(ctx), fs.StableAttr{ - Type: fs.Socket, - DeviceID: d.DeviceID(), - InodeID: ino, - BlockSize: hostarch.PageSize, - }) - - // Dirent name matches net/socket.c:sockfs_dname. - return fs.NewDirent(ctx, inode, fmt.Sprintf("socket:[%d]", ino)) -} - -// ProviderVFS2 is the vfs2 interface implemented by providers of sockets for -// specific address families (e.g., AF_INET). -type ProviderVFS2 interface { - // Socket creates a new socket. - // - // If a nil Socket _and_ a nil error is returned, it means that the - // protocol is not supported. A non-nil error should only be returned - // if the protocol is supported, but an error occurs during creation. - Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) - - // Pair creates a pair of connected sockets. - // - // See Socket for error information. - Pair(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) -} - -// familiesVFS2 holds a map of all known address families and their providers. -var familiesVFS2 = make(map[int][]ProviderVFS2) - -// RegisterProviderVFS2 registers the provider of a given address family so that -// sockets of that type can be created via socket() and/or socketpair() -// syscalls. -// -// This should only be called during the initialization of the address family. -func RegisterProviderVFS2(family int, provider ProviderVFS2) { - familiesVFS2[family] = append(familiesVFS2[family], provider) -} - -// NewVFS2 creates a new socket with the given family, type and protocol. -func NewVFS2(t *kernel.Task, family int, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { - for _, p := range familiesVFS2[family] { - s, err := p.Socket(t, stype, protocol) - if err != nil { - return nil, err - } - if s != nil { - t.Kernel().RecordSocketVFS2(s) - return s, nil - } - } - - return nil, syserr.ErrAddressFamilyNotSupported -} - -// PairVFS2 creates a new connected socket pair with the given family, type and -// protocol. -func PairVFS2(t *kernel.Task, family int, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { - providers, ok := familiesVFS2[family] - if !ok { - return nil, nil, syserr.ErrAddressFamilyNotSupported - } - - for _, p := range providers { - s1, s2, err := p.Pair(t, stype, protocol) - if err != nil { - return nil, nil, err - } - if s1 != nil && s2 != nil { - k := t.Kernel() - k.RecordSocketVFS2(s1) - k.RecordSocketVFS2(s2) - return s1, s2, nil - } - } - - return nil, nil, syserr.ErrSocketNotSupported -} - // SendReceiveTimeout stores timeouts for send and receive calls. // // It is meant to be embedded into Socket implementations to help satisfy the diff --git a/pkg/sentry/socket/unix/BUILD b/pkg/sentry/socket/unix/BUILD index 289ca0f6a..5c9f62819 100644 --- a/pkg/sentry/socket/unix/BUILD +++ b/pkg/sentry/socket/unix/BUILD @@ -7,21 +7,10 @@ go_template_instance( name = "socket_refs", out = "socket_refs.go", package = "unix", - prefix = "socketOperations", + prefix = "socket", template = "//pkg/refsvfs2:refs_template", types = { - "T": "SocketOperations", - }, -) - -go_template_instance( - name = "socket_vfs2_refs", - out = "socket_vfs2_refs.go", - package = "unix", - prefix = "socketVFS2", - template = "//pkg/refsvfs2:refs_template", - types = { - "T": "SocketVFS2", + "T": "Socket", }, ) @@ -31,9 +20,7 @@ go_library( "device.go", "io.go", "socket_refs.go", - "socket_vfs2_refs.go", "unix.go", - "unix_vfs2.go", ], visibility = ["//pkg/sentry:internal"], deps = [ @@ -50,10 +37,9 @@ go_library( "//pkg/safemem", "//pkg/sentry/arch", "//pkg/sentry/device", - "//pkg/sentry/fs", - "//pkg/sentry/fs/fsutil", "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsimpl/sockfs", + "//pkg/sentry/fsutil", "//pkg/sentry/inet", "//pkg/sentry/kernel", "//pkg/sentry/kernel/time", diff --git a/pkg/sentry/socket/unix/unix.go b/pkg/sentry/socket/unix/unix.go index 7c5ce6bb9..d2001554d 100644 --- a/pkg/sentry/socket/unix/unix.go +++ b/pkg/sentry/socket/unix/unix.go @@ -18,7 +18,6 @@ package unix import ( "fmt" - "strings" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" @@ -29,8 +28,7 @@ import ( "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/marshal" "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" "gvisor.dev/gvisor/pkg/sentry/kernel" ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time" "gvisor.dev/gvisor/pkg/sentry/socket" @@ -44,70 +42,17 @@ import ( "gvisor.dev/gvisor/pkg/waiter" ) -// SocketOperations is a Unix socket. It is similar to a netstack socket, -// except it is backed by a transport.Endpoint instead of a tcpip.Endpoint. +// Socket implements socket.Socket (and by extension, +// vfs.FileDescriptionImpl) for Unix sockets. // // +stateify savable -type SocketOperations 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"` - - socketOperationsRefs - socketOpsCommon -} - -// New creates a new unix socket. -func New(ctx context.Context, endpoint transport.Endpoint, stype linux.SockType) *fs.File { - dirent := socket.NewDirent(ctx, unixSocketDevice) - defer dirent.DecRef(ctx) - return NewWithDirent(ctx, dirent, endpoint, stype, fs.FileFlags{Read: true, Write: true, NonSeekable: true}) -} - -// NewWithDirent creates a new unix socket using an existing dirent. -func NewWithDirent(ctx context.Context, d *fs.Dirent, ep transport.Endpoint, stype linux.SockType, flags fs.FileFlags) *fs.File { - // You can create AF_UNIX, SOCK_RAW sockets. They're the same as - // SOCK_DGRAM and don't require CAP_NET_RAW. - if stype == linux.SOCK_RAW { - stype = linux.SOCK_DGRAM - } - - s := SocketOperations{ - socketOpsCommon: socketOpsCommon{ - ep: ep, - stype: stype, - }, - } - s.InitRefs() - return fs.NewFile(ctx, d, flags, &s) -} - -// DecRef implements RefCounter.DecRef. -func (s *SocketOperations) DecRef(ctx context.Context) { - s.socketOperationsRefs.DecRef(func() { - s.ep.Close(ctx) - if s.abstractNamespace != nil { - s.abstractNamespace.Remove(s.abstractName, s) - } - }) -} - -// Release implemements fs.FileOperations.Release. -func (s *SocketOperations) Release(ctx context.Context) { - // Release only decrements a reference on s because s may be referenced in - // the abstract socket namespace. - s.DecRef(ctx) -} - -// socketOpsCommon contains the socket operations common to VFS1 and VFS2. -// -// +stateify savable -type socketOpsCommon struct { +type Socket struct { + vfsfd vfs.FileDescription + vfs.FileDescriptionDefaultImpl + vfs.DentryMetadataFileDescriptionImpl + vfs.LockFD socket.SendReceiveTimeout + socketRefs ep transport.Endpoint stype linux.SockType @@ -119,94 +64,75 @@ type socketOpsCommon struct { abstractNamespace *kernel.AbstractSocketNamespace } -func (s *socketOpsCommon) isPacket() bool { - switch s.stype { - case linux.SOCK_DGRAM, linux.SOCK_SEQPACKET: - return true - case linux.SOCK_STREAM: - return false - default: - // We shouldn't have allowed any other socket types during creation. - panic(fmt.Sprintf("Invalid socket type %d", s.stype)) - } -} +var _ = socket.Socket(&Socket{}) -// Endpoint extracts the transport.Endpoint. -func (s *socketOpsCommon) Endpoint() transport.Endpoint { - return s.ep -} +// NewSockfsFile creates a new socket file in the global sockfs mount and +// returns a corresponding file description. +func NewSockfsFile(t *kernel.Task, ep transport.Endpoint, stype linux.SockType) (*vfs.FileDescription, *syserr.Error) { + mnt := t.Kernel().SocketMount() + d := sockfs.NewDentry(t, mnt) + defer d.DecRef(t) -// extractPath extracts and validates the address. -func extractPath(sockaddr []byte) (string, *syserr.Error) { - addr, family, err := socket.AddressAndFamily(sockaddr) + fd, err := NewFileDescription(ep, stype, linux.O_RDWR, mnt, d, &vfs.FileLocks{}) if err != nil { - if err == syserr.ErrAddressFamilyNotSupported { - err = syserr.ErrInvalidArgument + return nil, syserr.FromError(err) + } + return fd, nil +} + +// NewFileDescription creates and returns a socket file description +// corresponding to the given mount and dentry. +func NewFileDescription(ep transport.Endpoint, stype linux.SockType, flags uint32, mnt *vfs.Mount, d *vfs.Dentry, locks *vfs.FileLocks) (*vfs.FileDescription, error) { + // You can create AF_UNIX, SOCK_RAW sockets. They're the same as + // SOCK_DGRAM and don't require CAP_NET_RAW. + if stype == linux.SOCK_RAW { + stype = linux.SOCK_DGRAM + } + + sock := &Socket{ + ep: ep, + stype: stype, + } + sock.InitRefs() + sock.LockFD.Init(locks) + vfsfd := &sock.vfsfd + if err := vfsfd.Init(sock, flags, mnt, d, &vfs.FileDescriptionOptions{ + DenyPRead: true, + DenyPWrite: true, + UseDentryMetadata: true, + }); err != nil { + return nil, err + } + return vfsfd, nil +} + +// DecRef implements RefCounter.DecRef. +func (s *Socket) DecRef(ctx context.Context) { + s.socketRefs.DecRef(func() { + kernel.KernelFromContext(ctx).DeleteSocket(&s.vfsfd) + s.ep.Close(ctx) + if s.abstractNamespace != nil { + s.abstractNamespace.Remove(s.abstractName, s) } - return "", err - } - if family != linux.AF_UNIX { - return "", syserr.ErrInvalidArgument - } - - // The address is trimmed by GetAddress. - p := string(addr.Addr) - if p == "" { - // Not allowed. - return "", syserr.ErrInvalidArgument - } - if p[len(p)-1] == '/' { - // Weird, they tried to bind '/a/b/c/'? - return "", syserr.ErrIsDir - } - - return p, nil + }) } -// GetPeerName implements the linux syscall getpeername(2) for sockets backed by -// a transport.Endpoint. -func (s *socketOpsCommon) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { - addr, err := s.ep.GetRemoteAddress() - if err != nil { - return nil, 0, syserr.TranslateNetstackError(err) - } - - a, l := socket.ConvertAddress(linux.AF_UNIX, addr) - return a, l, nil -} - -// GetSockName implements the linux syscall getsockname(2) for sockets backed by -// a transport.Endpoint. -func (s *socketOpsCommon) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { - addr, err := s.ep.GetLocalAddress() - if err != nil { - return nil, 0, syserr.TranslateNetstackError(err) - } - - a, l := socket.ConvertAddress(linux.AF_UNIX, addr) - return a, l, nil -} - -// Ioctl implements fs.FileOperations.Ioctl. -func (s *SocketOperations) Ioctl(ctx context.Context, _ *fs.File, io usermem.IO, args arch.SyscallArguments) (uintptr, error) { - return netstack.Ioctl(ctx, s.ep, io, args) +// Release implements vfs.FileDescriptionImpl.Release. +func (s *Socket) Release(ctx context.Context) { + // Release only decrements a reference on s because s may be referenced in + // the abstract socket namespace. + s.DecRef(ctx) } // GetSockOpt implements the linux syscall getsockopt(2) for sockets backed by // a transport.Endpoint. -func (s *SocketOperations) GetSockOpt(t *kernel.Task, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { +func (s *Socket) GetSockOpt(t *kernel.Task, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { return netstack.GetSockOpt(t, s, s.ep, linux.AF_UNIX, s.ep.Type(), level, name, outPtr, outLen) } -// Listen implements the linux syscall listen(2) for sockets backed by -// a transport.Endpoint. -func (s *socketOpsCommon) Listen(t *kernel.Task, backlog int) *syserr.Error { - return s.ep.Listen(t, backlog) -} - // blockingAccept implements a blocking version of accept(2), that is, if no // connections are ready to be accept, it will block until one becomes ready. -func (s *SocketOperations) blockingAccept(t *kernel.Task, peerAddr *tcpip.FullAddress) (transport.Endpoint, *syserr.Error) { +func (s *Socket) blockingAccept(t *kernel.Task, peerAddr *tcpip.FullAddress) (transport.Endpoint, *syserr.Error) { // Register for notifications. e, ch := waiter.NewChannelEntry(waiter.ReadableEvents) s.EventRegister(&e) @@ -227,7 +153,7 @@ func (s *SocketOperations) blockingAccept(t *kernel.Task, peerAddr *tcpip.FullAd // Accept implements the linux syscall accept(2) for sockets backed by // a transport.Endpoint. -func (s *SocketOperations) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { +func (s *Socket) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { var peerAddr *tcpip.FullAddress if peerRequested { peerAddr = &tcpip.FullAddress{} @@ -245,13 +171,14 @@ func (s *SocketOperations) Accept(t *kernel.Task, peerRequested bool, flags int, } } - ns := New(t, ep, s.stype) + ns, err := NewSockfsFile(t, ep, s.stype) + if err != nil { + return 0, nil, 0, err + } defer ns.DecRef(t) if flags&linux.SOCK_NONBLOCK != 0 { - flags := ns.Flags() - flags.NonBlocking = true - ns.SetFlags(flags.Settable()) + ns.SetStatusFlags(t, t.Credentials(), linux.SOCK_NONBLOCK) } var addr linux.SockAddr @@ -268,12 +195,11 @@ func (s *SocketOperations) Accept(t *kernel.Task, peerRequested bool, flags int, } t.Kernel().RecordSocket(ns) - return fd, addr, addrLen, nil } // Bind implements the linux syscall bind(2) for unix sockets. -func (s *SocketOperations) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error { +func (s *Socket) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error { p, e := extractPath(sockaddr) if e != nil { return e @@ -307,60 +233,270 @@ func (s *SocketOperations) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error { } // See net/unix/af_unix.c:unix_bind_bsd(). - - // The parent and name. - var d *fs.Dirent - var name string - - cwd := t.FSContext().WorkingDirectory() - defer cwd.DecRef(t) - - // Is there no slash at all? - if !strings.Contains(p, "/") { - d = cwd - name = p - } else { - root := t.FSContext().RootDirectory() - defer root.DecRef(t) - // Find the last path component, we know that something follows - // that final slash, otherwise extractPath() would have failed. - lastSlash := strings.LastIndex(p, "/") - subPath := p[:lastSlash] - if subPath == "" { - // Fix up subpath in case file is in root. - subPath = "/" - } - var err error - remainingTraversals := uint(fs.DefaultTraversalLimit) - d, err = t.MountNamespace().FindInode(t, root, cwd, subPath, &remainingTraversals) - if err != nil { - // No path available. - return syserr.ErrNoSuchFile - } - defer d.DecRef(t) - name = p[lastSlash+1:] + path := fspath.Parse(p) + root := t.FSContext().RootDirectory() + defer root.DecRef(t) + start := root + relPath := !path.Absolute + if relPath { + start = t.FSContext().WorkingDirectory() + defer start.DecRef(t) } - - // Create the socket. - // - // Note that the file permissions here are not set correctly (see - // gvisor.dev/issue/2324). There is no convenient way to get permissions - // on the socket referred to by s, so we will leave this discrepancy - // unresolved until VFS2 replaces this code. - childDir, err := d.Bind(t, t.FSContext().RootDirectory(), name, bep, fs.FilePermissions{User: fs.PermMask{Read: true}}) + pop := vfs.PathOperation{ + Root: root, + Start: start, + Path: path, + } + stat, err := s.vfsfd.Stat(t, vfs.StatOptions{Mask: linux.STATX_MODE}) if err != nil { - return syserr.ErrPortInUse + return syserr.FromError(err) + } + err = t.Kernel().VFS().MknodAt(t, t.Credentials(), &pop, &vfs.MknodOptions{ + Mode: linux.FileMode(linux.S_IFSOCK | uint(stat.Mode)&^t.FSContext().Umask()), + Endpoint: bep, + }) + if linuxerr.Equals(linuxerr.EEXIST, err) { + return syserr.ErrAddressInUse + } + if err != nil { + return syserr.FromError(err) } - childDir.DecRef(t) if err := s.ep.Bind(tcpip.FullAddress{Addr: tcpip.Address(p)}); err != nil { - if removeErr := d.Remove(t, t.FSContext().RootDirectory(), name, false /* dirPath */); removeErr != nil { - log.Warningf("failed to remove socket file created for bind(%q): %v", p, removeErr) + if unlinkErr := t.Kernel().VFS().UnlinkAt(t, t.Credentials(), &pop); unlinkErr != nil { + log.Warningf("failed to unlink socket file created for bind(%q): %v", p, unlinkErr) } return err } return nil } +// Ioctl implements vfs.FileDescriptionImpl. +func (s *Socket) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { + return netstack.Ioctl(ctx, s.ep, uio, args) +} + +// PRead implements vfs.FileDescriptionImpl. +func (s *Socket) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { + return 0, linuxerr.ESPIPE +} + +// Read implements vfs.FileDescriptionImpl. +func (s *Socket) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP + } + + if dst.NumBytes() == 0 { + return 0, nil + } + r := &EndpointReader{ + Ctx: ctx, + Endpoint: s.ep, + NumRights: 0, + Peek: false, + From: nil, + } + n, err := dst.CopyOutFrom(ctx, r) + if r.Notify != nil { + r.Notify() + } + // Drop control messages. + r.Control.Release(ctx) + return n, err +} + +// PWrite implements vfs.FileDescriptionImpl. +func (s *Socket) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { + return 0, linuxerr.ESPIPE +} + +// Write implements vfs.FileDescriptionImpl. +func (s *Socket) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { + // All flags other than RWF_NOWAIT should be ignored. + // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. + if opts.Flags != 0 { + return 0, linuxerr.EOPNOTSUPP + } + + t := kernel.TaskFromContext(ctx) + ctrl := control.New(t, s.ep) + + if src.NumBytes() == 0 { + nInt, notify, err := s.ep.SendMsg(ctx, [][]byte{}, ctrl, nil) + if notify != nil { + notify() + } + return int64(nInt), err.ToError() + } + + w := &EndpointWriter{ + Ctx: ctx, + Endpoint: s.ep, + Control: ctrl, + To: nil, + } + + n, err := src.CopyInTo(ctx, w) + if w.Notify != nil { + w.Notify() + } + return n, err + +} + +// Epollable implements FileDescriptionImpl.Epollable. +func (s *Socket) Epollable() bool { + return true +} + +// SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by +// a transport.Endpoint. +func (s *Socket) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error { + return netstack.SetSockOpt(t, s, s.ep, level, name, optVal) +} + +// provider is a unix domain socket provider. +type provider struct{} + +func (*provider) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { + // Check arguments. + if protocol != 0 && protocol != linux.AF_UNIX /* PF_UNIX */ { + return nil, syserr.ErrProtocolNotSupported + } + + // Create the endpoint and socket. + var ep transport.Endpoint + switch stype { + case linux.SOCK_DGRAM, linux.SOCK_RAW: + ep = transport.NewConnectionless(t) + case linux.SOCK_SEQPACKET, linux.SOCK_STREAM: + ep = transport.NewConnectioned(t, stype, t.Kernel()) + default: + return nil, syserr.ErrInvalidArgument + } + + f, err := NewSockfsFile(t, ep, stype) + if err != nil { + ep.Close(t) + return nil, err + } + return f, nil +} + +// Pair creates a new pair of AF_UNIX connected sockets. +func (*provider) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { + // Check arguments. + if protocol != 0 && protocol != linux.AF_UNIX /* PF_UNIX */ { + return nil, nil, syserr.ErrProtocolNotSupported + } + + switch stype { + case linux.SOCK_STREAM, linux.SOCK_DGRAM, linux.SOCK_SEQPACKET, linux.SOCK_RAW: + // Ok + default: + return nil, nil, syserr.ErrInvalidArgument + } + + // Create the endpoints and sockets. + ep1, ep2 := transport.NewPair(t, stype, t.Kernel()) + s1, err := NewSockfsFile(t, ep1, stype) + if err != nil { + ep1.Close(t) + ep2.Close(t) + return nil, nil, err + } + s2, err := NewSockfsFile(t, ep2, stype) + if err != nil { + s1.DecRef(t) + ep2.Close(t) + return nil, nil, err + } + + return s1, s2, nil +} + +// socketOpsCommon contains the socket operations common to VFS1 and VFS2. +// +// +stateify savable +type socketOpsCommon struct { +} + +func (s *Socket) isPacket() bool { + switch s.stype { + case linux.SOCK_DGRAM, linux.SOCK_SEQPACKET: + return true + case linux.SOCK_STREAM: + return false + default: + // We shouldn't have allowed any other socket types during creation. + panic(fmt.Sprintf("Invalid socket type %d", s.stype)) + } +} + +// Endpoint extracts the transport.Endpoint. +func (s *Socket) Endpoint() transport.Endpoint { + return s.ep +} + +// extractPath extracts and validates the address. +func extractPath(sockaddr []byte) (string, *syserr.Error) { + addr, family, err := socket.AddressAndFamily(sockaddr) + if err != nil { + if err == syserr.ErrAddressFamilyNotSupported { + err = syserr.ErrInvalidArgument + } + return "", err + } + if family != linux.AF_UNIX { + return "", syserr.ErrInvalidArgument + } + + // The address is trimmed by GetAddress. + p := string(addr.Addr) + if p == "" { + // Not allowed. + return "", syserr.ErrInvalidArgument + } + if p[len(p)-1] == '/' { + // Weird, they tried to bind '/a/b/c/'? + return "", syserr.ErrIsDir + } + + return p, nil +} + +// GetPeerName implements the linux syscall getpeername(2) for sockets backed by +// a transport.Endpoint. +func (s *Socket) GetPeerName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { + addr, err := s.ep.GetRemoteAddress() + if err != nil { + return nil, 0, syserr.TranslateNetstackError(err) + } + + a, l := socket.ConvertAddress(linux.AF_UNIX, addr) + return a, l, nil +} + +// GetSockName implements the linux syscall getsockname(2) for sockets backed by +// a transport.Endpoint. +func (s *Socket) GetSockName(t *kernel.Task) (linux.SockAddr, uint32, *syserr.Error) { + addr, err := s.ep.GetLocalAddress() + if err != nil { + return nil, 0, syserr.TranslateNetstackError(err) + } + + a, l := socket.ConvertAddress(linux.AF_UNIX, addr) + return a, l, nil +} + +// Listen implements the linux syscall listen(2) for sockets backed by +// a transport.Endpoint. +func (s *Socket) Listen(t *kernel.Task, backlog int) *syserr.Error { + return s.ep.Listen(t, backlog) +} + // extractEndpoint retrieves the transport.BoundEndpoint associated with a Unix // socket path. The Release must be called on the transport.BoundEndpoint when // the caller is done with it. @@ -386,11 +522,11 @@ func extractEndpoint(t *kernel.Task, sockaddr []byte) (transport.BoundEndpoint, } p := fspath.Parse(path) - root := t.FSContext().RootDirectoryVFS2() + root := t.FSContext().RootDirectory() start := root relPath := !p.Absolute if relPath { - start = t.FSContext().WorkingDirectoryVFS2() + start = t.FSContext().WorkingDirectory() } pop := vfs.PathOperation{ Root: root, @@ -410,7 +546,7 @@ func extractEndpoint(t *kernel.Task, sockaddr []byte) (transport.BoundEndpoint, } // Connect implements the linux syscall connect(2) for unix sockets. -func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { +func (s *Socket) Connect(t *kernel.Task, sockaddr []byte, blocking bool) *syserr.Error { ep, err := extractEndpoint(t, sockaddr) if err != nil { return err @@ -432,36 +568,9 @@ func (s *socketOpsCommon) Connect(t *kernel.Task, sockaddr []byte, blocking bool return err } -// Write implements fs.FileOperations.Write. -func (s *SocketOperations) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, _ int64) (int64, error) { - t := kernel.TaskFromContext(ctx) - ctrl := control.New(t, s.ep, nil) - - if src.NumBytes() == 0 { - nInt, notify, err := s.ep.SendMsg(ctx, [][]byte{}, ctrl, nil) - if notify != nil { - notify() - } - return int64(nInt), err.ToError() - } - - w := &EndpointWriter{ - Ctx: ctx, - Endpoint: s.ep, - Control: ctrl, - To: nil, - } - - n, err := src.CopyInTo(ctx, w) - if w.Notify != nil { - w.Notify() - } - return n, err -} - // SendMsg implements the linux syscall sendmsg(2) for unix sockets backed by // a transport.Endpoint. -func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { +func (s *Socket) SendMsg(t *kernel.Task, src usermem.IOSequence, to []byte, flags int, haveDeadline bool, deadline ktime.Time, controlMessages socket.ControlMessages) (int, *syserr.Error) { w := EndpointWriter{ Ctx: t, Endpoint: s.ep, @@ -534,39 +643,33 @@ func (s *socketOpsCommon) SendMsg(t *kernel.Task, src usermem.IOSequence, to []b } // Passcred implements transport.Credentialer.Passcred. -func (s *socketOpsCommon) Passcred() bool { +func (s *Socket) Passcred() bool { return s.ep.Passcred() } // ConnectedPasscred implements transport.Credentialer.ConnectedPasscred. -func (s *socketOpsCommon) ConnectedPasscred() bool { +func (s *Socket) ConnectedPasscred() bool { return s.ep.ConnectedPasscred() } // Readiness implements waiter.Waitable.Readiness. -func (s *socketOpsCommon) Readiness(mask waiter.EventMask) waiter.EventMask { +func (s *Socket) Readiness(mask waiter.EventMask) waiter.EventMask { return s.ep.Readiness(mask) } // EventRegister implements waiter.Waitable.EventRegister. -func (s *socketOpsCommon) EventRegister(e *waiter.Entry) error { +func (s *Socket) EventRegister(e *waiter.Entry) error { return s.ep.EventRegister(e) } // EventUnregister implements waiter.Waitable.EventUnregister. -func (s *socketOpsCommon) EventUnregister(e *waiter.Entry) { +func (s *Socket) EventUnregister(e *waiter.Entry) { s.ep.EventUnregister(e) } -// SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by -// a transport.Endpoint. -func (s *SocketOperations) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error { - return netstack.SetSockOpt(t, s, s.ep, level, name, optVal) -} - // Shutdown implements the linux syscall shutdown(2) for sockets backed by // a transport.Endpoint. -func (s *socketOpsCommon) Shutdown(t *kernel.Task, how int) *syserr.Error { +func (s *Socket) Shutdown(t *kernel.Task, how int) *syserr.Error { f, err := netstack.ConvertShutdown(how) if err != nil { return err @@ -576,30 +679,9 @@ func (s *socketOpsCommon) Shutdown(t *kernel.Task, how int) *syserr.Error { return s.ep.Shutdown(f) } -// Read implements fs.FileOperations.Read. -func (s *SocketOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _ int64) (int64, error) { - if dst.NumBytes() == 0 { - return 0, nil - } - r := &EndpointReader{ - Ctx: ctx, - Endpoint: s.ep, - NumRights: 0, - Peek: false, - From: nil, - } - n, err := dst.CopyOutFrom(ctx, r) - if r.Notify != nil { - r.Notify() - } - // Drop control messages. - r.Control.Release(ctx) - return n, err -} - // RecvMsg implements the linux syscall recvmsg(2) for sockets backed by // a transport.Endpoint. -func (s *socketOpsCommon) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, controlDataLen uint64) (n int, msgFlags int, senderAddr linux.SockAddr, senderAddrLen uint32, controlMessages socket.ControlMessages, err *syserr.Error) { +func (s *Socket) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags int, haveDeadline bool, deadline ktime.Time, senderRequested bool, controlDataLen uint64) (n int, msgFlags int, senderAddr linux.SockAddr, senderAddrLen uint32, controlMessages socket.ControlMessages, err *syserr.Error) { trunc := flags&linux.MSG_TRUNC != 0 peek := flags&linux.MSG_PEEK != 0 dontWait := flags&linux.MSG_DONTWAIT != 0 @@ -740,63 +822,16 @@ func (s *socketOpsCommon) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags } // State implements socket.Socket.State. -func (s *socketOpsCommon) State() uint32 { +func (s *Socket) State() uint32 { return s.ep.State() } // Type implements socket.Socket.Type. -func (s *socketOpsCommon) Type() (family int, skType linux.SockType, protocol int) { +func (s *Socket) Type() (family int, skType linux.SockType, protocol int) { // Unix domain sockets always have a protocol of 0. return linux.AF_UNIX, s.stype, 0 } -// provider is a unix domain socket provider. -type provider struct{} - -// Socket returns a new unix domain socket. -func (*provider) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *syserr.Error) { - // Check arguments. - if protocol != 0 && protocol != linux.AF_UNIX /* PF_UNIX */ { - return nil, syserr.ErrProtocolNotSupported - } - - // Create the endpoint and socket. - var ep transport.Endpoint - switch stype { - case linux.SOCK_DGRAM, linux.SOCK_RAW: - ep = transport.NewConnectionless(t) - case linux.SOCK_SEQPACKET, linux.SOCK_STREAM: - ep = transport.NewConnectioned(t, stype, t.Kernel()) - default: - return nil, syserr.ErrInvalidArgument - } - - return New(t, ep, stype), nil -} - -// Pair creates a new pair of AF_UNIX connected sockets. -func (*provider) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*fs.File, *fs.File, *syserr.Error) { - // Check arguments. - if protocol != 0 && protocol != linux.AF_UNIX /* PF_UNIX */ { - return nil, nil, syserr.ErrProtocolNotSupported - } - - switch stype { - case linux.SOCK_STREAM, linux.SOCK_DGRAM, linux.SOCK_SEQPACKET, linux.SOCK_RAW: - // Ok - default: - return nil, nil, syserr.ErrInvalidArgument - } - - // Create the endpoints and sockets. - ep1, ep2 := transport.NewPair(t, stype, t.Kernel()) - s1 := New(t, ep1, stype) - s2 := New(t, ep2, stype) - - return s1, s2, nil -} - func init() { socket.RegisterProvider(linux.AF_UNIX, &provider{}) - socket.RegisterProviderVFS2(linux.AF_UNIX, &providerVFS2{}) } diff --git a/pkg/sentry/socket/unix/unix_vfs2.go b/pkg/sentry/socket/unix/unix_vfs2.go deleted file mode 100644 index 556e535d1..000000000 --- a/pkg/sentry/socket/unix/unix_vfs2.go +++ /dev/null @@ -1,421 +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 unix - -import ( - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/errors/linuxerr" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/marshal" - "gvisor.dev/gvisor/pkg/sentry/arch" - "gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/socket" - "gvisor.dev/gvisor/pkg/sentry/socket/control" - "gvisor.dev/gvisor/pkg/sentry/socket/netstack" - "gvisor.dev/gvisor/pkg/sentry/socket/unix/transport" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserr" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/pkg/usermem" - "gvisor.dev/gvisor/pkg/waiter" -) - -// SocketVFS2 implements socket.SocketVFS2 (and by extension, -// vfs.FileDescriptionImpl) for Unix sockets. -// -// +stateify savable -type SocketVFS2 struct { - vfsfd vfs.FileDescription - vfs.FileDescriptionDefaultImpl - vfs.DentryMetadataFileDescriptionImpl - vfs.LockFD - - socketVFS2Refs - socketOpsCommon -} - -var _ = socket.SocketVFS2(&SocketVFS2{}) - -// NewSockfsFile creates a new socket file in the global sockfs mount and -// returns a corresponding file description. -func NewSockfsFile(t *kernel.Task, ep transport.Endpoint, stype linux.SockType) (*vfs.FileDescription, *syserr.Error) { - mnt := t.Kernel().SocketMount() - d := sockfs.NewDentry(t, mnt) - defer d.DecRef(t) - - fd, err := NewFileDescription(ep, stype, linux.O_RDWR, mnt, d, &vfs.FileLocks{}) - if err != nil { - return nil, syserr.FromError(err) - } - return fd, nil -} - -// NewFileDescription creates and returns a socket file description -// corresponding to the given mount and dentry. -func NewFileDescription(ep transport.Endpoint, stype linux.SockType, flags uint32, mnt *vfs.Mount, d *vfs.Dentry, locks *vfs.FileLocks) (*vfs.FileDescription, error) { - // You can create AF_UNIX, SOCK_RAW sockets. They're the same as - // SOCK_DGRAM and don't require CAP_NET_RAW. - if stype == linux.SOCK_RAW { - stype = linux.SOCK_DGRAM - } - - sock := &SocketVFS2{ - socketOpsCommon: socketOpsCommon{ - ep: ep, - stype: stype, - }, - } - sock.InitRefs() - sock.LockFD.Init(locks) - vfsfd := &sock.vfsfd - if err := vfsfd.Init(sock, flags, mnt, d, &vfs.FileDescriptionOptions{ - DenyPRead: true, - DenyPWrite: true, - UseDentryMetadata: true, - }); err != nil { - return nil, err - } - return vfsfd, nil -} - -// DecRef implements RefCounter.DecRef. -func (s *SocketVFS2) DecRef(ctx context.Context) { - s.socketVFS2Refs.DecRef(func() { - kernel.KernelFromContext(ctx).DeleteSocketVFS2(&s.vfsfd) - s.ep.Close(ctx) - if s.abstractNamespace != nil { - s.abstractNamespace.Remove(s.abstractName, s) - } - }) -} - -// Release implements vfs.FileDescriptionImpl.Release. -func (s *SocketVFS2) Release(ctx context.Context) { - // Release only decrements a reference on s because s may be referenced in - // the abstract socket namespace. - s.DecRef(ctx) -} - -// GetSockOpt implements the linux syscall getsockopt(2) for sockets backed by -// a transport.Endpoint. -func (s *SocketVFS2) GetSockOpt(t *kernel.Task, level, name int, outPtr hostarch.Addr, outLen int) (marshal.Marshallable, *syserr.Error) { - return netstack.GetSockOpt(t, s, s.ep, linux.AF_UNIX, s.ep.Type(), level, name, outPtr, outLen) -} - -// blockingAccept implements a blocking version of accept(2), that is, if no -// connections are ready to be accept, it will block until one becomes ready. -func (s *SocketVFS2) blockingAccept(t *kernel.Task, peerAddr *tcpip.FullAddress) (transport.Endpoint, *syserr.Error) { - // Register for notifications. - e, ch := waiter.NewChannelEntry(waiter.ReadableEvents) - s.socketOpsCommon.EventRegister(&e) - defer s.socketOpsCommon.EventUnregister(&e) - - // Try to accept the connection; if it fails, then wait until we get a - // notification. - for { - if ep, err := s.ep.Accept(t, peerAddr); err != syserr.ErrWouldBlock { - return ep, err - } - - if err := t.Block(ch); err != nil { - return nil, syserr.FromError(err) - } - } -} - -// Accept implements the linux syscall accept(2) for sockets backed by -// a transport.Endpoint. -func (s *SocketVFS2) Accept(t *kernel.Task, peerRequested bool, flags int, blocking bool) (int32, linux.SockAddr, uint32, *syserr.Error) { - var peerAddr *tcpip.FullAddress - if peerRequested { - peerAddr = &tcpip.FullAddress{} - } - ep, err := s.ep.Accept(t, peerAddr) - if err != nil { - if err != syserr.ErrWouldBlock || !blocking { - return 0, nil, 0, err - } - - var err *syserr.Error - ep, err = s.blockingAccept(t, peerAddr) - if err != nil { - return 0, nil, 0, err - } - } - - ns, err := NewSockfsFile(t, ep, s.stype) - if err != nil { - return 0, nil, 0, err - } - defer ns.DecRef(t) - - if flags&linux.SOCK_NONBLOCK != 0 { - ns.SetStatusFlags(t, t.Credentials(), linux.SOCK_NONBLOCK) - } - - var addr linux.SockAddr - var addrLen uint32 - if peerAddr != nil { - addr, addrLen = socket.ConvertAddress(linux.AF_UNIX, *peerAddr) - } - - fd, e := t.NewFDFromVFS2(0, ns, kernel.FDFlags{ - CloseOnExec: flags&linux.SOCK_CLOEXEC != 0, - }) - if e != nil { - return 0, nil, 0, syserr.FromError(e) - } - - t.Kernel().RecordSocketVFS2(ns) - return fd, addr, addrLen, nil -} - -// Bind implements the linux syscall bind(2) for unix sockets. -func (s *SocketVFS2) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error { - p, e := extractPath(sockaddr) - if e != nil { - return e - } - - bep, ok := s.ep.(transport.BoundEndpoint) - if !ok { - // This socket can't be bound. - return syserr.ErrInvalidArgument - } - - if p[0] == 0 { - // Abstract socket. See net/unix/af_unix.c:unix_bind_abstract(). - if t.IsNetworkNamespaced() { - return syserr.ErrInvalidEndpointState - } - asn := t.AbstractSockets() - name := p[1:] - if err := asn.Bind(t, name, bep, s); err != nil { - // syserr.ErrPortInUse corresponds to EADDRINUSE. - return syserr.ErrPortInUse - } - if err := s.ep.Bind(tcpip.FullAddress{Addr: tcpip.Address(p)}); err != nil { - asn.Remove(name, s) - return err - } - // The socket has been successfully bound. We can update the following. - s.abstractName = name - s.abstractNamespace = asn - return nil - } - - // See net/unix/af_unix.c:unix_bind_bsd(). - path := fspath.Parse(p) - root := t.FSContext().RootDirectoryVFS2() - defer root.DecRef(t) - start := root - relPath := !path.Absolute - if relPath { - start = t.FSContext().WorkingDirectoryVFS2() - defer start.DecRef(t) - } - pop := vfs.PathOperation{ - Root: root, - Start: start, - Path: path, - } - stat, err := s.vfsfd.Stat(t, vfs.StatOptions{Mask: linux.STATX_MODE}) - if err != nil { - return syserr.FromError(err) - } - err = t.Kernel().VFS().MknodAt(t, t.Credentials(), &pop, &vfs.MknodOptions{ - Mode: linux.FileMode(linux.S_IFSOCK | uint(stat.Mode)&^t.FSContext().Umask()), - Endpoint: bep, - }) - if linuxerr.Equals(linuxerr.EEXIST, err) { - return syserr.ErrAddressInUse - } - if err != nil { - return syserr.FromError(err) - } - if err := s.ep.Bind(tcpip.FullAddress{Addr: tcpip.Address(p)}); err != nil { - if unlinkErr := t.Kernel().VFS().UnlinkAt(t, t.Credentials(), &pop); unlinkErr != nil { - log.Warningf("failed to unlink socket file created for bind(%q): %v", p, unlinkErr) - } - return err - } - return nil -} - -// Ioctl implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { - return netstack.Ioctl(ctx, s.ep, uio, args) -} - -// PRead implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// Read implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - if dst.NumBytes() == 0 { - return 0, nil - } - r := &EndpointReader{ - Ctx: ctx, - Endpoint: s.ep, - NumRights: 0, - Peek: false, - From: nil, - } - n, err := dst.CopyOutFrom(ctx, r) - if r.Notify != nil { - r.Notify() - } - // Drop control messages. - r.Control.Release(ctx) - return n, err -} - -// PWrite implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { - return 0, linuxerr.ESPIPE -} - -// Write implements vfs.FileDescriptionImpl. -func (s *SocketVFS2) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { - // All flags other than RWF_NOWAIT should be ignored. - // TODO(gvisor.dev/issue/2601): Support RWF_NOWAIT. - if opts.Flags != 0 { - return 0, linuxerr.EOPNOTSUPP - } - - t := kernel.TaskFromContext(ctx) - ctrl := control.New(t, s.ep, nil) - - if src.NumBytes() == 0 { - nInt, notify, err := s.ep.SendMsg(ctx, [][]byte{}, ctrl, nil) - if notify != nil { - notify() - } - return int64(nInt), err.ToError() - } - - w := &EndpointWriter{ - Ctx: ctx, - Endpoint: s.ep, - Control: ctrl, - To: nil, - } - - n, err := src.CopyInTo(ctx, w) - if w.Notify != nil { - w.Notify() - } - return n, err - -} - -// Readiness implements waiter.Waitable.Readiness. -func (s *SocketVFS2) Readiness(mask waiter.EventMask) waiter.EventMask { - return s.socketOpsCommon.Readiness(mask) -} - -// EventRegister implements waiter.Waitable.EventRegister. -func (s *SocketVFS2) EventRegister(e *waiter.Entry) error { - return s.socketOpsCommon.EventRegister(e) -} - -// EventUnregister implements waiter.Waitable.EventUnregister. -func (s *SocketVFS2) EventUnregister(e *waiter.Entry) { - s.socketOpsCommon.EventUnregister(e) -} - -// Epollable implements FileDescriptionImpl.Epollable. -func (s *SocketVFS2) Epollable() bool { - return true -} - -// SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by -// a transport.Endpoint. -func (s *SocketVFS2) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error { - return netstack.SetSockOpt(t, s, s.ep, level, name, optVal) -} - -// providerVFS2 is a unix domain socket provider for VFS2. -type providerVFS2 struct{} - -func (*providerVFS2) Socket(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *syserr.Error) { - // Check arguments. - if protocol != 0 && protocol != linux.AF_UNIX /* PF_UNIX */ { - return nil, syserr.ErrProtocolNotSupported - } - - // Create the endpoint and socket. - var ep transport.Endpoint - switch stype { - case linux.SOCK_DGRAM, linux.SOCK_RAW: - ep = transport.NewConnectionless(t) - case linux.SOCK_SEQPACKET, linux.SOCK_STREAM: - ep = transport.NewConnectioned(t, stype, t.Kernel()) - default: - return nil, syserr.ErrInvalidArgument - } - - f, err := NewSockfsFile(t, ep, stype) - if err != nil { - ep.Close(t) - return nil, err - } - return f, nil -} - -// Pair creates a new pair of AF_UNIX connected sockets. -func (*providerVFS2) Pair(t *kernel.Task, stype linux.SockType, protocol int) (*vfs.FileDescription, *vfs.FileDescription, *syserr.Error) { - // Check arguments. - if protocol != 0 && protocol != linux.AF_UNIX /* PF_UNIX */ { - return nil, nil, syserr.ErrProtocolNotSupported - } - - switch stype { - case linux.SOCK_STREAM, linux.SOCK_DGRAM, linux.SOCK_SEQPACKET, linux.SOCK_RAW: - // Ok - default: - return nil, nil, syserr.ErrInvalidArgument - } - - // Create the endpoints and sockets. - ep1, ep2 := transport.NewPair(t, stype, t.Kernel()) - s1, err := NewSockfsFile(t, ep1, stype) - if err != nil { - ep1.Close(t) - ep2.Close(t) - return nil, nil, err - } - s2, err := NewSockfsFile(t, ep2, stype) - if err != nil { - s1.DecRef(t) - ep2.Close(t) - return nil, nil, err - } - - return s1, s2, nil -} diff --git a/pkg/sentry/strace/strace.go b/pkg/sentry/strace/strace.go index fa311f1f2..e43287a26 100644 --- a/pkg/sentry/strace/strace.go +++ b/pkg/sentry/strace/strace.go @@ -151,19 +151,19 @@ func path(t *kernel.Task, addr hostarch.Addr) string { } func fd(t *kernel.Task, fd int32) string { - root := t.FSContext().RootDirectoryVFS2() + root := t.FSContext().RootDirectory() defer root.DecRef(t) vfsObj := root.Mount().Filesystem().VirtualFilesystem() if fd == linux.AT_FDCWD { - wd := t.FSContext().WorkingDirectoryVFS2() + wd := t.FSContext().WorkingDirectory() defer wd.DecRef(t) name, _ := vfsObj.PathnameWithDeleted(t, root, wd) return fmt.Sprintf("AT_FDCWD %s", name) } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { // Cast FD to uint64 to avoid printing negative hex. return fmt.Sprintf("%#x (bad FD)", uint64(fd)) diff --git a/pkg/sentry/syscalls/linux/path.go b/pkg/sentry/syscalls/linux/path.go index 275400316..c471c830a 100644 --- a/pkg/sentry/syscalls/linux/path.go +++ b/pkg/sentry/syscalls/linux/path.go @@ -37,7 +37,7 @@ type taskPathOperation struct { } func getTaskPathOperation(t *kernel.Task, dirfd int32, path fspath.Path, shouldAllowEmptyPath shouldAllowEmptyPath, shouldFollowFinalSymlink shouldFollowFinalSymlink) (taskPathOperation, error) { - root := t.FSContext().RootDirectoryVFS2() + root := t.FSContext().RootDirectory() start := root haveStartRef := false if !path.Absolute { @@ -46,10 +46,10 @@ func getTaskPathOperation(t *kernel.Task, dirfd int32, path fspath.Path, shouldA return taskPathOperation{}, linuxerr.ENOENT } if dirfd == linux.AT_FDCWD { - start = t.FSContext().WorkingDirectoryVFS2() + start = t.FSContext().WorkingDirectory() haveStartRef = true } else { - dirfile := t.GetFileVFS2(dirfd) + dirfile := t.GetFile(dirfd) if dirfile == nil { root.DecRef(t) return taskPathOperation{}, linuxerr.EBADF diff --git a/pkg/sentry/syscalls/linux/points.go b/pkg/sentry/syscalls/linux/points.go index fc325f86b..2c41265bc 100644 --- a/pkg/sentry/syscalls/linux/points.go +++ b/pkg/sentry/syscalls/linux/points.go @@ -45,13 +45,13 @@ func getFilePath(t *kernel.Task, fd int32) string { if fdt == nil { return "[err: no FD table]" } - file, _ := fdt.GetVFS2(fd) + file, _ := fdt.Get(fd) if file == nil { return "[err: FD not found]" } defer file.DecRef(t) - root := t.MountNamespaceVFS2().Root() + root := t.MountNamespace().Root() path, err := t.Kernel().VFS().PathnameWithDeleted(t, root, file.VirtualDentry()) if err != nil { return fmt.Sprintf("[err: %v]", err) diff --git a/pkg/sentry/syscalls/linux/sys_aio.go b/pkg/sentry/syscalls/linux/sys_aio.go index d807a70e8..010b83f8c 100644 --- a/pkg/sentry/syscalls/linux/sys_aio.go +++ b/pkg/sentry/syscalls/linux/sys_aio.go @@ -281,7 +281,7 @@ func submitCallback(t *kernel.Task, id uint64, cb *linux.IOCallback, cbAddr host return linuxerr.EINVAL } - fd := t.GetFileVFS2(cb.FD) + fd := t.GetFile(cb.FD) if fd == nil { return linuxerr.EBADF } @@ -290,7 +290,7 @@ func submitCallback(t *kernel.Task, id uint64, cb *linux.IOCallback, cbAddr host // Was there an eventFD? Extract it. var eventFD *vfs.FileDescription if cb.Flags&linux.IOCB_FLAG_RESFD != 0 { - eventFD = t.GetFileVFS2(cb.ResFD) + eventFD = t.GetFile(cb.ResFD) if eventFD == nil { return linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_epoll.go b/pkg/sentry/syscalls/linux/sys_epoll.go index d148198ff..4d4a4db69 100644 --- a/pkg/sentry/syscalls/linux/sys_epoll.go +++ b/pkg/sentry/syscalls/linux/sys_epoll.go @@ -43,7 +43,7 @@ func EpollCreate1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel. } defer file.DecRef(t) - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, file, kernel.FDFlags{ CloseOnExec: flags&linux.EPOLL_CLOEXEC != 0, }) if err != nil { @@ -68,7 +68,7 @@ func EpollCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S } defer file.DecRef(t) - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{}) + fd, err := t.NewFDFrom(0, file, kernel.FDFlags{}) if err != nil { return 0, nil, err } @@ -82,7 +82,7 @@ func EpollCtl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc fd := args[2].Int() eventAddr := args[3].Pointer() - epfile := t.GetFileVFS2(epfd) + epfile := t.GetFile(epfd) if epfile == nil { return 0, nil, linuxerr.EBADF } @@ -91,7 +91,7 @@ func EpollCtl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc if !ok { return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -125,7 +125,7 @@ func waitEpoll(t *kernel.Task, epfd int32, eventsAddr hostarch.Addr, maxEvents i return 0, nil, linuxerr.EINVAL } - epfile := t.GetFileVFS2(epfd) + epfile := t.GetFile(epfd) if epfile == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_eventfd.go b/pkg/sentry/syscalls/linux/sys_eventfd.go index 2a7716f7c..051473e0d 100644 --- a/pkg/sentry/syscalls/linux/sys_eventfd.go +++ b/pkg/sentry/syscalls/linux/sys_eventfd.go @@ -44,7 +44,7 @@ func Eventfd2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc } defer eventfd.DecRef(t) - fd, err := t.NewFDFromVFS2(0, eventfd, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, eventfd, kernel.FDFlags{ CloseOnExec: flags&linux.EFD_CLOEXEC != 0, }) if err != nil { diff --git a/pkg/sentry/syscalls/linux/sys_file.go b/pkg/sentry/syscalls/linux/sys_file.go index c2c1da34d..d669316ef 100644 --- a/pkg/sentry/syscalls/linux/sys_file.go +++ b/pkg/sentry/syscalls/linux/sys_file.go @@ -118,7 +118,7 @@ func openat(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, flags uint32, m } defer file.DecRef(t) - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, file, kernel.FDFlags{ CloseOnExec: flags&linux.O_CLOEXEC != 0, }) return uintptr(fd), nil, err @@ -202,7 +202,7 @@ func accessAt(t *kernel.Task, dirfd int32, pathAddr hostarch.Addr, mode uint, fl func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -215,13 +215,13 @@ func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall // Handle ioctls that apply to all FDs. switch args[1].Int() { case linux.FIONCLEX: - t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ + t.FDTable().SetFlags(t, fd, kernel.FDFlags{ CloseOnExec: false, }) return 0, nil, nil case linux.FIOCLEX: - t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ + t.FDTable().SetFlags(t, fd, kernel.FDFlags{ CloseOnExec: true, }) return 0, nil, nil @@ -292,8 +292,8 @@ func Getcwd(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal addr := args[0].Pointer() size := args[1].SizeT() - root := t.FSContext().RootDirectoryVFS2() - wd := t.FSContext().WorkingDirectoryVFS2() + root := t.FSContext().RootDirectory() + wd := t.FSContext().WorkingDirectory() s, err := t.Kernel().VFS().PathnameForGetcwd(t, root, wd) root.DecRef(t) wd.DecRef(t) @@ -339,7 +339,7 @@ func Chdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall if err != nil { return 0, nil, err } - t.FSContext().SetWorkingDirectoryVFS2(t, vd) + t.FSContext().SetWorkingDirectory(t, vd) vd.DecRef(t) return 0, nil, nil } @@ -360,7 +360,7 @@ func Fchdir(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal if err != nil { return 0, nil, err } - t.FSContext().SetWorkingDirectoryVFS2(t, vd) + t.FSContext().SetWorkingDirectory(t, vd) vd.DecRef(t) return 0, nil, nil } @@ -389,7 +389,7 @@ func Chroot(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal if err != nil { return 0, nil, err } - t.FSContext().SetRootDirectoryVFS2(t, vd) + t.FSContext().SetRootDirectory(t, vd) vd.DecRef(t) return 0, nil, nil } @@ -422,7 +422,7 @@ func PivotRoot(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys } defer putOldTpop.Release(t) - oldRootVd := t.FSContext().RootDirectoryVFS2() + oldRootVd := t.FSContext().RootDirectory() defer oldRootVd.DecRef(t) newRootVd, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &newRootTpop.pop, &vfs.GetDentryOptions{ CheckSearchable: true, @@ -446,7 +446,7 @@ func Close(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall // Note that Remove provides a reference on the file that we may use to // flush. It is still active until we drop the final reference below // (and other reference-holding operations complete). - _, file := t.FDTable().Remove(t, fd) + file := t.FDTable().Remove(t, fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -489,14 +489,14 @@ func CloseRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy flagToApply := kernel.FDFlags{ CloseOnExec: true, } - t.FDTable().SetFlagsForRangeVFS2(t.AsyncContext(), int32(first), int32(last), flagToApply) + t.FDTable().SetFlagsForRange(t.AsyncContext(), int32(first), int32(last), flagToApply) return 0, nil, nil } fdTable := t.FDTable() fd := int32(first) for { - fd, _, file := fdTable.RemoveNextInRange(t, fd, int32(last)) + fd, file := fdTable.RemoveNextInRange(t, fd, int32(last)) if file == nil { break } @@ -514,13 +514,13 @@ func CloseRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy func Dup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - newFD, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{}) + newFD, err := t.NewFDFrom(0, file, kernel.FDFlags{}) if err != nil { return 0, nil, linuxerr.EMFILE } @@ -534,7 +534,7 @@ func Dup2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC if oldfd == newfd { // As long as oldfd is valid, dup2() does nothing and returns newfd. - file := t.GetFileVFS2(oldfd) + file := t.GetFile(oldfd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -563,13 +563,13 @@ func dup3(t *kernel.Task, oldfd, newfd int32, flags uint32) (uintptr, *kernel.Sy return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(oldfd) + file := t.GetFile(oldfd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) - err := t.NewFDAtVFS2(newfd, file, kernel.FDFlags{ + err := t.NewFDAt(newfd, file, kernel.FDFlags{ CloseOnExec: flags&linux.O_CLOEXEC != 0, }) if err != nil { @@ -583,7 +583,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall fd := args[0].Int() cmd := args[1].Int() - file, flags := t.FDTable().GetVFS2(fd) + file, flags := t.FDTable().Get(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -601,7 +601,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall switch cmd { case linux.F_DUPFD, linux.F_DUPFD_CLOEXEC: minfd := args[2].Int() - fd, err := t.NewFDFromVFS2(minfd, file, kernel.FDFlags{ + fd, err := t.NewFDFrom(minfd, file, kernel.FDFlags{ CloseOnExec: cmd == linux.F_DUPFD_CLOEXEC, }) if err != nil { @@ -612,7 +612,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return uintptr(flags.ToLinuxFDFlags()), nil, nil case linux.F_SETFD: flags := args[2].Uint() - err := t.FDTable().SetFlagsVFS2(t, fd, kernel.FDFlags{ + err := t.FDTable().SetFlags(t, fd, kernel.FDFlags{ CloseOnExec: flags&linux.FD_CLOEXEC != 0, }) return 0, nil, err @@ -694,7 +694,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall } return uintptr(a.(*fasync.FileAsync).Signal()), nil, nil case linux.F_SETSIG: - a, err := file.SetAsyncHandler(fasync.NewVFS2(int(fd))) + a, err := file.SetAsyncHandler(fasync.New(int(fd))) if err != nil { return 0, nil, err } @@ -742,7 +742,7 @@ func setAsyncOwner(t *kernel.Task, fd int, file *vfs.FileDescription, ownerType, return linuxerr.EINVAL } - a, err := file.SetAsyncHandler(fasync.NewVFS2(fd)) + a, err := file.SetAsyncHandler(fasync.New(fd)) if err != nil { return err } @@ -867,7 +867,7 @@ func Fadvise64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -1115,7 +1115,7 @@ func Unlinkat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc } func setstatat(t *kernel.Task, dirfd int32, path fspath.Path, shouldAllowEmptyPath shouldAllowEmptyPath, shouldFollowFinalSymlink shouldFollowFinalSymlink, opts *vfs.SetStatOptions) error { - root := t.FSContext().RootDirectoryVFS2() + root := t.FSContext().RootDirectory() defer root.DecRef(t) start := root if !path.Absolute { @@ -1123,10 +1123,10 @@ func setstatat(t *kernel.Task, dirfd int32, path fspath.Path, shouldAllowEmptyPa return linuxerr.ENOENT } if dirfd == linux.AT_FDCWD { - start = t.FSContext().WorkingDirectoryVFS2() + start = t.FSContext().WorkingDirectory() defer start.DecRef(t) } else { - dirfile := t.GetFileVFS2(dirfd) + dirfile := t.GetFile(dirfd) if dirfile == nil { return linuxerr.EBADF } @@ -1194,7 +1194,7 @@ func Ftruncate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -1291,7 +1291,7 @@ func Fchown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal owner := args[1].Int() group := args[2].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -1340,7 +1340,7 @@ func Fchmod(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal fd := args[0].Int() mode := args[1].ModeT() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -1588,7 +1588,7 @@ func Fallocate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys offset := args[2].Int64() length := args[3].Int64() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -1625,7 +1625,7 @@ func Flock(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall fd := args[0].Int() operation := args[1].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { // flock(2): EBADF fd is not an open file descriptor. return 0, nil, linuxerr.EBADF @@ -1687,7 +1687,7 @@ func MemfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S } defer file.DecRef(t) - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, file, kernel.FDFlags{ CloseOnExec: cloExec, }) if err != nil { diff --git a/pkg/sentry/syscalls/linux/sys_getdents.go b/pkg/sentry/syscalls/linux/sys_getdents.go index e2e3d4342..528254eff 100644 --- a/pkg/sentry/syscalls/linux/sys_getdents.go +++ b/pkg/sentry/syscalls/linux/sys_getdents.go @@ -48,7 +48,7 @@ func getdents(t *kernel.Task, args arch.SyscallArguments, isGetdents64 bool) (ui return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_inotify.go b/pkg/sentry/syscalls/linux/sys_inotify.go index edd45a8cd..b1897a9e6 100644 --- a/pkg/sentry/syscalls/linux/sys_inotify.go +++ b/pkg/sentry/syscalls/linux/sys_inotify.go @@ -37,7 +37,7 @@ func InotifyInit1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel. } defer ino.DecRef(t) - fd, err := t.NewFDFromVFS2(0, ino, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, ino, kernel.FDFlags{ CloseOnExec: flags&linux.IN_CLOEXEC != 0, }) @@ -57,7 +57,7 @@ func InotifyInit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S // fdToInotify resolves an fd to an inotify object. If successful, the file will // have an extra ref and the caller is responsible for releasing the ref. func fdToInotify(t *kernel.Task, fd int32) (*vfs.Inotify, *vfs.FileDescription, error) { - f := t.GetFileVFS2(fd) + f := t.GetFile(fd) if f == nil { // Invalid fd. return nil, nil, linuxerr.EBADF diff --git a/pkg/sentry/syscalls/linux/sys_iouring.go b/pkg/sentry/syscalls/linux/sys_iouring.go index 326dc8f5a..5be795ecd 100644 --- a/pkg/sentry/syscalls/linux/sys_iouring.go +++ b/pkg/sentry/syscalls/linux/sys_iouring.go @@ -59,7 +59,7 @@ func IOUringSetup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel. } defer iouringfd.DecRef(t) - fd, err := t.NewFDFromVFS2(0, iouringfd, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, iouringfd, kernel.FDFlags{ // O_CLOEXEC is always set up. See io_uring/io_uring.c:io_uring_install_fd(). CloseOnExec: true, }) @@ -103,7 +103,7 @@ func IOUringEnter(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel. return uintptr(ret), nil, nil } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return uintptr(ret), nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_mmap.go b/pkg/sentry/syscalls/linux/sys_mmap.go index 611114d1d..17a884245 100644 --- a/pkg/sentry/syscalls/linux/sys_mmap.go +++ b/pkg/sentry/syscalls/linux/sys_mmap.go @@ -80,7 +80,7 @@ func Mmap(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC if !anon { // Convert the passed FD to a file reference. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_mount.go b/pkg/sentry/syscalls/linux/sys_mount.go index 7f20a7741..1cc9c69ff 100644 --- a/pkg/sentry/syscalls/linux/sys_mount.go +++ b/pkg/sentry/syscalls/linux/sys_mount.go @@ -36,7 +36,7 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall // Must have CAP_SYS_ADMIN in the current mount namespace's associated user // namespace. creds := t.Credentials() - if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) { + if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespace().Owner) { return 0, nil, linuxerr.EPERM } @@ -145,7 +145,7 @@ func Umount2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca // // Currently, this is always the init task's user namespace. creds := t.Credentials() - if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespaceVFS2().Owner) { + if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, t.MountNamespace().Owner) { return 0, nil, linuxerr.EPERM } diff --git a/pkg/sentry/syscalls/linux/sys_mq.go b/pkg/sentry/syscalls/linux/sys_mq.go index 479dbfbfd..d6ea28bdc 100644 --- a/pkg/sentry/syscalls/linux/sys_mq.go +++ b/pkg/sentry/syscalls/linux/sys_mq.go @@ -58,7 +58,7 @@ func MqOpen(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal return 0, nil, err } - fd, err := t.NewFDFromVFS2(0, queue, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, queue, kernel.FDFlags{ CloseOnExec: flag&linux.O_CLOEXEC != 0, }) if err != nil { diff --git a/pkg/sentry/syscalls/linux/sys_pipe.go b/pkg/sentry/syscalls/linux/sys_pipe.go index 4a203636d..8ae2a75c1 100644 --- a/pkg/sentry/syscalls/linux/sys_pipe.go +++ b/pkg/sentry/syscalls/linux/sys_pipe.go @@ -49,7 +49,7 @@ func pipe2(t *kernel.Task, addr hostarch.Addr, flags int32) error { defer r.DecRef(t) defer w.DecRef(t) - fds, err := t.NewFDsVFS2(0, []*vfs.FileDescription{r, w}, kernel.FDFlags{ + fds, err := t.NewFDs(0, []*vfs.FileDescription{r, w}, kernel.FDFlags{ CloseOnExec: flags&linux.O_CLOEXEC != 0, }) if err != nil { @@ -57,7 +57,7 @@ func pipe2(t *kernel.Task, addr hostarch.Addr, flags int32) error { } if _, err := primitive.CopyInt32SliceOut(t, addr, fds); err != nil { for _, fd := range fds { - if _, file := t.FDTable().Remove(t, fd); file != nil { + if file := t.FDTable().Remove(t, fd); file != nil { file.DecRef(t) } } diff --git a/pkg/sentry/syscalls/linux/sys_poll.go b/pkg/sentry/syscalls/linux/sys_poll.go index ffffeaef4..0ae9d4ef0 100644 --- a/pkg/sentry/syscalls/linux/sys_poll.go +++ b/pkg/sentry/syscalls/linux/sys_poll.go @@ -66,7 +66,7 @@ func initReadiness(t *kernel.Task, pfd *linux.PollFD, state *pollState, ch chan return nil } - file := t.GetFileVFS2(pfd.FD) + file := t.GetFile(pfd.FD) if file == nil { pfd.REvents = linux.POLLNVAL return nil @@ -270,7 +270,7 @@ func doSelect(t *kernel.Task, nfds int, readFDs, writeFDs, exceptFDs hostarch.Ad // immediately to ensure we don't leak. Note, another thread // might be about to close fd. This is racy, but that's // OK. Linux is racy in the same way. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_prctl.go b/pkg/sentry/syscalls/linux/sys_prctl.go index d855cbc02..515d14f92 100644 --- a/pkg/sentry/syscalls/linux/sys_prctl.go +++ b/pkg/sentry/syscalls/linux/sys_prctl.go @@ -125,7 +125,7 @@ func Prctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall case linux.PR_SET_MM_EXE_FILE: fd := args[2].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_read_write.go b/pkg/sentry/syscalls/linux/sys_read_write.go index df85fcb57..cf9dc73f3 100644 --- a/pkg/sentry/syscalls/linux/sys_read_write.go +++ b/pkg/sentry/syscalls/linux/sys_read_write.go @@ -39,7 +39,7 @@ func Read(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC addr := args[1].Pointer() size := args[2].SizeT() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -70,7 +70,7 @@ func Readv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall addr := args[1].Pointer() iovcnt := int(args[2].Int()) - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -139,7 +139,7 @@ func Pread64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca size := args[2].SizeT() offset := args[3].Int64() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -176,7 +176,7 @@ func Preadv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal iovcnt := int(args[2].Int()) offset := args[3].Int64() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -214,7 +214,7 @@ func Preadv2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca offset := args[3].Int64() flags := args[5].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -293,7 +293,7 @@ func Write(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall addr := args[1].Pointer() size := args[2].SizeT() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -324,7 +324,7 @@ func Writev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal addr := args[1].Pointer() iovcnt := int(args[2].Int()) - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -392,7 +392,7 @@ func Pwrite64(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc size := args[2].SizeT() offset := args[3].Int64() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -429,7 +429,7 @@ func Pwritev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca iovcnt := int(args[2].Int()) offset := args[3].Int64() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -467,7 +467,7 @@ func Pwritev2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc offset := args[3].Int64() flags := args[5].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -546,7 +546,7 @@ func blockPolicy(t *kernel.Task, file *vfs.FileDescription) (allowBlock bool, de return false, ktime.Time{}, false } // Sockets support read/write timeouts. - if s, ok := file.Impl().(socket.SocketVFS2); ok { + if s, ok := file.Impl().(socket.Socket); ok { dl := s.RecvTimeout() if dl < 0 { return false, ktime.Time{}, false @@ -564,7 +564,7 @@ func Lseek(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall offset := args[1].Int64() whence := args[2].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -580,7 +580,7 @@ func Readahead(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys offset := args[1].Int64() size := args[2].SizeT() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_signal.go b/pkg/sentry/syscalls/linux/sys_signal.go index c7d77dce8..413363366 100644 --- a/pkg/sentry/syscalls/linux/sys_signal.go +++ b/pkg/sentry/syscalls/linux/sys_signal.go @@ -531,7 +531,7 @@ func sharedSignalfd(t *kernel.Task, fd int32, sigset hostarch.Addr, sigsetsize u // // The spec indicates that this should adjust the mask. if fd != -1 { - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -561,7 +561,7 @@ func sharedSignalfd(t *kernel.Task, fd int32, sigset hostarch.Addr, sigsetsize u defer file.DecRef(t) // Create a new descriptor. - fd, err = t.NewFDFromVFS2(0, file, kernel.FDFlags{ + fd, err = t.NewFDFrom(0, file, kernel.FDFlags{ CloseOnExec: flags&linux.SFD_CLOEXEC != 0, }) if err != nil { diff --git a/pkg/sentry/syscalls/linux/sys_socket.go b/pkg/sentry/syscalls/linux/sys_socket.go index e4bb151d6..bd6b609d1 100644 --- a/pkg/sentry/syscalls/linux/sys_socket.go +++ b/pkg/sentry/syscalls/linux/sys_socket.go @@ -180,7 +180,7 @@ func Socket(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal } // Create the new socket. - s, e := socket.NewVFS2(t, domain, linux.SockType(stype&0xf), protocol) + s, e := socket.New(t, domain, linux.SockType(stype&0xf), protocol) if e != nil { return 0, nil, e.ToError() } @@ -190,7 +190,7 @@ func Socket(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal return 0, nil, err } - fd, err := t.NewFDFromVFS2(0, s, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, s, kernel.FDFlags{ CloseOnExec: stype&linux.SOCK_CLOEXEC != 0, }) if err != nil { @@ -213,7 +213,7 @@ func SocketPair(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy } // Create the socket pair. - s1, s2, e := socket.PairVFS2(t, domain, linux.SockType(stype&0xf), protocol) + s1, s2, e := socket.Pair(t, domain, linux.SockType(stype&0xf), protocol) if e != nil { return 0, nil, e.ToError() } @@ -233,14 +233,14 @@ func SocketPair(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy flags := kernel.FDFlags{ CloseOnExec: stype&linux.SOCK_CLOEXEC != 0, } - fds, err := t.NewFDsVFS2(0, []*vfs.FileDescription{s1, s2}, flags) + fds, err := t.NewFDs(0, []*vfs.FileDescription{s1, s2}, flags) if err != nil { return 0, nil, err } if _, err := primitive.CopyInt32SliceOut(t, addr, fds); err != nil { for _, fd := range fds { - if _, file := t.FDTable().Remove(t, fd); file != nil { + if file := t.FDTable().Remove(t, fd); file != nil { file.DecRef(t) } } @@ -257,14 +257,14 @@ func Connect(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca addrlen := args[2].Uint() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -288,14 +288,14 @@ func accept(t *kernel.Task, fd int32, addr hostarch.Addr, addrLen hostarch.Addr, } // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, linuxerr.ENOTSOCK } @@ -347,14 +347,14 @@ func Bind(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC addrlen := args[2].Uint() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -374,14 +374,14 @@ func Listen(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal backlog := args[1].Uint() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -412,14 +412,14 @@ func Shutdown(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc how := args[1].Int() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -443,14 +443,14 @@ func GetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy optLenAddr := args[4].Pointer() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -485,7 +485,7 @@ func GetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy // getSockOpt tries to handle common socket options, or dispatches to a specific // socket implementation. -func getSockOpt(t *kernel.Task, s socket.SocketVFS2, level, name int, optValAddr hostarch.Addr, len int) (marshal.Marshallable, *syserr.Error) { +func getSockOpt(t *kernel.Task, s socket.Socket, level, name int, optValAddr hostarch.Addr, len int) (marshal.Marshallable, *syserr.Error) { if level == linux.SOL_SOCKET { switch name { case linux.SO_TYPE, linux.SO_DOMAIN, linux.SO_PROTOCOL: @@ -524,14 +524,14 @@ func SetSockOpt(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy optLen := args[4].Int() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -562,14 +562,14 @@ func GetSockName(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S addrlen := args[2].Pointer() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -590,14 +590,14 @@ func GetPeerName(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.S addrlen := args[2].Pointer() // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -623,14 +623,14 @@ func RecvMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca } // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -680,14 +680,14 @@ func RecvMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc } // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -748,15 +748,15 @@ func RecvMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return uintptr(count), nil, nil } -func getSCMRightsVFS2(t *kernel.Task, rights transport.RightsControlMessage) control.SCMRightsVFS2 { +func getSCMRights(t *kernel.Task, rights transport.RightsControlMessage) control.SCMRights { switch v := rights.(type) { - case control.SCMRightsVFS2: + case control.SCMRights: return v case *transport.SCMRights: - rf := control.RightsFilesVFS2(fdsToHostFiles(t, v.FDs)) + rf := control.RightsFiles(fdsToHostFiles(t, v.FDs)) return &rf default: - panic(fmt.Sprintf("rights of type %T must be *transport.SCMRights or implement SCMRightsVFS2", rights)) + panic(fmt.Sprintf("rights of type %T must be *transport.SCMRights or implement SCMRights", rights)) } } @@ -790,7 +790,7 @@ func fdsToHostFiles(ctx context.Context, fds []int) []*vfs.FileDescription { return files } -func recvSingleMsg(t *kernel.Task, s socket.SocketVFS2, msgPtr hostarch.Addr, flags int32, haveDeadline bool, deadline ktime.Time) (uintptr, error) { +func recvSingleMsg(t *kernel.Task, s socket.Socket, msgPtr hostarch.Addr, flags int32, haveDeadline bool, deadline ktime.Time) (uintptr, error) { // Capture the message header and io vectors. var msg MessageHeader64 if _, err := msg.CopyIn(t, msgPtr); err != nil { @@ -846,8 +846,8 @@ func recvSingleMsg(t *kernel.Task, s socket.SocketVFS2, msgPtr hostarch.Addr, fl } if cms.Unix.Rights != nil { - cms.Unix.Rights = getSCMRightsVFS2(t, cms.Unix.Rights) - controlData, mflags = control.PackRightsVFS2(t, cms.Unix.Rights.(control.SCMRightsVFS2), flags&linux.MSG_CMSG_CLOEXEC != 0, controlData, mflags) + cms.Unix.Rights = getSCMRights(t, cms.Unix.Rights) + controlData, mflags = control.PackRights(t, cms.Unix.Rights.(control.SCMRights), flags&linux.MSG_CMSG_CLOEXEC != 0, controlData, mflags) } // Copy the address to the caller. @@ -888,14 +888,14 @@ func recvFrom(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, fla } // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, linuxerr.ENOTSOCK } @@ -961,14 +961,14 @@ func SendMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca } // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -1003,14 +1003,14 @@ func SendMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc } // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, nil, linuxerr.ENOTSOCK } @@ -1053,7 +1053,7 @@ func SendMMsg(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return uintptr(count), nil, nil } -func sendSingleMsg(t *kernel.Task, s socket.SocketVFS2, file *vfs.FileDescription, msgPtr hostarch.Addr, flags int32) (uintptr, error) { +func sendSingleMsg(t *kernel.Task, s socket.Socket, file *vfs.FileDescription, msgPtr hostarch.Addr, flags int32) (uintptr, error) { // Capture the message header. var msg MessageHeader64 if _, err := msg.CopyIn(t, msgPtr); err != nil { @@ -1127,14 +1127,14 @@ func sendTo(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, flags } // Get socket from the file descriptor. - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, linuxerr.EBADF } defer file.DecRef(t) // Extract the socket. - s, ok := file.Impl().(socket.SocketVFS2) + s, ok := file.Impl().(socket.Socket) if !ok { return 0, linuxerr.ENOTSOCK } @@ -1170,7 +1170,7 @@ func sendTo(t *kernel.Task, fd int32, bufPtr hostarch.Addr, bufLen uint64, flags } // Call the syscall implementation. - n, e := s.SendMsg(t, src, to, int(flags), haveDeadline, deadline, socket.ControlMessages{Unix: control.New(t, s, nil)}) + n, e := s.SendMsg(t, src, to, int(flags), haveDeadline, deadline, socket.ControlMessages{Unix: control.New(t, s)}) return uintptr(n), HandleIOError(t, n != 0, e.ToError(), linuxerr.ERESTARTSYS, "sendto", file) } diff --git a/pkg/sentry/syscalls/linux/sys_splice.go b/pkg/sentry/syscalls/linux/sys_splice.go index 862431e8d..593d09f02 100644 --- a/pkg/sentry/syscalls/linux/sys_splice.go +++ b/pkg/sentry/syscalls/linux/sys_splice.go @@ -54,12 +54,12 @@ func Splice(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal } // Get file descriptions. - inFile := t.GetFileVFS2(inFD) + inFile := t.GetFile(inFD) if inFile == nil { return 0, nil, linuxerr.EBADF } defer inFile.DecRef(t) - outFile := t.GetFileVFS2(outFD) + outFile := t.GetFile(outFD) if outFile == nil { return 0, nil, linuxerr.EBADF } @@ -200,12 +200,12 @@ func Tee(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallCo } // Get file descriptions. - inFile := t.GetFileVFS2(inFD) + inFile := t.GetFile(inFD) if inFile == nil { return 0, nil, linuxerr.EBADF } defer inFile.DecRef(t) - outFile := t.GetFileVFS2(outFD) + outFile := t.GetFile(outFD) if outFile == nil { return 0, nil, linuxerr.EBADF } @@ -273,7 +273,7 @@ func Sendfile(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc offsetAddr := args[2].Pointer() count := int64(args[3].SizeT()) - inFile := t.GetFileVFS2(inFD) + inFile := t.GetFile(inFD) if inFile == nil { return 0, nil, linuxerr.EBADF } @@ -282,7 +282,7 @@ func Sendfile(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc return 0, nil, linuxerr.EBADF } - outFile := t.GetFileVFS2(outFD) + outFile := t.GetFile(outFD) if outFile == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_stat.go b/pkg/sentry/syscalls/linux/sys_stat.go index 92c3cf0e7..284067dc9 100644 --- a/pkg/sentry/syscalls/linux/sys_stat.go +++ b/pkg/sentry/syscalls/linux/sys_stat.go @@ -63,7 +63,7 @@ func fstatat(t *kernel.Task, dirfd int32, pathAddr, statAddr hostarch.Addr, flag return err } - root := t.FSContext().RootDirectoryVFS2() + root := t.FSContext().RootDirectory() defer root.DecRef(t) start := root if !path.Absolute { @@ -71,10 +71,10 @@ func fstatat(t *kernel.Task, dirfd int32, pathAddr, statAddr hostarch.Addr, flag return linuxerr.ENOENT } if dirfd == linux.AT_FDCWD { - start = t.FSContext().WorkingDirectoryVFS2() + start = t.FSContext().WorkingDirectory() defer start.DecRef(t) } else { - dirfile := t.GetFileVFS2(dirfd) + dirfile := t.GetFile(dirfd) if dirfile == nil { return linuxerr.EBADF } @@ -127,7 +127,7 @@ func Fstat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall fd := args[0].Int() statAddr := args[1].Pointer() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -175,7 +175,7 @@ func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return 0, nil, err } - root := t.FSContext().RootDirectoryVFS2() + root := t.FSContext().RootDirectory() defer root.DecRef(t) start := root if !path.Absolute { @@ -183,10 +183,10 @@ func Statx(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall return 0, nil, linuxerr.ENOENT } if dirfd == linux.AT_FDCWD { - start = t.FSContext().WorkingDirectoryVFS2() + start = t.FSContext().WorkingDirectory() defer start.DecRef(t) } else { - dirfile := t.GetFileVFS2(dirfd) + dirfile := t.GetFile(dirfd) if dirfile == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_sync.go b/pkg/sentry/syscalls/linux/sys_sync.go index d46ab31f1..3b0fa158a 100644 --- a/pkg/sentry/syscalls/linux/sys_sync.go +++ b/pkg/sentry/syscalls/linux/sys_sync.go @@ -30,7 +30,7 @@ func Sync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC func Syncfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -47,7 +47,7 @@ func Syncfs(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal func Fsync(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { fd := args[0].Int() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -77,7 +77,7 @@ func SyncFileRange(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_thread.go b/pkg/sentry/syscalls/linux/sys_thread.go index b70555297..e4606a856 100644 --- a/pkg/sentry/syscalls/linux/sys_thread.go +++ b/pkg/sentry/syscalls/linux/sys_thread.go @@ -106,7 +106,7 @@ func execveat(t *kernel.Task, dirfd int32, pathnameAddr, argvAddr, envvAddr host } } - root := t.FSContext().RootDirectoryVFS2() + root := t.FSContext().RootDirectory() defer root.DecRef(t) var executable fsbridge.File defer func() { @@ -125,7 +125,7 @@ func execveat(t *kernel.Task, dirfd int32, pathnameAddr, argvAddr, envvAddr host if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 { return 0, nil, linuxerr.ENOENT } - dirfile, dirfileFlags := t.FDTable().GetVFS2(dirfd) + dirfile, dirfileFlags := t.FDTable().Get(dirfd) if dirfile == nil { return 0, nil, linuxerr.EBADF } @@ -151,8 +151,8 @@ func execveat(t *kernel.Task, dirfd int32, pathnameAddr, argvAddr, envvAddr host } // Load the new TaskImage. - mntns := t.MountNamespaceVFS2() - wd := t.FSContext().WorkingDirectoryVFS2() + mntns := t.MountNamespace() + wd := t.FSContext().WorkingDirectory() defer wd.DecRef(t) remainingTraversals := uint(linux.MaxSymlinkTraversals) loadArgs := loader.LoadArgs{ diff --git a/pkg/sentry/syscalls/linux/sys_timerfd.go b/pkg/sentry/syscalls/linux/sys_timerfd.go index 5a07456bf..784cec917 100644 --- a/pkg/sentry/syscalls/linux/sys_timerfd.go +++ b/pkg/sentry/syscalls/linux/sys_timerfd.go @@ -55,7 +55,7 @@ func TimerfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel return 0, nil, err } defer file.DecRef(t) - fd, err := t.NewFDFromVFS2(0, file, kernel.FDFlags{ + fd, err := t.NewFDFrom(0, file, kernel.FDFlags{ CloseOnExec: flags&linux.TFD_CLOEXEC != 0, }) if err != nil { @@ -75,7 +75,7 @@ func TimerfdSettime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -109,7 +109,7 @@ func TimerfdGettime(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne fd := args[0].Int() curValAddr := args[1].Pointer() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/syscalls/linux/sys_xattr.go b/pkg/sentry/syscalls/linux/sys_xattr.go index 9c157d399..592eece48 100644 --- a/pkg/sentry/syscalls/linux/sys_xattr.go +++ b/pkg/sentry/syscalls/linux/sys_xattr.go @@ -68,7 +68,7 @@ func Flistxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sy listAddr := args[1].Pointer() size := args[2].SizeT() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -137,7 +137,7 @@ func Fgetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys valueAddr := args[2].Pointer() size := args[3].SizeT() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -218,7 +218,7 @@ func Fsetxattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sys return 0, nil, linuxerr.EINVAL } - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } @@ -277,7 +277,7 @@ func Fremovexattr(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel. fd := args[0].Int() nameAddr := args[1].Pointer() - file := t.GetFileVFS2(fd) + file := t.GetFile(fd) if file == nil { return 0, nil, linuxerr.EBADF } diff --git a/pkg/sentry/vfs/BUILD b/pkg/sentry/vfs/BUILD index 47f262ee3..b9476cce5 100644 --- a/pkg/sentry/vfs/BUILD +++ b/pkg/sentry/vfs/BUILD @@ -163,7 +163,6 @@ go_library( "//pkg/refsvfs2", "//pkg/safemem", "//pkg/sentry/arch", - "//pkg/sentry/fs", "//pkg/sentry/fsimpl/lock", "//pkg/sentry/fsmetric", "//pkg/sentry/kernel/auth", diff --git a/pkg/sentry/vfs/README.md b/pkg/sentry/vfs/README.md index 82ee2c521..8ba0c4168 100644 --- a/pkg/sentry/vfs/README.md +++ b/pkg/sentry/vfs/README.md @@ -119,64 +119,3 @@ This construction, which is essentially a type-safe analogue to Linux's fetching of the `Dentry.impl` interface object to also fetch `DentryImpl` fields, either because they are in the same cache line or via next-line prefetching. - -## Future Work - -- Most `mount(2)` features, and unmounting, are incomplete. - -- VFS1 filesystems are not directly compatible with VFS2. It may be possible - to implement shims that implement `vfs.FilesystemImpl` for - `fs.MountNamespace`, `vfs.DentryImpl` for `fs.Dirent`, and - `vfs.FileDescriptionImpl` for `fs.File`, which may be adequate for - filesystems that are not performance-critical (e.g. sysfs); however, it is - not clear that this will be less effort than simply porting the filesystems - in question. Practically speaking, the following filesystems will probably - need to be ported or made compatible through a shim to evaluate filesystem - performance on realistic workloads: - - - devfs/procfs/sysfs, which will realistically be necessary to execute - most applications. (Note that procfs and sysfs do not support hard - links, so they do not require the complexity of separate inode objects. - Also note that Linux's /dev is actually a variant of tmpfs called - devtmpfs.) - - - tmpfs. This should be relatively straightforward: copy/paste memfs, - store regular file contents in pgalloc-allocated memory instead of - `[]byte`, and add support for file timestamps. (In fact, it probably - makes more sense to convert memfs to tmpfs and not keep the former.) - - - A remote filesystem, either lisafs (if it is ready by the time that - other benchmarking prerequisites are) or v9fs (aka 9P, aka gofers). - - - epoll files. - - Filesystems that will need to be ported before switching to VFS2, but can - probably be skipped for early testing: - - - overlayfs, which is needed for (at least) synthetic mount points. - - - Support for host ttys. - - - timerfd files. - - Filesystems that can be probably dropped: - - - ashmem, which is far too incomplete to use. - - - binder, which is similarly far too incomplete to use. - -- Save/restore. For instance, it is unclear if the current implementation of - the `state` package supports the inheritance pattern described above. - -- Many features that were previously implemented by VFS must now be - implemented by individual filesystems (though, in most cases, this should - consist of calls to hooks or libraries provided by `vfs` or other packages). - This includes, but is not necessarily limited to: - - - Block and character device special files - - - Inotify - - - File locking - - - `O_ASYNC` diff --git a/pkg/sentry/vfs/g3doc/fuse.md b/pkg/sentry/vfs/g3doc/fuse.md index 05e043583..ace5c2191 100644 --- a/pkg/sentry/vfs/g3doc/fuse.md +++ b/pkg/sentry/vfs/g3doc/fuse.md @@ -36,16 +36,16 @@ implementation of this protocol. ## FUSE in the Sentry -The sentry's FUSE client targets VFS2 and has the following components: +The sentry's FUSE client has the following components: - An implementation of `/dev/fuse`. -- A VFS2 filesystem for mapping syscalls to FUSE ops. Since we're targeting - VFS2, one point of contention may be the lack of inodes in VFS2. We can - tentatively implement a kernfs-based filesystem to bridge the gap in APIs. - The kernfs base functionality can serve the role of the Linux inode cache - and, the filesystem can map VFS2 syscalls to kernfs inode operations; see - the `kernfs.Inode` interface. +- A filesystem for mapping syscalls to FUSE ops. One point of contention may + be the lack of inodes in the VFS layer. We can tentatively implement a + kernfs-based filesystem to bridge the gap in APIs. The kernfs base + functionality can serve the role of the Linux inode cache and, the + filesystem can map syscalls to kernfs inode operations; see the + `kernfs.Inode` interface. The FUSE protocol lends itself well to marshaling with `go_marshal`. The various request and response packets can be defined in the ABI package and converted to @@ -148,7 +148,7 @@ FUSE. We describe the design and ways to improve it here: ##### Basic FUSE Read -The vfs2 expects implementations of `vfs.FileDescriptionImpl.Read()` and +The VFS expects implementations of `vfs.FileDescriptionImpl.Read()` and `vfs.FileDescriptionImpl.PRead()`. When a syscall is made, it will eventually reach our implementation of those interface functions located at `pkg/sentry/fsimpl/fuse/regular_file.go` for regular files. @@ -175,7 +175,7 @@ so. ##### Basic FUSE Write -The vfs2 invokes implementations of `vfs.FileDescriptionImpl.Write()` and +The VFS invokes implementations of `vfs.FileDescriptionImpl.Write()` and `vfs.FileDescriptionImpl.PWrite()` on the regular file descriptor of FUSE when a user makes write(2) and pwrite(2) syscall. diff --git a/pkg/sentry/vfs/g3doc/inotify.md b/pkg/sentry/vfs/g3doc/inotify.md index 833db213f..5772918d4 100644 --- a/pkg/sentry/vfs/g3doc/inotify.md +++ b/pkg/sentry/vfs/g3doc/inotify.md @@ -7,7 +7,7 @@ that create inotify instances (inotify_init/inotify_init1) and add/remove watches on files to an instance (inotify_add_watch/inotify_rm_watch). Events are generated from various places in the sentry, including the syscall layer, the vfs layer, the process fd table, and within each filesystem implementation. This -document outlines the implementation details of inotify in VFS2. +document outlines the implementation details of inotify. ## Inotify Objects @@ -75,10 +75,10 @@ ordering of filesystem locks. In Linux, watches reside on inodes at the virtual filesystem layer. As a result, all hard links and file descriptions on a single file will all share the same -watch set. In VFS2, there is no common inode structure across filesystem types -(some may not even have inodes), so we have to plumb inotify support through -each specific filesystem implementation. Some of the technical considerations -are outlined below. +watch set. There is no common inode structure across filesystem types (some may +not even have inodes), so we have to plumb inotify support through each specific +filesystem implementation. Some of the technical considerations are outlined +below. ### Tmpfs diff --git a/pkg/sentry/vfs/lock.go b/pkg/sentry/vfs/lock.go index 7c2b64755..cfe306aa3 100644 --- a/pkg/sentry/vfs/lock.go +++ b/pkg/sentry/vfs/lock.go @@ -23,7 +23,7 @@ import ( // FileLocks supports POSIX and BSD style locks, which correspond to fcntl(2) // and flock(2) respectively in Linux. It can be embedded into various file -// implementations for VFS2 that support locking. +// implementations that support locking. // // Note that in Linux these two types of locks are _not_ cooperative, because // race and deadlock conditions make merging them prohibitive. We do the same diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 38aaae3e1..6ce4fb8cb 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -966,8 +966,8 @@ func (l *Loader) executeAsync(args *control.ExecArgs) (kernel.ThreadID, error) { // Get the container MountNamespace from the Task. Try to acquire ref may fail // in case it raced with task exit. - // task.MountNamespaceVFS2() does not take a ref, so we must do so ourselves. - args.MountNamespace = tg.Leader().MountNamespaceVFS2() + // task.MountNamespace() does not take a ref, so we must do so ourselves. + args.MountNamespace = tg.Leader().MountNamespace() if args.MountNamespace == nil || !args.MountNamespace.TryIncRef() { return 0, fmt.Errorf("container %q has stopped", args.ContainerID) } diff --git a/runsc/boot/procfs/dump.go b/runsc/boot/procfs/dump.go index 9c002fc2c..81ea5e5b3 100644 --- a/runsc/boot/procfs/dump.go +++ b/runsc/boot/procfs/dump.go @@ -147,7 +147,7 @@ func getMetadataArray(ctx context.Context, pid kernel.ThreadID, mm *mm.MemoryMan } func getCWD(ctx context.Context, t *kernel.Task, pid kernel.ThreadID) string { - cwdDentry := t.FSContext().WorkingDirectoryVFS2() + cwdDentry := t.FSContext().WorkingDirectory() if !cwdDentry.Ok() { log.Warningf("No CWD dentry found for PID %s", pid) return "" @@ -185,7 +185,7 @@ func getFDs(ctx context.Context, t *kernel.Task, pid kernel.ThreadID) []FDInfo { fdNos := fdTable.GetFDs(ctx) fds = make([]fdInfo, 0, len(fdNos)) for _, fd := range fdNos { - file, _ := fdTable.GetVFS2(fd) + file, _ := fdTable.Get(fd) if file != nil { fds = append(fds, fdInfo{fd: file, no: fd}) } @@ -215,8 +215,8 @@ func getFDs(ctx context.Context, t *kernel.Task, pid kernel.ThreadID) []FDInfo { } func getRoot(t *kernel.Task, pid kernel.ThreadID) string { - realRoot := t.MountNamespaceVFS2().Root() - root := t.FSContext().RootDirectoryVFS2() + realRoot := t.MountNamespace().Root() + root := t.FSContext().RootDirectory() defer root.DecRef(t) path, err := t.Kernel().VFS().PathnameWithDeleted(t, realRoot, root) if err != nil { diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index 2897ec729..99a4e9132 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -357,7 +357,7 @@ func (c *containerMounter) getMountAccessType(conf *config.Config, mount *specs. } func (c *containerMounter) mountAll(conf *config.Config, procArgs *kernel.CreateProcessArgs) (*vfs.MountNamespace, error) { - log.Infof("Configuring container's file system with VFS2") + log.Infof("Configuring container's file system") // Create context with root credentials to mount the filesystem (the current // user may not be privileged enough). diff --git a/test/fuse/README.md b/test/fuse/README.md index 65add57e2..0831f422c 100644 --- a/test/fuse/README.md +++ b/test/fuse/README.md @@ -1,7 +1,7 @@ # gVisor FUSE Test Suite This is an integration test suite for fuse(4) filesystem. It runs under gVisor -sandbox container with VFS2 and FUSE function enabled. +sandbox container with FUSE function enabled. This document describes the framework of FUSE integration test, how to use it, and the guidelines that should be followed when adding new testing features.