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
This commit is contained in:
Jamie Liu
2020-02-04 11:29:22 -08:00
committed by gVisor bot
parent d7cd484091
commit 492229d017
18 changed files with 4103 additions and 29 deletions
+17
View File
@@ -18,6 +18,7 @@ import (
"bytes"
"fmt"
"reflect"
"syscall"
"unsafe"
)
@@ -297,3 +298,19 @@ func ZeroSeq(dsts BlockSeq) (uint64, error) {
}
return done, nil
}
// IovecsFromBlockSeq returns a []syscall.Iovec representing seq.
func IovecsFromBlockSeq(bs BlockSeq) []syscall.Iovec {
iovs := make([]syscall.Iovec, 0, bs.NumBlocks())
for ; !bs.IsEmpty(); bs = bs.Tail() {
b := bs.Head()
iovs = append(iovs, syscall.Iovec{
Base: &b.ToSlice()[0],
Len: uint64(b.Len()),
})
// We don't need to care about b.NeedSafecopy(), because the host
// kernel will handle such address ranges just fine (by returning
// EFAULT).
}
return iovs
}
+2 -2
View File
@@ -28,13 +28,13 @@ go_template_instance(
"platform": "gvisor.dev/gvisor/pkg/sentry/platform",
},
package = "fsutil",
prefix = "frameRef",
prefix = "FrameRef",
template = "//pkg/segment:generic_set",
types = {
"Key": "uint64",
"Range": "platform.FileRange",
"Value": "uint64",
"Functions": "frameRefSetFunctions",
"Functions": "FrameRefSetFunctions",
},
)
+7 -6
View File
@@ -20,24 +20,25 @@ import (
"gvisor.dev/gvisor/pkg/sentry/platform"
)
type frameRefSetFunctions struct{}
// FrameRefSetFunctions implements segment.Functions for FrameRefSet.
type FrameRefSetFunctions struct{}
// MinKey implements segment.Functions.MinKey.
func (frameRefSetFunctions) MinKey() uint64 {
func (FrameRefSetFunctions) MinKey() uint64 {
return 0
}
// MaxKey implements segment.Functions.MaxKey.
func (frameRefSetFunctions) MaxKey() uint64 {
func (FrameRefSetFunctions) MaxKey() uint64 {
return math.MaxUint64
}
// ClearValue implements segment.Functions.ClearValue.
func (frameRefSetFunctions) ClearValue(val *uint64) {
func (FrameRefSetFunctions) ClearValue(val *uint64) {
}
// Merge implements segment.Functions.Merge.
func (frameRefSetFunctions) Merge(_ platform.FileRange, val1 uint64, _ platform.FileRange, val2 uint64) (uint64, bool) {
func (FrameRefSetFunctions) Merge(_ platform.FileRange, val1 uint64, _ platform.FileRange, val2 uint64) (uint64, bool) {
if val1 != val2 {
return 0, false
}
@@ -45,6 +46,6 @@ func (frameRefSetFunctions) Merge(_ platform.FileRange, val1 uint64, _ platform.
}
// Split implements segment.Functions.Split.
func (frameRefSetFunctions) Split(_ platform.FileRange, val uint64, _ uint64) (uint64, uint64) {
func (FrameRefSetFunctions) Split(_ platform.FileRange, val uint64, _ uint64) (uint64, uint64) {
return val, val
}
+1 -1
View File
@@ -111,7 +111,7 @@ type CachingInodeOperations struct {
// refs tracks active references to data in the cache.
//
// refs is protected by dataMu.
refs frameRefSet
refs FrameRefSet
}
// CachingInodeOperationsOptions configures a CachingInodeOperations.
+55
View File
@@ -0,0 +1,55 @@
load("//tools:defs.bzl", "go_library")
load("//tools/go_generics:defs.bzl", "go_template_instance")
licenses(["notice"])
go_template_instance(
name = "dentry_list",
out = "dentry_list.go",
package = "gofer",
prefix = "dentry",
template = "//pkg/ilist:generic_list",
types = {
"Element": "*dentry",
"Linker": "*dentry",
},
)
go_library(
name = "gofer",
srcs = [
"dentry_list.go",
"directory.go",
"filesystem.go",
"gofer.go",
"handle.go",
"handle_unsafe.go",
"p9file.go",
"pagemath.go",
"regular_file.go",
"special_file.go",
"symlink.go",
"time.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/fd",
"//pkg/fspath",
"//pkg/log",
"//pkg/p9",
"//pkg/safemem",
"//pkg/sentry/fs/fsutil",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/time",
"//pkg/sentry/memmap",
"//pkg/sentry/pgalloc",
"//pkg/sentry/platform",
"//pkg/sentry/usage",
"//pkg/sentry/vfs",
"//pkg/syserror",
"//pkg/unet",
"//pkg/usermem",
],
)
+190
View File
@@ -0,0 +1,190 @@
// 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 (
"sync"
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
func (d *dentry) isDir() bool {
return d.fileType() == linux.S_IFDIR
}
// Preconditions: d.dirMu must be locked. d.isDir(). fs.opts.interop !=
// InteropModeShared.
func (d *dentry) cacheNegativeChildLocked(name string) {
if d.negativeChildren == nil {
d.negativeChildren = make(map[string]struct{})
}
d.negativeChildren[name] = struct{}{}
}
type directoryFD struct {
fileDescription
vfs.DirectoryFileDescriptionDefaultImpl
mu sync.Mutex
off int64
dirents []vfs.Dirent
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *directoryFD) Release() {
}
// IterDirents implements vfs.FileDescriptionImpl.IterDirents.
func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
fd.mu.Lock()
defer fd.mu.Unlock()
if fd.dirents == nil {
ds, err := fd.dentry().getDirents(ctx)
if err != nil {
return err
}
fd.dirents = ds
}
for fd.off < int64(len(fd.dirents)) {
if !cb.Handle(fd.dirents[fd.off]) {
return nil
}
fd.off++
}
return nil
}
// Preconditions: d.isDir(). There exists at least one directoryFD representing d.
func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
// 9P2000.L's readdir does not specify behavior in the presence of
// concurrent mutation of an iterated directory, so implementations may
// duplicate or omit entries in this case, which violates POSIX semantics.
// Thus we read all directory entries while holding d.dirMu to exclude
// directory mutations. (Note that it is impossible for the client to
// exclude concurrent mutation from other remote filesystem users. Since
// there is no way to detect if the server has incorrectly omitted
// directory entries, we simply assume that the server is well-behaved
// under InteropModeShared.) This is inconsistent with Linux (which appears
// to assume that directory fids have the correct semantics, and translates
// struct file_operations::readdir calls directly to readdir RPCs), but is
// consistent with VFS1.
d.fs.renameMu.RLock()
defer d.fs.renameMu.RUnlock()
d.dirMu.Lock()
defer d.dirMu.Unlock()
if d.dirents != nil {
return d.dirents, nil
}
// It's not clear if 9P2000.L's readdir is expected to return "." and "..",
// so we generate them here.
parent := d.vfsd.ParentOrSelf().Impl().(*dentry)
dirents := []vfs.Dirent{
{
Name: ".",
Type: linux.DT_DIR,
Ino: d.ino,
NextOff: 1,
},
{
Name: "..",
Type: uint8(atomic.LoadUint32(&parent.mode) >> 12),
Ino: parent.ino,
NextOff: 2,
},
}
off := uint64(0)
const count = 64 * 1024 // for consistency with the vfs1 client
d.handleMu.RLock()
defer d.handleMu.RUnlock()
if !d.handleReadable {
// This should not be possible because a readable handle should have
// been opened when the calling directoryFD was opened.
panic("gofer.dentry.getDirents called without a readable handle")
}
for {
p9ds, err := d.handle.file.readdir(ctx, off, count)
if err != nil {
return nil, err
}
if len(p9ds) == 0 {
// Cache dirents for future directoryFDs if permitted.
if d.fs.opts.interop != InteropModeShared {
d.dirents = dirents
}
return dirents, nil
}
for _, p9d := range p9ds {
if p9d.Name == "." || p9d.Name == ".." {
continue
}
dirent := vfs.Dirent{
Name: p9d.Name,
Ino: p9d.QID.Path,
NextOff: int64(len(dirents) + 1),
}
// p9 does not expose 9P2000.U's DMDEVICE, DMNAMEDPIPE, or
// DMSOCKET.
switch p9d.Type {
case p9.TypeSymlink:
dirent.Type = linux.DT_LNK
case p9.TypeDir:
dirent.Type = linux.DT_DIR
default:
dirent.Type = linux.DT_REG
}
dirents = append(dirents, dirent)
}
off = p9ds[len(p9ds)-1].Offset
}
}
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *directoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
fd.mu.Lock()
defer fd.mu.Unlock()
switch whence {
case linux.SEEK_SET:
if offset < 0 {
return 0, syserror.EINVAL
}
if offset == 0 {
// Ensure that the next call to fd.IterDirents() calls
// fd.dentry().getDirents().
fd.dirents = nil
}
fd.off = offset
return fd.off, nil
case linux.SEEK_CUR:
offset += fd.off
if offset < 0 {
return 0, syserror.EINVAL
}
// Don't clear fd.dirents in this case, even if offset == 0.
fd.off = offset
return fd.off, nil
default:
return 0, syserror.EINVAL
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+135
View File
@@ -0,0 +1,135 @@
// 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 (
"syscall"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/safemem"
)
// handle represents a remote "open file descriptor", consisting of an opened
// fid (p9.File) and optionally a host file descriptor.
type handle struct {
file p9file
fd int32 // -1 if unavailable
}
// Preconditions: read || write.
func openHandle(ctx context.Context, file p9file, read, write, trunc bool) (handle, error) {
_, newfile, err := file.walk(ctx, nil)
if err != nil {
return handle{fd: -1}, err
}
var flags p9.OpenFlags
switch {
case read && !write:
flags = p9.ReadOnly
case !read && write:
flags = p9.WriteOnly
case read && write:
flags = p9.ReadWrite
}
if trunc {
flags |= p9.OpenTruncate
}
fdobj, _, _, err := newfile.open(ctx, flags)
if err != nil {
newfile.close(ctx)
return handle{fd: -1}, err
}
fd := int32(-1)
if fdobj != nil {
fd = int32(fdobj.Release())
}
return handle{
file: newfile,
fd: fd,
}, nil
}
func (h *handle) close(ctx context.Context) {
h.file.close(ctx)
h.file = p9file{}
if h.fd >= 0 {
syscall.Close(int(h.fd))
h.fd = -1
}
}
func (h *handle) readToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
if dsts.IsEmpty() {
return 0, nil
}
if h.fd >= 0 {
ctx.UninterruptibleSleepStart(false)
n, err := hostPreadv(h.fd, dsts, int64(offset))
ctx.UninterruptibleSleepFinish(false)
return n, err
}
if dsts.NumBlocks() == 1 && !dsts.Head().NeedSafecopy() {
n, err := h.file.readAt(ctx, dsts.Head().ToSlice(), offset)
return uint64(n), err
}
// Buffer the read since p9.File.ReadAt() takes []byte.
buf := make([]byte, dsts.NumBytes())
n, err := h.file.readAt(ctx, buf, offset)
if n == 0 {
return 0, err
}
if cp, cperr := safemem.CopySeq(dsts, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf[:n]))); cperr != nil {
return cp, cperr
}
return uint64(n), err
}
func (h *handle) writeFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
if srcs.IsEmpty() {
return 0, nil
}
if h.fd >= 0 {
ctx.UninterruptibleSleepStart(false)
n, err := hostPwritev(h.fd, srcs, int64(offset))
ctx.UninterruptibleSleepFinish(false)
return n, err
}
if srcs.NumBlocks() == 1 && !srcs.Head().NeedSafecopy() {
n, err := h.file.writeAt(ctx, srcs.Head().ToSlice(), offset)
return uint64(n), err
}
// Buffer the write since p9.File.WriteAt() takes []byte.
buf := make([]byte, srcs.NumBytes())
cp, cperr := safemem.CopySeq(safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)), srcs)
if cp == 0 {
return 0, cperr
}
n, err := h.file.writeAt(ctx, buf[:cp], offset)
if err != nil {
return uint64(n), err
}
return cp, cperr
}
func (h *handle) sync(ctx context.Context) error {
if h.fd >= 0 {
ctx.UninterruptibleSleepStart(false)
err := syscall.Fsync(int(h.fd))
ctx.UninterruptibleSleepFinish(false)
return err
}
return h.file.fsync(ctx)
}
+66
View File
@@ -0,0 +1,66 @@
// 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 (
"syscall"
"unsafe"
"gvisor.dev/gvisor/pkg/safemem"
)
// Preconditions: !dsts.IsEmpty().
func hostPreadv(fd int32, dsts safemem.BlockSeq, off int64) (uint64, error) {
// No buffering is necessary regardless of safecopy; host syscalls will
// return EFAULT if appropriate, instead of raising SIGBUS.
if dsts.NumBlocks() == 1 {
// Use pread() instead of preadv() to avoid iovec allocation and
// copying.
dst := dsts.Head()
n, _, e := syscall.Syscall6(syscall.SYS_PREAD64, uintptr(fd), dst.Addr(), uintptr(dst.Len()), uintptr(off), 0, 0)
if e != 0 {
return 0, e
}
return uint64(n), nil
}
iovs := safemem.IovecsFromBlockSeq(dsts)
n, _, e := syscall.Syscall6(syscall.SYS_PREADV, uintptr(fd), uintptr((unsafe.Pointer)(&iovs[0])), uintptr(len(iovs)), uintptr(off), 0, 0)
if e != 0 {
return 0, e
}
return uint64(n), nil
}
// Preconditions: !srcs.IsEmpty().
func hostPwritev(fd int32, srcs safemem.BlockSeq, off int64) (uint64, error) {
// No buffering is necessary regardless of safecopy; host syscalls will
// return EFAULT if appropriate, instead of raising SIGBUS.
if srcs.NumBlocks() == 1 {
// Use pwrite() instead of pwritev() to avoid iovec allocation and
// copying.
src := srcs.Head()
n, _, e := syscall.Syscall6(syscall.SYS_PWRITE64, uintptr(fd), src.Addr(), uintptr(src.Len()), uintptr(off), 0, 0)
if e != 0 {
return 0, e
}
return uint64(n), nil
}
iovs := safemem.IovecsFromBlockSeq(srcs)
n, _, e := syscall.Syscall6(syscall.SYS_PWRITEV, uintptr(fd), uintptr((unsafe.Pointer)(&iovs[0])), uintptr(len(iovs)), uintptr(off), 0, 0)
if e != 0 {
return 0, e
}
return uint64(n), nil
}
+219
View File
@@ -0,0 +1,219 @@
// 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
}
+31
View File
@@ -0,0 +1,31 @@
// 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/usermem"
)
// This are equivalent to usermem.Addr.RoundDown/Up, but without the
// potentially truncating conversion to usermem.Addr. This is necessary because
// there is no way to define generic "PageRoundDown/Up" functions in Go.
func pageRoundDown(x uint64) uint64 {
return x &^ (usermem.PageSize - 1)
}
func pageRoundUp(x uint64) uint64 {
return pageRoundDown(x + usermem.PageSize - 1)
}
File diff suppressed because it is too large Load Diff
+159
View File
@@ -0,0 +1,159 @@
// 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 (
"sync"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/safemem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
)
// specialFileFD implements vfs.FileDescriptionImpl for files other than
// regular files, directories, and symlinks: pipes, sockets, etc. It is also
// used for regular files when filesystemOptions.specialRegularFiles is in
// effect. specialFileFD differs from regularFileFD by using per-FD handles
// instead of shared per-dentry handles, and never buffering I/O.
type specialFileFD struct {
fileDescription
// handle is immutable.
handle handle
// off is the file offset. off is protected by mu. (POSIX 2.9.7 only
// requires operations using the file offset to be atomic for regular files
// and symlinks; however, since specialFileFD may be used for regular
// files, we apply this atomicity unconditionally.)
mu sync.Mutex
off int64
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *specialFileFD) Release() {
fd.handle.close(context.Background())
fs := fd.vfsfd.Mount().Filesystem().Impl().(*filesystem)
fs.syncMu.Lock()
delete(fs.specialFileFDs, fd)
fs.syncMu.Unlock()
}
// OnClose implements vfs.FileDescriptionImpl.OnClose.
func (fd *specialFileFD) OnClose(ctx context.Context) error {
if !fd.vfsfd.IsWritable() {
return nil
}
return fd.handle.file.flush(ctx)
}
// PRead implements vfs.FileDescriptionImpl.PRead.
func (fd *specialFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
if offset < 0 {
return 0, syserror.EINVAL
}
if opts.Flags != 0 {
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.fs.opts.interop != InteropModeShared {
d.touchAtime(ctx, fd.vfsfd.Mount())
}
buf := make([]byte, dst.NumBytes())
n, err := fd.handle.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)), uint64(offset))
if n == 0 {
return 0, err
}
if cp, cperr := dst.CopyOut(ctx, buf[:n]); cperr != nil {
return int64(cp), cperr
}
return int64(n), err
}
// Read implements vfs.FileDescriptionImpl.Read.
func (fd *specialFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
fd.mu.Lock()
n, err := fd.PRead(ctx, dst, fd.off, opts)
fd.off += n
fd.mu.Unlock()
return n, err
}
// PWrite implements vfs.FileDescriptionImpl.PWrite.
func (fd *specialFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
if offset < 0 {
return 0, syserror.EINVAL
}
if opts.Flags != 0 {
return 0, syserror.EOPNOTSUPP
}
// Do a buffered write. See rationale in PRead.
if d := fd.dentry(); d.fs.opts.interop != InteropModeShared {
d.touchCMtime(ctx)
}
buf := make([]byte, src.NumBytes())
// Don't do partial writes if we get a partial read from src.
if _, err := src.CopyIn(ctx, buf); err != nil {
return 0, err
}
n, err := fd.handle.writeFromBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)), uint64(offset))
return int64(n), err
}
// Write implements vfs.FileDescriptionImpl.Write.
func (fd *specialFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
fd.mu.Lock()
n, err := fd.PWrite(ctx, src, fd.off, opts)
fd.off += n
fd.mu.Unlock()
return n, err
}
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *specialFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
fd.mu.Lock()
defer fd.mu.Unlock()
switch whence {
case linux.SEEK_SET:
// Use offset as given.
case linux.SEEK_CUR:
offset += fd.off
default:
// SEEK_END, SEEK_DATA, and SEEK_HOLE aren't supported since it's not
// clear that file size is even meaningful for these files.
return 0, syserror.EINVAL
}
if offset < 0 {
return 0, syserror.EINVAL
}
fd.off = offset
return offset, nil
}
// Sync implements vfs.FileDescriptionImpl.Sync.
func (fd *specialFileFD) Sync(ctx context.Context) error {
if !fd.vfsfd.IsWritable() {
return nil
}
return fd.handle.sync(ctx)
}
+47
View File
@@ -0,0 +1,47 @@
// 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/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
func (d *dentry) isSymlink() bool {
return d.fileType() == linux.S_IFLNK
}
// Precondition: d.isSymlink().
func (d *dentry) readlink(ctx context.Context, mnt *vfs.Mount) (string, error) {
if d.fs.opts.interop != InteropModeShared {
d.touchAtime(ctx, mnt)
d.dataMu.Lock()
if d.haveTarget {
target := d.target
d.dataMu.Unlock()
return target, nil
}
}
target, err := d.file.readlink(ctx)
if d.fs.opts.interop != InteropModeShared {
if err == nil {
d.haveTarget = true
d.target = target
}
d.dataMu.Unlock()
}
return target, err
}
+75
View File
@@ -0,0 +1,75 @@
// 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 (
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
func dentryTimestampFromP9(s, ns uint64) int64 {
return int64(s*1e9 + ns)
}
func dentryTimestampFromStatx(ts linux.StatxTimestamp) int64 {
return ts.Sec*1e9 + int64(ts.Nsec)
}
func statxTimestampFromDentry(ns int64) linux.StatxTimestamp {
return linux.StatxTimestamp{
Sec: ns / 1e9,
Nsec: uint32(ns % 1e9),
}
}
func nowFromContext(ctx context.Context) (int64, bool) {
if clock := ktime.RealtimeClockFromContext(ctx); clock != nil {
return clock.Now().Nanoseconds(), true
}
return 0, false
}
// Preconditions: fs.interop != InteropModeShared.
func (d *dentry) touchAtime(ctx context.Context, mnt *vfs.Mount) {
if err := mnt.CheckBeginWrite(); err != nil {
return
}
now, ok := nowFromContext(ctx)
if !ok {
mnt.EndWrite()
return
}
d.metadataMu.Lock()
atomic.StoreInt64(&d.atime, now)
d.metadataMu.Unlock()
mnt.EndWrite()
}
// Preconditions: fs.interop != InteropModeShared. The caller has successfully
// called vfs.Mount.CheckBeginWrite().
func (d *dentry) touchCMtime(ctx context.Context) {
now, ok := nowFromContext(ctx)
if !ok {
return
}
d.metadataMu.Lock()
atomic.StoreInt64(&d.mtime, now)
atomic.StoreInt64(&d.ctime, now)
d.metadataMu.Unlock()
}
+1 -1
View File
@@ -622,7 +622,7 @@ func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error
if child.inode.isDir() {
return syserror.EISDIR
}
if !rp.MustBeDir() {
if rp.MustBeDir() {
return syserror.ENOTDIR
}
mnt := rp.Mount()
+4 -19
View File
@@ -126,7 +126,7 @@ func (s *socketOperations) Read(ctx context.Context, _ *fs.File, dst usermem.IOS
}
return uint64(n), nil
}
return readv(s.fd, iovecsFromBlockSeq(dsts))
return readv(s.fd, safemem.IovecsFromBlockSeq(dsts))
}))
return int64(n), err
}
@@ -149,7 +149,7 @@ func (s *socketOperations) Write(ctx context.Context, _ *fs.File, src usermem.IO
}
return uint64(n), nil
}
return writev(s.fd, iovecsFromBlockSeq(srcs))
return writev(s.fd, safemem.IovecsFromBlockSeq(srcs))
}))
return int64(n), err
}
@@ -402,7 +402,7 @@ func (s *socketOperations) RecvMsg(t *kernel.Task, dst usermem.IOSequence, flags
// We always do a non-blocking recv*().
sysflags := flags | syscall.MSG_DONTWAIT
iovs := iovecsFromBlockSeq(dsts)
iovs := safemem.IovecsFromBlockSeq(dsts)
msg := syscall.Msghdr{
Iov: &iovs[0],
Iovlen: uint64(len(iovs)),
@@ -522,7 +522,7 @@ func (s *socketOperations) SendMsg(t *kernel.Task, src usermem.IOSequence, to []
return uint64(n), nil
}
iovs := iovecsFromBlockSeq(srcs)
iovs := safemem.IovecsFromBlockSeq(srcs)
msg := syscall.Msghdr{
Iov: &iovs[0],
Iovlen: uint64(len(iovs)),
@@ -567,21 +567,6 @@ func (s *socketOperations) SendMsg(t *kernel.Task, src usermem.IOSequence, to []
return int(n), syserr.FromError(err)
}
func iovecsFromBlockSeq(bs safemem.BlockSeq) []syscall.Iovec {
iovs := make([]syscall.Iovec, 0, bs.NumBlocks())
for ; !bs.IsEmpty(); bs = bs.Tail() {
b := bs.Head()
iovs = append(iovs, syscall.Iovec{
Base: &b.ToSlice()[0],
Len: uint64(b.Len()),
})
// We don't need to care about b.NeedSafecopy(), because the host
// kernel will handle such address ranges just fine (by returning
// EFAULT).
}
return iovs
}
func translateIOSyscallError(err error) error {
if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
return syserror.ErrWouldBlock