mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user