From: Kir Kolyshkin Date: Thu, 21 Jul 2022 11:32:11 +0000 (-0700) Subject: Do not suppress Chmod on non-existent file (#260) X-Git-Tag: v1.7.2~109 X-Git-Url: https://go.fuhry.dev/?a=commitdiff_plain;h=4193dfd6fb01f55805360fc55b3f46be8749808c;p=fsnotify.git Do not suppress Chmod on non-existent file (#260) Currently fsnotify suppresses a Chmod event if the file does not exist when the 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)). (to clarify, 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). A test case is added. Signed-off-by: Kir Kolyshkin --- diff --git a/inotify.go b/inotify.go index e6abddf..9e020ed 100644 --- a/inotify.go +++ b/inotify.go @@ -317,12 +317,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 728f5c2..361f198 100644 --- a/inotify_test.go +++ b/inotify_test.go @@ -499,3 +499,52 @@ func TestInotifyWatchList(t *testing.T) { } } } + +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) +}