Add vfs.MkdirOptions.ForSyntheticMountpoint.

PiperOrigin-RevId: 308143529
This commit is contained in:
Jamie Liu
2020-04-23 15:37:10 -07:00
committed by gVisor bot
parent cc5de905e6
commit 5042ea7e2c
7 changed files with 462 additions and 217 deletions
+105 -42
View File
@@ -21,8 +21,10 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
)
func (d *dentry) isDir() bool {
@@ -41,15 +43,46 @@ func (d *dentry) cacheNewChildLocked(child *dentry, name string) {
d.children[name] = child
}
// Preconditions: d.dirMu must be locked. d.isDir(). fs.opts.interop !=
// InteropModeShared.
func (d *dentry) cacheNegativeChildLocked(name string) {
// Preconditions: d.dirMu must be locked. d.isDir().
func (d *dentry) cacheNegativeLookupLocked(name string) {
// Don't cache negative lookups if InteropModeShared is in effect (since
// this makes remote lookup unavoidable), or if d.isSynthetic() (in which
// case the only files in the directory are those for which a dentry exists
// in d.children). Instead, just delete any previously-cached dentry.
if d.fs.opts.interop == InteropModeShared || d.isSynthetic() {
delete(d.children, name)
return
}
if d.children == nil {
d.children = make(map[string]*dentry)
}
d.children[name] = nil
}
// createSyntheticDirectory creates a synthetic directory with the given name
// in d.
//
// Preconditions: d.dirMu must be locked. d.isDir(). d does not already contain
// a child with the given name.
func (d *dentry) createSyntheticDirectoryLocked(name string, mode linux.FileMode, kuid auth.KUID, kgid auth.KGID) {
d2 := &dentry{
refs: 1, // held by d
fs: d.fs,
mode: uint32(mode) | linux.S_IFDIR,
uid: uint32(kuid),
gid: uint32(kgid),
blockSize: usermem.PageSize, // arbitrary
handle: handle{
fd: -1,
},
}
d2.pf.dentry = d2
d2.vfsd.Init(d2)
d.cacheNewChildLocked(d2, name)
d.syntheticChildren++
}
type directoryFD struct {
fileDescription
vfs.DirectoryFileDescriptionDefaultImpl
@@ -77,7 +110,7 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
fd.dirents = ds
}
if d.fs.opts.interop != InteropModeShared {
if d.cachedMetadataAuthoritative() {
d.touchAtime(fd.vfsfd.Mount())
}
@@ -108,10 +141,10 @@ func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
// filesystem.renameMu is needed for d.parent, and must be locked before
// dentry.dirMu.
d.fs.renameMu.RLock()
defer d.fs.renameMu.RUnlock()
d.dirMu.Lock()
defer d.dirMu.Unlock()
if d.dirents != nil {
d.fs.renameMu.RUnlock()
return d.dirents, nil
}
@@ -132,51 +165,81 @@ func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
NextOff: 2,
},
}
d.fs.renameMu.RUnlock()
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
var realChildren map[string]struct{}
if !d.isSynthetic() {
if d.syntheticChildren != 0 && d.fs.opts.interop == InteropModeShared {
// Record the set of children d actually has so that we don't emit
// duplicate entries for synthetic children.
realChildren = make(map[string]struct{})
}
if len(p9ds) == 0 {
// Cache dirents for future directoryFDs if permitted.
if d.fs.opts.interop != InteropModeShared {
d.dirents = dirents
off := uint64(0)
const count = 64 * 1024 // for consistency with the vfs1 client
d.handleMu.RLock()
if !d.handleReadable {
// This should not be possible because a readable handle should
// have been opened when the calling directoryFD was opened.
d.handleMu.RUnlock()
panic("gofer.dentry.getDirents called without a readable handle")
}
for {
p9ds, err := d.handle.file.readdir(ctx, off, count)
if err != nil {
d.handleMu.RUnlock()
return nil, err
}
return dirents, nil
if len(p9ds) == 0 {
d.handleMu.RUnlock()
break
}
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)
if realChildren != nil {
realChildren[p9d.Name] = struct{}{}
}
}
off = p9ds[len(p9ds)-1].Offset
}
for _, p9d := range p9ds {
if p9d.Name == "." || p9d.Name == ".." {
}
// Emit entries for synthetic children.
if d.syntheticChildren != 0 {
for _, child := range d.children {
if child == nil || !child.isSynthetic() {
continue
}
dirent := vfs.Dirent{
Name: p9d.Name,
Ino: p9d.QID.Path,
if _, ok := realChildren[child.name]; ok {
continue
}
dirents = append(dirents, vfs.Dirent{
Name: child.name,
Type: uint8(atomic.LoadUint32(&child.mode) >> 12),
Ino: child.ino,
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
}
// Cache dirents for future directoryFDs if permitted.
if d.cachedMetadataAuthoritative() {
d.dirents = dirents
}
return dirents, nil
}
// Seek implements vfs.FileDescriptionImpl.Seek.
File diff suppressed because it is too large Load Diff
+114 -63
View File
@@ -27,8 +27,9 @@
// dentry.handleMu
// dentry.dataMu
//
// Locking dentry.dirMu in multiple dentries requires holding
// filesystem.renameMu for writing.
// Locking dentry.dirMu in multiple dentries requires that either ancestor
// dentries are locked before descendant dentries, or that filesystem.renameMu
// is locked for writing.
package gofer
import (
@@ -102,11 +103,12 @@ type filesystem struct {
cachedDentries dentryList
cachedDentriesLen uint64
// dentries contains all dentries in this filesystem. specialFileFDs
// contains all open specialFileFDs. These fields are protected by syncMu.
syncMu sync.Mutex
dentries map[*dentry]struct{}
specialFileFDs map[*specialFileFD]struct{}
// syncableDentries contains all dentries in this filesystem for which
// !dentry.file.isNil(). specialFileFDs contains all open specialFileFDs.
// These fields are protected by syncMu.
syncMu sync.Mutex
syncableDentries map[*dentry]struct{}
specialFileFDs map[*specialFileFD]struct{}
}
type filesystemOptions struct {
@@ -187,7 +189,8 @@ const (
// InteropModeShared is appropriate when there are users of the remote
// filesystem that may mutate its state other than the client.
//
// - The client must verify cached filesystem state before using it.
// - The client must verify ("revalidate") cached filesystem state before
// using it.
//
// - Client changes to filesystem state must be sent to the remote
// filesystem synchronously.
@@ -376,14 +379,14 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
// Construct the filesystem object.
fs := &filesystem{
mfp: mfp,
opts: fsopts,
uid: creds.EffectiveKUID,
gid: creds.EffectiveKGID,
client: client,
clock: ktime.RealtimeClockFromContext(ctx),
dentries: make(map[*dentry]struct{}),
specialFileFDs: make(map[*specialFileFD]struct{}),
mfp: mfp,
opts: fsopts,
uid: creds.EffectiveKUID,
gid: creds.EffectiveKGID,
client: client,
clock: ktime.RealtimeClockFromContext(ctx),
syncableDentries: make(map[*dentry]struct{}),
specialFileFDs: make(map[*specialFileFD]struct{}),
}
fs.vfsfs.Init(vfsObj, &fstype, fs)
@@ -409,7 +412,7 @@ func (fs *filesystem) Release() {
mf := fs.mfp.MemoryFile()
fs.syncMu.Lock()
for d := range fs.dentries {
for d := range fs.syncableDentries {
d.handleMu.Lock()
d.dataMu.Lock()
if d.handleWritable {
@@ -444,9 +447,11 @@ type dentry struct {
vfsd vfs.Dentry
// refs is the reference count. Each dentry holds a reference on its
// parent, even if disowned. refs is accessed using atomic memory
// operations. When refs reaches 0, the dentry may be added to the cache or
// destroyed. If refs==-1 the dentry has already been destroyed.
// parent, even if disowned. An additional reference is held on all
// synthetic dentries until they are unlinked or invalidated. When refs
// reaches 0, the dentry may be added to the cache or destroyed. If refs ==
// -1, the dentry has already been destroyed. refs is accessed using atomic
// memory operations.
refs int64
// fs is the owning filesystem. fs is immutable.
@@ -465,6 +470,12 @@ type dentry struct {
// We don't support hard links, so each dentry maps 1:1 to an inode.
// file is the unopened p9.File that backs this dentry. file is immutable.
//
// If file.isNil(), this dentry represents a synthetic file, i.e. a file
// that does not exist on the remote filesystem. As of this writing, this
// is only possible for a directory created with
// MkdirOptions.ForSyntheticMountpoint == true.
// TODO(gvisor.dev/issue/1476): Support synthetic sockets (and pipes).
file p9file
// If deleted is non-zero, the file represented by this dentry has been
@@ -484,15 +495,21 @@ type dentry struct {
// - Mappings of child filenames to dentries representing those children.
//
// - Mappings of child filenames that are known not to exist to nil
// dentries (only if InteropModeShared is not in effect).
// dentries (only if InteropModeShared is not in effect and the directory
// is not synthetic).
//
// children is protected by dirMu.
children map[string]*dentry
// If this dentry represents a directory, InteropModeShared is not in
// effect, and dirents is not nil, it is a cache of all entries in the
// directory, in the order they were returned by the server. dirents is
// protected by dirMu.
// If this dentry represents a directory, syntheticChildren is the number
// of child dentries for which dentry.isSynthetic() == true.
// syntheticChildren is protected by dirMu.
syntheticChildren int
// If this dentry represents a directory,
// dentry.cachedMetadataAuthoritative() == true, and dirents is not nil, it
// is a cache of all entries in the directory, in the order they were
// returned by the server. dirents is protected by dirMu.
dirents []vfs.Dirent
// Cached metadata; protected by metadataMu and accessed using atomic
@@ -589,6 +606,8 @@ func dentryAttrMask() p9.AttrMask {
// initially has no references, but is not cached; it is the caller's
// responsibility to set the dentry's reference count and/or call
// dentry.checkCachingLocked() as appropriate.
//
// Preconditions: !file.isNil().
func (fs *filesystem) newDentry(ctx context.Context, file p9file, qid p9.QID, mask p9.AttrMask, attr *p9.Attr) (*dentry, error) {
if !mask.Mode {
ctx.Warningf("can't create gofer.dentry without file type")
@@ -612,10 +631,10 @@ func (fs *filesystem) newDentry(ctx context.Context, file p9file, qid p9.QID, ma
},
}
d.pf.dentry = d
if mask.UID {
if mask.UID && attr.UID != auth.NoID {
d.uid = uint32(attr.UID)
}
if mask.GID {
if mask.GID && attr.GID != auth.NoID {
d.gid = uint32(attr.GID)
}
if mask.Size {
@@ -642,11 +661,19 @@ func (fs *filesystem) newDentry(ctx context.Context, file p9file, qid p9.QID, ma
d.vfsd.Init(d)
fs.syncMu.Lock()
fs.dentries[d] = struct{}{}
fs.syncableDentries[d] = struct{}{}
fs.syncMu.Unlock()
return d, nil
}
func (d *dentry) isSynthetic() bool {
return d.file.isNil()
}
func (d *dentry) cachedMetadataAuthoritative() bool {
return d.fs.opts.interop != InteropModeShared || d.isSynthetic()
}
// updateFromP9Attrs is called to update d's metadata after an update from the
// remote filesystem.
func (d *dentry) updateFromP9Attrs(mask p9.AttrMask, attr *p9.Attr) {
@@ -691,6 +718,7 @@ func (d *dentry) updateFromP9Attrs(mask p9.AttrMask, attr *p9.Attr) {
d.metadataMu.Unlock()
}
// Preconditions: !d.isSynthetic()
func (d *dentry) updateFromGetattr(ctx context.Context) error {
// Use d.handle.file, which represents a 9P fid that has been opened, in
// preference to d.file, which represents a 9P fid that has not. This may
@@ -758,7 +786,7 @@ func (d *dentry) setStat(ctx context.Context, creds *auth.Credentials, stat *lin
defer mnt.EndWrite()
setLocalAtime := false
setLocalMtime := false
if d.fs.opts.interop != InteropModeShared {
if d.cachedMetadataAuthoritative() {
// Timestamp updates will be handled locally.
setLocalAtime = stat.Mask&linux.STATX_ATIME != 0
setLocalMtime = stat.Mask&linux.STATX_MTIME != 0
@@ -771,35 +799,37 @@ func (d *dentry) setStat(ctx context.Context, creds *auth.Credentials, stat *lin
}
d.metadataMu.Lock()
defer d.metadataMu.Unlock()
if stat.Mask != 0 {
if err := d.file.setAttr(ctx, p9.SetAttrMask{
Permissions: stat.Mask&linux.STATX_MODE != 0,
UID: stat.Mask&linux.STATX_UID != 0,
GID: stat.Mask&linux.STATX_GID != 0,
Size: stat.Mask&linux.STATX_SIZE != 0,
ATime: stat.Mask&linux.STATX_ATIME != 0,
MTime: stat.Mask&linux.STATX_MTIME != 0,
ATimeNotSystemTime: stat.Atime.Nsec != linux.UTIME_NOW,
MTimeNotSystemTime: stat.Mtime.Nsec != linux.UTIME_NOW,
}, p9.SetAttr{
Permissions: p9.FileMode(stat.Mode),
UID: p9.UID(stat.UID),
GID: p9.GID(stat.GID),
Size: stat.Size,
ATimeSeconds: uint64(stat.Atime.Sec),
ATimeNanoSeconds: uint64(stat.Atime.Nsec),
MTimeSeconds: uint64(stat.Mtime.Sec),
MTimeNanoSeconds: uint64(stat.Mtime.Nsec),
}); err != nil {
return err
if !d.isSynthetic() {
if stat.Mask != 0 {
if err := d.file.setAttr(ctx, p9.SetAttrMask{
Permissions: stat.Mask&linux.STATX_MODE != 0,
UID: stat.Mask&linux.STATX_UID != 0,
GID: stat.Mask&linux.STATX_GID != 0,
Size: stat.Mask&linux.STATX_SIZE != 0,
ATime: stat.Mask&linux.STATX_ATIME != 0,
MTime: stat.Mask&linux.STATX_MTIME != 0,
ATimeNotSystemTime: stat.Atime.Nsec != linux.UTIME_NOW,
MTimeNotSystemTime: stat.Mtime.Nsec != linux.UTIME_NOW,
}, p9.SetAttr{
Permissions: p9.FileMode(stat.Mode),
UID: p9.UID(stat.UID),
GID: p9.GID(stat.GID),
Size: stat.Size,
ATimeSeconds: uint64(stat.Atime.Sec),
ATimeNanoSeconds: uint64(stat.Atime.Nsec),
MTimeSeconds: uint64(stat.Mtime.Sec),
MTimeNanoSeconds: uint64(stat.Mtime.Nsec),
}); err != nil {
return err
}
}
if d.fs.opts.interop == InteropModeShared {
// There's no point to updating d's metadata in this case since
// it'll be overwritten by revalidation before the next time it's
// used anyway. (InteropModeShared inhibits client caching of
// regular file data, so there's no cache to truncate either.)
return nil
}
}
if d.fs.opts.interop == InteropModeShared {
// There's no point to updating d's metadata in this case since it'll
// be overwritten by revalidation before the next time it's used
// anyway. (InteropModeShared inhibits client caching of regular file
// data, so there's no cache to truncate either.)
return nil
}
now := d.fs.clock.Now().Nanoseconds()
if stat.Mask&linux.STATX_MODE != 0 {
@@ -897,6 +927,15 @@ func (d *dentry) DecRef() {
}
}
// decRefLocked decrements d's reference count without calling
// d.checkCachingLocked, even if d's reference count reaches 0; callers are
// responsible for ensuring that d.checkCachingLocked will be called later.
func (d *dentry) decRefLocked() {
if refs := atomic.AddInt64(&d.refs, -1); refs < 0 {
panic("gofer.dentry.decRefLocked() called without holding a reference")
}
}
// checkCachingLocked should be called after d's reference count becomes 0 or it
// becomes disowned.
//
@@ -1013,11 +1052,11 @@ func (d *dentry) destroyLocked() {
if !d.file.isNil() {
d.file.close(ctx)
d.file = p9file{}
// Remove d from the set of syncable dentries.
d.fs.syncMu.Lock()
delete(d.fs.syncableDentries, d)
d.fs.syncMu.Unlock()
}
// Remove d from the set of all dentries.
d.fs.syncMu.Lock()
delete(d.fs.dentries, d)
d.fs.syncMu.Unlock()
// Drop the reference held by d on its parent without recursively locking
// d.fs.renameMu.
if d.parent != nil {
@@ -1040,6 +1079,9 @@ func (d *dentry) setDeleted() {
// We only support xattrs prefixed with "user." (see b/148380782). Currently,
// there is no need to expose any other xattrs through a gofer.
func (d *dentry) listxattr(ctx context.Context, creds *auth.Credentials, size uint64) ([]string, error) {
if d.file.isNil() {
return nil, nil
}
xattrMap, err := d.file.listXattr(ctx, size)
if err != nil {
return nil, err
@@ -1054,6 +1096,9 @@ func (d *dentry) listxattr(ctx context.Context, creds *auth.Credentials, size ui
}
func (d *dentry) getxattr(ctx context.Context, creds *auth.Credentials, opts *vfs.GetxattrOptions) (string, error) {
if d.file.isNil() {
return "", syserror.ENODATA
}
if err := d.checkPermissions(creds, vfs.MayRead); err != nil {
return "", err
}
@@ -1064,6 +1109,9 @@ func (d *dentry) getxattr(ctx context.Context, creds *auth.Credentials, opts *vf
}
func (d *dentry) setxattr(ctx context.Context, creds *auth.Credentials, opts *vfs.SetxattrOptions) error {
if d.file.isNil() {
return syserror.EPERM
}
if err := d.checkPermissions(creds, vfs.MayWrite); err != nil {
return err
}
@@ -1074,6 +1122,9 @@ func (d *dentry) setxattr(ctx context.Context, creds *auth.Credentials, opts *vf
}
func (d *dentry) removexattr(ctx context.Context, creds *auth.Credentials, name string) error {
if d.file.isNil() {
return syserror.EPERM
}
if err := d.checkPermissions(creds, vfs.MayWrite); err != nil {
return err
}
@@ -1083,7 +1134,7 @@ func (d *dentry) removexattr(ctx context.Context, creds *auth.Credentials, name
return d.file.removeXattr(ctx, name)
}
// Preconditions: d.isRegularFile() || d.isDirectory().
// Preconditions: !d.file.isNil(). d.isRegularFile() || d.isDirectory().
func (d *dentry) ensureSharedHandle(ctx context.Context, read, write, trunc bool) error {
// O_TRUNC unconditionally requires us to obtain a new handle (opened with
// O_TRUNC).
@@ -1213,7 +1264,7 @@ func (fd *fileDescription) dentry() *dentry {
func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
d := fd.dentry()
const validMask = uint32(linux.STATX_MODE | linux.STATX_UID | linux.STATX_GID | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME | linux.STATX_SIZE | linux.STATX_BLOCKS | linux.STATX_BTIME)
if d.fs.opts.interop == InteropModeShared && opts.Mask&(validMask) != 0 && opts.Sync != linux.AT_STATX_DONT_SYNC {
if !d.cachedMetadataAuthoritative() && opts.Mask&validMask != 0 && opts.Sync != linux.AT_STATX_DONT_SYNC {
// TODO(jamieliu): Use specialFileFD.handle.file for the getattr if
// available?
if err := d.updateFromGetattr(ctx); err != nil {
+1 -1
View File
@@ -24,7 +24,7 @@ import (
func TestDestroyIdempotent(t *testing.T) {
fs := filesystem{
dentries: make(map[*dentry]struct{}),
syncableDentries: make(map[*dentry]struct{}),
opts: filesystemOptions{
// Test relies on no dentry being held in the cache.
maxCachedDentries: 0,
+1 -2
View File
@@ -443,8 +443,7 @@ type FilesystemImpl interface {
// Errors:
//
// - If extended attributes are not supported by the filesystem,
// ListxattrAt returns nil. (See FileDescription.Listxattr for an
// explanation.)
// ListxattrAt returns ENOTSUP.
//
// - If the size of the list (including a NUL terminating byte after every
// entry) would exceed size, ERANGE may be returned. Note that
+19
View File
@@ -33,6 +33,25 @@ type GetDentryOptions struct {
type MkdirOptions struct {
// Mode is the file mode bits for the created directory.
Mode linux.FileMode
// If ForSyntheticMountpoint is true, FilesystemImpl.MkdirAt() may create
// the given directory in memory only (as opposed to persistent storage).
// The created directory should be able to support the creation of
// subdirectories with ForSyntheticMountpoint == true. It does not need to
// support the creation of subdirectories with ForSyntheticMountpoint ==
// false, or files of other types.
//
// FilesystemImpls are permitted to ignore the ForSyntheticMountpoint
// option.
//
// The ForSyntheticMountpoint option exists because, unlike mount(2), the
// OCI Runtime Specification permits the specification of mount points that
// do not exist, under the expectation that container runtimes will create
// them. (More accurately, the OCI Runtime Specification completely fails
// to document this feature, but it's implemented by runc.)
// ForSyntheticMountpoint allows such mount points to be created even when
// the underlying persistent filesystem is immutable.
ForSyntheticMountpoint bool
}
// MknodOptions contains options to VirtualFilesystem.MknodAt() and
+6
View File
@@ -251,6 +251,12 @@ func (c *containerMounter) mountSubmountVFS2(ctx context.Context, conf *Config,
// All writes go to upper, be paranoid and make lower readonly.
opts.ReadOnly = useOverlay
if err := c.k.VFS().MkdirAt(ctx, creds, target, &vfs.MkdirOptions{
ForSyntheticMountpoint: true,
}); err != nil && err != syserror.EEXIST {
// Log a warning, but attempt the mount anyway.
log.Warningf("Failed to create mount point at %q: %v", submount.Destination, err)
}
if err := c.k.VFS().MountAt(ctx, creds, "", target, submount.Type, opts); err != nil {
return fmt.Errorf("failed to mount %q (type: %s): %w, opts: %v", submount.Destination, submount.Type, err, opts)
}