mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Port inotify to vfs2, with support in tmpfs.
Support in other filesystem impls is still needed. Unlike in Linux and vfs1, we need to plumb inotify down to each filesystem implementation in order to keep track of links/inode structures properly. IN_EXCL_UNLINK still needs to be implemented, as well as a few inotify hooks that are not present in either vfs1 or vfs2. Those will be addressed in subsequent changes. Updates #1479. PiperOrigin-RevId: 313781995
This commit is contained in:
@@ -60,3 +60,15 @@ func (d *dentry) DecRef() {
|
||||
// inode.decRef().
|
||||
d.inode.decRef()
|
||||
}
|
||||
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *dentry) Watches() *vfs.Watches {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1039,6 +1039,18 @@ func (d *dentry) decRefLocked() {
|
||||
}
|
||||
}
|
||||
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *dentry) Watches() *vfs.Watches {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkCachingLocked should be called after d's reference count becomes 0 or it
|
||||
// becomes disowned.
|
||||
//
|
||||
|
||||
@@ -225,6 +225,18 @@ func (d *Dentry) destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *Dentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *Dentry) Watches() *vfs.Watches {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertChild inserts child into the vfs dentry cache with the given name under
|
||||
// this dentry. This does not update the directory inode, so calling this on
|
||||
// it's own isn't sufficient to insert a child into a directory. InsertChild
|
||||
|
||||
@@ -59,6 +59,7 @@ go_library(
|
||||
"//pkg/sentry/pgalloc",
|
||||
"//pkg/sentry/platform",
|
||||
"//pkg/sentry/socket/unix/transport",
|
||||
"//pkg/sentry/uniqueid",
|
||||
"//pkg/sentry/usage",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/sentry/vfs/lock",
|
||||
|
||||
@@ -112,6 +112,7 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
|
||||
dir.iterMu.Lock()
|
||||
defer dir.iterMu.Unlock()
|
||||
|
||||
fd.dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
fd.inode().touchAtime(fd.vfsfd.Mount())
|
||||
|
||||
if fd.off == 0 {
|
||||
|
||||
@@ -177,6 +177,12 @@ func (fs *filesystem) doCreateAt(rp *vfs.ResolvingPath, dir bool, create func(pa
|
||||
if err := create(parentDir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ev := linux.IN_CREATE
|
||||
if dir {
|
||||
ev |= linux.IN_ISDIR
|
||||
}
|
||||
parentDir.inode.watches.Notify(name, uint32(ev), 0)
|
||||
parentDir.inode.touchCMtime()
|
||||
return nil
|
||||
}
|
||||
@@ -241,6 +247,7 @@ func (fs *filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs.
|
||||
return syserror.EMLINK
|
||||
}
|
||||
d.inode.incLinksLocked()
|
||||
d.inode.watches.Notify("", linux.IN_ATTRIB, 0)
|
||||
parentDir.insertChildLocked(fs.newDentry(d.inode), name)
|
||||
return nil
|
||||
})
|
||||
@@ -354,6 +361,7 @@ afterTrailingSymlink:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentDir.inode.watches.Notify(name, linux.IN_CREATE, 0)
|
||||
parentDir.inode.touchCMtime()
|
||||
return fd, nil
|
||||
}
|
||||
@@ -559,6 +567,8 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
|
||||
newParentDir.inode.touchCMtime()
|
||||
}
|
||||
renamed.inode.touchCtime()
|
||||
|
||||
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParentDir.inode.watches, &newParentDir.inode.watches, oldName, newName, renamed.inode.isDir())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -603,6 +613,7 @@ func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error
|
||||
return err
|
||||
}
|
||||
parentDir.removeChildLocked(child)
|
||||
parentDir.inode.watches.Notify(name, linux.IN_DELETE|linux.IN_ISDIR, 0)
|
||||
// Remove links for child, child/., and child/..
|
||||
child.inode.decLinksLocked()
|
||||
child.inode.decLinksLocked()
|
||||
@@ -620,7 +631,14 @@ func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.inode.setStat(ctx, rp.Credentials(), &opts.Stat)
|
||||
if err := d.inode.setStat(ctx, rp.Credentials(), &opts.Stat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ev := vfs.InotifyEventFromStatMask(opts.Stat.Mask); ev != 0 {
|
||||
d.InotifyWithParent(ev, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatAt implements vfs.FilesystemImpl.StatAt.
|
||||
@@ -700,6 +718,12 @@ func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error
|
||||
if err := vfsObj.PrepareDeleteDentry(mntns, &child.vfsd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate inotify events. Note that this must take place before the link
|
||||
// count of the child is decremented, or else the watches may be dropped
|
||||
// before these events are added.
|
||||
vfs.InotifyRemoveChild(&child.inode.watches, &parentDir.inode.watches, name)
|
||||
|
||||
parentDir.removeChildLocked(child)
|
||||
child.inode.decLinksLocked()
|
||||
vfsObj.CommitDeleteDentry(&child.vfsd)
|
||||
@@ -756,7 +780,12 @@ func (fs *filesystem) SetxattrAt(ctx context.Context, rp *vfs.ResolvingPath, opt
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.inode.setxattr(rp.Credentials(), &opts)
|
||||
if err := d.inode.setxattr(rp.Credentials(), &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovexattrAt implements vfs.FilesystemImpl.RemovexattrAt.
|
||||
@@ -767,7 +796,12 @@ func (fs *filesystem) RemovexattrAt(ctx context.Context, rp *vfs.ResolvingPath,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.inode.removexattr(rp.Credentials(), name)
|
||||
if err := d.inode.removexattr(rp.Credentials(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrependPath implements vfs.FilesystemImpl.PrependPath.
|
||||
|
||||
@@ -201,6 +201,26 @@ func (d *dentry) DecRef() {
|
||||
d.inode.decRef()
|
||||
}
|
||||
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32) {
|
||||
if d.inode.isDir() {
|
||||
events |= linux.IN_ISDIR
|
||||
}
|
||||
|
||||
// The ordering below is important, Linux always notifies the parent first.
|
||||
if d.parent != nil {
|
||||
// Note that d.parent or d.name may be stale if there is a concurrent
|
||||
// rename operation. Inotify does not provide consistency guarantees.
|
||||
d.parent.inode.watches.Notify(d.name, events, cookie)
|
||||
}
|
||||
d.inode.watches.Notify("", events, cookie)
|
||||
}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
func (d *dentry) Watches() *vfs.Watches {
|
||||
return &d.inode.watches
|
||||
}
|
||||
|
||||
// inode represents a filesystem object.
|
||||
type inode struct {
|
||||
// fs is the owning filesystem. fs is immutable.
|
||||
@@ -236,6 +256,9 @@ type inode struct {
|
||||
// Advisory file locks, which lock at the inode level.
|
||||
locks lock.FileLocks
|
||||
|
||||
// Inotify watches for this inode.
|
||||
watches vfs.Watches
|
||||
|
||||
impl interface{} // immutable
|
||||
}
|
||||
|
||||
@@ -257,6 +280,7 @@ func (i *inode) init(impl interface{}, fs *filesystem, creds *auth.Credentials,
|
||||
i.ctime = now
|
||||
i.mtime = now
|
||||
// i.nlink initialized by caller
|
||||
i.watches = vfs.Watches{}
|
||||
i.impl = impl
|
||||
}
|
||||
|
||||
@@ -307,6 +331,7 @@ func (i *inode) tryIncRef() bool {
|
||||
|
||||
func (i *inode) decRef() {
|
||||
if refs := atomic.AddInt64(&i.refs, -1); refs == 0 {
|
||||
i.watches.HandleDeletion()
|
||||
if regFile, ok := i.impl.(*regularFile); ok {
|
||||
// Release memory used by regFile to store data. Since regFile is
|
||||
// no longer usable, we don't need to grab any locks or update any
|
||||
@@ -628,8 +653,12 @@ func (fd *fileDescription) filesystem() *filesystem {
|
||||
return fd.vfsfd.Mount().Filesystem().Impl().(*filesystem)
|
||||
}
|
||||
|
||||
func (fd *fileDescription) dentry() *dentry {
|
||||
return fd.vfsfd.Dentry().Impl().(*dentry)
|
||||
}
|
||||
|
||||
func (fd *fileDescription) inode() *inode {
|
||||
return fd.vfsfd.Dentry().Impl().(*dentry).inode
|
||||
return fd.dentry().inode
|
||||
}
|
||||
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
@@ -642,7 +671,16 @@ func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linu
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
return fd.inode().setStat(ctx, creds, &opts.Stat)
|
||||
d := fd.dentry()
|
||||
if err := d.inode.setStat(ctx, creds, &opts.Stat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate inotify events.
|
||||
if ev := vfs.InotifyEventFromStatMask(opts.Stat.Mask); ev != 0 {
|
||||
d.InotifyWithParent(ev, 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Listxattr implements vfs.FileDescriptionImpl.Listxattr.
|
||||
@@ -657,12 +695,26 @@ func (fd *fileDescription) Getxattr(ctx context.Context, opts vfs.GetxattrOption
|
||||
|
||||
// Setxattr implements vfs.FileDescriptionImpl.Setxattr.
|
||||
func (fd *fileDescription) Setxattr(ctx context.Context, opts vfs.SetxattrOptions) error {
|
||||
return fd.inode().setxattr(auth.CredentialsFromContext(ctx), &opts)
|
||||
d := fd.dentry()
|
||||
if err := d.inode.setxattr(auth.CredentialsFromContext(ctx), &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate inotify events.
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removexattr implements vfs.FileDescriptionImpl.Removexattr.
|
||||
func (fd *fileDescription) Removexattr(ctx context.Context, name string) error {
|
||||
return fd.inode().removexattr(auth.CredentialsFromContext(ctx), name)
|
||||
d := fd.dentry()
|
||||
if err := d.inode.removexattr(auth.CredentialsFromContext(ctx), name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate inotify events.
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMemfd creates a new tmpfs regular file and file description that can back
|
||||
|
||||
@@ -152,7 +152,13 @@ func (f *FDTable) drop(file *fs.File) {
|
||||
// dropVFS2 drops the table reference.
|
||||
func (f *FDTable) dropVFS2(file *vfs.FileDescription) {
|
||||
// TODO(gvisor.dev/issue/1480): Release locks.
|
||||
// TODO(gvisor.dev/issue/1479): Send inotify events.
|
||||
|
||||
// Generate inotify events.
|
||||
ev := uint32(linux.IN_CLOSE_NOWRITE)
|
||||
if file.IsWritable() {
|
||||
ev = linux.IN_CLOSE_WRITE
|
||||
}
|
||||
file.Dentry().InotifyWithParent(ev, 0)
|
||||
|
||||
// Drop the table reference.
|
||||
file.DecRef()
|
||||
|
||||
@@ -12,6 +12,7 @@ go_library(
|
||||
"filesystem.go",
|
||||
"fscontext.go",
|
||||
"getdents.go",
|
||||
"inotify.go",
|
||||
"ioctl.go",
|
||||
"memfd.go",
|
||||
"mmap.go",
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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 vfs2
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
const allFlags = linux.IN_NONBLOCK | linux.IN_CLOEXEC
|
||||
|
||||
// InotifyInit1 implements the inotify_init1() syscalls.
|
||||
func InotifyInit1(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
flags := args[0].Int()
|
||||
if flags&^allFlags != 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
ino, err := vfs.NewInotifyFD(t, t.Kernel().VFS(), uint32(flags))
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer ino.DecRef()
|
||||
|
||||
fd, err := t.NewFDFromVFS2(0, ino, kernel.FDFlags{
|
||||
CloseOnExec: flags&linux.IN_CLOEXEC != 0,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return uintptr(fd), nil, nil
|
||||
}
|
||||
|
||||
// InotifyInit implements the inotify_init() syscalls.
|
||||
func InotifyInit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
args[0].Value = 0
|
||||
return InotifyInit1(t, args)
|
||||
}
|
||||
|
||||
// fdToInotify resolves an fd to an inotify object. If successful, the file will
|
||||
// have an extra ref and the caller is responsible for releasing the ref.
|
||||
func fdToInotify(t *kernel.Task, fd int32) (*vfs.Inotify, *vfs.FileDescription, error) {
|
||||
f := t.GetFileVFS2(fd)
|
||||
if f == nil {
|
||||
// Invalid fd.
|
||||
return nil, nil, syserror.EBADF
|
||||
}
|
||||
|
||||
ino, ok := f.Impl().(*vfs.Inotify)
|
||||
if !ok {
|
||||
// Not an inotify fd.
|
||||
f.DecRef()
|
||||
return nil, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
return ino, f, nil
|
||||
}
|
||||
|
||||
// InotifyAddWatch implements the inotify_add_watch() syscall.
|
||||
func InotifyAddWatch(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
fd := args[0].Int()
|
||||
addr := args[1].Pointer()
|
||||
mask := args[2].Uint()
|
||||
|
||||
// "EINVAL: The given event mask contains no valid events."
|
||||
// -- inotify_add_watch(2)
|
||||
if validBits := mask & linux.ALL_INOTIFY_BITS; validBits == 0 {
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
|
||||
// "IN_DONT_FOLLOW: Don't dereference pathname if it is a symbolic link."
|
||||
// -- inotify(7)
|
||||
follow := followFinalSymlink
|
||||
if mask&linux.IN_DONT_FOLLOW == 0 {
|
||||
follow = nofollowFinalSymlink
|
||||
}
|
||||
|
||||
ino, f, err := fdToInotify(t, fd)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer f.DecRef()
|
||||
|
||||
path, err := copyInPath(t, addr)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if mask&linux.IN_ONLYDIR != 0 {
|
||||
path.Dir = true
|
||||
}
|
||||
tpop, err := getTaskPathOperation(t, linux.AT_FDCWD, path, disallowEmptyPath, follow)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer tpop.Release()
|
||||
d, err := t.Kernel().VFS().GetDentryAt(t, t.Credentials(), &tpop.pop, &vfs.GetDentryOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer d.DecRef()
|
||||
|
||||
fd = ino.AddWatch(d.Dentry(), mask)
|
||||
return uintptr(fd), nil, err
|
||||
}
|
||||
|
||||
// InotifyRmWatch implements the inotify_rm_watch() syscall.
|
||||
func InotifyRmWatch(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
fd := args[0].Int()
|
||||
wd := args[1].Int()
|
||||
|
||||
ino, f, err := fdToInotify(t, fd)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer f.DecRef()
|
||||
return 0, nil, ino.RmWatch(wd)
|
||||
}
|
||||
@@ -93,11 +93,17 @@ func Readv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
|
||||
func read(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
n, err := file.Read(t, dst, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -128,6 +134,9 @@ func read(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, opt
|
||||
}
|
||||
file.EventUnregister(&w)
|
||||
|
||||
if total > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
|
||||
@@ -248,11 +257,17 @@ func Preadv2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca
|
||||
func pread(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
n, err := file.PRead(t, dst, offset, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -283,6 +298,9 @@ func pread(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, of
|
||||
}
|
||||
file.EventUnregister(&w)
|
||||
|
||||
if total > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
|
||||
@@ -345,11 +363,17 @@ func Writev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal
|
||||
func write(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
n, err := file.Write(t, src, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -380,6 +404,9 @@ func write(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, op
|
||||
}
|
||||
file.EventUnregister(&w)
|
||||
|
||||
if total > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
|
||||
@@ -500,11 +527,17 @@ func Pwritev2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysc
|
||||
func pwrite(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
n, err := file.PWrite(t, src, offset, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -535,6 +568,9 @@ func pwrite(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, o
|
||||
}
|
||||
file.EventUnregister(&w)
|
||||
|
||||
if total > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,9 @@ func Override() {
|
||||
s.Table[232] = syscalls.Supported("epoll_wait", EpollWait)
|
||||
s.Table[233] = syscalls.Supported("epoll_ctl", EpollCtl)
|
||||
s.Table[235] = syscalls.Supported("utimes", Utimes)
|
||||
delete(s.Table, 253) // inotify_init
|
||||
delete(s.Table, 254) // inotify_add_watch
|
||||
delete(s.Table, 255) // inotify_rm_watch
|
||||
s.Table[253] = syscalls.PartiallySupported("inotify_init", InotifyInit, "inotify events are only available inside the sandbox.", nil)
|
||||
s.Table[254] = syscalls.PartiallySupported("inotify_add_watch", InotifyAddWatch, "inotify events are only available inside the sandbox.", nil)
|
||||
s.Table[255] = syscalls.PartiallySupported("inotify_rm_watch", InotifyRmWatch, "inotify events are only available inside the sandbox.", nil)
|
||||
s.Table[257] = syscalls.Supported("openat", Openat)
|
||||
s.Table[258] = syscalls.Supported("mkdirat", Mkdirat)
|
||||
s.Table[259] = syscalls.Supported("mknodat", Mknodat)
|
||||
@@ -151,7 +151,7 @@ func Override() {
|
||||
s.Table[291] = syscalls.Supported("epoll_create1", EpollCreate1)
|
||||
s.Table[292] = syscalls.Supported("dup3", Dup3)
|
||||
s.Table[293] = syscalls.Supported("pipe2", Pipe2)
|
||||
delete(s.Table, 294) // inotify_init1
|
||||
s.Table[294] = syscalls.PartiallySupported("inotify_init1", InotifyInit1, "inotify events are only available inside the sandbox.", nil)
|
||||
s.Table[295] = syscalls.Supported("preadv", Preadv)
|
||||
s.Table[296] = syscalls.Supported("pwritev", Pwritev)
|
||||
s.Table[299] = syscalls.Supported("recvmmsg", RecvMMsg)
|
||||
|
||||
@@ -15,6 +15,18 @@ go_template_instance(
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "event_list",
|
||||
out = "event_list.go",
|
||||
package = "vfs",
|
||||
prefix = "event",
|
||||
template = "//pkg/ilist:generic_list",
|
||||
types = {
|
||||
"Element": "*Event",
|
||||
"Linker": "*Event",
|
||||
},
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "vfs",
|
||||
srcs = [
|
||||
@@ -25,11 +37,13 @@ go_library(
|
||||
"device.go",
|
||||
"epoll.go",
|
||||
"epoll_interest_list.go",
|
||||
"event_list.go",
|
||||
"file_description.go",
|
||||
"file_description_impl_util.go",
|
||||
"filesystem.go",
|
||||
"filesystem_impl_util.go",
|
||||
"filesystem_type.go",
|
||||
"inotify.go",
|
||||
"mount.go",
|
||||
"mount_unsafe.go",
|
||||
"options.go",
|
||||
@@ -57,6 +71,7 @@ go_library(
|
||||
"//pkg/sentry/limits",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/socket/unix/transport",
|
||||
"//pkg/sentry/uniqueid",
|
||||
"//pkg/sync",
|
||||
"//pkg/syserror",
|
||||
"//pkg/usermem",
|
||||
|
||||
@@ -297,3 +297,15 @@ func (d *anonDentry) TryIncRef() bool {
|
||||
func (d *anonDentry) DecRef() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// InotifyWithParent implements DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *anonDentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
|
||||
// Watches implements DentryImpl.Watches.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *anonDentry) Watches() *Watches {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -103,6 +103,22 @@ type DentryImpl interface {
|
||||
|
||||
// DecRef decrements the Dentry's reference count.
|
||||
DecRef()
|
||||
|
||||
// InotifyWithParent notifies all watches on the targets represented by this
|
||||
// dentry and its parent. The parent's watches are notified first, followed
|
||||
// by this dentry's.
|
||||
//
|
||||
// InotifyWithParent automatically adds the IN_ISDIR flag for dentries
|
||||
// representing directories.
|
||||
//
|
||||
// Note that the events may not actually propagate up to the user, depending
|
||||
// on the event masks.
|
||||
InotifyWithParent(events uint32, cookie uint32)
|
||||
|
||||
// Watches returns the set of inotify watches for the file corresponding to
|
||||
// the Dentry. Dentries that are hard links to the same underlying file
|
||||
// share the same watches.
|
||||
Watches() *Watches
|
||||
}
|
||||
|
||||
// IncRef increments d's reference count.
|
||||
@@ -133,6 +149,17 @@ func (d *Dentry) isMounted() bool {
|
||||
return atomic.LoadUint32(&d.mounts) != 0
|
||||
}
|
||||
|
||||
// InotifyWithParent notifies all watches on the inodes for this dentry and
|
||||
// its parent of events.
|
||||
func (d *Dentry) InotifyWithParent(events uint32, cookie uint32) {
|
||||
d.impl.InotifyWithParent(events, cookie)
|
||||
}
|
||||
|
||||
// Watches returns the set of inotify watches associated with d.
|
||||
func (d *Dentry) Watches() *Watches {
|
||||
return d.impl.Watches()
|
||||
}
|
||||
|
||||
// The following functions are exported so that filesystem implementations can
|
||||
// use them. The vfs package, and users of VFS, should not call these
|
||||
// functions.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -422,6 +422,7 @@ func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credential
|
||||
}
|
||||
}
|
||||
|
||||
fd.Dentry().InotifyWithParent(linux.IN_OPEN, 0)
|
||||
return fd, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
|
||||
@@ -951,6 +951,7 @@ cc_binary(
|
||||
"//test/util:epoll_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:fs_util",
|
||||
"//test/util:posix_error",
|
||||
"//test/util:temp_path",
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
@@ -1382,7 +1383,7 @@ cc_binary(
|
||||
srcs = ["partial_bad_buffer.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
"//test/syscalls/linux:socket_test_util",
|
||||
":socket_test_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:fs_util",
|
||||
"@com_google_absl//absl/time",
|
||||
@@ -3461,7 +3462,7 @@ cc_binary(
|
||||
deps = [
|
||||
":socket_test_util",
|
||||
gtest,
|
||||
"//test/syscalls/linux:socket_netlink_route_util",
|
||||
":socket_netlink_route_util",
|
||||
"//test/util:capability_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:fs_util",
|
||||
|
||||
+165
-11
@@ -33,6 +33,7 @@
|
||||
#include "test/util/epoll_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
@@ -335,6 +336,11 @@ TEST(Inotify, InotifyFdNotWritable) {
|
||||
EXPECT_THAT(write(fd.get(), "x", 1), SyscallFailsWithErrno(EBADF));
|
||||
}
|
||||
|
||||
TEST(Inotify, InitFlags) {
|
||||
EXPECT_THAT(inotify_init1(IN_NONBLOCK | IN_CLOEXEC), SyscallSucceeds());
|
||||
EXPECT_THAT(inotify_init1(12345), SyscallFailsWithErrno(EINVAL));
|
||||
}
|
||||
|
||||
TEST(Inotify, NonBlockingReadReturnsEagain) {
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
@@ -395,7 +401,7 @@ TEST(Inotify, CanDeleteFileAfterRemovingWatch) {
|
||||
file1.reset();
|
||||
}
|
||||
|
||||
TEST(Inotify, CanRemoveWatchAfterDeletingFile) {
|
||||
TEST(Inotify, RemoveWatchAfterDeletingFileFails) {
|
||||
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
TempPath file1 =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
|
||||
@@ -491,17 +497,23 @@ TEST(Inotify, DeletingChildGeneratesEvents) {
|
||||
Event(IN_DELETE, root_wd, Basename(file1_path))}));
|
||||
}
|
||||
|
||||
// Creating a file in "parent/child" should generate events for child, but not
|
||||
// parent.
|
||||
TEST(Inotify, CreatingFileGeneratesEvents) {
|
||||
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath child =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS));
|
||||
const int wd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
|
||||
InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS));
|
||||
|
||||
// Create a new file in the directory.
|
||||
const TempPath file1 =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(child.path()));
|
||||
const std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
|
||||
|
||||
@@ -554,6 +566,47 @@ TEST(Inotify, WritingFileGeneratesModifyEvent) {
|
||||
ASSERT_THAT(events, Are({Event(IN_MODIFY, wd, Basename(file1.path()))}));
|
||||
}
|
||||
|
||||
TEST(Inotify, SizeZeroReadWriteGeneratesNothing) {
|
||||
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
const TempPath file1 =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
|
||||
|
||||
const FileDescriptor file1_fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
|
||||
ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
|
||||
|
||||
// Read from the empty file.
|
||||
int val;
|
||||
ASSERT_THAT(read(file1_fd.get(), &val, sizeof(val)),
|
||||
SyscallSucceedsWithValue(0));
|
||||
|
||||
// Write zero bytes.
|
||||
ASSERT_THAT(write(file1_fd.get(), "", 0), SyscallSucceedsWithValue(0));
|
||||
|
||||
const std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
|
||||
ASSERT_THAT(events, Are({}));
|
||||
}
|
||||
|
||||
TEST(Inotify, FailedFileCreationGeneratesNoEvents) {
|
||||
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), dir.path(), IN_ALL_EVENTS));
|
||||
|
||||
const char* p = dir.path().c_str();
|
||||
ASSERT_THAT(mkdir(p, 0777), SyscallFails());
|
||||
ASSERT_THAT(mknod(p, S_IFIFO, 0777), SyscallFails());
|
||||
ASSERT_THAT(symlink(p, p), SyscallFails());
|
||||
ASSERT_THAT(link(p, p), SyscallFails());
|
||||
std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
|
||||
ASSERT_THAT(events, Are({}));
|
||||
}
|
||||
|
||||
TEST(Inotify, WatchSetAfterOpenReportsCloseFdEvent) {
|
||||
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const FileDescriptor fd =
|
||||
@@ -602,7 +655,7 @@ TEST(Inotify, ChildrenDeletionInWatchedDirGeneratesEvent) {
|
||||
Event(IN_DELETE | IN_ISDIR, wd, Basename(dir1_path))}));
|
||||
}
|
||||
|
||||
TEST(Inotify, WatchTargetDeletionGeneratesEvent) {
|
||||
TEST(Inotify, RmdirOnWatchedTargetGeneratesEvent) {
|
||||
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
@@ -1228,7 +1281,7 @@ TEST(Inotify, LinkGeneratesAttribAndCreateEvents) {
|
||||
InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
|
||||
|
||||
const int rc = link(file1.path().c_str(), link1.path().c_str());
|
||||
// link(2) is only supported on tmpfs in the sandbox.
|
||||
// NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
|
||||
SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
|
||||
(errno == EPERM || errno == ENOENT));
|
||||
ASSERT_THAT(rc, SyscallSucceeds());
|
||||
@@ -1322,21 +1375,27 @@ TEST(Inotify, HardlinksReuseSameWatch) {
|
||||
Event(IN_DELETE, root_wd, Basename(file1_path))}));
|
||||
}
|
||||
|
||||
// Calling mkdir within "parent/child" should generate an event for child, but
|
||||
// not parent.
|
||||
TEST(Inotify, MkdirGeneratesCreateEventWithDirFlag) {
|
||||
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath child =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
|
||||
ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS));
|
||||
const int child_wd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS));
|
||||
|
||||
const TempPath dir1(NewTempAbsPathInDir(root.path()));
|
||||
const TempPath dir1(NewTempAbsPathInDir(child.path()));
|
||||
ASSERT_THAT(mkdir(dir1.path().c_str(), 0777), SyscallSucceeds());
|
||||
|
||||
const std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
|
||||
ASSERT_THAT(
|
||||
events,
|
||||
Are({Event(IN_CREATE | IN_ISDIR, root_wd, Basename(dir1.path()))}));
|
||||
Are({Event(IN_CREATE | IN_ISDIR, child_wd, Basename(dir1.path()))}));
|
||||
}
|
||||
|
||||
TEST(Inotify, MultipleInotifyInstancesAndWatchesAllGetEvents) {
|
||||
@@ -1597,6 +1656,8 @@ TEST(Inotify, EpollNoDeadlock) {
|
||||
}
|
||||
|
||||
TEST(Inotify, SpliceEvent) {
|
||||
// TODO(gvisor.dev/issue/138): Implement splice in VFS2.
|
||||
SKIP_IF(IsRunningOnGvisor() && !IsRunningWithVFS1());
|
||||
int pipes[2];
|
||||
ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds());
|
||||
|
||||
@@ -1624,6 +1685,99 @@ TEST(Inotify, SpliceEvent) {
|
||||
ASSERT_THAT(events, Are({Event(IN_ACCESS, watcher)}));
|
||||
}
|
||||
|
||||
// Watches on a parent should not be triggered by actions on a hard link to one
|
||||
// of its children that has a different parent.
|
||||
TEST(Inotify, LinkOnOtherParent) {
|
||||
const TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath file =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
|
||||
std::string link_path = NewTempAbsPathInDir(dir2.path());
|
||||
|
||||
const int rc = link(file.path().c_str(), link_path.c_str());
|
||||
// NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
|
||||
SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
|
||||
(errno == EPERM || errno == ENOENT));
|
||||
ASSERT_THAT(rc, SyscallSucceeds());
|
||||
|
||||
const FileDescriptor inotify_fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(inotify_fd.get(), dir1.path(), IN_ALL_EVENTS));
|
||||
|
||||
// Perform various actions on the link outside of dir1, which should trigger
|
||||
// no inotify events.
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(link_path.c_str(), O_RDWR));
|
||||
int val = 0;
|
||||
ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
|
||||
ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
|
||||
ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds());
|
||||
ASSERT_THAT(unlink(link_path.c_str()), SyscallSucceeds());
|
||||
const std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
|
||||
EXPECT_THAT(events, Are({}));
|
||||
}
|
||||
|
||||
TEST(Inotify, Exec) {
|
||||
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath bin = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
TempPath::CreateSymlinkTo(dir.path(), "/bin/true"));
|
||||
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
const int wd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(fd.get(), bin.path(), IN_ALL_EVENTS));
|
||||
|
||||
// Perform exec.
|
||||
ScopedThread t([&bin]() {
|
||||
ASSERT_THAT(execl(bin.path().c_str(), bin.path().c_str(), (char*)nullptr),
|
||||
SyscallSucceeds());
|
||||
});
|
||||
t.Join();
|
||||
|
||||
std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
|
||||
EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd)}));
|
||||
}
|
||||
|
||||
// Watches without IN_EXCL_UNLINK, should continue to emit events for file
|
||||
// descriptors after their corresponding files have been unlinked.
|
||||
//
|
||||
// We need to disable S/R because there are filesystems where we cannot re-open
|
||||
// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
|
||||
TEST(Inotify, IncludeUnlinkedFile_NoRandomSave) {
|
||||
const DisableSave ds;
|
||||
|
||||
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
TempPath::CreateFileWith(dir.path(), "123", TempPath::kDefaultFileMode));
|
||||
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
|
||||
|
||||
const FileDescriptor inotify_fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(inotify_fd.get(), dir.path(), IN_ALL_EVENTS));
|
||||
const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
|
||||
|
||||
ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
|
||||
int val = 0;
|
||||
ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
|
||||
ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
|
||||
const std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
|
||||
EXPECT_THAT(events, Are({
|
||||
Event(IN_ATTRIB, file_wd),
|
||||
Event(IN_DELETE, dir_wd, Basename(file.path())),
|
||||
Event(IN_ACCESS, dir_wd, Basename(file.path())),
|
||||
Event(IN_ACCESS, file_wd),
|
||||
Event(IN_MODIFY, dir_wd, Basename(file.path())),
|
||||
Event(IN_MODIFY, file_wd),
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user