Add most VFS methods for syscalls.

PiperOrigin-RevId: 284892289
This commit is contained in:
Jamie Liu
2019-12-10 18:21:07 -08:00
committed by gVisor bot
parent 4ff71b5be4
commit 46651a7d26
13 changed files with 606 additions and 269 deletions
+7 -3
View File
@@ -144,9 +144,13 @@ const (
ModeCharacterDevice = S_IFCHR
ModeNamedPipe = S_IFIFO
ModeSetUID = 04000
ModeSetGID = 02000
ModeSticky = 01000
S_ISUID = 04000
S_ISGID = 02000
S_ISVTX = 01000
ModeSetUID = S_ISUID
ModeSetGID = S_ISGID
ModeSticky = S_ISVTX
ModeUserAll = 0700
ModeUserRead = 0400
@@ -81,7 +81,11 @@ func mount(b *testing.B, imagePath string, vfsfs *vfs.VirtualFilesystem, pop *vf
ctx := contexttest.Context(b)
creds := auth.CredentialsFromContext(ctx)
if err := vfsfs.NewMount(ctx, creds, imagePath, pop, "extfs", &vfs.GetFilesystemOptions{InternalData: int(f.Fd())}); err != nil {
if err := vfsfs.MountAt(ctx, creds, imagePath, pop, "extfs", &vfs.MountOptions{
GetFilesystemOptions: vfs.GetFilesystemOptions{
InternalData: int(f.Fd()),
},
}); err != nil {
b.Fatalf("failed to mount tmpfs submount: %v", err)
}
return func() {
+14 -15
View File
@@ -147,55 +147,54 @@ func TestSeek(t *testing.T) {
t.Fatalf("vfsfs.OpenAt failed: %v", err)
}
if n, err := fd.Impl().Seek(ctx, 0, linux.SEEK_SET); n != 0 || err != nil {
if n, err := fd.Seek(ctx, 0, linux.SEEK_SET); n != 0 || err != nil {
t.Errorf("expected seek position 0, got %d and error %v", n, err)
}
stat, err := fd.Impl().Stat(ctx, vfs.StatOptions{})
stat, err := fd.Stat(ctx, vfs.StatOptions{})
if err != nil {
t.Errorf("fd.stat failed for file %s in image %s: %v", test.path, test.image, err)
}
// We should be able to seek beyond the end of file.
size := int64(stat.Size)
if n, err := fd.Impl().Seek(ctx, size, linux.SEEK_SET); n != size || err != nil {
if n, err := fd.Seek(ctx, size, linux.SEEK_SET); n != size || err != nil {
t.Errorf("expected seek position %d, got %d and error %v", size, n, err)
}
// EINVAL should be returned if the resulting offset is negative.
if _, err := fd.Impl().Seek(ctx, -1, linux.SEEK_SET); err != syserror.EINVAL {
if _, err := fd.Seek(ctx, -1, linux.SEEK_SET); err != syserror.EINVAL {
t.Errorf("expected error EINVAL but got %v", err)
}
if n, err := fd.Impl().Seek(ctx, 3, linux.SEEK_CUR); n != size+3 || err != nil {
if n, err := fd.Seek(ctx, 3, linux.SEEK_CUR); n != size+3 || err != nil {
t.Errorf("expected seek position %d, got %d and error %v", size+3, n, err)
}
// Make sure negative offsets work with SEEK_CUR.
if n, err := fd.Impl().Seek(ctx, -2, linux.SEEK_CUR); n != size+1 || err != nil {
if n, err := fd.Seek(ctx, -2, linux.SEEK_CUR); n != size+1 || err != nil {
t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err)
}
// EINVAL should be returned if the resulting offset is negative.
if _, err := fd.Impl().Seek(ctx, -(size + 2), linux.SEEK_CUR); err != syserror.EINVAL {
if _, err := fd.Seek(ctx, -(size + 2), linux.SEEK_CUR); err != syserror.EINVAL {
t.Errorf("expected error EINVAL but got %v", err)
}
// Make sure SEEK_END works with regular files.
switch fd.Impl().(type) {
case *regularFileFD:
if _, ok := fd.Impl().(*regularFileFD); ok {
// Seek back to 0.
if n, err := fd.Impl().Seek(ctx, -size, linux.SEEK_END); n != 0 || err != nil {
if n, err := fd.Seek(ctx, -size, linux.SEEK_END); n != 0 || err != nil {
t.Errorf("expected seek position %d, got %d and error %v", 0, n, err)
}
// Seek forward beyond EOF.
if n, err := fd.Impl().Seek(ctx, 1, linux.SEEK_END); n != size+1 || err != nil {
if n, err := fd.Seek(ctx, 1, linux.SEEK_END); n != size+1 || err != nil {
t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err)
}
// EINVAL should be returned if the resulting offset is negative.
if _, err := fd.Impl().Seek(ctx, -(size + 1), linux.SEEK_END); err != syserror.EINVAL {
if _, err := fd.Seek(ctx, -(size + 1), linux.SEEK_END); err != syserror.EINVAL {
t.Errorf("expected error EINVAL but got %v", err)
}
}
@@ -456,7 +455,7 @@ func TestRead(t *testing.T) {
want := make([]byte, 1)
for {
n, err := f.Read(want)
fd.Impl().Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{})
fd.Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{})
if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("file data mismatch (-want +got):\n%s", diff)
@@ -464,7 +463,7 @@ func TestRead(t *testing.T) {
// Make sure there is no more file data left after getting EOF.
if n == 0 || err == io.EOF {
if n, _ := fd.Impl().Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{}); n != 0 {
if n, _ := fd.Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{}); n != 0 {
t.Errorf("extra unexpected file data in file %s in image %s", test.absPath, test.image)
}
@@ -574,7 +573,7 @@ func TestIterDirents(t *testing.T) {
}
cb := &iterDirentsCb{}
if err = fd.Impl().IterDirents(ctx, cb); err != nil {
if err = fd.IterDirents(ctx, cb); err != nil {
t.Fatalf("dir fd.IterDirents() failed: %v", err)
}
+1 -1
View File
@@ -394,7 +394,7 @@ func BenchmarkVFS2MemfsMountStat(b *testing.B) {
}
defer mountPoint.DecRef()
// Create and mount the submount.
if err := vfsObj.NewMount(ctx, creds, "", &pop, "memfs", &vfs.GetFilesystemOptions{}); err != nil {
if err := vfsObj.MountAt(ctx, creds, "", &pop, "memfs", &vfs.MountOptions{}); err != nil {
b.Fatalf("failed to mount tmpfs submount: %v", err)
}
filePathBuilder.WriteString(mountPointName)
+3 -3
View File
@@ -194,7 +194,7 @@ func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesy
func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
readData := make([]byte, 1)
dst := usermem.BytesIOSequence(readData)
bytesRead, err := fd.Impl().Read(ctx, dst, vfs.ReadOptions{})
bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
if err != syserror.ErrWouldBlock {
t.Fatalf("expected ErrWouldBlock reading from empty pipe %q, but got: %v", fileName, err)
}
@@ -207,7 +207,7 @@ func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
writeData := []byte(msg)
src := usermem.BytesIOSequence(writeData)
bytesWritten, err := fd.Impl().Write(ctx, src, vfs.WriteOptions{})
bytesWritten, err := fd.Write(ctx, src, vfs.WriteOptions{})
if err != nil {
t.Fatalf("error writing to pipe %q: %v", fileName, err)
}
@@ -220,7 +220,7 @@ func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg
func checkRead(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
readData := make([]byte, len(msg))
dst := usermem.BytesIOSequence(readData)
bytesRead, err := fd.Impl().Read(ctx, dst, vfs.ReadOptions{})
bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
if err != nil {
t.Fatalf("error reading from pipe %q: %v", fileName, err)
}
-1
View File
@@ -19,7 +19,6 @@ go_library(
"options.go",
"permissions.go",
"resolving_path.go",
"syscalls.go",
"testutil.go",
"vfs.go",
],
+93
View File
@@ -241,3 +241,96 @@ type IterDirentsCallback interface {
// called.
Handle(dirent Dirent) bool
}
// OnClose is called when a file descriptor representing the FileDescription is
// closed. Returning a non-nil error should not prevent the file descriptor
// from being closed.
func (fd *FileDescription) OnClose(ctx context.Context) error {
return fd.impl.OnClose(ctx)
}
// StatusFlags returns file description status flags, as for fcntl(F_GETFL).
func (fd *FileDescription) StatusFlags(ctx context.Context) (uint32, error) {
flags, err := fd.impl.StatusFlags(ctx)
flags |= linux.O_LARGEFILE
return flags, err
}
// SetStatusFlags sets file description status flags, as for fcntl(F_SETFL).
func (fd *FileDescription) SetStatusFlags(ctx context.Context, flags uint32) error {
return fd.impl.SetStatusFlags(ctx, flags)
}
// Stat returns metadata for the file represented by fd.
func (fd *FileDescription) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) {
return fd.impl.Stat(ctx, opts)
}
// SetStat updates metadata for the file represented by fd.
func (fd *FileDescription) SetStat(ctx context.Context, opts SetStatOptions) error {
return fd.impl.SetStat(ctx, opts)
}
// StatFS returns metadata for the filesystem containing the file represented
// by fd.
func (fd *FileDescription) StatFS(ctx context.Context) (linux.Statfs, error) {
return fd.impl.StatFS(ctx)
}
// PRead reads from the file represented by fd into dst, starting at the given
// offset, and returns the number of bytes read. PRead is permitted to return
// partial reads with a nil error.
func (fd *FileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) {
return fd.impl.PRead(ctx, dst, offset, opts)
}
// Read is similar to PRead, but does not specify an offset.
func (fd *FileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) {
return fd.impl.Read(ctx, dst, opts)
}
// PWrite writes src to the file represented by fd, starting at the given
// offset, and returns the number of bytes written. PWrite is permitted to
// return partial writes with a nil error.
func (fd *FileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) {
return fd.impl.PWrite(ctx, src, offset, opts)
}
// Write is similar to PWrite, but does not specify an offset.
func (fd *FileDescription) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) {
return fd.impl.Write(ctx, src, opts)
}
// IterDirents invokes cb on each entry in the directory represented by fd. If
// IterDirents has been called since the last call to Seek, it continues
// iteration from the end of the last call.
func (fd *FileDescription) IterDirents(ctx context.Context, cb IterDirentsCallback) error {
return fd.impl.IterDirents(ctx, cb)
}
// Seek changes fd's offset (assuming one exists) and returns its new value.
func (fd *FileDescription) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
return fd.impl.Seek(ctx, offset, whence)
}
// Sync has the semantics of fsync(2).
func (fd *FileDescription) Sync(ctx context.Context) error {
return fd.impl.Sync(ctx)
}
// ConfigureMMap mutates opts to implement mmap(2) for the file represented by
// fd.
func (fd *FileDescription) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
return fd.impl.ConfigureMMap(ctx, opts)
}
// Ioctl implements the ioctl(2) syscall.
func (fd *FileDescription) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) {
return fd.impl.Ioctl(ctx, uio, args)
}
// SyncFS instructs the filesystem containing fd to execute the semantics of
// syncfs(2).
func (fd *FileDescription) SyncFS(ctx context.Context) error {
return fd.vd.mount.fs.impl.Sync(ctx)
}
@@ -103,7 +103,7 @@ func TestGenCountFD(t *testing.T) {
// The first read causes Generate to be called to fill the FD's buffer.
buf := make([]byte, 2)
ioseq := usermem.BytesIOSequence(buf)
n, err := fd.Impl().Read(ctx, ioseq, ReadOptions{})
n, err := fd.Read(ctx, ioseq, ReadOptions{})
if n != 1 || (err != nil && err != io.EOF) {
t.Fatalf("first Read: got (%d, %v), wanted (1, nil or EOF)", n, err)
}
@@ -112,17 +112,17 @@ func TestGenCountFD(t *testing.T) {
}
// A second read without seeking is still at EOF.
n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
n, err = fd.Read(ctx, ioseq, ReadOptions{})
if n != 0 || err != io.EOF {
t.Fatalf("second Read: got (%d, %v), wanted (0, EOF)", n, err)
}
// Seeking to the beginning of the file causes it to be regenerated.
n, err = fd.Impl().Seek(ctx, 0, linux.SEEK_SET)
n, err = fd.Seek(ctx, 0, linux.SEEK_SET)
if n != 0 || err != nil {
t.Fatalf("Seek: got (%d, %v), wanted (0, nil)", n, err)
}
n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
n, err = fd.Read(ctx, ioseq, ReadOptions{})
if n != 1 || (err != nil && err != io.EOF) {
t.Fatalf("Read after Seek: got (%d, %v), wanted (1, nil or EOF)", n, err)
}
@@ -131,7 +131,7 @@ func TestGenCountFD(t *testing.T) {
}
// PRead at the beginning of the file also causes it to be regenerated.
n, err = fd.Impl().PRead(ctx, ioseq, 0, ReadOptions{})
n, err = fd.PRead(ctx, ioseq, 0, ReadOptions{})
if n != 1 || (err != nil && err != io.EOF) {
t.Fatalf("PRead: got (%d, %v), wanted (1, nil or EOF)", n, err)
}
+22
View File
@@ -47,6 +47,9 @@ func (fs *Filesystem) Init(vfsObj *VirtualFilesystem, impl FilesystemImpl) {
fs.refs = 1
fs.vfs = vfsObj
fs.impl = impl
vfsObj.filesystemsMu.Lock()
vfsObj.filesystems[fs] = struct{}{}
vfsObj.filesystemsMu.Unlock()
}
// VirtualFilesystem returns the containing VirtualFilesystem.
@@ -66,9 +69,28 @@ func (fs *Filesystem) IncRef() {
}
}
// TryIncRef increments fs' reference count and returns true. If fs' reference
// count is zero, TryIncRef does nothing and returns false.
//
// TryIncRef does not require that a reference is held on fs.
func (fs *Filesystem) TryIncRef() bool {
for {
refs := atomic.LoadInt64(&fs.refs)
if refs <= 0 {
return false
}
if atomic.CompareAndSwapInt64(&fs.refs, refs, refs+1) {
return true
}
}
}
// DecRef decrements fs' reference count.
func (fs *Filesystem) DecRef() {
if refs := atomic.AddInt64(&fs.refs, -1); refs == 0 {
fs.vfs.filesystemsMu.Lock()
delete(fs.vfs.filesystems, fs)
fs.vfs.filesystemsMu.Unlock()
fs.impl.Release()
} else if refs < 0 {
panic("Filesystem.decRef() called without holding a reference")
+66 -3
View File
@@ -18,6 +18,7 @@ import (
"math"
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/syserror"
@@ -133,13 +134,13 @@ func (vfs *VirtualFilesystem) NewMountNamespace(ctx context.Context, creds *auth
return mntns, nil
}
// NewMount creates and mounts a Filesystem configured by the given arguments.
func (vfs *VirtualFilesystem) NewMount(ctx context.Context, creds *auth.Credentials, source string, target *PathOperation, fsTypeName string, opts *GetFilesystemOptions) error {
// MountAt creates and mounts a Filesystem configured by the given arguments.
func (vfs *VirtualFilesystem) MountAt(ctx context.Context, creds *auth.Credentials, source string, target *PathOperation, fsTypeName string, opts *MountOptions) error {
fsType := vfs.getFilesystemType(fsTypeName)
if fsType == nil {
return syserror.ENODEV
}
fs, root, err := fsType.GetFilesystem(ctx, vfs, creds, source, *opts)
fs, root, err := fsType.GetFilesystem(ctx, vfs, creds, source, opts.GetFilesystemOptions)
if err != nil {
return err
}
@@ -207,6 +208,68 @@ func (vfs *VirtualFilesystem) NewMount(ctx context.Context, creds *auth.Credenti
return nil
}
// UmountAt removes the Mount at the given path.
func (vfs *VirtualFilesystem) UmountAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *UmountOptions) error {
if opts.Flags&^(linux.MNT_FORCE|linux.MNT_DETACH) != 0 {
return syserror.EINVAL
}
// MNT_FORCE is currently unimplemented except for the permission check.
if opts.Flags&linux.MNT_FORCE != 0 && creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, creds.UserNamespace.Root()) {
return syserror.EPERM
}
vd, err := vfs.GetDentryAt(ctx, creds, pop, &GetDentryOptions{})
if err != nil {
return err
}
defer vd.DecRef()
if vd.dentry != vd.mount.root {
return syserror.EINVAL
}
vfs.mountMu.Lock()
if mntns := MountNamespaceFromContext(ctx); mntns != nil && mntns != vd.mount.ns {
vfs.mountMu.Unlock()
return syserror.EINVAL
}
// TODO(jamieliu): Linux special-cases umount of the caller's root, which
// we don't implement yet (we'll just fail it since the caller holds a
// reference on it).
vfs.mounts.seq.BeginWrite()
if opts.Flags&linux.MNT_DETACH == 0 {
if len(vd.mount.children) != 0 {
vfs.mounts.seq.EndWrite()
vfs.mountMu.Unlock()
return syserror.EBUSY
}
// We are holding a reference on vd.mount.
expectedRefs := int64(1)
if !vd.mount.umounted {
expectedRefs = 2
}
if atomic.LoadInt64(&vd.mount.refs)&^math.MinInt64 != expectedRefs { // mask out MSB
vfs.mounts.seq.EndWrite()
vfs.mountMu.Unlock()
return syserror.EBUSY
}
}
vdsToDecRef, mountsToDecRef := vfs.umountRecursiveLocked(vd.mount, &umountRecursiveOptions{
eager: opts.Flags&linux.MNT_DETACH == 0,
disconnectHierarchy: true,
}, nil, nil)
vfs.mounts.seq.EndWrite()
vfs.mountMu.Unlock()
for _, vd := range vdsToDecRef {
vd.DecRef()
}
for _, mnt := range mountsToDecRef {
mnt.DecRef()
}
return nil
}
type umountRecursiveOptions struct {
// If eager is true, ensure that future calls to Mount.tryIncMountedRef()
// on umounted mounts fail.
+12
View File
@@ -46,6 +46,12 @@ type MknodOptions struct {
DevMinor uint32
}
// MountOptions contains options to VirtualFilesystem.MountAt().
type MountOptions struct {
// GetFilesystemOptions contains options to FilesystemType.GetFilesystem().
GetFilesystemOptions GetFilesystemOptions
}
// OpenOptions contains options to VirtualFilesystem.OpenAt() and
// FilesystemImpl.OpenAt().
type OpenOptions struct {
@@ -114,6 +120,12 @@ type StatOptions struct {
Sync uint32
}
// UmountOptions contains options to VirtualFilesystem.UmountAt().
type UmountOptions struct {
// Flags contains flags as specified for umount2(2).
Flags uint32
}
// WriteOptions contains options to FileDescription.PWrite(),
// FileDescriptionImpl.PWrite(), FileDescription.Write(), and
// FileDescriptionImpl.Write().
-237
View File
@@ -1,237 +0,0 @@
// 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 vfs
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/syserror"
)
// PathOperation specifies the path operated on by a VFS method.
//
// PathOperation is passed to VFS methods by pointer to reduce memory copying:
// it's somewhat large and should never escape. (Options structs are passed by
// pointer to VFS and FileDescription methods for the same reason.)
type PathOperation struct {
// Root is the VFS root. References on Root are borrowed from the provider
// of the PathOperation.
//
// Invariants: Root.Ok().
Root VirtualDentry
// Start is the starting point for the path traversal. References on Start
// are borrowed from the provider of the PathOperation (i.e. the caller of
// the VFS method to which the PathOperation was passed).
//
// Invariants: Start.Ok(). If Pathname.Absolute, then Start == Root.
Start VirtualDentry
// Path is the pathname traversed by this operation.
Pathname string
// If FollowFinalSymlink is true, and the Dentry traversed by the final
// path component represents a symbolic link, the symbolic link should be
// followed.
FollowFinalSymlink bool
}
// GetDentryAt returns a VirtualDentry representing the given path, at which a
// file must exist. A reference is taken on the returned VirtualDentry.
func (vfs *VirtualFilesystem) GetDentryAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *GetDentryOptions) (VirtualDentry, error) {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return VirtualDentry{}, err
}
for {
d, err := rp.mount.fs.impl.GetDentryAt(ctx, rp, *opts)
if err == nil {
vd := VirtualDentry{
mount: rp.mount,
dentry: d,
}
rp.mount.IncRef()
vfs.putResolvingPath(rp)
return vd, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return VirtualDentry{}, err
}
}
}
// MkdirAt creates a directory at the given path.
func (vfs *VirtualFilesystem) MkdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MkdirOptions) error {
// "Under Linux, apart from the permission bits, the S_ISVTX mode bit is
// also honored." - mkdir(2)
opts.Mode &= 01777
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return err
}
for {
err := rp.mount.fs.impl.MkdirAt(ctx, rp, *opts)
if err == nil {
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// MknodAt creates a file of the given mode at the given path. It returns an
// error from the syserror package.
func (vfs *VirtualFilesystem) MknodAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MknodOptions) error {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return nil
}
for {
if err = rp.mount.fs.impl.MknodAt(ctx, rp, *opts); err == nil {
vfs.putResolvingPath(rp)
return nil
}
// Handle mount traversals.
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// OpenAt returns a FileDescription providing access to the file at the given
// path. A reference is taken on the returned FileDescription.
func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *OpenOptions) (*FileDescription, error) {
// Remove:
//
// - O_LARGEFILE, which we always report in FileDescription status flags
// since only 64-bit architectures are supported at this time.
//
// - O_CLOEXEC, which affects file descriptors and therefore must be
// handled outside of VFS.
//
// - Unknown flags.
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC | linux.O_APPEND | linux.O_NONBLOCK | linux.O_DSYNC | linux.O_ASYNC | linux.O_DIRECT | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NOATIME | linux.O_SYNC | linux.O_PATH | linux.O_TMPFILE
// Linux's __O_SYNC (which we call linux.O_SYNC) implies O_DSYNC.
if opts.Flags&linux.O_SYNC != 0 {
opts.Flags |= linux.O_DSYNC
}
// Linux's __O_TMPFILE (which we call linux.O_TMPFILE) must be specified
// with O_DIRECTORY and a writable access mode (to ensure that it fails on
// filesystem implementations that do not support it).
if opts.Flags&linux.O_TMPFILE != 0 {
if opts.Flags&linux.O_DIRECTORY == 0 {
return nil, syserror.EINVAL
}
if opts.Flags&linux.O_CREAT != 0 {
return nil, syserror.EINVAL
}
if opts.Flags&linux.O_ACCMODE == linux.O_RDONLY {
return nil, syserror.EINVAL
}
}
// O_PATH causes most other flags to be ignored.
if opts.Flags&linux.O_PATH != 0 {
opts.Flags &= linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_PATH
}
// "On Linux, the following bits are also honored in mode: [S_ISUID,
// S_ISGID, S_ISVTX]" - open(2)
opts.Mode &= 07777
if opts.Flags&linux.O_NOFOLLOW != 0 {
pop.FollowFinalSymlink = false
}
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return nil, err
}
if opts.Flags&linux.O_DIRECTORY != 0 {
rp.mustBeDir = true
rp.mustBeDirOrig = true
}
for {
fd, err := rp.mount.fs.impl.OpenAt(ctx, rp, *opts)
if err == nil {
vfs.putResolvingPath(rp)
return fd, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return nil, err
}
}
}
// StatAt returns metadata for the file at the given path.
func (vfs *VirtualFilesystem) StatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *StatOptions) (linux.Statx, error) {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return linux.Statx{}, err
}
for {
stat, err := rp.mount.fs.impl.StatAt(ctx, rp, *opts)
if err == nil {
vfs.putResolvingPath(rp)
return stat, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return linux.Statx{}, err
}
}
}
// StatusFlags returns file description status flags.
func (fd *FileDescription) StatusFlags(ctx context.Context) (uint32, error) {
flags, err := fd.impl.StatusFlags(ctx)
flags |= linux.O_LARGEFILE
return flags, err
}
// SetStatusFlags sets file description status flags.
func (fd *FileDescription) SetStatusFlags(ctx context.Context, flags uint32) error {
return fd.impl.SetStatusFlags(ctx, flags)
}
// TODO:
//
// - VFS.SyncAllFilesystems() for sync(2)
//
// - Something for syncfs(2)
//
// - VFS.LinkAt()
//
// - VFS.ReadlinkAt()
//
// - VFS.RenameAt()
//
// - VFS.RmdirAt()
//
// - VFS.SetStatAt()
//
// - VFS.StatFSAt()
//
// - VFS.SymlinkAt()
//
// - VFS.UmountAt()
//
// - VFS.UnlinkAt()
//
// - FileDescription.(almost everything)
+378
View File
@@ -20,6 +20,7 @@
// VirtualFilesystem.mountMu
// Dentry.mu
// Locks acquired by FilesystemImpls between Prepare{Delete,Rename}Dentry and Commit{Delete,Rename*}Dentry
// VirtualFilesystem.filesystemsMu
// VirtualFilesystem.fsTypesMu
//
// Locking Dentry.mu in multiple Dentries requires holding
@@ -28,6 +29,11 @@ package vfs
import (
"sync"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/syserror"
)
// A VirtualFilesystem (VFS for short) combines Filesystems in trees of Mounts.
@@ -67,6 +73,11 @@ type VirtualFilesystem struct {
// mountpoints is analogous to Linux's mountpoint_hashtable.
mountpoints map[*Dentry]map[*Mount]struct{}
// filesystems contains all Filesystems. filesystems is protected by
// filesystemsMu.
filesystemsMu sync.Mutex
filesystems map[*Filesystem]struct{}
// fsTypes contains all FilesystemTypes that are usable in the
// VirtualFilesystem. fsTypes is protected by fsTypesMu.
fsTypesMu sync.RWMutex
@@ -77,12 +88,379 @@ type VirtualFilesystem struct {
func New() *VirtualFilesystem {
vfs := &VirtualFilesystem{
mountpoints: make(map[*Dentry]map[*Mount]struct{}),
filesystems: make(map[*Filesystem]struct{}),
fsTypes: make(map[string]FilesystemType),
}
vfs.mounts.Init()
return vfs
}
// PathOperation specifies the path operated on by a VFS method.
//
// PathOperation is passed to VFS methods by pointer to reduce memory copying:
// it's somewhat large and should never escape. (Options structs are passed by
// pointer to VFS and FileDescription methods for the same reason.)
type PathOperation struct {
// Root is the VFS root. References on Root are borrowed from the provider
// of the PathOperation.
//
// Invariants: Root.Ok().
Root VirtualDentry
// Start is the starting point for the path traversal. References on Start
// are borrowed from the provider of the PathOperation (i.e. the caller of
// the VFS method to which the PathOperation was passed).
//
// Invariants: Start.Ok(). If Pathname.Absolute, then Start == Root.
Start VirtualDentry
// Path is the pathname traversed by this operation.
Pathname string
// If FollowFinalSymlink is true, and the Dentry traversed by the final
// path component represents a symbolic link, the symbolic link should be
// followed.
FollowFinalSymlink bool
}
// GetDentryAt returns a VirtualDentry representing the given path, at which a
// file must exist. A reference is taken on the returned VirtualDentry.
func (vfs *VirtualFilesystem) GetDentryAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *GetDentryOptions) (VirtualDentry, error) {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return VirtualDentry{}, err
}
for {
d, err := rp.mount.fs.impl.GetDentryAt(ctx, rp, *opts)
if err == nil {
vd := VirtualDentry{
mount: rp.mount,
dentry: d,
}
rp.mount.IncRef()
vfs.putResolvingPath(rp)
return vd, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return VirtualDentry{}, err
}
}
}
// LinkAt creates a hard link at newpop representing the existing file at
// oldpop.
func (vfs *VirtualFilesystem) LinkAt(ctx context.Context, creds *auth.Credentials, oldpop, newpop *PathOperation) error {
oldVD, err := vfs.GetDentryAt(ctx, creds, oldpop, &GetDentryOptions{})
if err != nil {
return err
}
rp, err := vfs.getResolvingPath(creds, newpop)
if err != nil {
oldVD.DecRef()
return err
}
for {
err := rp.mount.fs.impl.LinkAt(ctx, rp, oldVD)
if err == nil {
oldVD.DecRef()
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
oldVD.DecRef()
vfs.putResolvingPath(rp)
return err
}
}
}
// MkdirAt creates a directory at the given path.
func (vfs *VirtualFilesystem) MkdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MkdirOptions) error {
// "Under Linux, apart from the permission bits, the S_ISVTX mode bit is
// also honored." - mkdir(2)
opts.Mode &= 0777 | linux.S_ISVTX
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return err
}
for {
err := rp.mount.fs.impl.MkdirAt(ctx, rp, *opts)
if err == nil {
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// MknodAt creates a file of the given mode at the given path. It returns an
// error from the syserror package.
func (vfs *VirtualFilesystem) MknodAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MknodOptions) error {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return nil
}
for {
if err = rp.mount.fs.impl.MknodAt(ctx, rp, *opts); err == nil {
vfs.putResolvingPath(rp)
return nil
}
// Handle mount traversals.
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// OpenAt returns a FileDescription providing access to the file at the given
// path. A reference is taken on the returned FileDescription.
func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *OpenOptions) (*FileDescription, error) {
// Remove:
//
// - O_LARGEFILE, which we always report in FileDescription status flags
// since only 64-bit architectures are supported at this time.
//
// - O_CLOEXEC, which affects file descriptors and therefore must be
// handled outside of VFS.
//
// - Unknown flags.
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC | linux.O_APPEND | linux.O_NONBLOCK | linux.O_DSYNC | linux.O_ASYNC | linux.O_DIRECT | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NOATIME | linux.O_SYNC | linux.O_PATH | linux.O_TMPFILE
// Linux's __O_SYNC (which we call linux.O_SYNC) implies O_DSYNC.
if opts.Flags&linux.O_SYNC != 0 {
opts.Flags |= linux.O_DSYNC
}
// Linux's __O_TMPFILE (which we call linux.O_TMPFILE) must be specified
// with O_DIRECTORY and a writable access mode (to ensure that it fails on
// filesystem implementations that do not support it).
if opts.Flags&linux.O_TMPFILE != 0 {
if opts.Flags&linux.O_DIRECTORY == 0 {
return nil, syserror.EINVAL
}
if opts.Flags&linux.O_CREAT != 0 {
return nil, syserror.EINVAL
}
if opts.Flags&linux.O_ACCMODE == linux.O_RDONLY {
return nil, syserror.EINVAL
}
}
// O_PATH causes most other flags to be ignored.
if opts.Flags&linux.O_PATH != 0 {
opts.Flags &= linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_PATH
}
// "On Linux, the following bits are also honored in mode: [S_ISUID,
// S_ISGID, S_ISVTX]" - open(2)
opts.Mode &= 0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX
if opts.Flags&linux.O_NOFOLLOW != 0 {
pop.FollowFinalSymlink = false
}
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return nil, err
}
if opts.Flags&linux.O_DIRECTORY != 0 {
rp.mustBeDir = true
rp.mustBeDirOrig = true
}
for {
fd, err := rp.mount.fs.impl.OpenAt(ctx, rp, *opts)
if err == nil {
vfs.putResolvingPath(rp)
return fd, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return nil, err
}
}
}
// ReadlinkAt returns the target of the symbolic link at the given path.
func (vfs *VirtualFilesystem) ReadlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) (string, error) {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return "", err
}
for {
target, err := rp.mount.fs.impl.ReadlinkAt(ctx, rp)
if err == nil {
vfs.putResolvingPath(rp)
return target, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return "", err
}
}
}
// RenameAt renames the file at oldpop to newpop.
func (vfs *VirtualFilesystem) RenameAt(ctx context.Context, creds *auth.Credentials, oldpop, newpop *PathOperation, opts *RenameOptions) error {
oldVD, err := vfs.GetDentryAt(ctx, creds, oldpop, &GetDentryOptions{})
if err != nil {
return err
}
rp, err := vfs.getResolvingPath(creds, newpop)
if err != nil {
oldVD.DecRef()
return err
}
for {
err := rp.mount.fs.impl.RenameAt(ctx, rp, oldVD, *opts)
if err == nil {
oldVD.DecRef()
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
oldVD.DecRef()
vfs.putResolvingPath(rp)
return err
}
}
}
// RmdirAt removes the directory at the given path.
func (vfs *VirtualFilesystem) RmdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) error {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return err
}
for {
err := rp.mount.fs.impl.RmdirAt(ctx, rp)
if err == nil {
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// SetStatAt changes metadata for the file at the given path.
func (vfs *VirtualFilesystem) SetStatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *SetStatOptions) error {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return err
}
for {
err := rp.mount.fs.impl.SetStatAt(ctx, rp, *opts)
if err == nil {
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// StatAt returns metadata for the file at the given path.
func (vfs *VirtualFilesystem) StatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *StatOptions) (linux.Statx, error) {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return linux.Statx{}, err
}
for {
stat, err := rp.mount.fs.impl.StatAt(ctx, rp, *opts)
if err == nil {
vfs.putResolvingPath(rp)
return stat, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return linux.Statx{}, err
}
}
}
// StatFSAt returns metadata for the filesystem containing the file at the
// given path.
func (vfs *VirtualFilesystem) StatFSAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) (linux.Statfs, error) {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return linux.Statfs{}, err
}
for {
statfs, err := rp.mount.fs.impl.StatFSAt(ctx, rp)
if err == nil {
vfs.putResolvingPath(rp)
return statfs, nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return linux.Statfs{}, err
}
}
}
// SymlinkAt creates a symbolic link at the given path with the given target.
func (vfs *VirtualFilesystem) SymlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, target string) error {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return err
}
for {
err := rp.mount.fs.impl.SymlinkAt(ctx, rp, target)
if err == nil {
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// UnlinkAt deletes the non-directory file at the given path.
func (vfs *VirtualFilesystem) UnlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) error {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return err
}
for {
err := rp.mount.fs.impl.UnlinkAt(ctx, rp)
if err == nil {
vfs.putResolvingPath(rp)
return nil
}
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// SyncAllFilesystems has the semantics of Linux's sync(2).
func (vfs *VirtualFilesystem) SyncAllFilesystems(ctx context.Context) error {
fss := make(map[*Filesystem]struct{})
vfs.filesystemsMu.Lock()
for fs := range vfs.filesystems {
if !fs.TryIncRef() {
continue
}
fss[fs] = struct{}{}
}
vfs.filesystemsMu.Unlock()
var retErr error
for fs := range fss {
if err := fs.impl.Sync(ctx); err != nil && retErr == nil {
retErr = err
}
fs.DecRef()
}
return retErr
}
// A VirtualDentry represents a node in a VFS tree, by combining a Dentry
// (which represents a node in a Filesystem's tree) and a Mount (which
// represents the Filesystem's position in a VFS mount tree).