// 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. // This file implements vfs.FilesystemImpl for kernfs. package kernfs import ( "fmt" "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" ) // stepExistingLocked resolves rp.Component() in parent directory vfsd. // // stepExistingLocked is loosely analogous to fs/namei.c:walk_component(). // // 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) { d := vfsd.Impl().(*Dentry) if !d.isDir() { return nil, syserror.ENOTDIR } // Directory searchable? if err := d.inode.CheckPermissions(ctx, rp.Credentials(), vfs.MayExec); err != nil { return nil, err } afterSymlink: name := rp.Component() // Revalidation must be skipped if name is "." or ".."; d or its parent // respectively can't be expected to transition from invalidated back to // valid, so detecting invalidation and retrying would loop forever. This // is consistent with Linux: fs/namei.c:walk_component() => lookup_fast() // calls d_revalidate(), but walk_component() => handle_dots() does not. if name == "." { rp.Advance() return vfsd, nil } if name == ".." { nextVFSD, err := rp.ResolveParent(vfsd) if err != nil { return nil, err } rp.Advance() return nextVFSD, nil } d.dirMu.Lock() nextVFSD, err := rp.ResolveChild(vfsd, name) if err != nil { d.dirMu.Unlock() return nil, err } next, err := fs.revalidateChildLocked(ctx, rp.VirtualFilesystem(), d, name, nextVFSD) d.dirMu.Unlock() if err != nil { return nil, err } // Resolve any symlink at current path component. if rp.ShouldFollowSymlink() && next.isSymlink() { // TODO: VFS2 needs something extra for /proc/[pid]/fd/ "magic symlinks". target, err := next.inode.Readlink(ctx) if err != nil { return nil, err } if err := rp.HandleSymlink(target); err != nil { return nil, err } goto afterSymlink } rp.Advance() return &next.vfsd, nil } // revalidateChildLocked must be called after a call to parent.vfsd.Child(name) // or vfs.ResolvingPath.ResolveChild(name) returns childVFSD (which may be // nil) to verify that the returned child (or lack thereof) is correct. // // Preconditions: Filesystem.mu must be locked for at least reading. // parent.dirMu must be locked. parent.isDir(). name is not "." or "..". // // Postconditions: Caller must call fs.processDeferredDecRefs*. func (fs *Filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.VirtualFilesystem, parent *Dentry, name string, childVFSD *vfs.Dentry) (*Dentry, error) { if childVFSD != nil { // Cached dentry exists, revalidate. child := childVFSD.Impl().(*Dentry) if !child.inode.Valid(ctx) { vfsObj.ForceDeleteDentry(childVFSD) fs.deferDecRef(childVFSD) // Reference from Lookup. childVFSD = nil } } if childVFSD == nil { // Dentry isn't cached; it either doesn't exist or failed // revalidation. Attempt to resolve it via Lookup. // // FIXME(gvisor.dev/issue/1193): Inode.Lookup() should return // *(kernfs.)Dentry, not *vfs.Dentry, since (kernfs.)Filesystem assumes // that all dentries in the filesystem are (kernfs.)Dentry and performs // vfs.DentryImpl casts accordingly. var err error childVFSD, err = parent.inode.Lookup(ctx, name) if err != nil { return nil, err } // Reference on childVFSD dropped by a corresponding Valid. parent.insertChildLocked(name, childVFSD) } return childVFSD.Impl().(*Dentry), nil } // walkExistingLocked resolves rp to an existing file. // // walkExistingLocked is loosely analogous to Linux's // fs/namei.c:path_lookupat(). // // Preconditions: Filesystem.mu must be locked for at least reading. // // Postconditions: Caller must call fs.processDeferredDecRefs*. func (fs *Filesystem) walkExistingLocked(ctx context.Context, rp *vfs.ResolvingPath) (*vfs.Dentry, Inode, error) { vfsd := rp.Start() for !rp.Done() { var err error vfsd, err = fs.stepExistingLocked(ctx, rp, vfsd) if err != nil { return nil, nil, err } } d := vfsd.Impl().(*Dentry) if rp.MustBeDir() && !d.isDir() { return nil, nil, syserror.ENOTDIR } return vfsd, d.inode, nil } // walkParentDirLocked resolves all but the last path component of rp to an // existing directory. It does not check that the returned directory is // searchable by the provider of rp. // // walkParentDirLocked is loosely analogous to Linux's // fs/namei.c:path_parentat(). // // Preconditions: Filesystem.mu must be locked for at least reading. !rp.Done(). // // Postconditions: Caller must call fs.processDeferredDecRefs*. func (fs *Filesystem) walkParentDirLocked(ctx context.Context, rp *vfs.ResolvingPath) (*vfs.Dentry, Inode, error) { vfsd := rp.Start() for !rp.Final() { var err error vfsd, err = fs.stepExistingLocked(ctx, rp, vfsd) if err != nil { return nil, nil, err } } d := vfsd.Impl().(*Dentry) if !d.isDir() { return nil, nil, syserror.ENOTDIR } return vfsd, d.inode, nil } // checkCreateLocked checks that a file named rp.Component() may be created in // directory parentVFSD, then returns rp.Component(). // // Preconditions: Filesystem.mu must be locked for at least reading. parentInode // == parentVFSD.Impl().(*Dentry).Inode. isDir(parentInode) == true. func checkCreateLocked(ctx context.Context, rp *vfs.ResolvingPath, parentVFSD *vfs.Dentry, parentInode Inode) (string, error) { if err := parentInode.CheckPermissions(ctx, rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil { return "", err } pc := rp.Component() if pc == "." || pc == ".." { return "", syserror.EEXIST } childVFSD, err := rp.ResolveChild(parentVFSD, pc) if err != nil { return "", err } if childVFSD != nil { return "", syserror.EEXIST } if parentVFSD.IsDisowned() { return "", syserror.ENOENT } return pc, nil } // checkDeleteLocked checks that the file represented by vfsd may be deleted. // // Preconditions: Filesystem.mu must be locked for at least reading. func checkDeleteLocked(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentry) error { parentVFSD := vfsd.Parent() if parentVFSD == nil { return syserror.EBUSY } if parentVFSD.IsDisowned() { return syserror.ENOENT } if err := parentVFSD.Impl().(*Dentry).inode.CheckPermissions(ctx, rp.Credentials(), vfs.MayWrite|vfs.MayExec); err != nil { return err } return nil } // Release implements vfs.FilesystemImpl.Release. func (fs *Filesystem) Release() { } // Sync implements vfs.FilesystemImpl.Sync. func (fs *Filesystem) Sync(ctx context.Context) error { // All filesystem state is in-memory. return nil } // GetDentryAt implements vfs.FilesystemImpl.GetDentryAt. func (fs *Filesystem) GetDentryAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.GetDentryOptions) (*vfs.Dentry, error) { fs.mu.RLock() defer fs.processDeferredDecRefs() defer fs.mu.RUnlock() vfsd, inode, err := fs.walkExistingLocked(ctx, rp) if err != nil { return nil, err } if opts.CheckSearchable { d := vfsd.Impl().(*Dentry) if !d.isDir() { return nil, syserror.ENOTDIR } if err := inode.CheckPermissions(ctx, rp.Credentials(), vfs.MayExec); err != nil { return nil, err } } vfsd.IncRef() // Ownership transferred to caller. return vfsd, nil } // GetParentDentryAt implements vfs.FilesystemImpl.GetParentDentryAt. func (fs *Filesystem) GetParentDentryAt(ctx context.Context, rp *vfs.ResolvingPath) (*vfs.Dentry, error) { fs.mu.RLock() defer fs.processDeferredDecRefs() defer fs.mu.RUnlock() vfsd, _, err := fs.walkParentDirLocked(ctx, rp) if err != nil { return nil, err } vfsd.IncRef() // Ownership transferred to caller. return vfsd, nil } // LinkAt implements vfs.FilesystemImpl.LinkAt. func (fs *Filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.VirtualDentry) error { if rp.Done() { return syserror.EEXIST } fs.mu.Lock() defer fs.mu.Unlock() parentVFSD, parentInode, err := fs.walkParentDirLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return err } pc, err := checkCreateLocked(ctx, rp, parentVFSD, parentInode) if err != nil { return err } if rp.Mount() != vd.Mount() { return syserror.EXDEV } if err := rp.Mount().CheckBeginWrite(); err != nil { return err } defer rp.Mount().EndWrite() d := vd.Dentry().Impl().(*Dentry) if d.isDir() { return syserror.EPERM } child, err := parentInode.NewLink(ctx, pc, d.inode) if err != nil { return err } parentVFSD.Impl().(*Dentry).InsertChild(pc, child) return nil } // MkdirAt implements vfs.FilesystemImpl.MkdirAt. func (fs *Filesystem) MkdirAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MkdirOptions) error { if rp.Done() { return syserror.EEXIST } fs.mu.Lock() defer fs.mu.Unlock() parentVFSD, parentInode, err := fs.walkParentDirLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return err } pc, err := checkCreateLocked(ctx, rp, parentVFSD, parentInode) if err != nil { return err } if err := rp.Mount().CheckBeginWrite(); err != nil { return err } defer rp.Mount().EndWrite() child, err := parentInode.NewDir(ctx, pc, opts) if err != nil { return err } parentVFSD.Impl().(*Dentry).InsertChild(pc, child) return nil } // MknodAt implements vfs.FilesystemImpl.MknodAt. func (fs *Filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.MknodOptions) error { if rp.Done() { return syserror.EEXIST } fs.mu.Lock() defer fs.mu.Unlock() parentVFSD, parentInode, err := fs.walkParentDirLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return err } pc, err := checkCreateLocked(ctx, rp, parentVFSD, parentInode) if err != nil { return err } if err := rp.Mount().CheckBeginWrite(); err != nil { return err } defer rp.Mount().EndWrite() new, err := parentInode.NewNode(ctx, pc, opts) if err != nil { return err } parentVFSD.Impl().(*Dentry).InsertChild(pc, new) return nil } // OpenAt implements vfs.FilesystemImpl.OpenAt. func (fs *Filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.OpenOptions) (*vfs.FileDescription, error) { // Filter out flags that are not supported by kernfs. O_DIRECTORY and // O_NOFOLLOW have no effect here (they're handled by VFS by setting // appropriate bits in rp), but are returned by // FileDescriptionImpl.StatusFlags(). opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC | linux.O_DIRECTORY | linux.O_NOFOLLOW ats := vfs.AccessTypesForOpenFlags(&opts) // Do not create new file. if opts.Flags&linux.O_CREAT == 0 { fs.mu.RLock() defer fs.processDeferredDecRefs() defer fs.mu.RUnlock() vfsd, inode, err := fs.walkExistingLocked(ctx, rp) if err != nil { return nil, err } if err := inode.CheckPermissions(ctx, rp.Credentials(), ats); err != nil { return nil, err } return inode.Open(rp, vfsd, opts) } // May create new file. mustCreate := opts.Flags&linux.O_EXCL != 0 vfsd := rp.Start() inode := vfsd.Impl().(*Dentry).inode fs.mu.Lock() defer fs.mu.Unlock() if rp.Done() { if rp.MustBeDir() { return nil, syserror.EISDIR } if mustCreate { return nil, syserror.EEXIST } if err := inode.CheckPermissions(ctx, rp.Credentials(), ats); err != nil { return nil, err } return inode.Open(rp, vfsd, opts) } afterTrailingSymlink: parentVFSD, parentInode, err := fs.walkParentDirLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return nil, err } // Check for search permission in the parent directory. if err := parentInode.CheckPermissions(ctx, rp.Credentials(), vfs.MayExec); err != nil { return nil, err } // Reject attempts to open directories with O_CREAT. if rp.MustBeDir() { return nil, syserror.EISDIR } pc := rp.Component() if pc == "." || pc == ".." { return nil, syserror.EISDIR } // Determine whether or not we need to create a file. childVFSD, err := rp.ResolveChild(parentVFSD, pc) if err != nil { return nil, err } if childVFSD == nil { // Already checked for searchability above; now check for writability. if err := parentInode.CheckPermissions(ctx, rp.Credentials(), vfs.MayWrite); err != nil { return nil, err } if err := rp.Mount().CheckBeginWrite(); err != nil { return nil, err } defer rp.Mount().EndWrite() // Create and open the child. child, err := parentInode.NewFile(ctx, pc, opts) if err != nil { return nil, err } parentVFSD.Impl().(*Dentry).InsertChild(pc, child) return child.Impl().(*Dentry).inode.Open(rp, child, opts) } // Open existing file or follow symlink. if mustCreate { return nil, syserror.EEXIST } childDentry := childVFSD.Impl().(*Dentry) childInode := childDentry.inode if rp.ShouldFollowSymlink() { if childDentry.isSymlink() { target, err := childInode.Readlink(ctx) if err != nil { return nil, err } if err := rp.HandleSymlink(target); err != nil { return nil, err } // rp.Final() may no longer be true since we now need to resolve the // symlink target. goto afterTrailingSymlink } } if err := childInode.CheckPermissions(ctx, rp.Credentials(), ats); err != nil { return nil, err } return childInode.Open(rp, childVFSD, opts) } // ReadlinkAt implements vfs.FilesystemImpl.ReadlinkAt. func (fs *Filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (string, error) { fs.mu.RLock() d, inode, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return "", err } if !d.Impl().(*Dentry).isSymlink() { return "", syserror.EINVAL } return inode.Readlink(ctx) } // RenameAt implements vfs.FilesystemImpl.RenameAt. func (fs *Filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldParentVD vfs.VirtualDentry, oldName string, opts vfs.RenameOptions) error { // Only RENAME_NOREPLACE is supported. if opts.Flags&^linux.RENAME_NOREPLACE != 0 { return syserror.EINVAL } noReplace := opts.Flags&linux.RENAME_NOREPLACE != 0 fs.mu.Lock() defer fs.mu.Lock() // Resolve the destination directory first to verify that it's on this // Mount. dstDirVFSD, dstDirInode, err := fs.walkParentDirLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return err } mnt := rp.Mount() if mnt != oldParentVD.Mount() { return syserror.EXDEV } if err := mnt.CheckBeginWrite(); err != nil { return err } defer mnt.EndWrite() srcDirVFSD := oldParentVD.Dentry() srcDir := srcDirVFSD.Impl().(*Dentry) srcDir.dirMu.Lock() src, err := fs.revalidateChildLocked(ctx, rp.VirtualFilesystem(), srcDir, oldName, srcDirVFSD.Child(oldName)) srcDir.dirMu.Unlock() fs.processDeferredDecRefsLocked() if err != nil { return err } srcVFSD := &src.vfsd // Can we remove the src dentry? if err := checkDeleteLocked(ctx, rp, srcVFSD); err != nil { return err } // Can we create the dst dentry? var dstVFSD *vfs.Dentry pc, err := checkCreateLocked(ctx, rp, dstDirVFSD, dstDirInode) switch err { case nil: // Ok, continue with rename as replacement. case syserror.EEXIST: if noReplace { // Won't overwrite existing node since RENAME_NOREPLACE was requested. return syserror.EEXIST } dstVFSD, err = rp.ResolveChild(dstDirVFSD, pc) if err != nil { panic(fmt.Sprintf("Child %q for parent Dentry %+v disappeared inside atomic section?", pc, dstDirVFSD)) } default: return err } mntns := vfs.MountNamespaceFromContext(ctx) defer mntns.DecRef() virtfs := rp.VirtualFilesystem() srcDirDentry := srcDirVFSD.Impl().(*Dentry) dstDirDentry := dstDirVFSD.Impl().(*Dentry) // We can't deadlock here due to lock ordering because we're protected from // concurrent renames by fs.mu held for writing. srcDirDentry.dirMu.Lock() defer srcDirDentry.dirMu.Unlock() dstDirDentry.dirMu.Lock() defer dstDirDentry.dirMu.Unlock() if err := virtfs.PrepareRenameDentry(mntns, srcVFSD, dstVFSD); err != nil { return err } srcDirInode := srcDirDentry.inode replaced, err := srcDirInode.Rename(ctx, srcVFSD.Name(), pc, srcVFSD, dstDirVFSD) if err != nil { virtfs.AbortRenameDentry(srcVFSD, dstVFSD) return err } virtfs.CommitRenameReplaceDentry(srcVFSD, dstDirVFSD, pc, replaced) return nil } // RmdirAt implements vfs.FilesystemImpl.RmdirAt. func (fs *Filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error { fs.mu.Lock() defer fs.mu.Unlock() vfsd, inode, err := fs.walkExistingLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return err } if err := rp.Mount().CheckBeginWrite(); err != nil { return err } defer rp.Mount().EndWrite() if err := checkDeleteLocked(ctx, rp, vfsd); err != nil { return err } if !vfsd.Impl().(*Dentry).isDir() { return syserror.ENOTDIR } if inode.HasChildren() { return syserror.ENOTEMPTY } virtfs := rp.VirtualFilesystem() parentDentry := vfsd.Parent().Impl().(*Dentry) parentDentry.dirMu.Lock() defer parentDentry.dirMu.Unlock() mntns := vfs.MountNamespaceFromContext(ctx) defer mntns.DecRef() if err := virtfs.PrepareDeleteDentry(mntns, vfsd); err != nil { return err } if err := parentDentry.inode.RmDir(ctx, rp.Component(), vfsd); err != nil { virtfs.AbortDeleteDentry(vfsd) return err } virtfs.CommitDeleteDentry(vfsd) return nil } // SetStatAt implements vfs.FilesystemImpl.SetStatAt. func (fs *Filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetStatOptions) error { fs.mu.RLock() _, inode, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return err } if opts.Stat.Mask == 0 { return nil } return inode.SetStat(fs.VFSFilesystem(), opts) } // StatAt implements vfs.FilesystemImpl.StatAt. func (fs *Filesystem) StatAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.StatOptions) (linux.Statx, error) { fs.mu.RLock() _, inode, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return linux.Statx{}, err } return inode.Stat(fs.VFSFilesystem()), nil } // StatFSAt implements vfs.FilesystemImpl.StatFSAt. func (fs *Filesystem) StatFSAt(ctx context.Context, rp *vfs.ResolvingPath) (linux.Statfs, error) { fs.mu.RLock() _, _, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return linux.Statfs{}, err } // TODO: actually implement statfs return linux.Statfs{}, syserror.ENOSYS } // SymlinkAt implements vfs.FilesystemImpl.SymlinkAt. func (fs *Filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, target string) error { if rp.Done() { return syserror.EEXIST } fs.mu.Lock() defer fs.mu.Unlock() parentVFSD, parentInode, err := fs.walkParentDirLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return err } pc, err := checkCreateLocked(ctx, rp, parentVFSD, parentInode) if err != nil { return err } if err := rp.Mount().CheckBeginWrite(); err != nil { return err } defer rp.Mount().EndWrite() child, err := parentInode.NewSymlink(ctx, pc, target) if err != nil { return err } parentVFSD.Impl().(*Dentry).InsertChild(pc, child) return nil } // UnlinkAt implements vfs.FilesystemImpl.UnlinkAt. func (fs *Filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error { fs.mu.Lock() defer fs.mu.Unlock() vfsd, _, err := fs.walkExistingLocked(ctx, rp) fs.processDeferredDecRefsLocked() if err != nil { return err } if err := rp.Mount().CheckBeginWrite(); err != nil { return err } defer rp.Mount().EndWrite() if err := checkDeleteLocked(ctx, rp, vfsd); err != nil { return err } if vfsd.Impl().(*Dentry).isDir() { return syserror.EISDIR } virtfs := rp.VirtualFilesystem() parentDentry := vfsd.Parent().Impl().(*Dentry) parentDentry.dirMu.Lock() defer parentDentry.dirMu.Unlock() mntns := vfs.MountNamespaceFromContext(ctx) defer mntns.DecRef() if err := virtfs.PrepareDeleteDentry(mntns, vfsd); err != nil { return err } if err := parentDentry.inode.Unlink(ctx, rp.Component(), vfsd); err != nil { virtfs.AbortDeleteDentry(vfsd) return err } virtfs.CommitDeleteDentry(vfsd) return nil } // ListxattrAt implements vfs.FilesystemImpl.ListxattrAt. func (fs *Filesystem) ListxattrAt(ctx context.Context, rp *vfs.ResolvingPath) ([]string, error) { fs.mu.RLock() _, _, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return nil, err } // kernfs currently does not support extended attributes. return nil, syserror.ENOTSUP } // GetxattrAt implements vfs.FilesystemImpl.GetxattrAt. func (fs *Filesystem) GetxattrAt(ctx context.Context, rp *vfs.ResolvingPath, name string) (string, error) { fs.mu.RLock() _, _, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return "", err } // kernfs currently does not support extended attributes. return "", syserror.ENOTSUP } // SetxattrAt implements vfs.FilesystemImpl.SetxattrAt. func (fs *Filesystem) SetxattrAt(ctx context.Context, rp *vfs.ResolvingPath, opts vfs.SetxattrOptions) error { fs.mu.RLock() _, _, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return err } // kernfs currently does not support extended attributes. return syserror.ENOTSUP } // RemovexattrAt implements vfs.FilesystemImpl.RemovexattrAt. func (fs *Filesystem) RemovexattrAt(ctx context.Context, rp *vfs.ResolvingPath, name string) error { fs.mu.RLock() _, _, err := fs.walkExistingLocked(ctx, rp) fs.mu.RUnlock() fs.processDeferredDecRefs() if err != nil { return err } // kernfs currently does not support extended attributes. return syserror.ENOTSUP } // PrependPath implements vfs.FilesystemImpl.PrependPath. func (fs *Filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error { fs.mu.RLock() defer fs.mu.RUnlock() return vfs.GenericPrependPath(vfsroot, vd, b) }