gvisor/pkg/sentry/fsimpl/gofer/p9file.go

220 lines
7.2 KiB
Go
Raw Normal View History

VFS2 gofer client Updates #1198 Opening host pipes (by spinning in fdpipe) and host sockets is not yet complete, and will be done in a future CL. Major differences from VFS1 gofer client (sentry/fs/gofer), with varying levels of backportability: - "Cache policies" are replaced by InteropMode, which control the behavior of timestamps in addition to caching. Under InteropModeExclusive (analogous to cacheAll) and InteropModeWritethrough (analogous to cacheAllWritethrough), client timestamps are *not* written back to the server (it is not possible in 9P or Linux for clients to set ctime, so writing back client-authoritative timestamps results in incoherence between atime/mtime and ctime). Under InteropModeShared (analogous to cacheRemoteRevalidating), client timestamps are not used at all (remote filesystem clocks are authoritative). cacheNone is translated to InteropModeShared + new option filesystemOptions.specialRegularFiles. - Under InteropModeShared, "unstable attribute" reloading for permission checks, lookup, and revalidation are fused, which is feasible in VFS2 since gofer.filesystem controls path resolution. This results in a ~33% reduction in RPCs for filesystem operations compared to cacheRemoteRevalidating. For example, consider stat("/foo/bar/baz") where "/foo/bar/baz" fails revalidation, resulting in the instantiation of a new dentry: VFS1 RPCs: getattr("/") // fs.MountNamespace.FindLink() => fs.Inode.CheckPermission() => gofer.inodeOperations.check() => gofer.inodeOperations.UnstableAttr() walkgetattr("/", "foo") = fid1 // fs.Dirent.walk() => gofer.session.Revalidate() => gofer.cachePolicy.Revalidate() clunk(fid1) getattr("/foo") // CheckPermission walkgetattr("/foo", "bar") = fid2 // Revalidate clunk(fid2) getattr("/foo/bar") // CheckPermission walkgetattr("/foo/bar", "baz") = fid3 // Revalidate clunk(fid3) walkgetattr("/foo/bar", "baz") = fid4 // fs.Dirent.walk() => gofer.inodeOperations.Lookup getattr("/foo/bar/baz") // linux.stat() => gofer.inodeOperations.UnstableAttr() VFS2 RPCs: getattr("/") // gofer.filesystem.walkExistingLocked() walkgetattr("/", "foo") = fid1 // gofer.filesystem.stepExistingLocked() clunk(fid1) // No getattr: walkgetattr already updated metadata for permission check walkgetattr("/foo", "bar") = fid2 clunk(fid2) walkgetattr("/foo/bar", "baz") = fid3 // No clunk: fid3 used for new gofer.dentry // No getattr: walkgetattr already updated metadata for stat() - gofer.filesystem.unlinkAt() does not require instantiation of a dentry that represents the file to be deleted. Updates #898. - gofer.regularFileFD.OnClose() skips Tflushf for regular files under InteropModeExclusive, as it's nonsensical to request a remote file flush without flushing locally-buffered writes to that remote file first. - Symlink targets are cached when InteropModeShared is not in effect. - p9.QID.Path (which is already required to be unique for each file within a server, and is accordingly already synthesized from device/inode numbers in all known gofers) is used as-is for inode numbers, rather than being mapped along with attr.RDev in the client to yet another synthetic inode number. - Relevant parts of fsutil.CachingInodeOperations are inlined directly into gofer package code. This avoids having to duplicate part of its functionality in fsutil.HostMappable. PiperOrigin-RevId: 293190213
2020-02-04 19:28:36 +00:00
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gofer
import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/syserror"
)
// p9file is a wrapper around p9.File that provides methods that are
// Context-aware.
type p9file struct {
file p9.File
}
func (f p9file) isNil() bool {
return f.file == nil
}
func (f p9file) walk(ctx context.Context, names []string) ([]p9.QID, p9file, error) {
ctx.UninterruptibleSleepStart(false)
qids, newfile, err := f.file.Walk(names)
ctx.UninterruptibleSleepFinish(false)
return qids, p9file{newfile}, err
}
func (f p9file) walkGetAttr(ctx context.Context, names []string) ([]p9.QID, p9file, p9.AttrMask, p9.Attr, error) {
ctx.UninterruptibleSleepStart(false)
qids, newfile, attrMask, attr, err := f.file.WalkGetAttr(names)
ctx.UninterruptibleSleepFinish(false)
return qids, p9file{newfile}, attrMask, attr, err
}
// walkGetAttrOne is a wrapper around p9.File.WalkGetAttr that takes a single
// path component and returns a single qid.
func (f p9file) walkGetAttrOne(ctx context.Context, name string) (p9.QID, p9file, p9.AttrMask, p9.Attr, error) {
ctx.UninterruptibleSleepStart(false)
qids, newfile, attrMask, attr, err := f.file.WalkGetAttr([]string{name})
ctx.UninterruptibleSleepFinish(false)
if err != nil {
return p9.QID{}, p9file{}, p9.AttrMask{}, p9.Attr{}, err
}
if len(qids) != 1 {
ctx.Warningf("p9.File.WalkGetAttr returned %d qids (%v), wanted 1", len(qids), qids)
if newfile != nil {
p9file{newfile}.close(ctx)
}
return p9.QID{}, p9file{}, p9.AttrMask{}, p9.Attr{}, syserror.EIO
}
return qids[0], p9file{newfile}, attrMask, attr, nil
}
func (f p9file) statFS(ctx context.Context) (p9.FSStat, error) {
ctx.UninterruptibleSleepStart(false)
fsstat, err := f.file.StatFS()
ctx.UninterruptibleSleepFinish(false)
return fsstat, err
}
func (f p9file) getAttr(ctx context.Context, req p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) {
ctx.UninterruptibleSleepStart(false)
qid, attrMask, attr, err := f.file.GetAttr(req)
ctx.UninterruptibleSleepFinish(false)
return qid, attrMask, attr, err
}
func (f p9file) setAttr(ctx context.Context, valid p9.SetAttrMask, attr p9.SetAttr) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.SetAttr(valid, attr)
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) getXattr(ctx context.Context, name string, size uint64) (string, error) {
ctx.UninterruptibleSleepStart(false)
val, err := f.file.GetXattr(name, size)
ctx.UninterruptibleSleepFinish(false)
return val, err
}
func (f p9file) setXattr(ctx context.Context, name, value string, flags uint32) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.SetXattr(name, value, flags)
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) allocate(ctx context.Context, mode p9.AllocateMode, offset, length uint64) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.Allocate(mode, offset, length)
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) close(ctx context.Context) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.Close()
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) open(ctx context.Context, flags p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) {
ctx.UninterruptibleSleepStart(false)
fdobj, qid, iounit, err := f.file.Open(flags)
ctx.UninterruptibleSleepFinish(false)
return fdobj, qid, iounit, err
}
func (f p9file) readAt(ctx context.Context, p []byte, offset uint64) (int, error) {
ctx.UninterruptibleSleepStart(false)
n, err := f.file.ReadAt(p, offset)
ctx.UninterruptibleSleepFinish(false)
return n, err
}
func (f p9file) writeAt(ctx context.Context, p []byte, offset uint64) (int, error) {
ctx.UninterruptibleSleepStart(false)
n, err := f.file.WriteAt(p, offset)
ctx.UninterruptibleSleepFinish(false)
return n, err
}
func (f p9file) fsync(ctx context.Context) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.FSync()
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) create(ctx context.Context, name string, flags p9.OpenFlags, permissions p9.FileMode, uid p9.UID, gid p9.GID) (*fd.FD, p9file, p9.QID, uint32, error) {
ctx.UninterruptibleSleepStart(false)
fdobj, newfile, qid, iounit, err := f.file.Create(name, flags, permissions, uid, gid)
ctx.UninterruptibleSleepFinish(false)
return fdobj, p9file{newfile}, qid, iounit, err
}
func (f p9file) mkdir(ctx context.Context, name string, permissions p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, error) {
ctx.UninterruptibleSleepStart(false)
qid, err := f.file.Mkdir(name, permissions, uid, gid)
ctx.UninterruptibleSleepFinish(false)
return qid, err
}
func (f p9file) symlink(ctx context.Context, oldName string, newName string, uid p9.UID, gid p9.GID) (p9.QID, error) {
ctx.UninterruptibleSleepStart(false)
qid, err := f.file.Symlink(oldName, newName, uid, gid)
ctx.UninterruptibleSleepFinish(false)
return qid, err
}
func (f p9file) link(ctx context.Context, target p9file, newName string) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.Link(target.file, newName)
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) mknod(ctx context.Context, name string, mode p9.FileMode, major uint32, minor uint32, uid p9.UID, gid p9.GID) (p9.QID, error) {
ctx.UninterruptibleSleepStart(false)
qid, err := f.file.Mknod(name, mode, major, minor, uid, gid)
ctx.UninterruptibleSleepFinish(false)
return qid, err
}
func (f p9file) rename(ctx context.Context, newDir p9file, newName string) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.Rename(newDir.file, newName)
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) unlinkAt(ctx context.Context, name string, flags uint32) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.UnlinkAt(name, flags)
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) readdir(ctx context.Context, offset uint64, count uint32) ([]p9.Dirent, error) {
ctx.UninterruptibleSleepStart(false)
dirents, err := f.file.Readdir(offset, count)
ctx.UninterruptibleSleepFinish(false)
return dirents, err
}
func (f p9file) readlink(ctx context.Context) (string, error) {
ctx.UninterruptibleSleepStart(false)
target, err := f.file.Readlink()
ctx.UninterruptibleSleepFinish(false)
return target, err
}
func (f p9file) flush(ctx context.Context) error {
ctx.UninterruptibleSleepStart(false)
err := f.file.Flush()
ctx.UninterruptibleSleepFinish(false)
return err
}
func (f p9file) connect(ctx context.Context, flags p9.ConnectFlags) (*fd.FD, error) {
ctx.UninterruptibleSleepStart(false)
fdobj, err := f.file.Connect(flags)
ctx.UninterruptibleSleepFinish(false)
return fdobj, err
}