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
This commit is contained in:
Ayush Ranjan
2022-11-21 13:57:52 -08:00
committed by gVisor bot
parent 38a0512f13
commit 1fa3c06f1e
176 changed files with 1587 additions and 17130 deletions
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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)
-1
View File
@@ -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 {
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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
}
}
-125
View File
@@ -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",
)
-229
View File
@@ -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.
File diff suppressed because it is too large Load Diff
-138
View File
@@ -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)
}
}
-442
View File
@@ -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
}
-234
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
-175
View File
@@ -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())
}
}
-56
View File
@@ -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()
}
-77
View File
@@ -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)
}
}
File diff suppressed because it is too large Load Diff
-177
View File
@@ -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)
}
File diff suppressed because it is too large Load Diff
-34
View File
@@ -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))
}
}
}
-160
View File
@@ -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
}

Some files were not shown because too many files have changed in this diff Show More