mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support VFS2 save/restore.
Inode number consistency checks are now skipped in save/restore tests for reasons described in greatest detail in StatTest.StateDoesntChangeAfterRename. They pass in VFS1 due to the bug described in new test case SimpleStatTest.DifferentFilesHaveDifferentDeviceInodeNumberPairs. Fixes #1663 PiperOrigin-RevId: 338776148
This commit is contained in:
@@ -166,3 +166,27 @@ var bgContext = &logContext{Logger: log.Log()}
|
||||
func Background() Context {
|
||||
return bgContext
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
func WithValue(parent Context, key, val interface{}) Context {
|
||||
return &withValue{
|
||||
Context: parent,
|
||||
key: key,
|
||||
val: val,
|
||||
}
|
||||
}
|
||||
|
||||
type withValue struct {
|
||||
Context
|
||||
key interface{}
|
||||
val interface{}
|
||||
}
|
||||
|
||||
// Value implements Context.Value.
|
||||
func (ctx *withValue) Value(key interface{}) interface{} {
|
||||
if key == ctx.key {
|
||||
return ctx.val
|
||||
}
|
||||
return ctx.Context.Value(key)
|
||||
}
|
||||
|
||||
@@ -69,5 +69,5 @@ func (s *State) Save(o *SaveOpts, _ *struct{}) error {
|
||||
s.Kernel.Kill(kernel.ExitStatus{})
|
||||
},
|
||||
}
|
||||
return saveOpts.Save(s.Kernel, s.Watchdog)
|
||||
return saveOpts.Save(s.Kernel.SupervisorContext(), s.Kernel, s.Watchdog)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ const (
|
||||
)
|
||||
|
||||
// tunDevice implements vfs.Device for /dev/net/tun.
|
||||
//
|
||||
// +stateify savable
|
||||
type tunDevice struct{}
|
||||
|
||||
// Open implements vfs.Device.Open.
|
||||
@@ -53,6 +55,8 @@ func (tunDevice) Open(ctx context.Context, mnt *vfs.Mount, vfsd *vfs.Dentry, opt
|
||||
}
|
||||
|
||||
// tunFD implements vfs.FileDescriptionImpl for /dev/net/tun.
|
||||
//
|
||||
// +stateify savable
|
||||
type tunFD struct {
|
||||
vfsfd vfs.FileDescription
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
|
||||
@@ -70,6 +70,13 @@ func (f *HostFileMapper) Init() {
|
||||
f.mappings = make(map[uint64]mapping)
|
||||
}
|
||||
|
||||
// IsInited returns true if f.Init() has been called. This is used when
|
||||
// restoring a checkpoint that contains a HostFileMapper that may or may not
|
||||
// have been initialized.
|
||||
func (f *HostFileMapper) IsInited() bool {
|
||||
return f.refs != nil
|
||||
}
|
||||
|
||||
// NewHostFileMapper returns an initialized HostFileMapper allocated on the
|
||||
// heap with no references or cached mappings.
|
||||
func NewHostFileMapper() *HostFileMapper {
|
||||
|
||||
@@ -100,10 +100,10 @@ type lineDiscipline struct {
|
||||
column int
|
||||
|
||||
// masterWaiter is used to wait on the master end of the TTY.
|
||||
masterWaiter waiter.Queue `state:"zerovalue"`
|
||||
masterWaiter waiter.Queue
|
||||
|
||||
// replicaWaiter is used to wait on the replica end of the TTY.
|
||||
replicaWaiter waiter.Queue `state:"zerovalue"`
|
||||
replicaWaiter waiter.Queue
|
||||
}
|
||||
|
||||
func newLineDiscipline(termios linux.KernelTermios) *lineDiscipline {
|
||||
|
||||
@@ -4,7 +4,10 @@ licenses(["notice"])
|
||||
|
||||
go_library(
|
||||
name = "devtmpfs",
|
||||
srcs = ["devtmpfs.go"],
|
||||
srcs = [
|
||||
"devtmpfs.go",
|
||||
"save_restore.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package devtmpfs
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (fst *FilesystemType) afterLoad() {
|
||||
if fst.fs != nil {
|
||||
// Ensure that we don't create another filesystem.
|
||||
fst.initOnce.Do(func() {})
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ type EventFileDescription struct {
|
||||
|
||||
// queue is used to notify interested parties when the event object
|
||||
// becomes readable or writable.
|
||||
queue waiter.Queue `state:"zerovalue"`
|
||||
queue waiter.Queue
|
||||
|
||||
// mu protects the fields below.
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
|
||||
@@ -38,6 +38,7 @@ go_library(
|
||||
"host_named_pipe.go",
|
||||
"p9file.go",
|
||||
"regular_file.go",
|
||||
"save_restore.go",
|
||||
"socket.go",
|
||||
"special_file.go",
|
||||
"symlink.go",
|
||||
@@ -70,6 +71,7 @@ go_library(
|
||||
"//pkg/sentry/socket/unix/transport",
|
||||
"//pkg/sentry/usage",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/sync",
|
||||
"//pkg/syserr",
|
||||
"//pkg/syserror",
|
||||
"//pkg/unet",
|
||||
|
||||
@@ -16,7 +16,6 @@ package gofer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
@@ -26,6 +25,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/pipe"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
@@ -92,7 +92,7 @@ func (d *dentry) createSyntheticChildLocked(opts *createSyntheticOpts) {
|
||||
child := &dentry{
|
||||
refs: 1, // held by d
|
||||
fs: d.fs,
|
||||
ino: d.fs.nextSyntheticIno(),
|
||||
ino: d.fs.nextIno(),
|
||||
mode: uint32(opts.mode),
|
||||
uid: uint32(opts.kuid),
|
||||
gid: uint32(opts.kgid),
|
||||
@@ -235,7 +235,7 @@ func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
|
||||
}
|
||||
dirent := vfs.Dirent{
|
||||
Name: p9d.Name,
|
||||
Ino: uint64(inoFromPath(p9d.QID.Path)),
|
||||
Ino: d.fs.inoFromQIDPath(p9d.QID.Path),
|
||||
NextOff: int64(len(dirents) + 1),
|
||||
}
|
||||
// p9 does not expose 9P2000.U's DMDEVICE, DMNAMEDPIPE, or
|
||||
|
||||
@@ -35,7 +35,7 @@ import (
|
||||
|
||||
// Sync implements vfs.FilesystemImpl.Sync.
|
||||
func (fs *filesystem) Sync(ctx context.Context) error {
|
||||
// Snapshot current syncable dentries and special files.
|
||||
// Snapshot current syncable dentries and special file FDs.
|
||||
fs.syncMu.Lock()
|
||||
ds := make([]*dentry, 0, len(fs.syncableDentries))
|
||||
for d := range fs.syncableDentries {
|
||||
@@ -53,22 +53,28 @@ func (fs *filesystem) Sync(ctx context.Context) error {
|
||||
// regardless.
|
||||
var retErr error
|
||||
|
||||
// Sync regular files.
|
||||
// Sync syncable dentries.
|
||||
for _, d := range ds {
|
||||
err := d.syncCachedFile(ctx)
|
||||
err := d.syncCachedFile(ctx, true /* forFilesystemSync */)
|
||||
d.DecRef(ctx)
|
||||
if err != nil && retErr == nil {
|
||||
retErr = err
|
||||
if err != nil {
|
||||
ctx.Infof("gofer.filesystem.Sync: dentry.syncCachedFile failed: %v", err)
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync special files, which may be writable but do not use dentry shared
|
||||
// handles (so they won't be synced by the above).
|
||||
for _, sffd := range sffds {
|
||||
err := sffd.Sync(ctx)
|
||||
err := sffd.sync(ctx, true /* forFilesystemSync */)
|
||||
sffd.vfsfd.DecRef(ctx)
|
||||
if err != nil && retErr == nil {
|
||||
retErr = err
|
||||
if err != nil {
|
||||
ctx.Infof("gofer.filesystem.Sync: specialFileFD.sync failed: %v", err)
|
||||
if retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +235,7 @@ func (fs *filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.Vir
|
||||
return nil, err
|
||||
}
|
||||
if child != nil {
|
||||
if !file.isNil() && inoFromPath(qid.Path) == child.ino {
|
||||
if !file.isNil() && qid.Path == child.qidPath {
|
||||
// The file at this path hasn't changed. Just update cached metadata.
|
||||
file.close(ctx)
|
||||
child.updateFromP9AttrsLocked(attrMask, &attr)
|
||||
@@ -1512,7 +1518,6 @@ func (fs *filesystem) BoundEndpointAt(ctx context.Context, rp *vfs.ResolvingPath
|
||||
d.IncRef()
|
||||
return &endpoint{
|
||||
dentry: d,
|
||||
file: d.file.file,
|
||||
path: opts.Addr,
|
||||
}, nil
|
||||
}
|
||||
@@ -1591,7 +1596,3 @@ func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDe
|
||||
defer fs.renameMu.RUnlock()
|
||||
return genericPrependPath(vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
|
||||
}
|
||||
|
||||
func (fs *filesystem) nextSyntheticIno() inodeNumber {
|
||||
return inodeNumber(atomic.AddUint64(&fs.syntheticSeq, 1) | syntheticInoMask)
|
||||
}
|
||||
|
||||
+201
-132
@@ -26,6 +26,9 @@
|
||||
// *** "memmap.Mappable locks taken by Translate" below this point
|
||||
// dentry.handleMu
|
||||
// dentry.dataMu
|
||||
// filesystem.inoMu
|
||||
// specialFileFD.mu
|
||||
// specialFileFD.bufMu
|
||||
//
|
||||
// Locking dentry.dirMu in multiple dentries requires that either ancestor
|
||||
// dentries are locked before descendant dentries, or that filesystem.renameMu
|
||||
@@ -36,7 +39,6 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
@@ -53,6 +55,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/unet"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
@@ -81,7 +84,7 @@ type filesystem struct {
|
||||
iopts InternalFilesystemOptions
|
||||
|
||||
// client is the client used by this filesystem. client is immutable.
|
||||
client *p9.Client `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
client *p9.Client `state:"nosave"`
|
||||
|
||||
// clock is a realtime clock used to set timestamps in file operations.
|
||||
clock ktime.Clock
|
||||
@@ -89,6 +92,9 @@ type filesystem struct {
|
||||
// devMinor is the filesystem's minor device number. devMinor is immutable.
|
||||
devMinor uint32
|
||||
|
||||
// root is the root dentry. root is immutable.
|
||||
root *dentry
|
||||
|
||||
// renameMu serves two purposes:
|
||||
//
|
||||
// - It synchronizes path resolution with renaming initiated by this
|
||||
@@ -108,34 +114,26 @@ type filesystem struct {
|
||||
cachedDentries dentryList
|
||||
cachedDentriesLen uint64
|
||||
|
||||
// syncableDentries contains all dentries in this filesystem for which
|
||||
// !dentry.file.isNil(). specialFileFDs contains all open specialFileFDs.
|
||||
// These fields are protected by syncMu.
|
||||
// syncableDentries contains all non-synthetic dentries. specialFileFDs
|
||||
// contains all open specialFileFDs. These fields are protected by syncMu.
|
||||
syncMu sync.Mutex `state:"nosave"`
|
||||
syncableDentries map[*dentry]struct{}
|
||||
specialFileFDs map[*specialFileFD]struct{}
|
||||
|
||||
// syntheticSeq stores a counter to used to generate unique inodeNumber for
|
||||
// synthetic dentries.
|
||||
syntheticSeq uint64
|
||||
}
|
||||
// inoByQIDPath maps previously-observed QID.Paths to inode numbers
|
||||
// assigned to those paths. inoByQIDPath is not preserved across
|
||||
// checkpoint/restore because QIDs may be reused between different gofer
|
||||
// processes, so QIDs may be repeated for different files across
|
||||
// checkpoint/restore. inoByQIDPath is protected by inoMu.
|
||||
inoMu sync.Mutex `state:"nosave"`
|
||||
inoByQIDPath map[uint64]uint64 `state:"nosave"`
|
||||
|
||||
// inodeNumber represents inode number reported in Dirent.Ino. For regular
|
||||
// dentries, it comes from QID.Path from the 9P server. Synthetic dentries
|
||||
// have have their inodeNumber generated sequentially, with the MSB reserved to
|
||||
// prevent conflicts with regular dentries.
|
||||
//
|
||||
// +stateify savable
|
||||
type inodeNumber uint64
|
||||
// lastIno is the last inode number assigned to a file. lastIno is accessed
|
||||
// using atomic memory operations.
|
||||
lastIno uint64
|
||||
|
||||
// Reserve MSB for synthetic mounts.
|
||||
const syntheticInoMask = uint64(1) << 63
|
||||
|
||||
func inoFromPath(path uint64) inodeNumber {
|
||||
if path&syntheticInoMask != 0 {
|
||||
log.Warningf("Dropping MSB from ino, collision is possible. Original: %d, new: %d", path, path&^syntheticInoMask)
|
||||
}
|
||||
return inodeNumber(path &^ syntheticInoMask)
|
||||
// savedDentryRW records open read/write handles during save/restore.
|
||||
savedDentryRW map[*dentry]savedDentryRW
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
@@ -247,6 +245,10 @@ const (
|
||||
//
|
||||
// +stateify savable
|
||||
type InternalFilesystemOptions struct {
|
||||
// If UniqueID is non-empty, it is an opaque string used to reassociate the
|
||||
// filesystem with a new server FD during restoration from checkpoint.
|
||||
UniqueID string
|
||||
|
||||
// If LeakConnection is true, do not close the connection to the server
|
||||
// when the Filesystem is released. This is necessary for deployments in
|
||||
// which servers can handle only a single client and report failure if that
|
||||
@@ -286,46 +288,11 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
mopts := vfs.GenericParseMountOptions(opts.Data)
|
||||
var fsopts filesystemOptions
|
||||
|
||||
// Check that the transport is "fd".
|
||||
trans, ok := mopts["trans"]
|
||||
if !ok {
|
||||
ctx.Warningf("gofer.FilesystemType.GetFilesystem: transport must be specified as 'trans=fd'")
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
delete(mopts, "trans")
|
||||
if trans != "fd" {
|
||||
ctx.Warningf("gofer.FilesystemType.GetFilesystem: unsupported transport: trans=%s", trans)
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
// Check that read and write FDs are provided and identical.
|
||||
rfdstr, ok := mopts["rfdno"]
|
||||
if !ok {
|
||||
ctx.Warningf("gofer.FilesystemType.GetFilesystem: read FD must be specified as 'rfdno=<file descriptor>")
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
delete(mopts, "rfdno")
|
||||
rfd, err := strconv.Atoi(rfdstr)
|
||||
fd, err := getFDFromMountOptionsMap(ctx, mopts)
|
||||
if err != nil {
|
||||
ctx.Warningf("gofer.FilesystemType.GetFilesystem: invalid read FD: rfdno=%s", rfdstr)
|
||||
return nil, nil, syserror.EINVAL
|
||||
return nil, nil, err
|
||||
}
|
||||
wfdstr, ok := mopts["wfdno"]
|
||||
if !ok {
|
||||
ctx.Warningf("gofer.FilesystemType.GetFilesystem: write FD must be specified as 'wfdno=<file descriptor>")
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
delete(mopts, "wfdno")
|
||||
wfd, err := strconv.Atoi(wfdstr)
|
||||
if err != nil {
|
||||
ctx.Warningf("gofer.FilesystemType.GetFilesystem: invalid write FD: wfdno=%s", wfdstr)
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
if rfd != wfd {
|
||||
ctx.Warningf("gofer.FilesystemType.GetFilesystem: read FD (%d) and write FD (%d) must be equal", rfd, wfd)
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
fsopts.fd = rfd
|
||||
fsopts.fd = fd
|
||||
|
||||
// Get the attach name.
|
||||
fsopts.aname = "/"
|
||||
@@ -441,57 +408,44 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
}
|
||||
// If !ok, iopts being the zero value is correct.
|
||||
|
||||
// Establish a connection with the server.
|
||||
conn, err := unet.NewSocket(fsopts.fd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Perform version negotiation with the server.
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
client, err := p9.NewClient(conn, fsopts.msize, fsopts.version)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
// Ownership of conn has been transferred to client.
|
||||
|
||||
// Perform attach to obtain the filesystem root.
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
attached, err := client.Attach(fsopts.aname)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
attachFile := p9file{attached}
|
||||
qid, attrMask, attr, err := attachFile.getAttr(ctx, dentryAttrMask())
|
||||
if err != nil {
|
||||
attachFile.close(ctx)
|
||||
client.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Construct the filesystem object.
|
||||
devMinor, err := vfsObj.GetAnonBlockDevMinor()
|
||||
if err != nil {
|
||||
attachFile.close(ctx)
|
||||
client.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
fs := &filesystem{
|
||||
mfp: mfp,
|
||||
opts: fsopts,
|
||||
iopts: iopts,
|
||||
client: client,
|
||||
clock: ktime.RealtimeClockFromContext(ctx),
|
||||
devMinor: devMinor,
|
||||
syncableDentries: make(map[*dentry]struct{}),
|
||||
specialFileFDs: make(map[*specialFileFD]struct{}),
|
||||
inoByQIDPath: make(map[uint64]uint64),
|
||||
}
|
||||
fs.vfsfs.Init(vfsObj, &fstype, fs)
|
||||
|
||||
// Connect to the server.
|
||||
if err := fs.dial(ctx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Perform attach to obtain the filesystem root.
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
attached, err := fs.client.Attach(fsopts.aname)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
fs.vfsfs.DecRef(ctx)
|
||||
return nil, nil, err
|
||||
}
|
||||
attachFile := p9file{attached}
|
||||
qid, attrMask, attr, err := attachFile.getAttr(ctx, dentryAttrMask())
|
||||
if err != nil {
|
||||
attachFile.close(ctx)
|
||||
fs.vfsfs.DecRef(ctx)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Construct the root dentry.
|
||||
root, err := fs.newDentry(ctx, attachFile, qid, attrMask, &attr)
|
||||
if err != nil {
|
||||
@@ -504,10 +458,72 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
// being "cached" and subsequently evicted. Its resources will still be
|
||||
// cleaned up by fs.Release().
|
||||
root.refs = 2
|
||||
fs.root = root
|
||||
|
||||
return &fs.vfsfs, &root.vfsd, nil
|
||||
}
|
||||
|
||||
func getFDFromMountOptionsMap(ctx context.Context, mopts map[string]string) (int, error) {
|
||||
// Check that the transport is "fd".
|
||||
trans, ok := mopts["trans"]
|
||||
if !ok || trans != "fd" {
|
||||
ctx.Warningf("gofer.getFDFromMountOptionsMap: transport must be specified as 'trans=fd'")
|
||||
return -1, syserror.EINVAL
|
||||
}
|
||||
delete(mopts, "trans")
|
||||
|
||||
// Check that read and write FDs are provided and identical.
|
||||
rfdstr, ok := mopts["rfdno"]
|
||||
if !ok {
|
||||
ctx.Warningf("gofer.getFDFromMountOptionsMap: read FD must be specified as 'rfdno=<file descriptor>'")
|
||||
return -1, syserror.EINVAL
|
||||
}
|
||||
delete(mopts, "rfdno")
|
||||
rfd, err := strconv.Atoi(rfdstr)
|
||||
if err != nil {
|
||||
ctx.Warningf("gofer.getFDFromMountOptionsMap: invalid read FD: rfdno=%s", rfdstr)
|
||||
return -1, syserror.EINVAL
|
||||
}
|
||||
wfdstr, ok := mopts["wfdno"]
|
||||
if !ok {
|
||||
ctx.Warningf("gofer.getFDFromMountOptionsMap: write FD must be specified as 'wfdno=<file descriptor>'")
|
||||
return -1, syserror.EINVAL
|
||||
}
|
||||
delete(mopts, "wfdno")
|
||||
wfd, err := strconv.Atoi(wfdstr)
|
||||
if err != nil {
|
||||
ctx.Warningf("gofer.getFDFromMountOptionsMap: invalid write FD: wfdno=%s", wfdstr)
|
||||
return -1, syserror.EINVAL
|
||||
}
|
||||
if rfd != wfd {
|
||||
ctx.Warningf("gofer.getFDFromMountOptionsMap: read FD (%d) and write FD (%d) must be equal", rfd, wfd)
|
||||
return -1, syserror.EINVAL
|
||||
}
|
||||
return rfd, nil
|
||||
}
|
||||
|
||||
// Preconditions: fs.client == nil.
|
||||
func (fs *filesystem) dial(ctx context.Context) error {
|
||||
// Establish a connection with the server.
|
||||
conn, err := unet.NewSocket(fs.opts.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform version negotiation with the server.
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
client, err := p9.NewClient(conn, fs.opts.msize, fs.opts.version)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
// Ownership of conn has been transferred to client.
|
||||
|
||||
fs.client = client
|
||||
return nil
|
||||
}
|
||||
|
||||
// Release implements vfs.FilesystemImpl.Release.
|
||||
func (fs *filesystem) Release(ctx context.Context) {
|
||||
mf := fs.mfp.MemoryFile()
|
||||
@@ -574,12 +590,15 @@ type dentry struct {
|
||||
// filesystem.renameMu.
|
||||
name string
|
||||
|
||||
// qidPath is the p9.QID.Path for this file. qidPath is immutable.
|
||||
qidPath uint64
|
||||
|
||||
// file is the unopened p9.File that backs this dentry. file is immutable.
|
||||
//
|
||||
// If file.isNil(), this dentry represents a synthetic file, i.e. a file
|
||||
// that does not exist on the remote filesystem. As of this writing, the
|
||||
// only files that can be synthetic are sockets, pipes, and directories.
|
||||
file p9file `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
file p9file `state:"nosave"`
|
||||
|
||||
// If deleted is non-zero, the file represented by this dentry has been
|
||||
// deleted. deleted is accessed using atomic memory operations.
|
||||
@@ -623,12 +642,12 @@ type dentry struct {
|
||||
// To mutate:
|
||||
// - Lock metadataMu and use atomic operations to update because we might
|
||||
// have atomic readers that don't hold the lock.
|
||||
metadataMu sync.Mutex `state:"nosave"`
|
||||
ino inodeNumber // immutable
|
||||
mode uint32 // type is immutable, perms are mutable
|
||||
uid uint32 // auth.KUID, but stored as raw uint32 for sync/atomic
|
||||
gid uint32 // auth.KGID, but ...
|
||||
blockSize uint32 // 0 if unknown
|
||||
metadataMu sync.Mutex `state:"nosave"`
|
||||
ino uint64 // immutable
|
||||
mode uint32 // type is immutable, perms are mutable
|
||||
uid uint32 // auth.KUID, but stored as raw uint32 for sync/atomic
|
||||
gid uint32 // auth.KGID, but ...
|
||||
blockSize uint32 // 0 if unknown
|
||||
// Timestamps, all nsecs from the Unix epoch.
|
||||
atime int64
|
||||
mtime int64
|
||||
@@ -679,9 +698,9 @@ type dentry struct {
|
||||
// (isNil() == false), it may be mutated with handleMu locked, but cannot
|
||||
// be closed until the dentry is destroyed.
|
||||
handleMu sync.RWMutex `state:"nosave"`
|
||||
readFile p9file `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
writeFile p9file `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
hostFD int32
|
||||
readFile p9file `state:"nosave"`
|
||||
writeFile p9file `state:"nosave"`
|
||||
hostFD int32 `state:"nosave"`
|
||||
|
||||
dataMu sync.RWMutex `state:"nosave"`
|
||||
|
||||
@@ -758,8 +777,9 @@ func (fs *filesystem) newDentry(ctx context.Context, file p9file, qid p9.QID, ma
|
||||
|
||||
d := &dentry{
|
||||
fs: fs,
|
||||
qidPath: qid.Path,
|
||||
file: file,
|
||||
ino: inoFromPath(qid.Path),
|
||||
ino: fs.inoFromQIDPath(qid.Path),
|
||||
mode: uint32(attr.Mode),
|
||||
uid: uint32(fs.opts.dfltuid),
|
||||
gid: uint32(fs.opts.dfltgid),
|
||||
@@ -802,6 +822,21 @@ func (fs *filesystem) newDentry(ctx context.Context, file p9file, qid p9.QID, ma
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (fs *filesystem) inoFromQIDPath(qidPath uint64) uint64 {
|
||||
fs.inoMu.Lock()
|
||||
defer fs.inoMu.Unlock()
|
||||
if ino, ok := fs.inoByQIDPath[qidPath]; ok {
|
||||
return ino
|
||||
}
|
||||
ino := fs.nextIno()
|
||||
fs.inoByQIDPath[qidPath] = ino
|
||||
return ino
|
||||
}
|
||||
|
||||
func (fs *filesystem) nextIno() uint64 {
|
||||
return atomic.AddUint64(&fs.lastIno, 1)
|
||||
}
|
||||
|
||||
func (d *dentry) isSynthetic() bool {
|
||||
return d.file.isNil()
|
||||
}
|
||||
@@ -853,7 +888,7 @@ func (d *dentry) updateFromP9AttrsLocked(mask p9.AttrMask, attr *p9.Attr) {
|
||||
}
|
||||
}
|
||||
|
||||
// Preconditions: !d.isSynthetic()
|
||||
// Preconditions: !d.isSynthetic().
|
||||
func (d *dentry) updateFromGetattr(ctx context.Context) error {
|
||||
// Use d.readFile or d.writeFile, which represent 9P fids that have been
|
||||
// opened, in preference to d.file, which represents a 9P fid that has not.
|
||||
@@ -1269,33 +1304,40 @@ func (d *dentry) checkCachingLocked(ctx context.Context) {
|
||||
d.fs.cachedDentriesLen++
|
||||
d.cached = true
|
||||
if d.fs.cachedDentriesLen > d.fs.opts.maxCachedDentries {
|
||||
victim := d.fs.cachedDentries.Back()
|
||||
d.fs.cachedDentries.Remove(victim)
|
||||
d.fs.cachedDentriesLen--
|
||||
victim.cached = false
|
||||
// victim.refs may have become non-zero from an earlier path resolution
|
||||
// since it was inserted into fs.cachedDentries.
|
||||
if atomic.LoadInt64(&victim.refs) == 0 {
|
||||
if victim.parent != nil {
|
||||
victim.parent.dirMu.Lock()
|
||||
if !victim.vfsd.IsDead() {
|
||||
// Note that victim can't be a mount point (in any mount
|
||||
// namespace), since VFS holds references on mount points.
|
||||
d.fs.vfsfs.VirtualFilesystem().InvalidateDentry(ctx, &victim.vfsd)
|
||||
delete(victim.parent.children, victim.name)
|
||||
// We're only deleting the dentry, not the file it
|
||||
// represents, so we don't need to update
|
||||
// victimParent.dirents etc.
|
||||
}
|
||||
victim.parent.dirMu.Unlock()
|
||||
}
|
||||
victim.destroyLocked(ctx)
|
||||
}
|
||||
d.fs.evictCachedDentryLocked(ctx)
|
||||
// Whether or not victim was destroyed, we brought fs.cachedDentriesLen
|
||||
// back down to fs.opts.maxCachedDentries, so we don't loop.
|
||||
}
|
||||
}
|
||||
|
||||
// Preconditions:
|
||||
// * fs.renameMu must be locked for writing; it may be temporarily unlocked.
|
||||
// * fs.cachedDentriesLen != 0.
|
||||
func (fs *filesystem) evictCachedDentryLocked(ctx context.Context) {
|
||||
victim := fs.cachedDentries.Back()
|
||||
fs.cachedDentries.Remove(victim)
|
||||
fs.cachedDentriesLen--
|
||||
victim.cached = false
|
||||
// victim.refs may have become non-zero from an earlier path resolution
|
||||
// since it was inserted into fs.cachedDentries.
|
||||
if atomic.LoadInt64(&victim.refs) == 0 {
|
||||
if victim.parent != nil {
|
||||
victim.parent.dirMu.Lock()
|
||||
if !victim.vfsd.IsDead() {
|
||||
// Note that victim can't be a mount point (in any mount
|
||||
// namespace), since VFS holds references on mount points.
|
||||
fs.vfsfs.VirtualFilesystem().InvalidateDentry(ctx, &victim.vfsd)
|
||||
delete(victim.parent.children, victim.name)
|
||||
// We're only deleting the dentry, not the file it
|
||||
// represents, so we don't need to update
|
||||
// victimParent.dirents etc.
|
||||
}
|
||||
victim.parent.dirMu.Unlock()
|
||||
}
|
||||
victim.destroyLocked(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// destroyLocked destroys the dentry.
|
||||
//
|
||||
// Preconditions:
|
||||
@@ -1623,6 +1665,33 @@ func (d *dentry) syncRemoteFileLocked(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dentry) syncCachedFile(ctx context.Context, forFilesystemSync bool) error {
|
||||
d.handleMu.RLock()
|
||||
defer d.handleMu.RUnlock()
|
||||
h := d.writeHandleLocked()
|
||||
if h.isOpen() {
|
||||
// Write back dirty pages to the remote file.
|
||||
d.dataMu.Lock()
|
||||
err := fsutil.SyncDirtyAll(ctx, &d.cache, &d.dirty, d.size, d.fs.mfp.MemoryFile(), h.writeFromBlocksAt)
|
||||
d.dataMu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.syncRemoteFileLocked(ctx); err != nil {
|
||||
if !forFilesystemSync {
|
||||
return err
|
||||
}
|
||||
// Only return err if we can reasonably have expected sync to succeed
|
||||
// (d is a regular file and was opened for writing).
|
||||
if d.isRegularFile() && h.isOpen() {
|
||||
return err
|
||||
}
|
||||
ctx.Debugf("gofer.dentry.syncCachedFile: syncing non-writable or non-regular-file dentry failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// incLinks increments link count.
|
||||
func (d *dentry) incLinks() {
|
||||
if atomic.LoadUint32(&d.nlink) == 0 {
|
||||
@@ -1650,7 +1719,7 @@ type fileDescription struct {
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.LockFD
|
||||
|
||||
lockLogging sync.Once `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
lockLogging sync.Once `state:"nosave"`
|
||||
}
|
||||
|
||||
func (fd *fileDescription) filesystem() *filesystem {
|
||||
|
||||
@@ -26,12 +26,13 @@ import (
|
||||
func TestDestroyIdempotent(t *testing.T) {
|
||||
ctx := contexttest.Context(t)
|
||||
fs := filesystem{
|
||||
mfp: pgalloc.MemoryFileProviderFromContext(ctx),
|
||||
syncableDentries: make(map[*dentry]struct{}),
|
||||
mfp: pgalloc.MemoryFileProviderFromContext(ctx),
|
||||
opts: filesystemOptions{
|
||||
// Test relies on no dentry being held in the cache.
|
||||
maxCachedDentries: 0,
|
||||
},
|
||||
syncableDentries: make(map[*dentry]struct{}),
|
||||
inoByQIDPath: make(map[uint64]uint64),
|
||||
}
|
||||
|
||||
attr := &p9.Attr{
|
||||
|
||||
@@ -51,8 +51,24 @@ func blockUntilNonblockingPipeHasWriter(ctx context.Context, fd int32) error {
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
if err := sleepBetweenNamedPipeOpenChecks(ctx); err != nil {
|
||||
return err
|
||||
if sleepErr := sleepBetweenNamedPipeOpenChecks(ctx); sleepErr != nil {
|
||||
// Another application thread may have opened this pipe for
|
||||
// writing, succeeded because we previously opened the pipe for
|
||||
// reading, and subsequently interrupted us for checkpointing (e.g.
|
||||
// this occurs in mknod tests under cooperative save/restore). In
|
||||
// this case, our open has to succeed for the checkpoint to include
|
||||
// a readable FD for the pipe, which is in turn necessary to
|
||||
// restore the other thread's writable FD for the same pipe
|
||||
// (otherwise it will get ENXIO). So we have to check
|
||||
// nonblockingPipeHasWriter() once last time.
|
||||
ok, err := nonblockingPipeHasWriter(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
return sleepErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
@@ -31,6 +30,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usage"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
@@ -624,23 +624,7 @@ func regularFileSeekLocked(ctx context.Context, d *dentry, fdOffset, offset int6
|
||||
|
||||
// Sync implements vfs.FileDescriptionImpl.Sync.
|
||||
func (fd *regularFileFD) Sync(ctx context.Context) error {
|
||||
return fd.dentry().syncCachedFile(ctx)
|
||||
}
|
||||
|
||||
func (d *dentry) syncCachedFile(ctx context.Context) error {
|
||||
d.handleMu.RLock()
|
||||
defer d.handleMu.RUnlock()
|
||||
|
||||
if h := d.writeHandleLocked(); h.isOpen() {
|
||||
d.dataMu.Lock()
|
||||
// Write dirty cached data to the remote file.
|
||||
err := fsutil.SyncDirtyAll(ctx, &d.cache, &d.dirty, d.size, d.fs.mfp.MemoryFile(), h.writeFromBlocksAt)
|
||||
d.dataMu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return d.syncRemoteFileLocked(ctx)
|
||||
return fd.dentry().syncCachedFile(ctx, false /* lowSyncExpectations */)
|
||||
}
|
||||
|
||||
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
|
||||
@@ -913,7 +897,7 @@ type dentryPlatformFile struct {
|
||||
hostFileMapper fsutil.HostFileMapper
|
||||
|
||||
// hostFileMapperInitOnce is used to lazily initialize hostFileMapper.
|
||||
hostFileMapperInitOnce sync.Once `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
hostFileMapperInitOnce sync.Once `state:"nosave"`
|
||||
}
|
||||
|
||||
// IncRef implements memmap.File.IncRef.
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gofer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/fdnotifier"
|
||||
"gvisor.dev/gvisor/pkg/p9"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
type saveRestoreContextID int
|
||||
|
||||
const (
|
||||
// CtxRestoreServerFDMap is a Context.Value key for a map[string]int
|
||||
// mapping filesystem unique IDs (cf. InternalFilesystemOptions.UniqueID)
|
||||
// to host FDs.
|
||||
CtxRestoreServerFDMap saveRestoreContextID = iota
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type savedDentryRW struct {
|
||||
read bool
|
||||
write bool
|
||||
}
|
||||
|
||||
// PreprareSave implements vfs.FilesystemImplSaveRestoreExtension.PrepareSave.
|
||||
func (fs *filesystem) PrepareSave(ctx context.Context) error {
|
||||
if len(fs.iopts.UniqueID) == 0 {
|
||||
return fmt.Errorf("gofer.filesystem with no UniqueID cannot be saved")
|
||||
}
|
||||
|
||||
// Purge cached dentries, which may not be reopenable after restore due to
|
||||
// permission changes.
|
||||
fs.renameMu.Lock()
|
||||
for fs.cachedDentriesLen != 0 {
|
||||
fs.evictCachedDentryLocked(ctx)
|
||||
}
|
||||
fs.renameMu.Unlock()
|
||||
|
||||
// Buffer pipe data so that it's available for reading after restore. (This
|
||||
// is a legacy VFS1 feature.)
|
||||
fs.syncMu.Lock()
|
||||
for sffd := range fs.specialFileFDs {
|
||||
if sffd.dentry().fileType() == linux.S_IFIFO && sffd.vfsfd.IsReadable() {
|
||||
if err := sffd.savePipeData(ctx); err != nil {
|
||||
fs.syncMu.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.syncMu.Unlock()
|
||||
|
||||
// Flush local state to the remote filesystem.
|
||||
if err := fs.Sync(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fs.savedDentryRW = make(map[*dentry]savedDentryRW)
|
||||
return fs.root.prepareSaveRecursive(ctx)
|
||||
}
|
||||
|
||||
// Preconditions:
|
||||
// * fd represents a pipe.
|
||||
// * fd is readable.
|
||||
func (fd *specialFileFD) savePipeData(ctx context.Context) error {
|
||||
fd.bufMu.Lock()
|
||||
defer fd.bufMu.Unlock()
|
||||
var buf [usermem.PageSize]byte
|
||||
for {
|
||||
n, err := fd.handle.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf[:])), ^uint64(0))
|
||||
if n != 0 {
|
||||
fd.buf = append(fd.buf, buf[:n]...)
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF || err == syserror.EAGAIN {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(fd.buf) != 0 {
|
||||
atomic.StoreUint32(&fd.haveBuf, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dentry) prepareSaveRecursive(ctx context.Context) error {
|
||||
if d.isRegularFile() && !d.cachedMetadataAuthoritative() {
|
||||
// Get updated metadata for d in case we need to perform metadata
|
||||
// validation during restore.
|
||||
if err := d.updateFromGetattr(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !d.readFile.isNil() || !d.writeFile.isNil() {
|
||||
d.fs.savedDentryRW[d] = savedDentryRW{
|
||||
read: !d.readFile.isNil(),
|
||||
write: !d.writeFile.isNil(),
|
||||
}
|
||||
}
|
||||
d.dirMu.Lock()
|
||||
defer d.dirMu.Unlock()
|
||||
for _, child := range d.children {
|
||||
if child != nil {
|
||||
if err := child.prepareSaveRecursive(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// beforeSave is invoked by stateify.
|
||||
func (d *dentry) beforeSave() {
|
||||
if d.vfsd.IsDead() {
|
||||
panic(fmt.Sprintf("gofer.dentry(%q).beforeSave: deleted and invalidated dentries can't be restored", genericDebugPathname(d)))
|
||||
}
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (d *dentry) afterLoad() {
|
||||
d.hostFD = -1
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (d *dentryPlatformFile) afterLoad() {
|
||||
if d.hostFileMapper.IsInited() {
|
||||
// Ensure that we don't call d.hostFileMapper.Init() again.
|
||||
d.hostFileMapperInitOnce.Do(func() {})
|
||||
}
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (fd *specialFileFD) afterLoad() {
|
||||
fd.handle.fd = -1
|
||||
}
|
||||
|
||||
// CompleteRestore implements
|
||||
// vfs.FilesystemImplSaveRestoreExtension.CompleteRestore.
|
||||
func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRestoreOptions) error {
|
||||
fdmapv := ctx.Value(CtxRestoreServerFDMap)
|
||||
if fdmapv == nil {
|
||||
return fmt.Errorf("no server FD map available")
|
||||
}
|
||||
fdmap := fdmapv.(map[string]int)
|
||||
fd, ok := fdmap[fs.iopts.UniqueID]
|
||||
if !ok {
|
||||
return fmt.Errorf("no server FD available for filesystem with unique ID %q", fs.iopts.UniqueID)
|
||||
}
|
||||
fs.opts.fd = fd
|
||||
if err := fs.dial(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
fs.inoByQIDPath = make(map[uint64]uint64)
|
||||
|
||||
// Restore the filesystem root.
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
attached, err := fs.client.Attach(fs.opts.aname)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attachFile := p9file{attached}
|
||||
qid, attrMask, attr, err := attachFile.getAttr(ctx, dentryAttrMask())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.root.restoreFile(ctx, attachFile, qid, attrMask, &attr, &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore remaining dentries.
|
||||
if err := fs.root.restoreDescendantsRecursive(ctx, &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Re-open handles for specialFileFDs. Unlike the initial open
|
||||
// (dentry.openSpecialFile()), pipes are always opened without blocking;
|
||||
// non-readable pipe FDs are opened last to ensure that they don't get
|
||||
// ENXIO if another specialFileFD represents the read end of the same pipe.
|
||||
// This is consistent with VFS1.
|
||||
haveWriteOnlyPipes := false
|
||||
for fd := range fs.specialFileFDs {
|
||||
if fd.dentry().fileType() == linux.S_IFIFO && !fd.vfsfd.IsReadable() {
|
||||
haveWriteOnlyPipes = true
|
||||
continue
|
||||
}
|
||||
if err := fd.completeRestore(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if haveWriteOnlyPipes {
|
||||
for fd := range fs.specialFileFDs {
|
||||
if fd.dentry().fileType() == linux.S_IFIFO && !fd.vfsfd.IsReadable() {
|
||||
if err := fd.completeRestore(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discard state only required during restore.
|
||||
fs.savedDentryRW = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dentry) restoreFile(ctx context.Context, file p9file, qid p9.QID, attrMask p9.AttrMask, attr *p9.Attr, opts *vfs.CompleteRestoreOptions) error {
|
||||
d.file = file
|
||||
|
||||
// Gofers do not preserve QID across checkpoint/restore, so:
|
||||
//
|
||||
// - We must assume that the remote filesystem did not change in a way that
|
||||
// would invalidate dentries, since we can't revalidate dentries by
|
||||
// checking QIDs.
|
||||
//
|
||||
// - We need to associate the new QID.Path with the existing d.ino.
|
||||
d.qidPath = qid.Path
|
||||
d.fs.inoMu.Lock()
|
||||
d.fs.inoByQIDPath[qid.Path] = d.ino
|
||||
d.fs.inoMu.Unlock()
|
||||
|
||||
// Check metadata stability before updating metadata.
|
||||
d.metadataMu.Lock()
|
||||
defer d.metadataMu.Unlock()
|
||||
if d.isRegularFile() {
|
||||
if opts.ValidateFileSizes {
|
||||
if !attrMask.Size {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(d))
|
||||
}
|
||||
if d.size != attr.Size {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d), d.size, attr.Size)
|
||||
}
|
||||
}
|
||||
if opts.ValidateFileModificationTimestamps {
|
||||
if !attrMask.MTime {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(d))
|
||||
}
|
||||
if want := dentryTimestampFromP9(attr.MTimeSeconds, attr.MTimeNanoSeconds); d.mtime != want {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d), linux.NsecToStatxTimestamp(d.mtime), linux.NsecToStatxTimestamp(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
if !d.cachedMetadataAuthoritative() {
|
||||
d.updateFromP9AttrsLocked(attrMask, attr)
|
||||
}
|
||||
|
||||
if rw, ok := d.fs.savedDentryRW[d]; ok {
|
||||
if err := d.ensureSharedHandle(ctx, rw.read, rw.write, false /* trunc */); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preconditions: d is not synthetic.
|
||||
func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
|
||||
for _, child := range d.children {
|
||||
if child == nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := d.fs.syncableDentries[child]; !ok {
|
||||
// child is synthetic.
|
||||
continue
|
||||
}
|
||||
if err := child.restoreRecursive(ctx, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preconditions: d is not synthetic (but note that since this function
|
||||
// restores d.file, d.file.isNil() is always true at this point, so this can
|
||||
// only be detected by checking filesystem.syncableDentries). d.parent has been
|
||||
// restored.
|
||||
func (d *dentry) restoreRecursive(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
|
||||
qid, file, attrMask, attr, err := d.parent.file.walkGetAttrOne(ctx, d.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.restoreFile(ctx, file, qid, attrMask, &attr, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.restoreDescendantsRecursive(ctx, opts)
|
||||
}
|
||||
|
||||
func (fd *specialFileFD) completeRestore(ctx context.Context) error {
|
||||
d := fd.dentry()
|
||||
h, err := openHandle(ctx, d.file, fd.vfsfd.IsReadable(), fd.vfsfd.IsWritable(), false /* trunc */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.handle = h
|
||||
|
||||
ftype := d.fileType()
|
||||
fd.haveQueue = (ftype == linux.S_IFIFO || ftype == linux.S_IFSOCK) && fd.handle.fd >= 0
|
||||
if fd.haveQueue {
|
||||
if err := fdnotifier.AddFD(fd.handle.fd, &fd.queue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -42,9 +42,6 @@ type endpoint struct {
|
||||
// dentry is the filesystem dentry which produced this endpoint.
|
||||
dentry *dentry
|
||||
|
||||
// file is the p9 file that contains a single unopened fid.
|
||||
file p9.File `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
|
||||
// path is the sentry path where this endpoint is bound.
|
||||
path string
|
||||
}
|
||||
@@ -116,7 +113,7 @@ func (e *endpoint) UnidirectionalConnect(ctx context.Context) (transport.Connect
|
||||
}
|
||||
|
||||
func (e *endpoint) newConnectedEndpoint(ctx context.Context, flags p9.ConnectFlags, queue *waiter.Queue) (*host.SCMConnectedEndpoint, *syserr.Error) {
|
||||
hostFile, err := e.file.Connect(flags)
|
||||
hostFile, err := e.dentry.file.connect(ctx, flags)
|
||||
if err != nil {
|
||||
return nil, syserr.ErrConnectionRefused
|
||||
}
|
||||
@@ -131,7 +128,7 @@ func (e *endpoint) newConnectedEndpoint(ctx context.Context, flags p9.ConnectFla
|
||||
|
||||
c, serr := host.NewSCMEndpoint(ctx, hostFD, queue, e.path)
|
||||
if serr != nil {
|
||||
log.Warningf("Gofer returned invalid host socket for BidirectionalConnect; file %+v flags %+v: %v", e.file, flags, serr)
|
||||
log.Warningf("Gofer returned invalid host socket for BidirectionalConnect; file %+v flags %+v: %v", e.dentry.file, flags, serr)
|
||||
return nil, serr
|
||||
}
|
||||
return c, nil
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package gofer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
@@ -25,6 +24,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/p9"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
@@ -40,7 +40,7 @@ type specialFileFD struct {
|
||||
fileDescription
|
||||
|
||||
// handle is used for file I/O. handle is immutable.
|
||||
handle handle `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
|
||||
handle handle `state:"nosave"`
|
||||
|
||||
// isRegularFile is true if this FD represents a regular file which is only
|
||||
// possible when filesystemOptions.regularFilesUseSpecialFileFD is in
|
||||
@@ -54,12 +54,20 @@ type specialFileFD struct {
|
||||
|
||||
// haveQueue is true if this file description represents a file for which
|
||||
// queue may send I/O readiness events. haveQueue is immutable.
|
||||
haveQueue bool
|
||||
haveQueue bool `state:"nosave"`
|
||||
queue waiter.Queue
|
||||
|
||||
// If seekable is true, off is the file offset. off is protected by mu.
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
off int64
|
||||
|
||||
// If haveBuf is non-zero, this FD represents a pipe, and buf contains data
|
||||
// read from the pipe from previous calls to specialFileFD.savePipeData().
|
||||
// haveBuf and buf are protected by bufMu. haveBuf is accessed using atomic
|
||||
// memory operations.
|
||||
bufMu sync.Mutex `state:"nosave"`
|
||||
haveBuf uint32
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newSpecialFileFD(h handle, mnt *vfs.Mount, d *dentry, locks *vfs.FileLocks, flags uint32) (*specialFileFD, error) {
|
||||
@@ -87,6 +95,9 @@ func newSpecialFileFD(h handle, mnt *vfs.Mount, d *dentry, locks *vfs.FileLocks,
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
d.fs.syncMu.Lock()
|
||||
d.fs.specialFileFDs[fd] = struct{}{}
|
||||
d.fs.syncMu.Unlock()
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
@@ -161,26 +172,51 @@ func (fd *specialFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offs
|
||||
return 0, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
// Going through dst.CopyOutFrom() holds MM locks around file operations of
|
||||
// unknown duration. For regularFileFD, doing so is necessary to support
|
||||
// mmap due to lock ordering; MM locks precede dentry.dataMu. That doesn't
|
||||
// hold here since specialFileFD doesn't client-cache data. Just buffer the
|
||||
// read instead.
|
||||
if d := fd.dentry(); d.cachedMetadataAuthoritative() {
|
||||
d.touchAtime(fd.vfsfd.Mount())
|
||||
}
|
||||
|
||||
bufN := int64(0)
|
||||
if atomic.LoadUint32(&fd.haveBuf) != 0 {
|
||||
var err error
|
||||
fd.bufMu.Lock()
|
||||
if len(fd.buf) != 0 {
|
||||
var n int
|
||||
n, err = dst.CopyOut(ctx, fd.buf)
|
||||
dst = dst.DropFirst(n)
|
||||
fd.buf = fd.buf[n:]
|
||||
if len(fd.buf) == 0 {
|
||||
atomic.StoreUint32(&fd.haveBuf, 0)
|
||||
fd.buf = nil
|
||||
}
|
||||
bufN = int64(n)
|
||||
if offset >= 0 {
|
||||
offset += bufN
|
||||
}
|
||||
}
|
||||
fd.bufMu.Unlock()
|
||||
if err != nil {
|
||||
return bufN, err
|
||||
}
|
||||
}
|
||||
|
||||
// Going through dst.CopyOutFrom() would hold MM locks around file
|
||||
// operations of unknown duration. For regularFileFD, doing so is necessary
|
||||
// to support mmap due to lock ordering; MM locks precede dentry.dataMu.
|
||||
// That doesn't hold here since specialFileFD doesn't client-cache data.
|
||||
// Just buffer the read instead.
|
||||
buf := make([]byte, dst.NumBytes())
|
||||
n, err := fd.handle.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)), uint64(offset))
|
||||
if err == syserror.EAGAIN {
|
||||
err = syserror.ErrWouldBlock
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, err
|
||||
return bufN, err
|
||||
}
|
||||
if cp, cperr := dst.CopyOut(ctx, buf[:n]); cperr != nil {
|
||||
return int64(cp), cperr
|
||||
return bufN + int64(cp), cperr
|
||||
}
|
||||
return int64(n), err
|
||||
return bufN + int64(n), err
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
@@ -217,16 +253,16 @@ func (fd *specialFileFD) pwrite(ctx context.Context, src usermem.IOSequence, off
|
||||
}
|
||||
|
||||
d := fd.dentry()
|
||||
// If the regular file fd was opened with O_APPEND, make sure the file size
|
||||
// is updated. There is a possible race here if size is modified externally
|
||||
// after metadata cache is updated.
|
||||
if fd.isRegularFile && fd.vfsfd.StatusFlags()&linux.O_APPEND != 0 && !d.cachedMetadataAuthoritative() {
|
||||
if err := d.updateFromGetattr(ctx); err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
}
|
||||
|
||||
if fd.isRegularFile {
|
||||
// If the regular file fd was opened with O_APPEND, make sure the file
|
||||
// size is updated. There is a possible race here if size is modified
|
||||
// externally after metadata cache is updated.
|
||||
if fd.vfsfd.StatusFlags()&linux.O_APPEND != 0 && !d.cachedMetadataAuthoritative() {
|
||||
if err := d.updateFromGetattr(ctx); err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
}
|
||||
|
||||
// We need to hold the metadataMu *while* writing to a regular file.
|
||||
d.metadataMu.Lock()
|
||||
defer d.metadataMu.Unlock()
|
||||
@@ -306,13 +342,31 @@ func (fd *specialFileFD) Seek(ctx context.Context, offset int64, whence int32) (
|
||||
|
||||
// Sync implements vfs.FileDescriptionImpl.Sync.
|
||||
func (fd *specialFileFD) Sync(ctx context.Context) error {
|
||||
// If we have a host FD, fsyncing it is likely to be faster than an fsync
|
||||
// RPC.
|
||||
if fd.handle.fd >= 0 {
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := syscall.Fsync(int(fd.handle.fd))
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
return fd.sync(ctx, false /* forFilesystemSync */)
|
||||
}
|
||||
|
||||
func (fd *specialFileFD) sync(ctx context.Context, forFilesystemSync bool) error {
|
||||
err := func() error {
|
||||
// If we have a host FD, fsyncing it is likely to be faster than an fsync
|
||||
// RPC.
|
||||
if fd.handle.fd >= 0 {
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := syscall.Fsync(int(fd.handle.fd))
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
return fd.handle.file.fsync(ctx)
|
||||
}()
|
||||
if err != nil {
|
||||
if !forFilesystemSync {
|
||||
return err
|
||||
}
|
||||
// Only return err if we can reasonably have expected sync to succeed
|
||||
// (fd represents a regular file that was opened for writing).
|
||||
if fd.isRegularFile && fd.vfsfd.IsWritable() {
|
||||
return err
|
||||
}
|
||||
ctx.Debugf("gofer.specialFileFD.sync: syncing non-writable or non-regular-file FD failed: %v", err)
|
||||
}
|
||||
return fd.handle.file.fsync(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ go_library(
|
||||
"inode_refs.go",
|
||||
"ioctl_unsafe.go",
|
||||
"mmap.go",
|
||||
"save_restore.go",
|
||||
"socket.go",
|
||||
"socket_iovec.go",
|
||||
"socket_unsafe.go",
|
||||
|
||||
@@ -79,7 +79,7 @@ func fdsToFiles(ctx context.Context, fds []int) []*vfs.FileDescription {
|
||||
}
|
||||
|
||||
// Create the file backed by hostFD.
|
||||
file, err := ImportFD(ctx, kernel.KernelFromContext(ctx).HostMount(), fd, false /* isTTY */)
|
||||
file, err := NewFD(ctx, kernel.KernelFromContext(ctx).HostMount(), fd, &NewFDOptions{})
|
||||
if err != nil {
|
||||
ctx.Warningf("Error creating file from host FD: %v", err)
|
||||
break
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user