mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add vfs.MkdirOptions.ForSyntheticMountpoint.
PiperOrigin-RevId: 308143529
This commit is contained in:
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user