From d3f1d2ddc31f19eae4704a4ea42c7dd9076e2c4a Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 16 Aug 2018 10:26:14 +0300 Subject: [PATCH] Do not suppress Chmod on non-existent file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- inotify.go | 12 ++++++------ inotify_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/inotify.go b/inotify.go index c56556f4..5de38b4f 100644 --- a/inotify.go +++ b/inotify.go @@ -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) } diff --git a/inotify_test.go b/inotify_test.go index 2c11d2ed..c0c6e355 100644 --- a/inotify_test.go +++ b/inotify_test.go @@ -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) +}