Add //pkg/sentry/fsimpl/overlay.

Major differences from existing overlay filesystems:

- Linux allows lower layers in an overlay to require revalidation, but not the
  upper layer. VFS1 allows the upper layer in an overlay to require
  revalidation, but not the lower layer. VFS2 does not allow any layers to
  require revalidation. (Now that vfs.MkdirOptions.ForSyntheticMountpoint
  exists, no uses of overlay in VFS1 are believed to require upper layer
  revalidation; in particular, the requirement that the upper layer support the
  creation of "trusted." extended attributes for whiteouts effectively required
  the upper filesystem to be tmpfs in most cases.)

- Like VFS1, but unlike Linux, VFS2 overlay does not attempt to make mutations
  of the upper layer atomic using a working directory and features like
  RENAME_WHITEOUT. (This may change in the future, since not having a working
  directory makes error recovery for some operations, e.g. rmdir, particularly
  painful.)

- Like Linux, but unlike VFS1, VFS2 represents whiteouts using character
  devices with rdev == 0; the equivalent of the whiteout attribute on
  directories is xattr trusted.overlay.opaque = "y"; and there is no equivalent
  to the whiteout attribute on non-directories since non-directories are never
  merged with lower layers.

- Device and inode numbers work as follows:

    - In Linux, modulo the xino feature and a special case for when all layers
      are the same filesystem:

        - Directories use the overlay filesystem's device number and an
          ephemeral inode number assigned by the overlay.

        - Non-directories that have been copied up use the device and inode
          number assigned by the upper filesystem.

        - Non-directories that have not been copied up use a per-(overlay,
          layer)-pair device number and the inode number assigned by the lower
          filesystem.

    - In VFS1, device and inode numbers always come from the lower layer unless
      "whited out"; this has the adverse effect of requiring interaction with
      the lower filesystem even for non-directory files that exist on the upper
      layer.

    - In VFS2, device and inode numbers are assigned as in Linux, except that
      xino and the samefs special case are not supported.

- Like Linux, but unlike VFS1, VFS2 does not attempt to maintain memory mapping
  coherence across copy-up. (This may have to change in the future, as users
  may be dependent on this property.)

- Like Linux, but unlike VFS1, VFS2 uses the overlayfs mounter's credentials
  when interacting with the overlay's layers, rather than the caller's.

- Like Linux, but unlike VFS1, VFS2 permits multiple lower layers in an
  overlay.

- Like Linux, but unlike VFS1, VFS2's overlay filesystem is
  application-mountable.

Updates #1199

