mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement IN_EXCL_UNLINK inotify option in vfs2.
Limited to tmpfs. Inotify support in other filesystem implementations to follow. Updates #1479 PiperOrigin-RevId: 313828648
This commit is contained in:
@@ -64,7 +64,7 @@ func (d *dentry) DecRef() {
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32, et vfs.EventType) {}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
//
|
||||
|
||||
@@ -1042,7 +1042,7 @@ func (d *dentry) decRefLocked() {
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32, et vfs.EventType) {}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
//
|
||||
|
||||
@@ -228,7 +228,7 @@ func (d *Dentry) destroy() {
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *Dentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
func (d *Dentry) InotifyWithParent(events uint32, cookie uint32, et vfs.EventType) {}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
//
|
||||
|
||||
@@ -79,6 +79,7 @@ func (dir *directory) removeChildLocked(child *dentry) {
|
||||
dir.iterMu.Lock()
|
||||
dir.childList.Remove(child)
|
||||
dir.iterMu.Unlock()
|
||||
child.unlinked = true
|
||||
}
|
||||
|
||||
type directoryFD struct {
|
||||
@@ -112,7 +113,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.dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
fd.inode().touchAtime(fd.vfsfd.Mount())
|
||||
|
||||
if fd.off == 0 {
|
||||
|
||||
@@ -182,7 +182,7 @@ func (fs *filesystem) doCreateAt(rp *vfs.ResolvingPath, dir bool, create func(pa
|
||||
if dir {
|
||||
ev |= linux.IN_ISDIR
|
||||
}
|
||||
parentDir.inode.watches.Notify(name, uint32(ev), 0)
|
||||
parentDir.inode.watches.Notify(name, uint32(ev), 0, vfs.InodeEvent)
|
||||
parentDir.inode.touchCMtime()
|
||||
return nil
|
||||
}
|
||||
@@ -247,7 +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)
|
||||
d.inode.watches.Notify("", linux.IN_ATTRIB, 0, vfs.InodeEvent)
|
||||
parentDir.insertChildLocked(fs.newDentry(d.inode), name)
|
||||
return nil
|
||||
})
|
||||
@@ -361,7 +361,7 @@ afterTrailingSymlink:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentDir.inode.watches.Notify(name, linux.IN_CREATE, 0)
|
||||
parentDir.inode.watches.Notify(name, linux.IN_CREATE, 0, vfs.PathEvent)
|
||||
parentDir.inode.touchCMtime()
|
||||
return fd, nil
|
||||
}
|
||||
@@ -613,7 +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)
|
||||
parentDir.inode.watches.Notify(name, linux.IN_DELETE|linux.IN_ISDIR, 0, vfs.InodeEvent)
|
||||
// Remove links for child, child/., and child/..
|
||||
child.inode.decLinksLocked()
|
||||
child.inode.decLinksLocked()
|
||||
@@ -636,7 +636,7 @@ func (fs *filesystem) SetStatAt(ctx context.Context, rp *vfs.ResolvingPath, opts
|
||||
}
|
||||
|
||||
if ev := vfs.InotifyEventFromStatMask(opts.Stat.Mask); ev != 0 {
|
||||
d.InotifyWithParent(ev, 0)
|
||||
d.InotifyWithParent(ev, 0, vfs.InodeEvent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -784,7 +784,7 @@ func (fs *filesystem) SetxattrAt(ctx context.Context, rp *vfs.ResolvingPath, opt
|
||||
return err
|
||||
}
|
||||
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0, vfs.InodeEvent)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -800,7 +800,7 @@ func (fs *filesystem) RemovexattrAt(ctx context.Context, rp *vfs.ResolvingPath,
|
||||
return err
|
||||
}
|
||||
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0, vfs.InodeEvent)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -163,6 +163,11 @@ type dentry struct {
|
||||
// filesystem.mu.
|
||||
name string
|
||||
|
||||
// unlinked indicates whether this dentry has been unlinked from its parent.
|
||||
// It is only set to true on an unlink operation, and never set from true to
|
||||
// false. unlinked is protected by filesystem.mu.
|
||||
unlinked bool
|
||||
|
||||
// dentryEntry (ugh) links dentries into their parent directory.childList.
|
||||
dentryEntry
|
||||
|
||||
@@ -202,7 +207,7 @@ func (d *dentry) DecRef() {
|
||||
}
|
||||
|
||||
// InotifyWithParent implements vfs.DentryImpl.InotifyWithParent.
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32) {
|
||||
func (d *dentry) InotifyWithParent(events uint32, cookie uint32, et vfs.EventType) {
|
||||
if d.inode.isDir() {
|
||||
events |= linux.IN_ISDIR
|
||||
}
|
||||
@@ -211,9 +216,9 @@ func (d *dentry) InotifyWithParent(events uint32, cookie uint32) {
|
||||
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.parent.inode.watches.NotifyWithExclusions(d.name, events, cookie, et, d.unlinked)
|
||||
}
|
||||
d.inode.watches.Notify("", events, cookie)
|
||||
d.inode.watches.Notify("", events, cookie, et)
|
||||
}
|
||||
|
||||
// Watches implements vfs.DentryImpl.Watches.
|
||||
@@ -676,9 +681,8 @@ func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions)
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate inotify events.
|
||||
if ev := vfs.InotifyEventFromStatMask(opts.Stat.Mask); ev != 0 {
|
||||
d.InotifyWithParent(ev, 0)
|
||||
d.InotifyWithParent(ev, 0, vfs.InodeEvent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -701,7 +705,7 @@ func (fd *fileDescription) Setxattr(ctx context.Context, opts vfs.SetxattrOption
|
||||
}
|
||||
|
||||
// Generate inotify events.
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0, vfs.InodeEvent)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -713,7 +717,7 @@ func (fd *fileDescription) Removexattr(ctx context.Context, name string) error {
|
||||
}
|
||||
|
||||
// Generate inotify events.
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0)
|
||||
d.InotifyWithParent(linux.IN_ATTRIB, 0, vfs.InodeEvent)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ func (f *FDTable) dropVFS2(file *vfs.FileDescription) {
|
||||
if file.IsWritable() {
|
||||
ev = linux.IN_CLOSE_WRITE
|
||||
}
|
||||
file.Dentry().InotifyWithParent(ev, 0)
|
||||
file.Dentry().InotifyWithParent(ev, 0, vfs.PathEvent)
|
||||
|
||||
// Drop the table reference.
|
||||
file.DecRef()
|
||||
|
||||
@@ -94,7 +94,7 @@ func read(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, opt
|
||||
n, err := file.Read(t, dst, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func read(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, opt
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -135,7 +135,7 @@ 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)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func pread(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, of
|
||||
n, err := file.PRead(t, dst, offset, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -266,7 +266,7 @@ func pread(t *kernel.Task, file *vfs.FileDescription, dst usermem.IOSequence, of
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -299,7 +299,7 @@ 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)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
@@ -364,7 +364,7 @@ func write(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, op
|
||||
n, err := file.Write(t, src, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -372,7 +372,7 @@ func write(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, op
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -405,7 +405,7 @@ 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)
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0, vfs.PathEvent)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
@@ -528,7 +528,7 @@ func pwrite(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, o
|
||||
n, err := file.PWrite(t, src, offset, opts)
|
||||
if err != syserror.ErrWouldBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_MODIFY, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -536,7 +536,7 @@ func pwrite(t *kernel.Task, file *vfs.FileDescription, src usermem.IOSequence, o
|
||||
allowBlock, deadline, hasDeadline := blockPolicy(t, file)
|
||||
if !allowBlock {
|
||||
if n > 0 {
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -569,7 +569,7 @@ 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)
|
||||
file.Dentry().InotifyWithParent(linux.IN_ACCESS, 0, vfs.PathEvent)
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ func (d *anonDentry) DecRef() {
|
||||
// InotifyWithParent implements DentryImpl.InotifyWithParent.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1479): Implement inotify.
|
||||
func (d *anonDentry) InotifyWithParent(events uint32, cookie uint32) {}
|
||||
func (d *anonDentry) InotifyWithParent(events uint32, cookie uint32, et EventType) {}
|
||||
|
||||
// Watches implements DentryImpl.Watches.
|
||||
//
|
||||
|
||||
@@ -113,7 +113,7 @@ type DentryImpl interface {
|
||||
//
|
||||
// Note that the events may not actually propagate up to the user, depending
|
||||
// on the event masks.
|
||||
InotifyWithParent(events uint32, cookie uint32)
|
||||
InotifyWithParent(events uint32, cookie uint32, et EventType)
|
||||
|
||||
// Watches returns the set of inotify watches for the file corresponding to
|
||||
// the Dentry. Dentries that are hard links to the same underlying file
|
||||
@@ -151,8 +151,8 @@ func (d *Dentry) isMounted() bool {
|
||||
|
||||
// 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)
|
||||
func (d *Dentry) InotifyWithParent(events uint32, cookie uint32, et EventType) {
|
||||
d.impl.InotifyWithParent(events, cookie, et)
|
||||
}
|
||||
|
||||
// Watches returns the set of inotify watches associated with d.
|
||||
|
||||
@@ -33,6 +33,19 @@ import (
|
||||
// must be a power 2 for rounding below.
|
||||
const inotifyEventBaseSize = 16
|
||||
|
||||
// EventType defines different kinds of inotfiy events.
|
||||
//
|
||||
// The way events are labelled appears somewhat arbitrary, but they must match
|
||||
// Linux so that IN_EXCL_UNLINK behaves as it does in Linux.
|
||||
type EventType uint8
|
||||
|
||||
// PathEvent and InodeEvent correspond to FSNOTIFY_EVENT_PATH and
|
||||
// FSNOTIFY_EVENT_INODE in Linux.
|
||||
const (
|
||||
PathEvent EventType = iota
|
||||
InodeEvent EventType = iota
|
||||
)
|
||||
|
||||
// Inotify represents an inotify instance created by inotify_init(2) or
|
||||
// inotify_init1(2). Inotify implements FileDescriptionImpl.
|
||||
//
|
||||
@@ -419,13 +432,22 @@ func (w *Watches) Remove(id uint64) {
|
||||
}
|
||||
|
||||
// Notify queues a new event with all watches in this set.
|
||||
func (w *Watches) Notify(name string, events, cookie uint32) {
|
||||
func (w *Watches) Notify(name string, events, cookie uint32, et EventType) {
|
||||
w.NotifyWithExclusions(name, events, cookie, et, false)
|
||||
}
|
||||
|
||||
// NotifyWithExclusions queues a new event with watches in this set. Watches
|
||||
// with IN_EXCL_UNLINK are skipped if the event is coming from a child that
|
||||
// has been unlinked.
|
||||
func (w *Watches) NotifyWithExclusions(name string, events, cookie uint32, et EventType, unlinked bool) {
|
||||
// N.B. We don't defer the unlocks because Notify is in the hot path of
|
||||
// all IO operations, and the defer costs too much for small IO
|
||||
// operations.
|
||||
w.mu.RLock()
|
||||
for _, watch := range w.ws {
|
||||
// TODO(gvisor.dev/issue/1479): Skip for IN_EXCL_UNLINK cases.
|
||||
if unlinked && watch.ExcludeUnlinkedChildren() && et == PathEvent {
|
||||
continue
|
||||
}
|
||||
watch.Notify(name, events, cookie)
|
||||
}
|
||||
w.mu.RUnlock()
|
||||
@@ -434,7 +456,7 @@ func (w *Watches) Notify(name string, events, cookie uint32) {
|
||||
// HandleDeletion is called when the watch target is destroyed to emit
|
||||
// the appropriate events.
|
||||
func (w *Watches) HandleDeletion() {
|
||||
w.Notify("", linux.IN_DELETE_SELF, 0)
|
||||
w.Notify("", linux.IN_DELETE_SELF, 0, InodeEvent)
|
||||
|
||||
// TODO(gvisor.dev/issue/1479): This doesn't work because maps are not copied
|
||||
// by value. Ideally, we wouldn't have this circular locking so we can just
|
||||
@@ -655,8 +677,8 @@ func InotifyEventFromStatMask(mask uint32) uint32 {
|
||||
// InotifyRemoveChild sends the appriopriate notifications to the watch sets of
|
||||
// the child being removed and its parent.
|
||||
func InotifyRemoveChild(self, parent *Watches, name string) {
|
||||
self.Notify("", linux.IN_ATTRIB, 0)
|
||||
parent.Notify(name, linux.IN_DELETE, 0)
|
||||
self.Notify("", linux.IN_ATTRIB, 0, InodeEvent)
|
||||
parent.Notify(name, linux.IN_DELETE, 0, InodeEvent)
|
||||
// TODO(gvisor.dev/issue/1479): implement IN_EXCL_UNLINK.
|
||||
}
|
||||
|
||||
@@ -668,8 +690,8 @@ func InotifyRename(ctx context.Context, renamed, oldParent, newParent *Watches,
|
||||
dirEv = linux.IN_ISDIR
|
||||
}
|
||||
cookie := uniqueid.InotifyCookie(ctx)
|
||||
oldParent.Notify(oldName, dirEv|linux.IN_MOVED_FROM, cookie)
|
||||
newParent.Notify(newName, dirEv|linux.IN_MOVED_TO, cookie)
|
||||
oldParent.Notify(oldName, dirEv|linux.IN_MOVED_FROM, cookie, InodeEvent)
|
||||
newParent.Notify(newName, dirEv|linux.IN_MOVED_TO, cookie, InodeEvent)
|
||||
// Somewhat surprisingly, self move events do not have a cookie.
|
||||
renamed.Notify("", linux.IN_MOVE_SELF, 0)
|
||||
renamed.Notify("", linux.IN_MOVE_SELF, 0, InodeEvent)
|
||||
}
|
||||
|
||||
@@ -422,7 +422,7 @@ func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credential
|
||||
}
|
||||
}
|
||||
|
||||
fd.Dentry().InotifyWithParent(linux.IN_OPEN, 0)
|
||||
fd.Dentry().InotifyWithParent(linux.IN_OPEN, 0, PathEvent)
|
||||
return fd, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
|
||||
@@ -1778,6 +1778,170 @@ TEST(Inotify, IncludeUnlinkedFile_NoRandomSave) {
|
||||
}));
|
||||
}
|
||||
|
||||
// Watches created with IN_EXCL_UNLINK will stop emitting events on fds for
|
||||
// children that have already 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, ExcludeUnlink_NoRandomSave) {
|
||||
const DisableSave ds;
|
||||
// TODO(gvisor.dev/issue/1624): This test fails on VFS1.
|
||||
SKIP_IF(IsRunningWithVFS1());
|
||||
|
||||
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath file =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
|
||||
|
||||
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 wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
|
||||
inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
|
||||
|
||||
// Unlink the child, which should cause further operations on the open file
|
||||
// descriptor to be ignored.
|
||||
ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
|
||||
int val = 0;
|
||||
ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
|
||||
ASSERT_THAT(read(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_DELETE, wd, Basename(file.path()))}));
|
||||
}
|
||||
|
||||
// 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, ExcludeUnlinkDirectory_NoRandomSave) {
|
||||
const DisableSave ds;
|
||||
|
||||
const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
TempPath dir =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
std::string dirPath = dir.path();
|
||||
const FileDescriptor inotify_fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(dirPath.c_str(), O_RDONLY | O_DIRECTORY));
|
||||
const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
|
||||
inotify_fd.get(), parent.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
|
||||
|
||||
// Unlink the dir, and then close the open fd.
|
||||
ASSERT_THAT(rmdir(dirPath.c_str()), SyscallSucceeds());
|
||||
dir.reset();
|
||||
|
||||
const std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
|
||||
// No close event should appear.
|
||||
ASSERT_THAT(events,
|
||||
Are({Event(IN_DELETE | IN_ISDIR, wd, Basename(dirPath))}));
|
||||
}
|
||||
|
||||
// If "dir/child" and "dir/child2" are links to the same file, and "dir/child"
|
||||
// is unlinked, a watch on "dir" with IN_EXCL_UNLINK will exclude future events
|
||||
// for fds on "dir/child" but not "dir/child2".
|
||||
//
|
||||
// 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, ExcludeUnlinkMultipleChildren_NoRandomSave) {
|
||||
const DisableSave ds;
|
||||
// TODO(gvisor.dev/issue/1624): This test fails on VFS1.
|
||||
SKIP_IF(IsRunningWithVFS1());
|
||||
|
||||
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath file =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
|
||||
std::string path1 = file.path();
|
||||
std::string path2 = NewTempAbsPathInDir(dir.path());
|
||||
|
||||
const int rc = link(path1.c_str(), path2.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 fd1 =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(path1.c_str(), O_RDWR));
|
||||
const FileDescriptor fd2 =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(path2.c_str(), O_RDWR));
|
||||
|
||||
const FileDescriptor inotify_fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
|
||||
inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
|
||||
|
||||
// After unlinking path1, only events on the fd for path2 should be generated.
|
||||
ASSERT_THAT(unlink(path1.c_str()), SyscallSucceeds());
|
||||
ASSERT_THAT(write(fd1.get(), "x", 1), SyscallSucceeds());
|
||||
ASSERT_THAT(write(fd2.get(), "x", 1), SyscallSucceeds());
|
||||
|
||||
const std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
|
||||
EXPECT_THAT(events, Are({
|
||||
Event(IN_DELETE, wd, Basename(path1)),
|
||||
Event(IN_MODIFY, wd, Basename(path2)),
|
||||
}));
|
||||
}
|
||||
|
||||
// On native Linux, actions of data type FSNOTIFY_EVENT_INODE are not affected
|
||||
// by IN_EXCL_UNLINK (see
|
||||
// fs/notify/inotify/inotify_fsnotify.c:inotify_handle_event). Inode-level
|
||||
// events include changes to metadata and extended attributes.
|
||||
//
|
||||
// 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, ExcludeUnlinkInodeEvents_NoRandomSave) {
|
||||
const DisableSave ds;
|
||||
|
||||
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
const TempPath file =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
|
||||
|
||||
const FileDescriptor fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));
|
||||
const FileDescriptor inotify_fd =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
|
||||
const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
|
||||
inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
|
||||
|
||||
// NOTE(b/157163751): Create another link before unlinking. This is needed for
|
||||
// the gofer filesystem in gVisor, where open fds will not work once the link
|
||||
// count hits zero. In VFS2, we end up skipping the gofer test anyway, because
|
||||
// hard links are not supported for gofer fs.
|
||||
if (IsRunningOnGvisor()) {
|
||||
std::string link_path = NewTempAbsPath();
|
||||
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(rc != 0 && (errno == EPERM || errno == ENOENT));
|
||||
ASSERT_THAT(rc, SyscallSucceeds());
|
||||
}
|
||||
|
||||
// Even after unlinking, inode-level operations will trigger events regardless
|
||||
// of IN_EXCL_UNLINK.
|
||||
ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
|
||||
|
||||
// Perform various actions on fd.
|
||||
ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds());
|
||||
std::vector<Event> events =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
|
||||
EXPECT_THAT(events, Are({
|
||||
Event(IN_DELETE, wd, Basename(file.path())),
|
||||
Event(IN_MODIFY, wd, Basename(file.path())),
|
||||
}));
|
||||
|
||||
struct timeval times[2] = {{1, 0}, {2, 0}};
|
||||
ASSERT_THAT(futimes(fd.get(), times), SyscallSucceeds());
|
||||
events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
|
||||
EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd, Basename(file.path()))}));
|
||||
|
||||
// S/R is disabled on this entire test due to behavior with unlink; it must
|
||||
// also be disabled after this point because of fchmod.
|
||||
ASSERT_THAT(fchmod(fd.get(), 0777), SyscallSucceeds());
|
||||
events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
|
||||
EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd, Basename(file.path()))}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user