Skip to content

Commit

Permalink
windows: ignore file attribute changes
Browse files Browse the repository at this point in the history
Windows allows filtering file events to include changes to file
attributes. However, when reporting the actual event, attribute
changes are included as `FILE_ACTION_MODIFIED` so it's impossible
to distinguish from file writes.

This means the library's `op::Chmod` is never actually populated
on Windows and so attribute changes aren't really usable. Since
the API does not support specifying an event type filter as a
caller, attribute changes on Windows are ignored rather than being
misreported.
  • Loading branch information
milas committed Jun 30, 2021
1 parent fb09a0d commit f6dca35
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 1 deletion.
66 changes: 66 additions & 0 deletions integration_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// +build windows

package fsnotify

import (
"os"
"path/filepath"
"testing"
"time"
)

func TestAttributeChangesIgnored(t *testing.T) {
watcher := newWatcher(t)
defer watcher.Close()

testDir := tempMkdir(t)
defer os.RemoveAll(testDir)

// Create a file before watching directory
testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
{
var f *os.File
f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
t.Fatalf("creating test file failed: %s", err)
}
f.Sync()
f.Close()
}

addWatch(t, watcher, testDir)

// Receive errors on the error channel on a separate goroutine
go func() {
for err := range watcher.Errors {
t.Errorf("error received: %s", err)
}
}()

var eventReceived counter
go func() {
for event := range watcher.Events {
if event.Name == filepath.Clean(testFileAlreadyExists) {
t.Logf("event received: %s", event)
eventReceived.increment()
} else {
t.Logf("unexpected event received: %s", event)
}
}
}()

// make the file read-only, which is an attribute change
err := os.Chmod(testFileAlreadyExists, 0400)
if err != nil {
t.Fatalf("Failed to mark file as read-only: %v", err)
}

// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
time.Sleep(500 * time.Millisecond)
watcher.Close()

eReceived := eventReceived.value()
if eReceived != 0 {
t.Fatalf("should not have received any events, received %d after 500 ms", eReceived)
}
}
15 changes: 14 additions & 1 deletion windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,23 @@ func (w *Watcher) Add(name string) error {
if w.isClosed {
return errors.New("watcher already closed")
}

// When reporting file notifications, FILE_ACTION_MODIFIED includes changes to file attributes;
// so attribute handling is broken for Windows (op.Chmod will NEVER get used in practice)
//
// To prevent events for attribute changes erroneously showing up as file modifications,
// FILE_NOTIFY_CHANGE_ATTRIBUTES (sysFSATTRIB) is excluded from the notify filter.
// NOTE: This means attribute changes will NOT be reported for Windows.
//
// See also:
// * ReadDirectoryChangesW flags: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
// * FILE_NOTIFY_INFORMATION: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-file_notify_information
flags := sysFSCREATE | sysFSDELETE | sysFSDELETESELF | sysFSMODIFY | sysFSMOVEDFROM | sysFSMOVEDTO | sysFSMOVESELF

in := &input{
op: opAddWatch,
path: filepath.Clean(name),
flags: sysFSALLEVENTS,
flags: uint32(flags),
reply: make(chan error),
}
w.input <- in
Expand Down

0 comments on commit f6dca35

Please sign in to comment.