]> go.fuhry.dev Git - fsnotify.git/commitdiff
Do not suppress Chmod on non-existent file (#260)
authorKir Kolyshkin <kolyshkin@gmail.com>
Thu, 21 Jul 2022 11:32:11 +0000 (04:32 -0700)
committerGitHub <noreply@github.com>
Thu, 21 Jul 2022 11:32:11 +0000 (13:32 +0200)
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 <kolyshkin@gmail.com>
inotify.go
inotify_test.go

index e6abddfdd04c390bf9f9ea5fbb03c8b9f95a57d4..9e020ed4be2259e03464b6f4484b65c767d670bb 100644 (file)
@@ -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)
        }
index 728f5c218ff752c7a61dbf337774463967c67dc4..361f19823b59c87f4c1ec927cb8b89a7f7599852 100644 (file)
@@ -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)
+}