PiperOrigin-RevId: 316019067
This commit is contained in:
Jamie Liu
2020-06-11 18:34:53 -07:00
committed by gVisor bot
parent dc4e0157ef
commit 77c206e371
12 changed files with 2843 additions and 13 deletions
+6 -6
View File
@@ -118,7 +118,7 @@ func putDentrySlice(ds *[]*dentry) {
// must be up to date.
//
// Postconditions: The returned dentry's cached metadata is up to date.
func (fs *filesystem) stepLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, ds **[]*dentry) (*dentry, error) {
func (fs *filesystem) stepLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, mayFollowSymlinks bool, ds **[]*dentry) (*dentry, error) {
if !d.isDir() {
return nil, syserror.ENOTDIR
}
@@ -168,7 +168,7 @@ afterSymlink:
if err := rp.CheckMount(&child.vfsd); err != nil {
return nil, err
}
if child.isSymlink() && rp.ShouldFollowSymlink() {
if child.isSymlink() && mayFollowSymlinks && rp.ShouldFollowSymlink() {
target, err := child.readlink(ctx, rp.Mount())
if err != nil {
return nil, err
@@ -275,7 +275,7 @@ func (fs *filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.Vir
func (fs *filesystem) walkParentDirLocked(ctx context.Context, rp *vfs.ResolvingPath, d *dentry, ds **[]*dentry) (*dentry, error) {
for !rp.Final() {
d.dirMu.Lock()
next, err := fs.stepLocked(ctx, rp, d, ds)
next, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
d.dirMu.Unlock()
if err != nil {
return nil, err
@@ -301,7 +301,7 @@ func (fs *filesystem) resolveLocked(ctx context.Context, rp *vfs.ResolvingPath,
}
for !rp.Done() {
d.dirMu.Lock()
next, err := fs.stepLocked(ctx, rp, d, ds)
next, err := fs.stepLocked(ctx, rp, d, true /* mayFollowSymlinks */, ds)
d.dirMu.Unlock()
if err != nil {
return nil, err
@@ -754,7 +754,7 @@ afterTrailingSymlink:
}
// Determine whether or not we need to create a file.
parent.dirMu.Lock()
child, err := fs.stepLocked(ctx, rp, parent, &ds)
child, err := fs.stepLocked(ctx, rp, parent, false /* mayFollowSymlinks */, &ds)
if err == syserror.ENOENT && mayCreate {
if parent.isSynthetic() {
parent.dirMu.Unlock()
@@ -939,7 +939,7 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
// Filter file creation flags and O_LARGEFILE out; the create RPC already
// has the semantics of O_CREAT|O_EXCL, while some servers will choke on
// O_LARGEFILE.
createFlags := p9.OpenFlags(opts.Flags &^ (linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC | linux.O_LARGEFILE))
createFlags := p9.OpenFlags(opts.Flags &^ (vfs.FileCreationFlags | linux.O_LARGEFILE))
fdobj, openFile, createQID, _, err := dirfile.create(ctx, name, createFlags, (p9.FileMode)(opts.Mode), (p9.UID)(creds.EffectiveKUID), (p9.GID)(creds.EffectiveKGID))
if err != nil {
dirfile.close(ctx)
+5 -5
View File
@@ -35,7 +35,7 @@ import (
// Preconditions: Filesystem.mu must be locked for at least reading. !rp.Done().
//
// Postcondition: Caller must call fs.processDeferredDecRefs*.
func (fs *Filesystem) stepExistingLocked(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentry) (*vfs.Dentry, error) {
func (fs *Filesystem) stepExistingLocked(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentry, mayFollowSymlinks bool) (*vfs.Dentry, error) {
d := vfsd.Impl().(*Dentry)
if !d.isDir() {
return nil, syserror.ENOTDIR
@@ -81,7 +81,7 @@ afterSymlink:
return nil, err
}
// Resolve any symlink at current path component.
if rp.ShouldFollowSymlink() && next.isSymlink() {
if mayFollowSymlinks && rp.ShouldFollowSymlink() && next.isSymlink() {
targetVD, targetPathname, err := next.inode.Getlink(ctx, rp.Mount())
if err != nil {
return nil, err
@@ -152,7 +152,7 @@ func (fs *Filesystem) walkExistingLocked(ctx context.Context, rp *vfs.ResolvingP
vfsd := rp.Start()
for !rp.Done() {
var err error
vfsd, err = fs.stepExistingLocked(ctx, rp, vfsd)
vfsd, err = fs.stepExistingLocked(ctx, rp, vfsd, true /* mayFollowSymlinks */)
if err != nil {
return nil, nil, err
}
@@ -178,7 +178,7 @@ func (fs *Filesystem) walkParentDirLocked(ctx context.Context, rp *vfs.Resolving
vfsd := rp.Start()
for !rp.Final() {
var err error
vfsd, err = fs.stepExistingLocked(ctx, rp, vfsd)
vfsd, err = fs.stepExistingLocked(ctx, rp, vfsd, true /* mayFollowSymlinks */)
if err != nil {
return nil, nil, err
}
@@ -449,7 +449,7 @@ afterTrailingSymlink:
return nil, syserror.ENAMETOOLONG
}
// Determine whether or not we need to create a file.
childVFSD, err := fs.stepExistingLocked(ctx, rp, parentVFSD)
childVFSD, err := fs.stepExistingLocked(ctx, rp, parentVFSD, false /* mayFollowSymlinks */)
if err == syserror.ENOENT {
// Already checked for searchability above; now check for writability.
if err := parentInode.CheckPermissions(ctx, rp.Credentials(), vfs.MayWrite); err != nil {
+41
View File
@@ -0,0 +1,41 @@
load("//tools:defs.bzl", "go_library")
load("//tools/go_generics:defs.bzl", "go_template_instance")
licenses(["notice"])
go_template_instance(
name = "fstree",
out = "fstree.go",
package = "overlay",
prefix = "generic",
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
types = {
"Dentry": "dentry",
},
)
go_library(
name = "overlay",
srcs = [
"copy_up.go",
"directory.go",
"filesystem.go",
"fstree.go",
"non_directory.go",
"overlay.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/fspath",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/memmap",
"//pkg/sentry/socket/unix/transport",
"//pkg/sentry/vfs",
"//pkg/sentry/vfs/lock",
"//pkg/sync",
"//pkg/syserror",
"//pkg/usermem",
],
)
+262
View File
@@ -0,0 +1,262 @@
// 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 overlay
import (
"fmt"
"io"
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
)
func (d *dentry) isCopiedUp() bool {
return atomic.LoadUint32(&d.copiedUp) != 0
}
// copyUpLocked ensures that d exists on the upper layer, i.e. d.upperVD.Ok().
//
// Preconditions: filesystem.renameMu must be locked.
func (d *dentry) copyUpLocked(ctx context.Context) error {
// Fast path.
if d.isCopiedUp() {
return nil
}
ftype := atomic.LoadUint32(&d.mode) & linux.S_IFMT
switch ftype {
case linux.S_IFREG, linux.S_IFDIR, linux.S_IFLNK, linux.S_IFBLK, linux.S_IFCHR:
// Can be copied-up.
default:
// Can't be copied-up.
return syserror.EPERM
}
// Ensure that our parent directory is copied-up.
if d.parent == nil {
// d is a filesystem root with no upper layer.
return syserror.EROFS
}
if err := d.parent.copyUpLocked(ctx); err != nil {
return err
}
d.copyMu.Lock()
defer d.copyMu.Unlock()
if d.upperVD.Ok() {
// Raced with another call to d.copyUpLocked().
return nil
}
if d.vfsd.IsDead() {
// Raced with deletion of d.
return syserror.ENOENT
}
// Perform copy-up.
vfsObj := d.fs.vfsfs.VirtualFilesystem()
newpop := vfs.PathOperation{
Root: d.parent.upperVD,
Start: d.parent.upperVD,
Path: fspath.Parse(d.name),
}
cleanupUndoCopyUp := func() {
var err error
if ftype == linux.S_IFDIR {
err = vfsObj.RmdirAt(ctx, d.fs.creds, &newpop)
} else {
err = vfsObj.UnlinkAt(ctx, d.fs.creds, &newpop)
}
if err != nil {
ctx.Warningf("Unrecoverable overlayfs inconsistency: failed to delete upper layer file after copy-up error: %v", err)
}
}
switch ftype {
case linux.S_IFREG:
oldFD, err := vfsObj.OpenAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: d.lowerVDs[0],
Start: d.lowerVDs[0],
}, &vfs.OpenOptions{
Flags: linux.O_RDONLY,
})
if err != nil {
return err
}
defer oldFD.DecRef()
newFD, err := vfsObj.OpenAt(ctx, d.fs.creds, &newpop, &vfs.OpenOptions{
Flags: linux.O_WRONLY | linux.O_CREAT | linux.O_EXCL,
Mode: linux.FileMode(d.mode &^ linux.S_IFMT),
})
if err != nil {
return err
}
defer newFD.DecRef()
bufIOSeq := usermem.BytesIOSequence(make([]byte, 32*1024)) // arbitrary buffer size
for {
readN, readErr := oldFD.Read(ctx, bufIOSeq, vfs.ReadOptions{})
if readErr != nil && readErr != io.EOF {
cleanupUndoCopyUp()
return readErr
}
total := int64(0)
for total < readN {
writeN, writeErr := newFD.Write(ctx, bufIOSeq.DropFirst64(total), vfs.WriteOptions{})
total += writeN
if writeErr != nil {
cleanupUndoCopyUp()
return writeErr
}
}
if readErr == io.EOF {
break
}
}
if err := newFD.SetStat(ctx, vfs.SetStatOptions{
Stat: linux.Statx{
Mask: linux.STATX_UID | linux.STATX_GID,
UID: d.uid,
GID: d.gid,
},
}); err != nil {
cleanupUndoCopyUp()
return err
}
d.upperVD = newFD.VirtualDentry()
d.upperVD.IncRef()
case linux.S_IFDIR:
if err := vfsObj.MkdirAt(ctx, d.fs.creds, &newpop, &vfs.MkdirOptions{
Mode: linux.FileMode(d.mode &^ linux.S_IFMT),
}); err != nil {
return err
}
if err := vfsObj.SetStatAt(ctx, d.fs.creds, &newpop, &vfs.SetStatOptions{
Stat: linux.Statx{
Mask: linux.STATX_UID | linux.STATX_GID,
UID: d.uid,
GID: d.gid,
},
}); err != nil {
cleanupUndoCopyUp()
return err
}
upperVD, err := vfsObj.GetDentryAt(ctx, d.fs.creds, &newpop, &vfs.GetDentryOptions{})
if err != nil {
cleanupUndoCopyUp()
return err
}
d.upperVD = upperVD
case linux.S_IFLNK:
target, err := vfsObj.ReadlinkAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: d.lowerVDs[0],
Start: d.lowerVDs[0],
})
if err != nil {
return err
}
if err := vfsObj.SymlinkAt(ctx, d.fs.creds, &newpop, target); err != nil {
return err
}
if err := vfsObj.SetStatAt(ctx, d.fs.creds, &newpop, &vfs.SetStatOptions{
Stat: linux.Statx{
Mask: linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID,
Mode: uint16(d.mode),
UID: d.uid,
GID: d.gid,
},
}); err != nil {
cleanupUndoCopyUp()
return err
}
upperVD, err := vfsObj.GetDentryAt(ctx, d.fs.creds, &newpop, &vfs.GetDentryOptions{})
if err != nil {
cleanupUndoCopyUp()
return err
}
d.upperVD = upperVD
case linux.S_IFBLK, linux.S_IFCHR:
lowerStat, err := vfsObj.StatAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: d.lowerVDs[0],
Start: d.lowerVDs[0],
}, &vfs.StatOptions{})
if err != nil {
return err
}
if err := vfsObj.MknodAt(ctx, d.fs.creds, &newpop, &vfs.MknodOptions{
Mode: linux.FileMode(d.mode),
DevMajor: lowerStat.RdevMajor,
DevMinor: lowerStat.RdevMinor,
}); err != nil {
return err
}
if err := vfsObj.SetStatAt(ctx, d.fs.creds, &newpop, &vfs.SetStatOptions{
Stat: linux.Statx{
Mask: linux.STATX_UID | linux.STATX_GID,
UID: d.uid,
GID: d.gid,
},
}); err != nil {
cleanupUndoCopyUp()
return err
}
upperVD, err := vfsObj.GetDentryAt(ctx, d.fs.creds, &newpop, &vfs.GetDentryOptions{})
if err != nil {
cleanupUndoCopyUp()
return err
}
d.upperVD = upperVD
default:
// Should have rejected this at the beginning of this function?
panic(fmt.Sprintf("unexpected file type %o", ftype))
}
// TODO(gvisor.dev/issue/1199): copy up xattrs
// Update the dentry's device and inode numbers (except for directories,
// for which these remain overlay-assigned).
if ftype != linux.S_IFDIR {
upperStat, err := vfsObj.StatAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: d.upperVD,
Start: d.upperVD,
}, &vfs.StatOptions{
Mask: linux.STATX_INO,
})
if err != nil {
d.upperVD.DecRef()
d.upperVD = vfs.VirtualDentry{}
cleanupUndoCopyUp()
return err
}
if upperStat.Mask&linux.STATX_INO == 0 {
d.upperVD.DecRef()
d.upperVD = vfs.VirtualDentry{}
cleanupUndoCopyUp()
return syserror.EREMOTE
}
atomic.StoreUint32(&d.devMajor, upperStat.DevMajor)
atomic.StoreUint32(&d.devMinor, upperStat.DevMinor)
atomic.StoreUint64(&d.ino, upperStat.Ino)
}
atomic.StoreUint32(&d.copiedUp, 1)
return nil
}
+265
View File
@@ -0,0 +1,265 @@
// 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 overlay
import (
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
)
func (d *dentry) isDir() bool {
return atomic.LoadUint32(&d.mode)&linux.S_IFMT == linux.S_IFDIR
}
// Preconditions: d.dirMu must be locked. d.isDir().
func (d *dentry) collectWhiteoutsForRmdirLocked(ctx context.Context) (map[string]bool, error) {
vfsObj := d.fs.vfsfs.VirtualFilesystem()
var readdirErr error
whiteouts := make(map[string]bool)
var maybeWhiteouts []string
d.iterLayers(func(layerVD vfs.VirtualDentry, isUpper bool) bool {
layerFD, err := vfsObj.OpenAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: layerVD,
Start: layerVD,
}, &vfs.OpenOptions{
Flags: linux.O_RDONLY | linux.O_DIRECTORY,
})
if err != nil {
readdirErr = err
return false
}
defer layerFD.DecRef()
// Reuse slice allocated for maybeWhiteouts from a previous layer to
// reduce allocations.
maybeWhiteouts = maybeWhiteouts[:0]
if err := layerFD.IterDirents(ctx, vfs.IterDirentsCallbackFunc(func(dirent vfs.Dirent) error {
if dirent.Name == "." || dirent.Name == ".." {
return nil
}
if _, ok := whiteouts[dirent.Name]; ok {
// This file has been whited-out in a previous layer.
return nil
}
if dirent.Type == linux.DT_CHR {
// We have to determine if this is a whiteout, which doesn't
// count against the directory's emptiness. However, we can't
// do so while holding locks held by layerFD.IterDirents().
maybeWhiteouts = append(maybeWhiteouts, dirent.Name)
return nil
}
// Non-whiteout file in the directory prevents rmdir.
return syserror.ENOTEMPTY
})); err != nil {
readdirErr = err
return false
}
for _, maybeWhiteoutName := range maybeWhiteouts {
stat, err := vfsObj.StatAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: layerVD,
Start: layerVD,
Path: fspath.Parse(maybeWhiteoutName),
}, &vfs.StatOptions{})
if err != nil {
readdirErr = err
return false
}
if stat.RdevMajor != 0 || stat.RdevMinor != 0 {
// This file is a real character device, not a whiteout.
readdirErr = syserror.ENOTEMPTY
return false
}
whiteouts[maybeWhiteoutName] = isUpper
}
// Continue iteration since we haven't found any non-whiteout files in
// this directory yet.
return true
})
return whiteouts, readdirErr
}
type directoryFD struct {
fileDescription
vfs.DirectoryFileDescriptionDefaultImpl
vfs.DentryMetadataFileDescriptionImpl
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()
d := fd.dentry()
if fd.dirents == nil {
ds, err := d.getDirents(ctx)
if err != nil {
return err
}
fd.dirents = ds
}
for fd.off < int64(len(fd.dirents)) {
if err := cb.Handle(fd.dirents[fd.off]); err != nil {
return err
}
fd.off++
}
return nil
}
// Preconditions: d.isDir().
func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
d.fs.renameMu.RLock()
defer d.fs.renameMu.RUnlock()
d.dirMu.Lock()
defer d.dirMu.Unlock()
if d.dirents != nil {
return d.dirents, nil
}
parent := genericParentOrSelf(d)
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,
},
}
// Merge dirents from all layers comprising this directory.
vfsObj := d.fs.vfsfs.VirtualFilesystem()
var readdirErr error
prevDirents := make(map[string]struct{})
var maybeWhiteouts []vfs.Dirent
d.iterLayers(func(layerVD vfs.VirtualDentry, isUpper bool) bool {
layerFD, err := vfsObj.OpenAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: layerVD,
Start: layerVD,
}, &vfs.OpenOptions{
Flags: linux.O_RDONLY | linux.O_DIRECTORY,
})
if err != nil {
readdirErr = err
return false
}
defer layerFD.DecRef()
// Reuse slice allocated for maybeWhiteouts from a previous layer to
// reduce allocations.
maybeWhiteouts = maybeWhiteouts[:0]
if err := layerFD.IterDirents(ctx, vfs.IterDirentsCallbackFunc(func(dirent vfs.Dirent) error {
if dirent.Name == "." || dirent.Name == ".." {
return nil
}
if _, ok := prevDirents[dirent.Name]; ok {
// This file is hidden by, or merged with, another file with
// the same name in a previous layer.
return nil
}
prevDirents[dirent.Name] = struct{}{}
if dirent.Type == linux.DT_CHR {
// We can't determine if this file is a whiteout while holding
// locks held by layerFD.IterDirents().
maybeWhiteouts = append(maybeWhiteouts, dirent)
return nil
}
dirent.NextOff = int64(len(dirents) + 1)
dirents = append(dirents, dirent)
return nil
})); err != nil {
readdirErr = err
return false
}
for _, dirent := range maybeWhiteouts {
stat, err := vfsObj.StatAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: layerVD,
Start: layerVD,
Path: fspath.Parse(dirent.Name),
}, &vfs.StatOptions{})
if err != nil {
readdirErr = err
return false
}
if stat.RdevMajor == 0 && stat.RdevMinor == 0 {
// This file is a whiteout; don't emit a dirent for it.
continue
}
dirent.NextOff = int64(len(dirents) + 1)
dirents = append(dirents, dirent)
}
return true
})
if readdirErr != nil {
return nil, readdirErr
}
// Cache dirents for future directoryFDs.
d.dirents = dirents
return dirents, nil
}
// 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
+266
View File
@@ -0,0 +1,266 @@
// 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 overlay
import (
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/usermem"
)
func (d *dentry) isSymlink() bool {
return atomic.LoadUint32(&d.mode)&linux.S_IFMT == linux.S_IFLNK
}
func (d *dentry) readlink(ctx context.Context) (string, error) {
layerVD := d.topLayer()
return d.fs.vfsfs.VirtualFilesystem().ReadlinkAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: layerVD,
Start: layerVD,
})
}
type nonDirectoryFD struct {
fileDescription
// If copiedUp is false, cachedFD represents
// fileDescription.dentry().lowerVDs[0]; otherwise, cachedFD represents
// fileDescription.dentry().upperVD. cachedFlags is the last known value of
// cachedFD.StatusFlags(). copiedUp, cachedFD, and cachedFlags are
// protected by mu.
mu sync.Mutex
copiedUp bool
cachedFD *vfs.FileDescription
cachedFlags uint32
}
func (fd *nonDirectoryFD) getCurrentFD(ctx context.Context) (*vfs.FileDescription, error) {
fd.mu.Lock()
defer fd.mu.Unlock()
wrappedFD, err := fd.currentFDLocked(ctx)
if err != nil {
return nil, err
}
wrappedFD.IncRef()
return wrappedFD, nil
}
func (fd *nonDirectoryFD) currentFDLocked(ctx context.Context) (*vfs.FileDescription, error) {
d := fd.dentry()
statusFlags := fd.vfsfd.StatusFlags()
if !fd.copiedUp && d.isCopiedUp() {
// Switch to the copied-up file.
upperVD := d.topLayer()
upperFD, err := fd.filesystem().vfsfs.VirtualFilesystem().OpenAt(ctx, d.fs.creds, &vfs.PathOperation{
Root: upperVD,
Start: upperVD,
}, &vfs.OpenOptions{
Flags: statusFlags,
})
if err != nil {
return nil, err
}
oldOff, oldOffErr := fd.cachedFD.Seek(ctx, 0, linux.SEEK_CUR)
if oldOffErr == nil {
if _, err := upperFD.Seek(ctx, oldOff, linux.SEEK_SET); err != nil {
upperFD.DecRef()
return nil, err
}
}
fd.cachedFD.DecRef()
fd.copiedUp = true
fd.cachedFD = upperFD
fd.cachedFlags = statusFlags
} else if fd.cachedFlags != statusFlags {
if err := fd.cachedFD.SetStatusFlags(ctx, d.fs.creds, statusFlags); err != nil {
return nil, err
}
fd.cachedFlags = statusFlags
}
return fd.cachedFD, nil
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *nonDirectoryFD) Release() {
fd.cachedFD.DecRef()
fd.cachedFD = nil
}
// OnClose implements vfs.FileDescriptionImpl.OnClose.
func (fd *nonDirectoryFD) OnClose(ctx context.Context) error {
// Linux doesn't define ovl_file_operations.flush at all (i.e. its
// equivalent to OnClose is a no-op). We pass through to
// fd.cachedFD.OnClose() without upgrading if fd.dentry() has been
// copied-up, since OnClose is mostly used to define post-close writeback,
// and if fd.cachedFD hasn't been updated then it can't have been used to
// mutate fd.dentry() anyway.
fd.mu.Lock()
if statusFlags := fd.vfsfd.StatusFlags(); fd.cachedFlags != statusFlags {
if err := fd.cachedFD.SetStatusFlags(ctx, fd.filesystem().creds, statusFlags); err != nil {
fd.mu.Unlock()
return err
}
fd.cachedFlags = statusFlags
}
wrappedFD := fd.cachedFD
defer wrappedFD.IncRef()
fd.mu.Unlock()
return wrappedFD.OnClose(ctx)
}
// Stat implements vfs.FileDescriptionImpl.Stat.
func (fd *nonDirectoryFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
var stat linux.Statx
if layerMask := opts.Mask &^ statInternalMask; layerMask != 0 {
wrappedFD, err := fd.getCurrentFD(ctx)
if err != nil {
return linux.Statx{}, err
}
stat, err = wrappedFD.Stat(ctx, vfs.StatOptions{
Mask: layerMask,
Sync: opts.Sync,
})
wrappedFD.DecRef()
if err != nil {
return linux.Statx{}, err
}
}
fd.dentry().statInternalTo(ctx, &opts, &stat)
return stat, nil
}
// SetStat implements vfs.FileDescriptionImpl.SetStat.
func (fd *nonDirectoryFD) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
d := fd.dentry()
mode := linux.FileMode(atomic.LoadUint32(&d.mode))
if err := vfs.CheckSetStat(ctx, auth.CredentialsFromContext(ctx), &opts.Stat, mode, auth.KUID(atomic.LoadUint32(&d.uid)), auth.KGID(atomic.LoadUint32(&d.gid))); err != nil {
return err
}
mnt := fd.vfsfd.Mount()
if err := mnt.CheckBeginWrite(); err != nil {
return err
}
defer mnt.EndWrite()
if err := d.copyUpLocked(ctx); err != nil {
return err
}
// Changes to d's attributes are serialized by d.copyMu.
d.copyMu.Lock()
defer d.copyMu.Unlock()
wrappedFD, err := fd.currentFDLocked(ctx)
if err != nil {
return err
}
if err := wrappedFD.SetStat(ctx, opts); err != nil {
return err
}
d.updateAfterSetStatLocked(&opts)
return nil
}
// StatFS implements vfs.FileDesciptionImpl.StatFS.
func (fd *nonDirectoryFD) StatFS(ctx context.Context) (linux.Statfs, error) {
return fd.filesystem().statFS(ctx)
}
// PRead implements vfs.FileDescriptionImpl.PRead.
func (fd *nonDirectoryFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
wrappedFD, err := fd.getCurrentFD(ctx)
if err != nil {
return 0, err
}
defer wrappedFD.DecRef()
return wrappedFD.PRead(ctx, dst, offset, opts)
}
// Read implements vfs.FileDescriptionImpl.Read.
func (fd *nonDirectoryFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
// Hold fd.mu during the read to serialize the file offset.
fd.mu.Lock()
defer fd.mu.Unlock()
wrappedFD, err := fd.currentFDLocked(ctx)
if err != nil {
return 0, err
}
return wrappedFD.Read(ctx, dst, opts)
}
// PWrite implements vfs.FileDescriptionImpl.PWrite.
func (fd *nonDirectoryFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
wrappedFD, err := fd.getCurrentFD(ctx)
if err != nil {
return 0, err
}
defer wrappedFD.DecRef()
return wrappedFD.PWrite(ctx, src, offset, opts)
}
// Write implements vfs.FileDescriptionImpl.Write.
func (fd *nonDirectoryFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
// Hold fd.mu during the write to serialize the file offset.
fd.mu.Lock()
defer fd.mu.Unlock()
wrappedFD, err := fd.currentFDLocked(ctx)
if err != nil {
return 0, err
}
return wrappedFD.Write(ctx, src, opts)
}
// Seek implements vfs.FileDescriptionImpl.Seek.
func (fd *nonDirectoryFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
// Hold fd.mu during the seek to serialize the file offset.
fd.mu.Lock()
defer fd.mu.Unlock()
wrappedFD, err := fd.currentFDLocked(ctx)
if err != nil {
return 0, err
}
return wrappedFD.Seek(ctx, offset, whence)
}
// Sync implements vfs.FileDescriptionImpl.Sync.
func (fd *nonDirectoryFD) Sync(ctx context.Context) error {
fd.mu.Lock()
if !fd.dentry().isCopiedUp() {
fd.mu.Unlock()
return nil
}
wrappedFD, err := fd.currentFDLocked(ctx)
if err != nil {
fd.mu.Unlock()
return err
}
wrappedFD.IncRef()
defer wrappedFD.DecRef()
fd.mu.Unlock()
return wrappedFD.Sync(ctx)
}
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
func (fd *nonDirectoryFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
wrappedFD, err := fd.getCurrentFD(ctx)
if err != nil {
return err
}
defer wrappedFD.DecRef()
return wrappedFD.ConfigureMMap(ctx, opts)
}
File diff suppressed because it is too large Load Diff
+15 -2
View File
@@ -108,6 +108,10 @@ type FileDescriptionOptions struct {
UseDentryMetadata bool
}
// FileCreationFlags are the set of flags passed to FileDescription.Init() but
// omitted from FileDescription.StatusFlags().
const FileCreationFlags = linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC
// Init must be called before first use of fd. If it succeeds, it takes
// references on mnt and d. flags is the initial file description flags, which
// is usually the full set of flags passed to open(2).
@@ -122,8 +126,8 @@ func (fd *FileDescription) Init(impl FileDescriptionImpl, flags uint32, mnt *Mou
fd.refs = 1
// Remove "file creation flags" to mirror the behavior from file.f_flags in
// fs/open.c:do_dentry_open
fd.statusFlags = flags &^ (linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC)
// fs/open.c:do_dentry_open.
fd.statusFlags = flags &^ FileCreationFlags
fd.vd = VirtualDentry{
mount: mnt,
dentry: d,
@@ -471,6 +475,15 @@ type IterDirentsCallback interface {
Handle(dirent Dirent) error
}
// IterDirentsCallbackFunc implements IterDirentsCallback for a function with
// the semantics of IterDirentsCallback.Handle.
type IterDirentsCallbackFunc func(dirent Dirent) error
// Handle implements IterDirentsCallback.Handle.
func (f IterDirentsCallbackFunc) Handle(dirent Dirent) error {
return f(dirent)
}
// 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.
+1
View File
@@ -72,6 +72,7 @@ var (
EPERM = error(syscall.EPERM)
EPIPE = error(syscall.EPIPE)
ERANGE = error(syscall.ERANGE)
EREMOTE = error(syscall.EREMOTE)
EROFS = error(syscall.EROFS)
ESPIPE = error(syscall.ESPIPE)
ESRCH = error(syscall.ESRCH)
+1
View File
@@ -55,6 +55,7 @@ go_library(
"//pkg/sentry/fsimpl/devtmpfs",
"//pkg/sentry/fsimpl/gofer",
"//pkg/sentry/fsimpl/host",
"//pkg/sentry/fsimpl/overlay",
"//pkg/sentry/fsimpl/proc",
"//pkg/sentry/fsimpl/sys",
"//pkg/sentry/fsimpl/tmpfs",
+5
View File
@@ -30,6 +30,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/fsimpl/devpts"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/devtmpfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/gofer"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/overlay"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/proc"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/sys"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
@@ -53,6 +54,10 @@ func registerFilesystems(ctx context.Context, vfsObj *vfs.VirtualFilesystem, cre
vfsObj.MustRegisterFilesystemType(gofer.Name, &gofer.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserList: true,
})
vfsObj.MustRegisterFilesystemType(overlay.Name, &overlay.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
AllowUserList: true,
})
vfsObj.MustRegisterFilesystemType(proc.Name, &proc.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
AllowUserList: true,