Skip to content

Commit

Permalink
Do not suppress Chmod on non-existent file
Browse files Browse the repository at this point in the history
Currently fsnorify suppresses a Chmod event if the file does not exist
when event is received.

This makes it impossible to use fsnotify to detect when an opened file
is removed. In such case the Linux kernel sends IN_ATTRIB event,
as described in inotify(7) man page:

> IN_ATTRIB (*)
>        Metadata  changed—for example, permissions (e.g., chmod(2)),
>        timestamps (e.g., utimensat(2)), extended attributes  (setx‐
>        attr(2)), link count (since Linux 2.6.25; e.g., for the tar‐
>        get of link(2) and for unlink(2)), and user/group ID  (e.g.,
>        chown(2)).

(in this very case it's link count that changes).

To fix:
 * Modify the code to only suppress MODIFY and CREATE events.
 * Add a test case to verify Chmod event is delivered.

While at it, fix the comment in ignoreLinux() to use the up-to-date
terminology (event ops).

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
  • Loading branch information
kolyshkin committed Mar 2, 2020
1 parent 4bf2d1f commit 5e53cc7
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 6 deletions.
12 changes: 6 additions & 6 deletions inotify.go
Expand Up @@ -303,12 +303,12 @@ func (e *Event) ignoreLinux(mask uint32) bool {
return true
}

// If the event is not a DELETE or RENAME, the file must exist.
// Otherwise the event is ignored.
// *Note*: this was put in place because it was seen that a MODIFY
// event was sent after the DELETE. This ignores that MODIFY and
// assumes a DELETE will come or has come if the file doesn't exist.
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
// If the event is Create or Write, the file must exist, or the
// event will be suppressed.
// *Note*: this was put in place because it was seen that a Write
// event was sent after the Remove. This ignores the Write and
// assumes a Remove will come or has come if the file doesn't exist.
if e.Op&Create == Create || e.Op&Write == Write {
_, statErr := os.Lstat(e.Name)
return os.IsNotExist(statErr)
}
Expand Down
49 changes: 49 additions & 0 deletions inotify_test.go
Expand Up @@ -453,3 +453,52 @@ func TestInotifyOverflow(t *testing.T) {
numDirs*numFiles, creates)
}
}

func TestInotifyDeleteOpenedFile(t *testing.T) {
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)

testFile := filepath.Join(testDir, "testfile")

// create and open a file
fd, err := os.Create(testFile)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
defer fd.Close()

w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher: %v", err)
}
defer w.Close()

err = w.Add(testFile)
if err != nil {
t.Fatalf("Failed to add watch for %s: %v", testFile, err)
}

checkEvent := func(exp Op) {
select {
case event := <-w.Events:
t.Logf("Event received: %s", event.Op)
if event.Op != exp {
t.Fatalf("Event expected: %s, got: %s", exp, event.Op)
}
case <-time.After(100 * time.Millisecond):
t.Fatalf("Expected %s event not received", exp)
}
}

// Remove the (opened) file, check Chmod event (notifying
// about file link count change) is received
err = os.Remove(testFile)
if err != nil {
t.Fatalf("Failed to remove file: %s", err)
}
checkEvent(Chmod)

// Close the file, check Remove event is received
fd.Close()
checkEvent(Remove)
}

0 comments on commit 5e53cc7

Please sign in to comment.