mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Fixes for procfs
- Return ENOENT for /proc/[pid]/task if task is zoombied or terminated - Allow directory to be Seek() to the end - Construct synthetic files for /proc/[pid]/ns/* - Changed GenericDirectoryFD.Init to not register with FileDescription, otherwise other implementation cannot change behavior. Updates #1195,1193 PiperOrigin-RevId: 308294649
This commit is contained in:
committed by
gVisor bot
parent
1b88c63b3e
commit
2cc0fd42f4
@@ -161,8 +161,10 @@ func (i *rootInode) masterClose(t *Terminal) {
|
||||
|
||||
// Open implements kernfs.Inode.Open.
|
||||
func (i *rootInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package kernfs
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
@@ -43,15 +45,27 @@ type GenericDirectoryFD struct {
|
||||
off int64
|
||||
}
|
||||
|
||||
// Init initializes a GenericDirectoryFD.
|
||||
func (fd *GenericDirectoryFD) Init(m *vfs.Mount, d *vfs.Dentry, children *OrderedChildren, opts *vfs.OpenOptions) error {
|
||||
// NewGenericDirectoryFD creates a new GenericDirectoryFD and returns its
|
||||
// dentry.
|
||||
func NewGenericDirectoryFD(m *vfs.Mount, d *vfs.Dentry, children *OrderedChildren, opts *vfs.OpenOptions) (*GenericDirectoryFD, error) {
|
||||
fd := &GenericDirectoryFD{}
|
||||
if err := fd.Init(children, opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := fd.vfsfd.Init(fd, opts.Flags, m, d, &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// Init initializes a GenericDirectoryFD. Use it when overriding
|
||||
// GenericDirectoryFD. Caller must call fd.VFSFileDescription.Init() with the
|
||||
// correct implementation.
|
||||
func (fd *GenericDirectoryFD) Init(children *OrderedChildren, opts *vfs.OpenOptions) error {
|
||||
if vfs.AccessTypesForOpenFlags(opts)&vfs.MayWrite != 0 {
|
||||
// Can't open directories for writing.
|
||||
return syserror.EISDIR
|
||||
}
|
||||
if err := fd.vfsfd.Init(fd, opts.Flags, m, d, &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
fd.children = children
|
||||
return nil
|
||||
}
|
||||
@@ -187,6 +201,10 @@ func (fd *GenericDirectoryFD) Seek(ctx context.Context, offset int64, whence int
|
||||
// Use offset as given.
|
||||
case linux.SEEK_CUR:
|
||||
offset += fd.off
|
||||
case linux.SEEK_END:
|
||||
// TODO(gvisor.dev/issue/1193): This can prevent new files from showing up
|
||||
// if they are added after SEEK_END.
|
||||
offset = math.MaxInt64
|
||||
default:
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ afterSymlink:
|
||||
}
|
||||
// Resolve any symlink at current path component.
|
||||
if rp.ShouldFollowSymlink() && next.isSymlink() {
|
||||
targetVD, targetPathname, err := next.inode.Getlink(ctx)
|
||||
targetVD, targetPathname, err := next.inode.Getlink(ctx, rp.Mount())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -477,7 +477,7 @@ afterTrailingSymlink:
|
||||
}
|
||||
child := childVFSD.Impl().(*Dentry)
|
||||
if rp.ShouldFollowSymlink() && child.isSymlink() {
|
||||
targetVD, targetPathname, err := child.inode.Getlink(ctx)
|
||||
targetVD, targetPathname, err := child.inode.Getlink(ctx, rp.Mount())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func (InodeNotSymlink) Readlink(context.Context) (string, error) {
|
||||
}
|
||||
|
||||
// Getlink implements Inode.Getlink.
|
||||
func (InodeNotSymlink) Getlink(context.Context) (vfs.VirtualDentry, string, error) {
|
||||
func (InodeNotSymlink) Getlink(context.Context, *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
return vfs.VirtualDentry{}, "", syserror.EINVAL
|
||||
}
|
||||
|
||||
@@ -568,8 +568,10 @@ func (s *StaticDirectory) Init(creds *auth.Credentials, ino uint64, perm linux.F
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (s *StaticDirectory) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &s.OrderedChildren, &opts)
|
||||
fd, err := NewGenericDirectoryFD(rp.Mount(), vfsd, &s.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -439,5 +439,5 @@ type inodeSymlink interface {
|
||||
//
|
||||
// - If the inode is not a symlink, Getlink returns (zero-value
|
||||
// VirtualDentry, "", EINVAL).
|
||||
Getlink(ctx context.Context) (vfs.VirtualDentry, string, error)
|
||||
Getlink(ctx context.Context, mnt *vfs.Mount) (vfs.VirtualDentry, string, error)
|
||||
}
|
||||
|
||||
@@ -117,8 +117,8 @@ func (fs *filesystem) newReadonlyDir(creds *auth.Credentials, mode linux.FileMod
|
||||
}
|
||||
|
||||
func (d *readonlyDir) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
if err := fd.Init(rp.Mount(), vfsd, &d.OrderedChildren, &opts); err != nil {
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &d.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
@@ -147,8 +147,10 @@ func (fs *filesystem) newDir(creds *auth.Credentials, mode linux.FileMode, conte
|
||||
}
|
||||
|
||||
func (d *dir) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &d.OrderedChildren, &opts)
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &d.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (s *StaticSymlink) Readlink(_ context.Context) (string, error) {
|
||||
}
|
||||
|
||||
// Getlink implements Inode.Getlink.
|
||||
func (s *StaticSymlink) Getlink(_ context.Context) (vfs.VirtualDentry, string, error) {
|
||||
func (s *StaticSymlink) Getlink(context.Context, *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
return vfs.VirtualDentry{}, s.target, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,9 @@ func (i *subtasksInode) IterDirents(ctx context.Context, cb vfs.IterDirentsCallb
|
||||
if len(tasks) == 0 {
|
||||
return offset, syserror.ENOENT
|
||||
}
|
||||
if relOffset >= int64(len(tasks)) {
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
tids := make([]int, 0, len(tasks))
|
||||
for _, tid := range tasks {
|
||||
@@ -110,10 +113,52 @@ func (i *subtasksInode) IterDirents(ctx context.Context, cb vfs.IterDirentsCallb
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
type subtasksFD struct {
|
||||
kernfs.GenericDirectoryFD
|
||||
|
||||
task *kernel.Task
|
||||
}
|
||||
|
||||
func (fd *subtasksFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback) error {
|
||||
if fd.task.ExitState() >= kernel.TaskExitZombie {
|
||||
return syserror.ENOENT
|
||||
}
|
||||
return fd.GenericDirectoryFD.IterDirents(ctx, cb)
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDecriptionImpl.Seek.
|
||||
func (fd *subtasksFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
if fd.task.ExitState() >= kernel.TaskExitZombie {
|
||||
return 0, syserror.ENOENT
|
||||
}
|
||||
return fd.GenericDirectoryFD.Seek(ctx, offset, whence)
|
||||
}
|
||||
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
func (fd *subtasksFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
if fd.task.ExitState() >= kernel.TaskExitZombie {
|
||||
return linux.Statx{}, syserror.ENOENT
|
||||
}
|
||||
return fd.GenericDirectoryFD.Stat(ctx, opts)
|
||||
}
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *subtasksFD) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
if fd.task.ExitState() >= kernel.TaskExitZombie {
|
||||
return syserror.ENOENT
|
||||
}
|
||||
return fd.GenericDirectoryFD.SetStat(ctx, opts)
|
||||
}
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (i *subtasksInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
fd := &subtasksFD{task: i.task}
|
||||
if err := fd.Init(&i.OrderedChildren, &opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := fd.VFSFileDescription().Init(fd, opts.Flags, rp.Mount(), vfsd, &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ type taskInode struct {
|
||||
var _ kernfs.Inode = (*taskInode)(nil)
|
||||
|
||||
func newTaskInode(inoGen InoGenerator, task *kernel.Task, pidns *kernel.PIDNamespace, isThreadGroup bool, cgroupControllers map[string]string) *kernfs.Dentry {
|
||||
// TODO(gvisor.dev/issue/164): Fail with ESRCH if task exited.
|
||||
contents := map[string]*kernfs.Dentry{
|
||||
"auxv": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &auxvData{task: task}),
|
||||
"cmdline": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &cmdlineData{task: task, arg: cmdlineDataArg}),
|
||||
@@ -102,8 +103,10 @@ func (i *taskInode) Valid(ctx context.Context) bool {
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (i *taskInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -143,8 +143,10 @@ func (i *fdDirInode) Lookup(ctx context.Context, name string) (*vfs.Dentry, erro
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (i *fdDirInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
@@ -207,7 +209,7 @@ func (s *fdSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
return s.task.Kernel().VFS().PathnameWithDeleted(ctx, root, file.VirtualDentry())
|
||||
}
|
||||
|
||||
func (s *fdSymlink) Getlink(ctx context.Context) (vfs.VirtualDentry, string, error) {
|
||||
func (s *fdSymlink) Getlink(ctx context.Context, mnt *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
file, _ := getTaskFD(s.task, s.fd)
|
||||
if file == nil {
|
||||
return vfs.VirtualDentry{}, "", syserror.ENOENT
|
||||
@@ -268,8 +270,10 @@ func (i *fdInfoDirInode) Lookup(ctx context.Context, name string) (*vfs.Dentry,
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (i *fdInfoDirInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -622,7 +622,7 @@ func (s *exeSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
}
|
||||
|
||||
// Getlink implements kernfs.Inode.Getlink.
|
||||
func (s *exeSymlink) Getlink(ctx context.Context) (vfs.VirtualDentry, string, error) {
|
||||
func (s *exeSymlink) Getlink(ctx context.Context, _ *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
if !kernel.ContextCanTrace(ctx, s.task, false) {
|
||||
return vfs.VirtualDentry{}, "", syserror.EACCES
|
||||
}
|
||||
@@ -754,9 +754,79 @@ func (s *namespaceSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
}
|
||||
|
||||
// Getlink implements Inode.Getlink.
|
||||
func (s *namespaceSymlink) Getlink(ctx context.Context) (vfs.VirtualDentry, string, error) {
|
||||
func (s *namespaceSymlink) Getlink(ctx context.Context, mnt *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
if err := checkTaskState(s.task); err != nil {
|
||||
return vfs.VirtualDentry{}, "", err
|
||||
}
|
||||
return s.StaticSymlink.Getlink(ctx)
|
||||
|
||||
// Create a synthetic inode to represent the namespace.
|
||||
dentry := &kernfs.Dentry{}
|
||||
dentry.Init(&namespaceInode{})
|
||||
vd := vfs.MakeVirtualDentry(mnt, dentry.VFSDentry())
|
||||
vd.IncRef()
|
||||
dentry.DecRef()
|
||||
return vd, "", nil
|
||||
}
|
||||
|
||||
// namespaceInode is a synthetic inode created to represent a namespace in
|
||||
// /proc/[pid]/ns/*.
|
||||
type namespaceInode struct {
|
||||
kernfs.InodeAttrs
|
||||
kernfs.InodeNoopRefCount
|
||||
kernfs.InodeNotDirectory
|
||||
kernfs.InodeNotSymlink
|
||||
}
|
||||
|
||||
var _ kernfs.Inode = (*namespaceInode)(nil)
|
||||
|
||||
// Init initializes a namespace inode.
|
||||
func (i *namespaceInode) Init(creds *auth.Credentials, ino uint64, perm linux.FileMode) {
|
||||
if perm&^linux.PermissionsMask != 0 {
|
||||
panic(fmt.Sprintf("Only permission mask must be set: %x", perm&linux.PermissionsMask))
|
||||
}
|
||||
i.InodeAttrs.Init(creds, ino, linux.ModeRegular|perm)
|
||||
}
|
||||
|
||||
// Open implements Inode.Open.
|
||||
func (i *namespaceInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &namespaceFD{inode: i}
|
||||
i.IncRef()
|
||||
if err := fd.vfsfd.Init(fd, opts.Flags, rp.Mount(), vfsd, &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
|
||||
// namespace FD is a synthetic file that represents a namespace in
|
||||
// /proc/[pid]/ns/*.
|
||||
type namespaceFD struct {
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
|
||||
vfsfd vfs.FileDescription
|
||||
inode *namespaceInode
|
||||
}
|
||||
|
||||
var _ vfs.FileDescriptionImpl = (*namespaceFD)(nil)
|
||||
|
||||
// Stat implements FileDescriptionImpl.
|
||||
func (fd *namespaceFD) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
vfs := fd.vfsfd.VirtualDentry().Mount().Filesystem()
|
||||
return fd.inode.Stat(vfs, opts)
|
||||
}
|
||||
|
||||
// SetStat implements FileDescriptionImpl.
|
||||
func (fd *namespaceFD) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
vfs := fd.vfsfd.VirtualDentry().Mount().Filesystem()
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
return fd.inode.SetStat(ctx, vfs, creds, opts)
|
||||
}
|
||||
|
||||
// Release implements FileDescriptionImpl.
|
||||
func (fd *namespaceFD) Release() {
|
||||
fd.inode.DecRef()
|
||||
}
|
||||
|
||||
// OnClose implements FileDescriptionImpl.
|
||||
func (*namespaceFD) OnClose(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -202,8 +202,10 @@ func (i *tasksInode) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (i *tasksInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &i.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ func (s *selfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
return strconv.FormatUint(uint64(tgid), 10), nil
|
||||
}
|
||||
|
||||
func (s *selfSymlink) Getlink(ctx context.Context) (vfs.VirtualDentry, string, error) {
|
||||
func (s *selfSymlink) Getlink(ctx context.Context, _ *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
target, err := s.Readlink(ctx)
|
||||
return vfs.VirtualDentry{}, target, err
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func (s *threadSelfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
return fmt.Sprintf("%d/task/%d", tgid, tid), nil
|
||||
}
|
||||
|
||||
func (s *threadSelfSymlink) Getlink(ctx context.Context) (vfs.VirtualDentry, string, error) {
|
||||
func (s *threadSelfSymlink) Getlink(ctx context.Context, _ *vfs.Mount) (vfs.VirtualDentry, string, error) {
|
||||
target, err := s.Readlink(ctx)
|
||||
return vfs.VirtualDentry{}, target, err
|
||||
}
|
||||
|
||||
@@ -106,8 +106,8 @@ func (*dir) SetStat(context.Context, *vfs.Filesystem, *auth.Credentials, vfs.Set
|
||||
|
||||
// Open implements kernfs.Inode.Open.
|
||||
func (d *dir) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
if err := fd.Init(rp.Mount(), vfsd, &d.OrderedChildren, &opts); err != nil {
|
||||
fd, err := kernfs.NewGenericDirectoryFD(rp.Mount(), vfsd, &d.OrderedChildren, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fd.VFSFileDescription(), nil
|
||||
|
||||
@@ -797,6 +797,14 @@ type VirtualDentry struct {
|
||||
dentry *Dentry
|
||||
}
|
||||
|
||||
// MakeVirtualDentry creates a VirtualDentry.
|
||||
func MakeVirtualDentry(mount *Mount, dentry *Dentry) VirtualDentry {
|
||||
return VirtualDentry{
|
||||
mount: mount,
|
||||
dentry: dentry,
|
||||
}
|
||||
}
|
||||
|
||||
// Ok returns true if vd is not empty. It does not require that a reference is
|
||||
// held.
|
||||
func (vd VirtualDentry) Ok() bool {
|
||||
|
||||
Reference in New Issue
Block a user