From e374e19bf9f65a4e79f4424142de50d0b9312235 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Fri, 28 May 2021 19:28:14 +0000 Subject: [PATCH 01/47] Add Solaris FEN using EventPorts from x/sys/unix --- AUTHORS | 3 + CHANGELOG.md | 4 + LICENSE | 2 +- fen.go | 259 +++++++++++++++++++++++++++++++++++++++++++- go.mod | 2 +- go.sum | 4 +- integration_test.go | 4 +- 7 files changed, 270 insertions(+), 8 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6cbabe5e..fc8b1317 100644 --- a/AUTHORS +++ b/AUTHORS @@ -26,14 +26,17 @@ Eric Lin Evan Phoenix Francisco Souza Gautam Dey +Gereon Frey Hari haran Ichinose Shogo +Isaac Davis Johannes Ebke John C Barstow Kelvin Fo Ken-ichirou MATSUZAWA Matt Layher Matthias Stone +Nahum Shalman Nathan Youngman Nickolai Zeldovich Oliver Bristow diff --git a/CHANGELOG.md b/CHANGELOG.md index a438fe4b..9d042428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## v1.6.0 / 2022-01-28 + +* Solaris: add support for File Event Notifications (fen) [#12](https://github.com/fsnotify/fsnotify/issues/12) + ## [1.5.1] - 2021-08-24 * Revert Add AddRaw to not follow symlinks diff --git a/LICENSE b/LICENSE index e180c8fb..4fc04b55 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright (c) 2012 The Go Authors. All rights reserved. -Copyright (c) 2012-2019 fsnotify Authors. All rights reserved. +Copyright (c) 2012-2021 fsnotify Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/fen.go b/fen.go index b3ac3d8f..af0c0109 100644 --- a/fen.go +++ b/fen.go @@ -9,30 +9,285 @@ package fsnotify import ( "errors" + "fmt" + "golang.org/x/sys/unix" + "io/ioutil" + "os" + "path/filepath" ) // Watcher watches a set of files, delivering events to a channel. type Watcher struct { Events chan Event Errors chan error + + port *unix.EventPort + + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + doneResp chan struct{} // Channel to respond to Close } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. func NewWatcher() (*Watcher, error) { - return nil, errors.New("FEN based watcher not yet supported for fsnotify\n") + var err error + + w := new(Watcher) + w.Events = make(chan Event) + w.Errors = make(chan error) + w.port, err = unix.NewEventPort() + if err != nil { + return nil, err + } + w.done = make(chan struct{}) + w.doneResp = make(chan struct{}) + + go w.readEvents() + return w, nil +} + +// sendEvent attempts to send an event to the user, returning true if the event +// was put in the channel successfully and false if the watcher has been closed. +func (w *Watcher) sendEvent(e Event) (sent bool) { + select { + case w.Events <- e: + return true + case <-w.done: + return false + } +} + +// sendError attempts to send an error to the user, returning true if the error +// was put in the channel successfully and false if the watcher has been closed. +func (w *Watcher) sendError(err error) (sent bool) { + select { + case w.Errors <- err: + return true + case <-w.done: + return false + } +} + +func (w *Watcher) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } } // Close removes all watches and closes the events channel. func (w *Watcher) Close() error { + if w.isClosed() { + return nil + } + close(w.done) + w.port.Close() + <-w.doneResp return nil } // Add starts watching the named file or directory (non-recursively). func (w *Watcher) Add(name string) error { - return nil + if w.isClosed() { + return errors.New("FEN watcher already closed") + } + if w.port.PathIsWatched(name) { + return nil + } + stat, err := os.Stat(name) + switch { + case err != nil: + return err + case stat.IsDir(): + return w.handleDirectory(name, stat, w.associateFile) + default: + return w.associateFile(name, stat) + } } // Remove stops watching the the named file or directory (non-recursively). func (w *Watcher) Remove(name string) error { + if w.isClosed() { + return errors.New("FEN watcher already closed") + } + if !w.port.PathIsWatched(name) { + return fmt.Errorf("can't remove non-existent FEN watch for: %s", name) + } + stat, err := os.Stat(name) + switch { + case err != nil: + return err + case stat.IsDir(): + return w.handleDirectory(name, stat, w.dissociateFile) + default: + return w.port.DissociatePath(name) + } +} + +// readEvents contains the main loop that runs in a goroutine watching for events. +func (w *Watcher) readEvents() { + // If this function returns, the watcher has been closed and we can + // close these channels + defer close(w.doneResp) + defer close(w.Errors) + defer close(w.Events) + + pevents := make([]unix.PortEvent, 8, 8) + for { + count, err := w.port.Get(pevents, 1, nil) + if err != nil { + // Interrupted system call (count should be 0) ignore and continue + if err == unix.EINTR && count == 0 { + continue + } + // Get failed because we called w.Close() + if err == unix.EBADF && w.isClosed() { + return + } + // There was an error not caused by calling w.Close() + if !w.sendError(err) { + return + } + } + + p := pevents[:count] + for _, pevent := range p { + if pevent.Source != unix.PORT_SOURCE_FILE { + // Event from unexpected source received; should never happen. + if !w.sendError(errors.New("Event from unexpected source received")) { + return + } + continue + } + + err = w.handleEvent(&pevent) + if err != nil { + if !w.sendError(err) { + return + } + } + } + } +} + +func (w *Watcher) handleDirectory(path string, stat os.FileInfo, handler func(string, os.FileInfo) error) error { + files, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + // Handle all children of the directory. + for _, finfo := range files { + if !finfo.IsDir() { + err := handler(filepath.Join(path, finfo.Name()), finfo) + if err != nil { + return err + } + } + } + + // And finally handle the directory itself. + return handler(path, stat) +} + +func (w *Watcher) handleEvent(event *unix.PortEvent) error { + events := event.Events + path := event.Path + fmode := event.Cookie.(os.FileMode) + + var toSend *Event + reRegister := true + + switch { + case events&unix.FILE_MODIFIED == unix.FILE_MODIFIED: + if fmode.IsDir() { + if err := w.updateDirectory(path); err != nil { + return err + } + } else { + toSend = &Event{path, Write} + } + case events&unix.FILE_ATTRIB == unix.FILE_ATTRIB: + toSend = &Event{path, Chmod} + case events&unix.FILE_DELETE == unix.FILE_DELETE: + toSend = &Event{path, Remove} + reRegister = false + case events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM: + toSend = &Event{path, Rename} + // Don't keep watching the new file name + reRegister = false + case events&unix.FILE_RENAME_TO == unix.FILE_RENAME_TO: + // We don't report a Rename event for this case, because + // Rename events are interpreted as referring to the _old_ name + // of the file, and in this case the event would refer to the + // new name of the file. This type of rename event is not + // supported by fsnotify. + + // inotify reports a Remove event in this case, so we simulate + // this here. + toSend = &Event{path, Remove} + // Don't keep watching the file that was removed + reRegister = false + default: + return errors.New("unknown event received") + } + + if toSend != nil { + if !w.sendEvent(*toSend) { + return nil + } + } + if !reRegister { + return nil + } + + // If we get here, it means we've hit an event above that requires us to + // continue watching the file or directory + stat, err := os.Stat(path) + if err != nil { + return err + } + return w.associateFile(path, stat) +} + +func (w *Watcher) updateDirectory(path string) error { + // The directory was modified, so we must find unwatched entites and + // watch them. If something was removed from the directory, nothing will + // happen, as everything else should still be watched. + files, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + for _, finfo := range files { + path := filepath.Join(path, finfo.Name()) + if w.port.PathIsWatched(path) { + continue + } + + err := w.associateFile(path, finfo) + if err != nil { + if !w.sendError(err) { + return nil + } + } + if !w.sendEvent(Event{path, Create}) { + return nil + } + } return nil } + +func (w *Watcher) associateFile(path string, stat os.FileInfo) error { + mode := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW + fmode := stat.Mode() + return w.port.AssociatePath(path, stat, mode, fmode) +} + +func (w *Watcher) dissociateFile(path string, stat os.FileInfo) error { + if !w.port.PathIsWatched(path) { + return nil + } + return w.port.DissociatePath(path) +} diff --git a/go.mod b/go.mod index 8d1fc129..0a53555c 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,6 @@ module github.com/fsnotify/fsnotify go 1.16 -require golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c +require golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3 retract v1.5.0 diff --git a/go.sum b/go.sum index 0f478630..0b26ba96 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3 h1:VU4cjb3pNZovHYRs/2E8zPrpLC05+GT2uqqgIFtt0jc= +golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/integration_test.go b/integration_test.go index 85d7b9bb..19211e80 100644 --- a/integration_test.go +++ b/integration_test.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !plan9 && !solaris -// +build !plan9,!solaris +//go:build !plan9 +// +build !plan9 package fsnotify From 96066fd2575e3fa1fc8510d4cd0cbcb021337a05 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Mon, 2 Aug 2021 18:59:31 +0000 Subject: [PATCH 02/47] GitHub Action to run illumos tests in a VM --- .github/workflows/slow-tests.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/slow-tests.yml diff --git a/.github/workflows/slow-tests.yml b/.github/workflows/slow-tests.yml new file mode 100644 index 00000000..b8f1fc82 --- /dev/null +++ b/.github/workflows/slow-tests.yml @@ -0,0 +1,17 @@ +name: slow-tests +on: [workflow_dispatch] +jobs: + test-illumos: + runs-on: macos-10.15 + name: Test in illumos VM + steps: + - uses: actions/checkout@v2 + - name: Test on illumos + id: test + uses: papertigers/illumos-vm@main + with: + prepare: | + pkg set-publisher -r -O https://pkg.omnios.org/r151036/extra extra.omnios + pkg install go-117 + run: | + /opt/ooce/go-1.17/bin/go test From 2226f96b41cc1dd025fcfc574adfc4b3ca406417 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Mon, 17 Jan 2022 18:47:17 +0000 Subject: [PATCH 03/47] XXX squashme: fixups --- LICENSE | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 4fc04b55..0845aa1d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright (c) 2012 The Go Authors. All rights reserved. -Copyright (c) 2012-2021 fsnotify Authors. All rights reserved. +Copyright (c) 2012-2022 fsnotify Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/README.md b/README.md index 7797745d..c9d5a52d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. -Cross platform: Windows, Linux, BSD and macOS. +Cross platform: Windows, Linux, BSD, macOS, and Solaris/illumos. | Adapter | OS | Status | | --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | @@ -14,7 +14,7 @@ Cross platform: Windows, Linux, BSD and macOS. | kqueue | BSD, macOS, iOS\* | Supported | | ReadDirectoryChangesW | Windows | Supported | | FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | -| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) | +| FEN | Solaris 11, illumos | Supported | | fanotify | Linux 2.6.37+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) | | USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | | Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | From 1fbfcc9ef7f5c833edcb361d68f325bfac0b9f30 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Mon, 17 Jan 2022 18:50:40 +0000 Subject: [PATCH 04/47] XXX squashme: remove doneResp channel - doesn't break tests... --- fen.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fen.go b/fen.go index af0c0109..80f75e7b 100644 --- a/fen.go +++ b/fen.go @@ -23,8 +23,7 @@ type Watcher struct { port *unix.EventPort - done chan struct{} // Channel for sending a "quit message" to the reader goroutine - doneResp chan struct{} // Channel to respond to Close + done chan struct{} // Channel for sending a "quit message" to the reader goroutine } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. @@ -39,7 +38,6 @@ func NewWatcher() (*Watcher, error) { return nil, err } w.done = make(chan struct{}) - w.doneResp = make(chan struct{}) go w.readEvents() return w, nil @@ -83,7 +81,6 @@ func (w *Watcher) Close() error { } close(w.done) w.port.Close() - <-w.doneResp return nil } @@ -129,7 +126,6 @@ func (w *Watcher) Remove(name string) error { func (w *Watcher) readEvents() { // If this function returns, the watcher has been closed and we can // close these channels - defer close(w.doneResp) defer close(w.Errors) defer close(w.Events) From cf84108b1a10e60de3d19aae3077410f924256da Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 18 Jan 2022 01:41:28 +0000 Subject: [PATCH 05/47] XXX squashme: send all fsnotify events found in the event ports event --- fen.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/fen.go b/fen.go index 80f75e7b..a87b4376 100644 --- a/fen.go +++ b/fen.go @@ -187,6 +187,11 @@ func (w *Watcher) handleDirectory(path string, stat os.FileInfo, handler func(st return handler(path, stat) } +// handleEvent might need to emit more than one fsnotify event +// if the events bitmap matches more than one event type +// (e.g. the file was both modified and had the +// attributes changed between when the association +// was created and the when event was returned) func (w *Watcher) handleEvent(event *unix.PortEvent) error { events := event.Events path := event.Path @@ -203,14 +208,26 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { } } else { toSend = &Event{path, Write} + if !w.sendEvent(*toSend) { + return nil + } } case events&unix.FILE_ATTRIB == unix.FILE_ATTRIB: toSend = &Event{path, Chmod} + if !w.sendEvent(*toSend) { + return nil + } case events&unix.FILE_DELETE == unix.FILE_DELETE: toSend = &Event{path, Remove} + if !w.sendEvent(*toSend) { + return nil + } reRegister = false case events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM: toSend = &Event{path, Rename} + if !w.sendEvent(*toSend) { + return nil + } // Don't keep watching the new file name reRegister = false case events&unix.FILE_RENAME_TO == unix.FILE_RENAME_TO: @@ -223,17 +240,15 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { // inotify reports a Remove event in this case, so we simulate // this here. toSend = &Event{path, Remove} + if !w.sendEvent(*toSend) { + return nil + } // Don't keep watching the file that was removed reRegister = false default: return errors.New("unknown event received") } - if toSend != nil { - if !w.sendEvent(*toSend) { - return nil - } - } if !reRegister { return nil } From 546c36ce44b219296591f19c19e4d030b44395a1 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 18 Jan 2022 14:02:52 +0000 Subject: [PATCH 06/47] XXX squashme: select -> if --- fen.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/fen.go b/fen.go index a87b4376..d13317fb 100644 --- a/fen.go +++ b/fen.go @@ -200,8 +200,9 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { var toSend *Event reRegister := true - switch { - case events&unix.FILE_MODIFIED == unix.FILE_MODIFIED: + fmt.Printf("NAHUM: %b\n", events) + + if events&unix.FILE_MODIFIED == unix.FILE_MODIFIED{ if fmode.IsDir() { if err := w.updateDirectory(path); err != nil { return err @@ -212,25 +213,29 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { return nil } } - case events&unix.FILE_ATTRIB == unix.FILE_ATTRIB: + } + if events&unix.FILE_ATTRIB == unix.FILE_ATTRIB{ toSend = &Event{path, Chmod} if !w.sendEvent(*toSend) { return nil } - case events&unix.FILE_DELETE == unix.FILE_DELETE: + } + if events&unix.FILE_DELETE == unix.FILE_DELETE{ toSend = &Event{path, Remove} if !w.sendEvent(*toSend) { return nil } reRegister = false - case events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM: + } + if events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM{ toSend = &Event{path, Rename} if !w.sendEvent(*toSend) { return nil } // Don't keep watching the new file name reRegister = false - case events&unix.FILE_RENAME_TO == unix.FILE_RENAME_TO: + } + if events&unix.FILE_RENAME_TO == unix.FILE_RENAME_TO{ // We don't report a Rename event for this case, because // Rename events are interpreted as referring to the _old_ name // of the file, and in this case the event would refer to the @@ -245,8 +250,6 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { } // Don't keep watching the file that was removed reRegister = false - default: - return errors.New("unknown event received") } if !reRegister { From d3c741d867f646d7087ae43c085227641215bf84 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Wed, 19 Jan 2022 02:34:14 +0000 Subject: [PATCH 07/47] XXX squashme: handle merged MODIFIED and ATTRIB events --- fen.go | 63 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/fen.go b/fen.go index d13317fb..0d9fa05e 100644 --- a/fen.go +++ b/fen.go @@ -130,9 +130,12 @@ func (w *Watcher) readEvents() { defer close(w.Events) pevents := make([]unix.PortEvent, 8, 8) + timeout := new(unix.Timespec) + timeout.Nsec = 500 for { count, err := w.port.Get(pevents, 1, nil) - if err != nil { + //count, err := w.port.Get(pevents, 8, timeout) + if err != nil && err != unix.ETIME { // Interrupted system call (count should be 0) ignore and continue if err == unix.EINTR && count == 0 { continue @@ -200,34 +203,14 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { var toSend *Event reRegister := true - fmt.Printf("NAHUM: %b\n", events) - - if events&unix.FILE_MODIFIED == unix.FILE_MODIFIED{ - if fmode.IsDir() { - if err := w.updateDirectory(path); err != nil { - return err - } - } else { - toSend = &Event{path, Write} - if !w.sendEvent(*toSend) { - return nil - } - } - } - if events&unix.FILE_ATTRIB == unix.FILE_ATTRIB{ - toSend = &Event{path, Chmod} - if !w.sendEvent(*toSend) { - return nil - } - } - if events&unix.FILE_DELETE == unix.FILE_DELETE{ + if events&unix.FILE_DELETE == unix.FILE_DELETE { toSend = &Event{path, Remove} if !w.sendEvent(*toSend) { return nil } reRegister = false } - if events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM{ + if events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM { toSend = &Event{path, Rename} if !w.sendEvent(*toSend) { return nil @@ -235,7 +218,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { // Don't keep watching the new file name reRegister = false } - if events&unix.FILE_RENAME_TO == unix.FILE_RENAME_TO{ + if events&unix.FILE_RENAME_TO == unix.FILE_RENAME_TO { // We don't report a Rename event for this case, because // Rename events are interpreted as referring to the _old_ name // of the file, and in this case the event would refer to the @@ -252,16 +235,44 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { reRegister = false } + // The file is gone, nothing left to do. if !reRegister { return nil } - // If we get here, it means we've hit an event above that requires us to - // continue watching the file or directory + // If we didn't get a deletion the file still exists and we're going to have to watch it again. + // Let's Stat it now so that we can compare permissions and have what we need + // to continue watching the file + stat, err := os.Stat(path) if err != nil { return err } + + if events&unix.FILE_MODIFIED == unix.FILE_MODIFIED { + if fmode.IsDir() { + if err := w.updateDirectory(path); err != nil { + return err + } + } else { + toSend = &Event{path, Write} + if !w.sendEvent(*toSend) { + return nil + } + } + } + if events&unix.FILE_ATTRIB == unix.FILE_ATTRIB { + // Only send Chmod if perms changed + if stat.Mode().Perm() != fmode.Perm() { + toSend = &Event{path, Chmod} + if !w.sendEvent(*toSend) { + return nil + } + } + } + + // If we get here, it means we've hit an event above that requires us to + // continue watching the file or directory return w.associateFile(path, stat) } From acb22e2413879ea898397eeb5bc732fbdaf43dfa Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Wed, 19 Jan 2022 02:36:48 +0000 Subject: [PATCH 08/47] fix changelog --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d042428..08a0f99f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## v1.6.0 / 2022-01-28 - * Solaris: add support for File Event Notifications (fen) [#12](https://github.com/fsnotify/fsnotify/issues/12) ## [1.5.1] - 2021-08-24 From e19ca771eb9bf54336525697fc3933342ef843ca Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Wed, 19 Jan 2022 02:38:33 +0000 Subject: [PATCH 09/47] ugprade golang.org/x/sys to latest --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0a53555c..09b30471 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,6 @@ module github.com/fsnotify/fsnotify go 1.16 -require golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3 +require golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 retract v1.5.0 diff --git a/go.sum b/go.sum index 0b26ba96..031dcee7 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3 h1:VU4cjb3pNZovHYRs/2E8zPrpLC05+GT2uqqgIFtt0jc= golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 545480935de64e3d43794baab5cf221071a71489 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Wed, 19 Jan 2022 02:41:00 +0000 Subject: [PATCH 10/47] add solaris and illumos cross-compilation builds --- .github/workflows/build.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04d6213b..a8db8109 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,3 +28,26 @@ jobs: - name: build-${{ matrix.goos }}-${{ matrix.goarch }} run: | GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build + + cross-compile-amd64-only: + strategy: + fail-fast: false + matrix: + goos: + - solaris + - illumos + goarch: + - amd64 + runs-on: ubuntu-latest + steps: + - name: setup Go + uses: actions/setup-go@v2 + with: + go-version: '1.17' + + - name: checkout + uses: actions/checkout@v2 + + - name: build-${{ matrix.goos }}-${{ matrix.goarch }} + run: | + GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build From 7b89a966f6aae41ebf17b763530185b377718ffb Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Wed, 19 Jan 2022 02:42:24 +0000 Subject: [PATCH 11/47] illumos tests: run 10 times If we're paying for a 15 minute wait to spin up the test VM run the tests multiple times to get some extra confidence out of it at the cost of another minute --- .github/workflows/slow-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/slow-tests.yml b/.github/workflows/slow-tests.yml index b8f1fc82..8cb2aa15 100644 --- a/.github/workflows/slow-tests.yml +++ b/.github/workflows/slow-tests.yml @@ -14,4 +14,4 @@ jobs: pkg set-publisher -r -O https://pkg.omnios.org/r151036/extra extra.omnios pkg install go-117 run: | - /opt/ooce/go-1.17/bin/go test + /opt/ooce/go-1.17/bin/go test -count=10 From 04e3205c075df83b6647f04ad24a2d1c7e675b65 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 20 Jan 2022 01:20:24 +0000 Subject: [PATCH 12/47] Fix FEN --- fen.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/fen.go b/fen.go index 0d9fa05e..c6298f49 100644 --- a/fen.go +++ b/fen.go @@ -14,6 +14,11 @@ import ( "io/ioutil" "os" "path/filepath" + "sync" +) + +var ( + eventBits = unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW ) // Watcher watches a set of files, delivering events to a channel. @@ -21,9 +26,10 @@ type Watcher struct { Events chan Event Errors chan error - port *unix.EventPort - done chan struct{} // Channel for sending a "quit message" to the reader goroutine + + mu sync.Mutex + port *unix.EventPort } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. @@ -130,11 +136,8 @@ func (w *Watcher) readEvents() { defer close(w.Events) pevents := make([]unix.PortEvent, 8, 8) - timeout := new(unix.Timespec) - timeout.Nsec = 500 for { count, err := w.port.Get(pevents, 1, nil) - //count, err := w.port.Get(pevents, 8, timeout) if err != nil && err != unix.ETIME { // Interrupted system call (count should be 0) ignore and continue if err == unix.EINTR && count == 0 { @@ -305,9 +308,22 @@ func (w *Watcher) updateDirectory(path string) error { } func (w *Watcher) associateFile(path string, stat os.FileInfo) error { - mode := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW + // This is primarily protecting the call to AssociatePath + // but it is important and intentional that the call to + // PathIsWatched is also protected by this mutex. + // Without this mutex, AssociatePath has been seen + // to error out that the path is already associated. + w.mu.Lock() + defer w.mu.Unlock() + + if w.port.PathIsWatched(path) { + // Remove the old association in favor of this one + if err := w.port.DissociatePath(path); err != nil { + return err + } + } fmode := stat.Mode() - return w.port.AssociatePath(path, stat, mode, fmode) + return w.port.AssociatePath(path, stat, eventBits, fmode) } func (w *Watcher) dissociateFile(path string, stat os.FileInfo) error { From 8b33f88cd0c9f63e070f47b18e89af3f67c263e1 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 20 Jan 2022 01:20:43 +0000 Subject: [PATCH 13/47] Integration Tests: change 1ms sleeps to 50ms When testing the FEN code repeatedly and under system load I can sometimes get these tests to fail with a missed WRITE event. With these longer sleeps I have been unable to produce any test failures. --- integration_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration_test.go b/integration_test.go index 19211e80..ec18e03e 100644 --- a/integration_test.go +++ b/integration_test.go @@ -135,7 +135,7 @@ func TestFsnotifyMultipleOperations(t *testing.T) { } f.Sync() - time.Sleep(time.Millisecond) + time.Sleep(50 * time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -248,7 +248,7 @@ func TestFsnotifyMultipleCreates(t *testing.T) { } f.Sync() - time.Sleep(time.Millisecond) + time.Sleep(50 * time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -274,7 +274,7 @@ func TestFsnotifyMultipleCreates(t *testing.T) { } f.Sync() - time.Sleep(time.Millisecond) + time.Sleep(50 * time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -288,7 +288,7 @@ func TestFsnotifyMultipleCreates(t *testing.T) { } f.Sync() - time.Sleep(time.Millisecond) + time.Sleep(50 * time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -387,7 +387,7 @@ func TestFsnotifyDirOnly(t *testing.T) { } f.Sync() - time.Sleep(time.Millisecond) + time.Sleep(50 * time.Millisecond) f.WriteString("data") f.Sync() f.Close() From bb0549e55b593d0c65404ef2cb293e7b7ce042f5 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 20 Jan 2022 01:56:49 +0000 Subject: [PATCH 14/47] make it a const --- fen.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fen.go b/fen.go index c6298f49..13295b22 100644 --- a/fen.go +++ b/fen.go @@ -17,9 +17,7 @@ import ( "sync" ) -var ( - eventBits = unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW -) +const eventBits = unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW // Watcher watches a set of files, delivering events to a channel. type Watcher struct { From deef20543448583d8b8df8daeb3cb82dd94350b5 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 20 Jan 2022 01:59:28 +0000 Subject: [PATCH 15/47] run slow tests automatically on pushes to main as a backstop of last resort --- .github/workflows/slow-tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/slow-tests.yml b/.github/workflows/slow-tests.yml index 8cb2aa15..3bf66339 100644 --- a/.github/workflows/slow-tests.yml +++ b/.github/workflows/slow-tests.yml @@ -1,5 +1,10 @@ name: slow-tests -on: [workflow_dispatch] +on: + workflow_dispatch: + push: + branches: + - main + jobs: test-illumos: runs-on: macos-10.15 From c375f66258cabda1f1ce413d423cce0bd2e6fea6 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 20 Jan 2022 02:42:07 +0000 Subject: [PATCH 16/47] Revert "Integration Tests: change 1ms sleeps to 50ms" This reverts commit 8b33f88cd0c9f63e070f47b18e89af3f67c263e1. --- integration_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration_test.go b/integration_test.go index ec18e03e..19211e80 100644 --- a/integration_test.go +++ b/integration_test.go @@ -135,7 +135,7 @@ func TestFsnotifyMultipleOperations(t *testing.T) { } f.Sync() - time.Sleep(50 * time.Millisecond) + time.Sleep(time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -248,7 +248,7 @@ func TestFsnotifyMultipleCreates(t *testing.T) { } f.Sync() - time.Sleep(50 * time.Millisecond) + time.Sleep(time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -274,7 +274,7 @@ func TestFsnotifyMultipleCreates(t *testing.T) { } f.Sync() - time.Sleep(50 * time.Millisecond) + time.Sleep(time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -288,7 +288,7 @@ func TestFsnotifyMultipleCreates(t *testing.T) { } f.Sync() - time.Sleep(50 * time.Millisecond) + time.Sleep(time.Millisecond) f.WriteString("data") f.Sync() f.Close() @@ -387,7 +387,7 @@ func TestFsnotifyDirOnly(t *testing.T) { } f.Sync() - time.Sleep(50 * time.Millisecond) + time.Sleep(time.Millisecond) f.WriteString("data") f.Sync() f.Close() From 12c8a00337eda94242eed94bf7651084fa14d668 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Sun, 30 Jan 2022 03:06:21 +0000 Subject: [PATCH 17/47] More fixups This code depends on improvements in https://go-review.googlesource.com/c/sys/+/380034/ That haven't landed yet. --- fen.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/fen.go b/fen.go index 13295b22..b32b1b05 100644 --- a/fen.go +++ b/fen.go @@ -84,8 +84,11 @@ func (w *Watcher) Close() error { return nil } close(w.done) - w.port.Close() - return nil + // Take the lock used by associateFile to prevent + // lingering events from being processed after the close + w.mu.Lock() + defer w.mu.Unlock() + return w.port.Close() } // Add starts watching the named file or directory (non-recursively). @@ -306,6 +309,9 @@ func (w *Watcher) updateDirectory(path string) error { } func (w *Watcher) associateFile(path string, stat os.FileInfo) error { + if w.isClosed() { + return errors.New("FEN watcher already closed") + } // This is primarily protecting the call to AssociatePath // but it is important and intentional that the call to // PathIsWatched is also protected by this mutex. @@ -316,7 +322,13 @@ func (w *Watcher) associateFile(path string, stat os.FileInfo) error { if w.port.PathIsWatched(path) { // Remove the old association in favor of this one - if err := w.port.DissociatePath(path); err != nil { + // If we get ENOENT, then while the x/sys/unix wrapper + // still thought that this path was associated, + // the underlying event port did not. This call will + // have cleared up that discrepancy. The most likely + // cause is that the event has fired but we haven't + // processed it yet. + if err := w.port.DissociatePath(path); err != nil && err != unix.ENOENT { return err } } From 2d884f3bc0055414aba76a4246a6df17cb10d638 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Sun, 30 Jan 2022 03:08:01 +0000 Subject: [PATCH 18/47] integration tests: slight consistency improvement --- integration_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration_test.go b/integration_test.go index 8c9eb3ee..3d616ba1 100644 --- a/integration_test.go +++ b/integration_test.go @@ -400,6 +400,7 @@ func TestFsnotifyDirOnly(t *testing.T) { time.Sleep(eventSeparator) // give system time to sync write change before delete os.Remove(testFile) + time.Sleep(eventSeparator) os.Remove(testFileAlreadyExists) // We expect this event to be received almost immediately, but let's wait 500 ms to be sure @@ -553,10 +554,11 @@ func TestFsnotifySubDir(t *testing.T) { fs.Sync() fs.Close() - time.Sleep(200 * time.Millisecond) + time.Sleep(eventSeparator) // Make sure receive deletes for both file and sub-directory os.RemoveAll(testSubDir) + time.Sleep(eventSeparator) os.Remove(testFile1) // We expect this event to be received almost immediately, but let's wait 500 ms to be sure From 5f7091a38e26f1ee579a9c85be27da3cc38ce02e Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 1 Feb 2022 17:26:03 +0000 Subject: [PATCH 19/47] bump illumos-vm action --- .github/workflows/slow-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/slow-tests.yml b/.github/workflows/slow-tests.yml index 3bf66339..3e4893db 100644 --- a/.github/workflows/slow-tests.yml +++ b/.github/workflows/slow-tests.yml @@ -13,10 +13,9 @@ jobs: - uses: actions/checkout@v2 - name: Test on illumos id: test - uses: papertigers/illumos-vm@main + uses: papertigers/illumos-vm@r38 with: prepare: | - pkg set-publisher -r -O https://pkg.omnios.org/r151036/extra extra.omnios pkg install go-117 run: | /opt/ooce/go-1.17/bin/go test -count=10 From 60215730c64cddc8a18bb3663d7d6572ce201b6b Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Mon, 28 Mar 2022 15:13:23 +0000 Subject: [PATCH 20/47] Update x/sys/unix Pull in https://go-review.googlesource.com/c/sys/+/380034 for solaris/illumos --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 09b30471..350046b5 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,6 @@ module github.com/fsnotify/fsnotify go 1.16 -require golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 +require golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 retract v1.5.0 diff --git a/go.sum b/go.sum index 031dcee7..ddeb8d49 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,2 @@ -golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3 h1:VU4cjb3pNZovHYRs/2E8zPrpLC05+GT2uqqgIFtt0jc= -golang.org/x/sys v0.0.0-20210817134402-fefb4affbef3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 h1:eJv7u3ksNXoLbGSKuv2s/SIO4tJVxc/A+MTpzxDgz/Q= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 0da5b8d67d9289412db5155d5df6c53a8b9ebb04 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 2 Aug 2022 00:26:28 +0000 Subject: [PATCH 21/47] add more t.Helper() to get to the right general area in the test files. --- helpers_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helpers_test.go b/helpers_test.go index 08164fd0..01715d8c 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -21,8 +21,10 @@ type testCase struct { } func (tt testCase) run(t *testing.T) { + t.Helper() t.Run(tt.name, func(t *testing.T) { t.Parallel() + t.Helper() tmp := t.TempDir() w := newCollector(t) From 4d18cecfb486b58daf4f1f55267eccfc2d3394c2 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 2 Aug 2022 01:19:40 +0000 Subject: [PATCH 22/47] solaris sometimes acts like windows --- integration_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/integration_test.go b/integration_test.go index 1ebe9e69..55235ff2 100644 --- a/integration_test.go +++ b/integration_test.go @@ -186,6 +186,11 @@ func TestWatchRename(t *testing.T) { RENAME "/dir" # mv CREATE "/dir-renamed" CREATE "/dir-renamed/file" # touch + solaris: + CREATE "/dir" # mkdir + RENAME "/dir" # mv + CREATE "/dir-renamed" + CREATE "/dir-renamed/file" # touch # TODO: no results for the touch; this is probably a bug; windows # was fixed in #370. From 401f603a7b73d84aa5d92d7a1a73c8ce4a4f2e36 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 2 Aug 2022 01:20:22 +0000 Subject: [PATCH 23/47] solaris sometimes acts like linux --- integration_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integration_test.go b/integration_test.go index 55235ff2..acd8bcc1 100644 --- a/integration_test.go +++ b/integration_test.go @@ -250,6 +250,10 @@ func TestWatchSymlink(t *testing.T) { remove /link create /link write /link + solaris: + remove /link + create /link + write /link `}, } From f329a9a769fcc3858962dfa6128060b8d5ee9216 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 2 Aug 2022 01:21:17 +0000 Subject: [PATCH 24/47] bug in fen? I don't know why this is happening --- integration_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integration_test.go b/integration_test.go index acd8bcc1..c657dd22 100644 --- a/integration_test.go +++ b/integration_test.go @@ -341,6 +341,10 @@ func TestWatchRm(t *testing.T) { linux: remove /file remove / + # Why is solaris like this?? Bug? + #solaris: + # remove / + # remove /file windows: remove /file remove / From 9ac83461075ae362540c50b49c1e93e52c1be611 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 2 Aug 2022 01:24:32 +0000 Subject: [PATCH 25/47] bug in fen? I don't know why this is happening --- integration_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/integration_test.go b/integration_test.go index c657dd22..051e1197 100644 --- a/integration_test.go +++ b/integration_test.go @@ -70,6 +70,15 @@ func TestWatch(t *testing.T) { remove /sub remove /file + # Why is solaris watching the subdirectory? + #solaris: + # create /sub + # create /file + # create /sub/file2 + # remove /sub + # remove /sub/file2 + # remove /file + # Windows includes a write for the /sub dir too, two of them even(?) windows: create /sub From 532460eb8175746dc7a95ae64dfb589ae0692529 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Tue, 2 Aug 2022 01:32:14 +0000 Subject: [PATCH 26/47] fix typo --- fen.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fen.go b/fen.go index 95cdfe46..792c419e 100644 --- a/fen.go +++ b/fen.go @@ -282,7 +282,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { } func (w *Watcher) updateDirectory(path string) error { - // The directory was modified, so we must find unwatched entites and + // The directory was modified, so we must find unwatched entities and // watch them. If something was removed from the directory, nothing will // happen, as everything else should still be watched. files, err := ioutil.ReadDir(path) From f8f708ce607b9bb58e655263f864b05d232a06e6 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Tue, 2 Aug 2022 17:53:27 +0200 Subject: [PATCH 27/47] test helper: make solaris and illumos synonyms --- helpers_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/helpers_test.go b/helpers_test.go index 01715d8c..c8c9f9a6 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -414,10 +414,20 @@ func newEvents(t *testing.T, s string) Events { return e } switch runtime.GOOS { + // kqueue shortcut case "freebsd", "netbsd", "openbsd", "dragonfly", "darwin": if e, ok := events["kqueue"]; ok { return e } + // Fall back to solaris for illumos, and vice versa. + case "solaris": + if e, ok := events["illumos"]; ok { + return e + } + case "illumos": + if e, ok := events["solaris"]; ok { + return e + } } return events[""] } From e6d3c16047b1ffe44b498d8bc614412c5775458f Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Tue, 2 Aug 2022 18:01:57 +0200 Subject: [PATCH 28/47] Fix testcase --- integration_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/integration_test.go b/integration_test.go index 051e1197..4533e367 100644 --- a/integration_test.go +++ b/integration_test.go @@ -350,13 +350,12 @@ func TestWatchRm(t *testing.T) { linux: remove /file remove / - # Why is solaris like this?? Bug? - #solaris: - # remove / - # remove /file windows: remove /file remove / + solaris: + remove /file + remove / `}, } From 58d1748f5f7beb5ae581241808f5f00ac8e11235 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 4 Aug 2022 01:45:00 +0000 Subject: [PATCH 29/47] small cleanup of eventBits and call to AssociatePath --- fen.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fen.go b/fen.go index 792c419e..86dbc1e9 100644 --- a/fen.go +++ b/fen.go @@ -18,8 +18,6 @@ import ( "golang.org/x/sys/unix" ) -const eventBits = unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW - // Watcher watches a set of files, delivering events to a channel. type Watcher struct { Events chan Event @@ -333,8 +331,10 @@ func (w *Watcher) associateFile(path string, stat os.FileInfo) error { return err } } - fmode := stat.Mode() - return w.port.AssociatePath(path, stat, eventBits, fmode) + // FILE_NOFOLLOW means we watch symlinks themselves rather than their targets + return w.port.AssociatePath(path, stat, + unix.FILE_MODIFIED|unix.FILE_ATTRIB|unix.FILE_NOFOLLOW, + stat.Mode()) } func (w *Watcher) dissociateFile(path string, stat os.FileInfo) error { From bb59feda98cb627bc24ff3b5d4db84fc10df2125 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 4 Aug 2022 02:21:06 +0000 Subject: [PATCH 30/47] solaris: only watch the contents of directories explicitly added --- fen.go | 23 +++++++++++++++++++++-- integration_test.go | 24 +++++++++--------------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/fen.go b/fen.go index 86dbc1e9..f8143d72 100644 --- a/fen.go +++ b/fen.go @@ -26,6 +26,7 @@ type Watcher struct { done chan struct{} // Channel for sending a "quit message" to the reader goroutine mu sync.Mutex + dirs map[string]struct{} // map of explicitly watched directories port *unix.EventPort } @@ -36,6 +37,7 @@ func NewWatcher() (*Watcher, error) { w := new(Watcher) w.Events = make(chan Event) w.Errors = make(chan error) + w.dirs = make(map[string]struct{}) w.port, err = unix.NewEventPort() if err != nil { return nil, err @@ -103,6 +105,9 @@ func (w *Watcher) Add(name string) error { case err != nil: return err case stat.IsDir(): + w.mu.Lock() + w.dirs[name] = struct{}{} + w.mu.Unlock() return w.handleDirectory(name, stat, w.associateFile) default: return w.associateFile(name, stat) @@ -122,6 +127,9 @@ func (w *Watcher) Remove(name string) error { case err != nil: return err case stat.IsDir(): + w.mu.Lock() + delete(w.dirs, name) + w.mu.Unlock() return w.handleDirectory(name, stat, w.dissociateFile) default: return w.port.DissociatePath(name) @@ -206,6 +214,10 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { var toSend *Event reRegister := true + w.mu.Lock() + _, watchedDir := w.dirs[path] + w.mu.Unlock() + if events&unix.FILE_DELETE == unix.FILE_DELETE { toSend = &Event{path, Remove} if !w.sendEvent(*toSend) { @@ -240,6 +252,11 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { // The file is gone, nothing left to do. if !reRegister { + if watchedDir { + w.mu.Lock() + delete(w.dirs, path) + w.mu.Unlock() + } return nil } @@ -254,8 +271,10 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { if events&unix.FILE_MODIFIED == unix.FILE_MODIFIED { if fmode.IsDir() { - if err := w.updateDirectory(path); err != nil { - return err + if watchedDir { + if err := w.updateDirectory(path); err != nil { + return err + } } } else { toSend = &Event{path, Write} diff --git a/integration_test.go b/integration_test.go index 4533e367..7a5e1727 100644 --- a/integration_test.go +++ b/integration_test.go @@ -70,15 +70,6 @@ func TestWatch(t *testing.T) { remove /sub remove /file - # Why is solaris watching the subdirectory? - #solaris: - # create /sub - # create /file - # create /sub/file2 - # remove /sub - # remove /sub/file2 - # remove /file - # Windows includes a write for the /sub dir too, two of them even(?) windows: create /sub @@ -195,18 +186,21 @@ func TestWatchRename(t *testing.T) { RENAME "/dir" # mv CREATE "/dir-renamed" CREATE "/dir-renamed/file" # touch - solaris: - CREATE "/dir" # mkdir - RENAME "/dir" # mv - CREATE "/dir-renamed" - CREATE "/dir-renamed/file" # touch - # TODO: no results for the touch; this is probably a bug; windows # was fixed in #370. kqueue: CREATE "/dir" # mkdir CREATE "/dir-renamed" # mv REMOVE|RENAME "/dir" + + # We don't currently have a way to realize that the CREATEd directory is the one that was RENAMEd, + # so we don't know we need to watch it for new files. + # Possibly similar to the issue with kqueue?? + solaris: + CREATE "/dir" # mkdir + RENAME "/dir" # mv + CREATE "/dir-renamed" + `}, } From 6f5aebdc645340d7e64c58e66a2daa9d72e2a620 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 4 Aug 2022 02:34:19 +0000 Subject: [PATCH 31/47] solaris: remove races on Close --- fen.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fen.go b/fen.go index f8143d72..af5b543e 100644 --- a/fen.go +++ b/fen.go @@ -81,14 +81,14 @@ func (w *Watcher) isClosed() bool { // Close removes all watches and closes the events channel. func (w *Watcher) Close() error { - if w.isClosed() { - return nil - } - close(w.done) // Take the lock used by associateFile to prevent // lingering events from being processed after the close w.mu.Lock() defer w.mu.Unlock() + if w.isClosed() { + return nil + } + close(w.done) return w.port.Close() } From 91a0d03f6cb3e784e966bbed92711d321ea0cc8e Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Sat, 6 Aug 2022 19:58:56 +0200 Subject: [PATCH 32/47] =?UTF-8?q?Clarify=20that=20Oracle=E2=84=A2=C2=AE=20?= =?UTF-8?q?Solaris=E2=84=A2=C2=AE=20is=20currently=20untested?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5000c8dd..3badf712 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ fsnotify is a Go library to provide cross-platform filesystem notifications on -Windows, Linux, macOS, BSD, illumos, and Solaris. +Windows, Linux, macOS, BSD, and illumos. fsnotify requires Go 1.16 or newer. @@ -7,18 +7,19 @@ API docs: https://pkg.go.dev/github.com/fsnotify/fsnotify Platform support: -| Adapter | OS | Status | -| --------------------- | -----------------| -------------------------------------------------------------| -| inotify | Linux 2.6.32+ | Supported | -| kqueue | BSD, macOS | Supported | -| ReadDirectoryChangesW | Windows | Supported | -| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | -| FEN | illumos, Solaris | Supported | -| fanotify | Linux 5.9+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) | -| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | -| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | - -Linux and macOS should include Android and iOS, but these are currently untested. +| Adapter | OS | Status | +| --------------------- | -------------- | ------------------------------------------------------------ | +| inotify | Linux 2.6.32+ | Supported | +| kqueue | BSD, macOS | Supported | +| ReadDirectoryChangesW | Windows | Supported | +| FEN | illumos | Supported | +| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) | +| fanotify | Linux 5.9+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) | +| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) | +| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) | + +Linux, macOS, and illumos should include Android, iOS, and Solaris, but these +are currently untested. Usage ----- From 2811cadd0122769365abe26563dfbfe5c6bf209e Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Wed, 10 Aug 2022 01:12:06 +0000 Subject: [PATCH 33/47] solaris test tweaks --- fsnotify_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fsnotify_test.go b/fsnotify_test.go index fc2fe1f1..930c8f56 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -474,7 +474,7 @@ func TestClose(t *testing.T) { // Need a small sleep as Close() on kqueue does all sorts of things, // which may take a little bit. switch runtime.GOOS { - case "freebsd", "openbsd", "netbsd", "dragonfly", "darwin": + case "freebsd", "openbsd", "netbsd", "dragonfly", "darwin", "solaris": time.Sleep(5 * time.Millisecond) } @@ -929,6 +929,10 @@ func TestWatchList(t *testing.T) { // TODO: probably should I guess... t.Skip("WatchList has always beek broken on Windows and I don't feel like fixing it") } + if runtime.GOOS == "solaris" { + // TODO: Decide on correct solution for this + t.Skip("backend_fen currently starts watching 'other' because it's a child of tmp") + } t.Parallel() From 6ad9ebaa096b0782dd66ca4e273a3cb5b1620bbf Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 10 Aug 2022 17:15:13 +0200 Subject: [PATCH 34/47] Fix indent (gofmt) gofmt uses tabs, rather than spaces for indents. --- backend_fen.go | 30 +++++++++++++++--------------- fsnotify_test.go | 8 ++++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index 8ecc85e6..97097e6d 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -111,10 +111,10 @@ type Watcher struct { done chan struct{} // Channel for sending a "quit message" to the reader goroutine - mu sync.Mutex - dirs map[string]struct{} // map of explicitly watched directories + mu sync.Mutex + dirs map[string]struct{} // map of explicitly watched directories watches map[string]struct{} //map of explicitly watched non-directories - port *unix.EventPort + port *unix.EventPort } // NewWatcher creates a new Watcher. @@ -508,16 +508,16 @@ func (w *Watcher) dissociateFile(path string, stat os.FileInfo) error { // WatchList returns the directories and files that are being monitered. func (w *Watcher) WatchList() []string { - w.mu.Lock() - defer w.mu.Unlock() - - entries := make([]string, 0, len(w.watches) + len(w.dirs)) - for pathname := range w.dirs { - entries = append(entries, pathname) - } - for pathname := range w.watches { - entries = append(entries, pathname) - } - - return entries + w.mu.Lock() + defer w.mu.Unlock() + + entries := make([]string, 0, len(w.watches)+len(w.dirs)) + for pathname := range w.dirs { + entries = append(entries, pathname) + } + for pathname := range w.watches { + entries = append(entries, pathname) + } + + return entries } diff --git a/fsnotify_test.go b/fsnotify_test.go index 81c06cb3..ee4b361c 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -411,10 +411,10 @@ func TestWatchRename(t *testing.T) { CREATE "/dir" # mkdir CREATE "/dir-renamed" # mv REMOVE|RENAME "/dir" - solaris: - CREATE "/dir" # mkdir - RENAME "/dir" # mv - CREATE "/dir-renamed" + solaris: + CREATE "/dir" # mkdir + RENAME "/dir" # mv + CREATE "/dir-renamed" `}, {"rename watched file", func(t *testing.T, w *Watcher, tmp string) { From 73ce13026f564f5e84fe1659fa25ce3861e9d9a4 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 10 Aug 2022 17:21:09 +0200 Subject: [PATCH 35/47] Try to kick off the CI --- backend_fen.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index 97097e6d..f8c3ff45 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -119,18 +119,19 @@ type Watcher struct { // NewWatcher creates a new Watcher. func NewWatcher() (*Watcher, error) { - var err error + w := &Watcher{ + Events: make(chan Event), + Errors: make(chan error), + dirs: make(map[string]struct{}), + watches: make(map[string]struct{}), + done: make(chan struct{}), + } - w := new(Watcher) - w.Events = make(chan Event) - w.Errors = make(chan error) - w.dirs = make(map[string]struct{}) - w.watches = make(map[string]struct{}) + var err error w.port, err = unix.NewEventPort() if err != nil { - return nil, err + return nil, fmt.Errorf("fsnotify.NewWatcher: %w", err) } - w.done = make(chan struct{}) go w.readEvents() return w, nil From 6f261e6a9eeac6d126da9fc49547426298983aba Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 10 Aug 2022 17:30:02 +0200 Subject: [PATCH 36/47] "Fix" (new) testcase Seems like the watcher gets lost if the watched file gets renamed, just as with kqueue. It's not a big deal as such because it's been broken in kqueue since forever; should fix it, but for now just adjusting the test is fine. --- fsnotify_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fsnotify_test.go b/fsnotify_test.go index ee4b361c..d5fe2f3f 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -412,9 +412,9 @@ func TestWatchRename(t *testing.T) { CREATE "/dir-renamed" # mv REMOVE|RENAME "/dir" solaris: - CREATE "/dir" # mkdir - RENAME "/dir" # mv - CREATE "/dir-renamed" + CREATE "/dir" # mkdir + RENAME "/dir" # mv + CREATE "/dir-renamed" `}, {"rename watched file", func(t *testing.T, w *Watcher, tmp string) { @@ -433,7 +433,7 @@ func TestWatchRename(t *testing.T) { rename /file # mv rename rename-two # TODO: seems to lose the watch? - kqueue: + kqueue, solaris: rename /file # It's actually more correct on Windows. From 79d942444a90ecc9d93ecfe44cc28f4f28d0e2b3 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 10 Aug 2022 17:34:40 +0200 Subject: [PATCH 37/47] Add internal.Debug() for illumos too --- internal/debug_solaris.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 internal/debug_solaris.go diff --git a/internal/debug_solaris.go b/internal/debug_solaris.go new file mode 100644 index 00000000..e432818e --- /dev/null +++ b/internal/debug_solaris.go @@ -0,0 +1,37 @@ +package internal + +import ( + "fmt" + "os" + "strings" + "time" + + "golang.org/x/sys/unix" +) + +func Debug(name string, mask int32) { + names := []struct { + n string + m int32 + }{ + {"FILE_ACCESS", unix.FILE_ACCESS}, + {"FILE_MODIFIED", unix.FILE_MODIFIED}, + {"FILE_ATTRIB", unix.FILE_ATTRIB}, + {"FILE_TRUNC", unix.FILE_TRUNC}, + {"FILE_NOFOLLOW", unix.FILE_NOFOLLOW}, + {"FILE_DELETE", unix.FILE_DELETE}, + {"FILE_RENAME_TO", unix.FILE_RENAME_TO}, + {"FILE_RENAME_FROM", unix.FILE_RENAME_FROM}, + {"UNMOUNTED", unix.UNMOUNTED}, + {"MOUNTEDOVER", unix.MOUNTEDOVER}, + {"FILE_EXCEPTION", unix.FILE_EXCEPTION}, + } + + var l []string + for _, n := range names { + if mask&n.m == n.m { + l = append(l, n.n) + } + } + fmt.Fprintf(os.Stderr, "%s %-20s → %s\n", time.Now().Format("15:04:05.0000"), strings.Join(l, " | "), name) +} From 33df526b7173f3b3195e9b50cb3080fb9d2913eb Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 10 Aug 2022 17:46:01 +0200 Subject: [PATCH 38/47] Few small changes for clarity --- backend_fen.go | 99 ++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index f8c3ff45..85bbc6a0 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -218,30 +218,34 @@ func (w *Watcher) Add(name string) error { if w.port.PathIsWatched(name) { return nil } + stat, err := os.Stat(name) - switch { - case err != nil: + if err != nil { return err - case stat.IsDir(): + } + + // Associate all files in the directory. + if stat.IsDir() { w.mu.Lock() w.dirs[name] = struct{}{} w.mu.Unlock() - err = w.handleDirectory(name, stat, w.associateFile) + + err := w.handleDirectory(name, stat, w.associateFile) if err != nil { w.mu.Lock() delete(w.dirs, name) w.mu.Unlock() } - return err - default: - err = w.associateFile(name, stat) - if err != nil { - w.mu.Lock() - w.watches[name] = struct{}{} - w.mu.Unlock() - } - return err + return nil } + + err = w.associateFile(name, stat) + if err != nil { + w.mu.Lock() + w.watches[name] = struct{}{} + w.mu.Unlock() + } + return nil } // Remove stops monitoring the path for changes. @@ -257,46 +261,51 @@ func (w *Watcher) Remove(name string) error { if !w.port.PathIsWatched(name) { return fmt.Errorf("%w: %s", ErrNonExistentWatch, name) } + stat, err := os.Stat(name) - switch { - case err != nil: + if err != nil { return err - case stat.IsDir(): - err = w.handleDirectory(name, stat, w.dissociateFile) + } + + // Remove associations for every file in the directory. + if stat.IsDir() { + err := w.handleDirectory(name, stat, w.dissociateFile) if err != nil { w.mu.Lock() delete(w.dirs, name) w.mu.Unlock() } - return err - default: - err = w.port.DissociatePath(name) - if err != nil { - w.mu.Lock() - delete(w.watches, name) - w.mu.Unlock() - } - return err + return nil } + + err = w.port.DissociatePath(name) + if err != nil { + w.mu.Lock() + delete(w.watches, name) + w.mu.Unlock() + } + return nil } // readEvents contains the main loop that runs in a goroutine watching for events. func (w *Watcher) readEvents() { // If this function returns, the watcher has been closed and we can // close these channels - defer close(w.Errors) - defer close(w.Events) + defer func() { + close(w.Errors) + close(w.Events) + }() pevents := make([]unix.PortEvent, 8, 8) for { count, err := w.port.Get(pevents, 1, nil) if err != nil && err != unix.ETIME { // Interrupted system call (count should be 0) ignore and continue - if err == unix.EINTR && count == 0 { + if errors.Is(err, unix.EINTR) && count == 0 { continue } // Get failed because we called w.Close() - if err == unix.EBADF && w.isClosed() { + if errors.Is(err, unix.EBADF) && w.isClosed() { return } // There was an error not caused by calling w.Close() @@ -351,27 +360,25 @@ func (w *Watcher) handleDirectory(path string, stat os.FileInfo, handler func(st // attributes changed between when the association // was created and the when event was returned) func (w *Watcher) handleEvent(event *unix.PortEvent) error { - events := event.Events - path := event.Path - fmode := event.Cookie.(os.FileMode) - - var toSend *Event - reRegister := true + var ( + events = event.Events + path = event.Path + fmode = event.Cookie.(os.FileMode) + reRegister = true + ) w.mu.Lock() _, watchedDir := w.dirs[path] w.mu.Unlock() if events&unix.FILE_DELETE == unix.FILE_DELETE { - toSend = &Event{path, Remove} - if !w.sendEvent(*toSend) { + if !w.sendEvent(Event{path, Remove}) { return nil } reRegister = false } if events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM { - toSend = &Event{path, Rename} - if !w.sendEvent(*toSend) { + if !w.sendEvent(Event{path, Rename}) { return nil } // Don't keep watching the new file name @@ -386,8 +393,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { // inotify reports a Remove event in this case, so we simulate // this here. - toSend = &Event{path, Remove} - if !w.sendEvent(*toSend) { + if !w.sendEvent(Event{path, Remove}) { return nil } // Don't keep watching the file that was removed @@ -421,8 +427,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { } } } else { - toSend = &Event{path, Write} - if !w.sendEvent(*toSend) { + if !w.sendEvent(Event{path, Write}) { return nil } } @@ -430,8 +435,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { if events&unix.FILE_ATTRIB == unix.FILE_ATTRIB { // Only send Chmod if perms changed if stat.Mode().Perm() != fmode.Perm() { - toSend = &Event{path, Chmod} - if !w.sendEvent(*toSend) { + if !w.sendEvent(Event{path, Chmod}) { return nil } } @@ -490,7 +494,8 @@ func (w *Watcher) associateFile(path string, stat os.FileInfo) error { // have cleared up that discrepancy. The most likely // cause is that the event has fired but we haven't // processed it yet. - if err := w.port.DissociatePath(path); err != nil && err != unix.ENOENT { + err := w.port.DissociatePath(path) + if err != nil && err != unix.ENOENT { return err } } From 96d8c268f8053547edfdbc47535d54234af37fe9 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 10 Aug 2022 17:59:12 +0200 Subject: [PATCH 39/47] Add TestRemoveState() for FEN too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This also actually fixed up a mistake I made in 33df526 in removing the switches 😅 It would no longer return the error. --- backend_fen.go | 43 ++++++++++++++++++---------------- backend_fen_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 backend_fen_test.go diff --git a/backend_fen.go b/backend_fen.go index 85bbc6a0..107211d3 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -109,12 +109,11 @@ type Watcher struct { // Errors sends any errors. Errors chan error - done chan struct{} // Channel for sending a "quit message" to the reader goroutine - mu sync.Mutex - dirs map[string]struct{} // map of explicitly watched directories - watches map[string]struct{} //map of explicitly watched non-directories port *unix.EventPort + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + dirs map[string]struct{} // Explicitly watched directories + watches map[string]struct{} // Explicitly watched non-directories } // NewWatcher creates a new Watcher. @@ -226,25 +225,25 @@ func (w *Watcher) Add(name string) error { // Associate all files in the directory. if stat.IsDir() { - w.mu.Lock() - w.dirs[name] = struct{}{} - w.mu.Unlock() - err := w.handleDirectory(name, stat, w.associateFile) if err != nil { - w.mu.Lock() - delete(w.dirs, name) - w.mu.Unlock() + return err } + + w.mu.Lock() + w.dirs[name] = struct{}{} + w.mu.Unlock() return nil } err = w.associateFile(name, stat) if err != nil { - w.mu.Lock() - w.watches[name] = struct{}{} - w.mu.Unlock() + return err } + + w.mu.Lock() + w.watches[name] = struct{}{} + w.mu.Unlock() return nil } @@ -271,19 +270,23 @@ func (w *Watcher) Remove(name string) error { if stat.IsDir() { err := w.handleDirectory(name, stat, w.dissociateFile) if err != nil { - w.mu.Lock() - delete(w.dirs, name) - w.mu.Unlock() + return err } + + w.mu.Lock() + delete(w.dirs, name) + w.mu.Unlock() return nil } err = w.port.DissociatePath(name) if err != nil { - w.mu.Lock() - delete(w.watches, name) - w.mu.Unlock() + return err } + + w.mu.Lock() + delete(w.watches, name) + w.mu.Unlock() return nil } diff --git a/backend_fen_test.go b/backend_fen_test.go new file mode 100644 index 00000000..16df761f --- /dev/null +++ b/backend_fen_test.go @@ -0,0 +1,57 @@ +//go:build solaris +// +build solaris + +package fsnotify + +import ( + "fmt" + "path/filepath" + "strings" + "testing" +) + +func TestRemoveState(t *testing.T) { + var ( + tmp = t.TempDir() + dir = filepath.Join(tmp, "dir") + file = filepath.Join(dir, "file") + ) + mkdir(t, dir) + touch(t, file) + + w := newWatcher(t, tmp) + addWatch(t, w, tmp) + addWatch(t, w, file) + + check := func(wantDirs, wantFiles int) { + t.Helper() + if len(w.watches) != wantFiles { + var d []string + for k, v := range w.watches { + d = append(d, fmt.Sprintf("%#v = %#v", k, v)) + } + t.Errorf("unexpected number of entries in w.watches (have %d, want %d):\n%v", + len(w.watches), wantFiles, strings.Join(d, "\n")) + } + if len(w.dirs) != wantDirs { + var d []string + for k, v := range w.dirs { + d = append(d, fmt.Sprintf("%#v = %#v", k, v)) + } + t.Errorf("unexpected number of entries in w.dirs (have %d, want %d):\n%v", + len(w.dirs), wantDirs, strings.Join(d, "\n")) + } + } + + check(1, 1) + + if err := w.Remove(file); err != nil { + t.Fatal(err) + } + check(1, 0) + + if err := w.Remove(tmp); err != nil { + t.Fatal(err) + } + check(0, 0) +} From 0f85e13e7a670b5f71b3dea151e4be4a4ebad863 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 10 Aug 2022 19:34:33 +0200 Subject: [PATCH 40/47] Run ./mkdoc.zsh --- backend_fen.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend_fen.go b/backend_fen.go index 107211d3..b24c7fe3 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -515,7 +515,7 @@ func (w *Watcher) dissociateFile(path string, stat os.FileInfo) error { return w.port.DissociatePath(path) } -// WatchList returns the directories and files that are being monitered. +// WatchList returns all paths added with Add() (and are not yet removed). func (w *Watcher) WatchList() []string { w.mu.Lock() defer w.mu.Unlock() From 0b29427396c8883ae6ad9d15363e5baca901559e Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Thu, 11 Aug 2022 02:28:26 +0000 Subject: [PATCH 41/47] solaris: fixes and thoughts I noticed an odd behavior when I ran: go run ./cmd/fsnotify watch /tmp/ & sleep 1; mkdir /tmp/foo ; touch /tmp/foo/bar; sleep 1; rm -r /tmp/foo ; fg At first I just got errors, then I fixed it part way and saw a WRITE on the removed directory. Now I see a REMOVE. --- backend_fen.go | 36 +++++++++++++++++++++++++++--------- fsnotify_test.go | 20 +++++++++++++++----- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index b24c7fe3..5d7a2183 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -263,6 +263,8 @@ func (w *Watcher) Remove(name string) error { stat, err := os.Stat(name) if err != nil { + // TODO If we get here, what if name is still in w.dirs or w.watches? + // TODO Can that happen? return err } @@ -345,11 +347,9 @@ func (w *Watcher) handleDirectory(path string, stat os.FileInfo, handler func(st // Handle all children of the directory. for _, finfo := range files { - if !finfo.IsDir() { - err := handler(filepath.Join(path, finfo.Name()), finfo) - if err != nil { - return err - } + err := handler(filepath.Join(path, finfo.Name()), finfo) + if err != nil { + return err } } @@ -419,6 +419,17 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { stat, err := os.Stat(path) if err != nil { + // This is unexpected, but we should still emit an event + // This happens most often on "rm -r" of a subdirectory inside a watched directory + // We get a modify event of something happening inside, but by the time + // we get here, the sudirectory is already gone. Clearly we were watching this path + // but now it is gone. Let's tell the user that it was removed. + if !w.sendEvent(Event{path, Remove}) { + return nil + } + // TODO I'm not totally convinced we should actually return and thus emit this error. + // If we don't return here, we will continue and also emit the Write event after the fact + // on a directory that is now gone. Maybe we should just return nil? return err } @@ -428,6 +439,10 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { if err := w.updateDirectory(path); err != nil { return err } + } else { + if !w.sendEvent(Event{path, Write}) { + return nil + } } } else { if !w.sendEvent(Event{path, Write}) { @@ -435,7 +450,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { } } } - if events&unix.FILE_ATTRIB == unix.FILE_ATTRIB { + if events&unix.FILE_ATTRIB == unix.FILE_ATTRIB && stat != nil { // Only send Chmod if perms changed if stat.Mode().Perm() != fmode.Perm() { if !w.sendEvent(Event{path, Chmod}) { @@ -444,9 +459,12 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { } } - // If we get here, it means we've hit an event above that requires us to - // continue watching the file or directory - return w.associateFile(path, stat) + if stat != nil { + // If we get here, it means we've hit an event above that requires us to + // continue watching the file or directory + return w.associateFile(path, stat) + } + return nil } func (w *Watcher) updateDirectory(path string) error { diff --git a/fsnotify_test.go b/fsnotify_test.go index 06cb1b1f..f27c5096 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -90,7 +90,12 @@ func TestWatch(t *testing.T) { create /sub create /file remove /file - + solaris: + create /sub + create /file + write /sub + remove /sub + remove /file # Windows includes a write for the /sub dir too, two of them even(?) windows: create /sub @@ -161,6 +166,10 @@ func TestWatch(t *testing.T) { // behaviour too. t.Skip("broken on macOS") } + if runtime.GOOS == "solaris" { + // TODO solaris + t.Skip("broken on solaris") + } file := filepath.Join(tmp, "file") link := filepath.Join(tmp, "link") @@ -191,6 +200,10 @@ func TestWatch(t *testing.T) { t.Skip("broken on macOS") } + if runtime.GOOS == "solaris" { + // TODO solaris + t.Skip("broken on solaris") + } dir := filepath.Join(tmp, "dir") link := filepath.Join(tmp, "link") @@ -470,6 +483,7 @@ func TestWatchRename(t *testing.T) { CREATE "/dir" # mkdir RENAME "/dir" # mv CREATE "/dir-renamed" + WRITE "/dir-renamed" # touch `}, {"rename watched file", func(t *testing.T, w *Watcher, tmp string) { @@ -1161,10 +1175,6 @@ func TestWatchList(t *testing.T) { // TODO: probably should I guess... t.Skip("WatchList has always beek broken on Windows and I don't feel like fixing it") } - if runtime.GOOS == "solaris" { - // TODO: Decide on correct solution for this - t.Skip("backend_fen currently starts watching 'other' because it's a child of tmp") - } t.Parallel() From a49818bd867c58d7a7d306deb452983899cf59b8 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Sun, 11 Sep 2022 12:29:25 +0000 Subject: [PATCH 42/47] fen cleanups: ioutil, LStat vs Stat Replace deprecated use of ioutil During even processing, use LStat, but Stat explicitly watched symlinks Make event bit checking more succinct --- backend_fen.go | 58 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index 5d7a2183..ed309297 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -6,7 +6,6 @@ package fsnotify import ( "errors" "fmt" - "io/ioutil" "os" "path/filepath" "sync" @@ -301,7 +300,7 @@ func (w *Watcher) readEvents() { close(w.Events) }() - pevents := make([]unix.PortEvent, 8, 8) + pevents := make([]unix.PortEvent, 8) for { count, err := w.port.Get(pevents, 1, nil) if err != nil && err != unix.ETIME { @@ -340,14 +339,18 @@ func (w *Watcher) readEvents() { } func (w *Watcher) handleDirectory(path string, stat os.FileInfo, handler func(string, os.FileInfo) error) error { - files, err := ioutil.ReadDir(path) + files, err := os.ReadDir(path) if err != nil { return err } // Handle all children of the directory. - for _, finfo := range files { - err := handler(filepath.Join(path, finfo.Name()), finfo) + for _, entry := range files { + finfo, err := entry.Info() + if err != nil { + return err + } + err = handler(filepath.Join(path, finfo.Name()), finfo) if err != nil { return err } @@ -372,22 +375,24 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { w.mu.Lock() _, watchedDir := w.dirs[path] + _, watchedPath := w.watches[path] w.mu.Unlock() + isWatched := watchedDir || watchedPath - if events&unix.FILE_DELETE == unix.FILE_DELETE { + if events&unix.FILE_DELETE != 0 { if !w.sendEvent(Event{path, Remove}) { return nil } reRegister = false } - if events&unix.FILE_RENAME_FROM == unix.FILE_RENAME_FROM { + if events&unix.FILE_RENAME_FROM != 0 { if !w.sendEvent(Event{path, Rename}) { return nil } // Don't keep watching the new file name reRegister = false } - if events&unix.FILE_RENAME_TO == unix.FILE_RENAME_TO { + if events&unix.FILE_RENAME_TO != 0 { // We don't report a Rename event for this case, because // Rename events are interpreted as referring to the _old_ name // of the file, and in this case the event would refer to the @@ -410,6 +415,11 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { delete(w.dirs, path) w.mu.Unlock() } + if watchedPath { + w.mu.Lock() + delete(w.watches, path) + w.mu.Unlock() + } return nil } @@ -417,7 +427,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { // Let's Stat it now so that we can compare permissions and have what we need // to continue watching the file - stat, err := os.Stat(path) + lstat, err := os.Lstat(path) if err != nil { // This is unexpected, but we should still emit an event // This happens most often on "rm -r" of a subdirectory inside a watched directory @@ -433,7 +443,21 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { return err } - if events&unix.FILE_MODIFIED == unix.FILE_MODIFIED { + // resolve symlinks that were explicitly watched + // this helps suppress spurious Chmod events on watched symlinks + stat := lstat + if isWatched { + stat, err = os.Stat(path) + if err != nil { + if !w.sendEvent(Event{path, Remove}) { + return nil + } + } + // The symlink still exists, but the target is gone. + // Don't return the error + } + + if events&unix.FILE_MODIFIED != 0 { if fmode.IsDir() { if watchedDir { if err := w.updateDirectory(path); err != nil { @@ -450,7 +474,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { } } } - if events&unix.FILE_ATTRIB == unix.FILE_ATTRIB && stat != nil { + if events&unix.FILE_ATTRIB != 0 && stat != nil { // Only send Chmod if perms changed if stat.Mode().Perm() != fmode.Perm() { if !w.sendEvent(Event{path, Chmod}) { @@ -471,18 +495,22 @@ func (w *Watcher) updateDirectory(path string) error { // The directory was modified, so we must find unwatched entities and // watch them. If something was removed from the directory, nothing will // happen, as everything else should still be watched. - files, err := ioutil.ReadDir(path) + files, err := os.ReadDir(path) if err != nil { return err } - for _, finfo := range files { - path := filepath.Join(path, finfo.Name()) + for _, entry := range files { + path := filepath.Join(path, entry.Name()) if w.port.PathIsWatched(path) { continue } - err := w.associateFile(path, finfo) + finfo, err := entry.Info() + if err != nil { + return err + } + err = w.associateFile(path, finfo) if err != nil { if !w.sendError(err) { return nil From bdcc47393d44c134a2dd4975dfc21fa72f54f443 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Sun, 11 Sep 2022 15:09:51 +0000 Subject: [PATCH 43/47] fen: address some TODOs --- backend_fen.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index ed309297..82f5075f 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -260,10 +260,16 @@ func (w *Watcher) Remove(name string) error { return fmt.Errorf("%w: %s", ErrNonExistentWatch, name) } + // The user has expressed an intent. Immediately remove this name + // from whichever watch list it might be in. If it's not in there + // the delete doesn't cause harm. + w.mu.Lock() + delete(w.watches, name) + delete(w.dirs, name) + w.mu.Unlock() + stat, err := os.Stat(name) if err != nil { - // TODO If we get here, what if name is still in w.dirs or w.watches? - // TODO Can that happen? return err } @@ -273,10 +279,6 @@ func (w *Watcher) Remove(name string) error { if err != nil { return err } - - w.mu.Lock() - delete(w.dirs, name) - w.mu.Unlock() return nil } @@ -285,9 +287,6 @@ func (w *Watcher) Remove(name string) error { return err } - w.mu.Lock() - delete(w.watches, name) - w.mu.Unlock() return nil } @@ -437,10 +436,9 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { if !w.sendEvent(Event{path, Remove}) { return nil } - // TODO I'm not totally convinced we should actually return and thus emit this error. - // If we don't return here, we will continue and also emit the Write event after the fact - // on a directory that is now gone. Maybe we should just return nil? - return err + // Suppress extra write events on removed directories; they are not informative + // and can be confusing. + return nil } // resolve symlinks that were explicitly watched From 60b7185516b9b0755419f9ce5fa9527cfc6d4a02 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Sun, 11 Sep 2022 20:53:00 +0000 Subject: [PATCH 44/47] fen: fix handling of explicitly watched symlinks --- backend_fen.go | 38 ++++++++++++++++++++++---------------- fsnotify_test.go | 8 -------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/backend_fen.go b/backend_fen.go index 82f5075f..b9fe4c08 100644 --- a/backend_fen.go +++ b/backend_fen.go @@ -217,6 +217,8 @@ func (w *Watcher) Add(name string) error { return nil } + // Currently we resolve symlinks that were explicitly requested to be + // watched. Otherwise we would use LStat here. stat, err := os.Stat(name) if err != nil { return err @@ -224,7 +226,7 @@ func (w *Watcher) Add(name string) error { // Associate all files in the directory. if stat.IsDir() { - err := w.handleDirectory(name, stat, w.associateFile) + err := w.handleDirectory(name, stat, true, w.associateFile) if err != nil { return err } @@ -235,7 +237,7 @@ func (w *Watcher) Add(name string) error { return nil } - err = w.associateFile(name, stat) + err = w.associateFile(name, stat, true) if err != nil { return err } @@ -275,7 +277,7 @@ func (w *Watcher) Remove(name string) error { // Remove associations for every file in the directory. if stat.IsDir() { - err := w.handleDirectory(name, stat, w.dissociateFile) + err := w.handleDirectory(name, stat, false, w.dissociateFile) if err != nil { return err } @@ -337,7 +339,7 @@ func (w *Watcher) readEvents() { } } -func (w *Watcher) handleDirectory(path string, stat os.FileInfo, handler func(string, os.FileInfo) error) error { +func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error { files, err := os.ReadDir(path) if err != nil { return err @@ -349,14 +351,14 @@ func (w *Watcher) handleDirectory(path string, stat os.FileInfo, handler func(st if err != nil { return err } - err = handler(filepath.Join(path, finfo.Name()), finfo) + err = handler(filepath.Join(path, finfo.Name()), finfo, false) if err != nil { return err } } // And finally handle the directory itself. - return handler(path, stat) + return handler(path, stat, follow) } // handleEvent might need to emit more than one fsnotify event @@ -426,7 +428,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { // Let's Stat it now so that we can compare permissions and have what we need // to continue watching the file - lstat, err := os.Lstat(path) + stat, err := os.Lstat(path) if err != nil { // This is unexpected, but we should still emit an event // This happens most often on "rm -r" of a subdirectory inside a watched directory @@ -441,18 +443,17 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { return nil } - // resolve symlinks that were explicitly watched + // resolve symlinks that were explicitly watched as we would have at Add() time. // this helps suppress spurious Chmod events on watched symlinks - stat := lstat if isWatched { stat, err = os.Stat(path) if err != nil { + // The symlink still exists, but the target is gone. Report the Remove similar to above. if !w.sendEvent(Event{path, Remove}) { return nil } + // Don't return the error } - // The symlink still exists, but the target is gone. - // Don't return the error } if events&unix.FILE_MODIFIED != 0 { @@ -484,7 +485,7 @@ func (w *Watcher) handleEvent(event *unix.PortEvent) error { if stat != nil { // If we get here, it means we've hit an event above that requires us to // continue watching the file or directory - return w.associateFile(path, stat) + return w.associateFile(path, stat, isWatched) } return nil } @@ -508,7 +509,7 @@ func (w *Watcher) updateDirectory(path string) error { if err != nil { return err } - err = w.associateFile(path, finfo) + err = w.associateFile(path, finfo, false) if err != nil { if !w.sendError(err) { return nil @@ -521,7 +522,7 @@ func (w *Watcher) updateDirectory(path string) error { return nil } -func (w *Watcher) associateFile(path string, stat os.FileInfo) error { +func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error { if w.isClosed() { return errors.New("FEN watcher already closed") } @@ -547,12 +548,17 @@ func (w *Watcher) associateFile(path string, stat os.FileInfo) error { } } // FILE_NOFOLLOW means we watch symlinks themselves rather than their targets + events := unix.FILE_MODIFIED|unix.FILE_ATTRIB|unix.FILE_NOFOLLOW + if follow { + // We *DO* follow symlinks for explicitly watched entries + events = unix.FILE_MODIFIED|unix.FILE_ATTRIB + } return w.port.AssociatePath(path, stat, - unix.FILE_MODIFIED|unix.FILE_ATTRIB|unix.FILE_NOFOLLOW, + events, stat.Mode()) } -func (w *Watcher) dissociateFile(path string, stat os.FileInfo) error { +func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error { if !w.port.PathIsWatched(path) { return nil } diff --git a/fsnotify_test.go b/fsnotify_test.go index f27c5096..b7296d09 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -166,10 +166,6 @@ func TestWatch(t *testing.T) { // behaviour too. t.Skip("broken on macOS") } - if runtime.GOOS == "solaris" { - // TODO solaris - t.Skip("broken on solaris") - } file := filepath.Join(tmp, "file") link := filepath.Join(tmp, "link") @@ -200,10 +196,6 @@ func TestWatch(t *testing.T) { t.Skip("broken on macOS") } - if runtime.GOOS == "solaris" { - // TODO solaris - t.Skip("broken on solaris") - } dir := filepath.Join(tmp, "dir") link := filepath.Join(tmp, "link") From cad1bd8f41f085771413f4575cd1630c9f347746 Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Wed, 14 Sep 2022 02:09:52 +0000 Subject: [PATCH 45/47] illumos: test on go 1.19 --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 89f6c6de..3fa43d2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,17 +113,17 @@ jobs: # illumos testillumos: runs-on: macos-12 - name: test (illumos, 1.17) + name: test (illumos, 1.19) steps: - uses: actions/checkout@v2 - - name: test (illumos, 1.17) + - name: test (illumos, 1.19) id: test uses: papertigers/illumos-vm@r38 with: prepare: | - pkg install go-117 + pkg install go-119 run: | - /opt/ooce/go-1.17/bin/go test ./... + /opt/ooce/go-1.19/bin/go test ./... # Older Debian 6, for old Linux kernels. testDebian6: From 996e478ae0dca2a6e974cb53e62a11b8baa7d2ed Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Fri, 16 Sep 2022 15:53:14 +0000 Subject: [PATCH 46/47] TestWatchRename/re-add_renamed_file: fen behaves like kqueue --- fsnotify_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsnotify_test.go b/fsnotify_test.go index 7be34b03..f9f58761 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -527,7 +527,7 @@ func TestWatchRename(t *testing.T) { WRITE "" # TODO: wrong. - kqueue: + kqueue, solaris: RENAME "/file" WRITE "/file" `}, From 9dcb6e29524fe0b10c120070761df8a1db6e9f9d Mon Sep 17 00:00:00 2001 From: Nahum Shalman Date: Fri, 16 Sep 2022 16:09:05 +0000 Subject: [PATCH 47/47] explicit alias of 'fen' for solaris and illumos similar to kqueue --- fsnotify_test.go | 14 +++++++------- helpers_test.go | 10 +++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/fsnotify_test.go b/fsnotify_test.go index f9f58761..ad322d35 100644 --- a/fsnotify_test.go +++ b/fsnotify_test.go @@ -90,7 +90,7 @@ func TestWatch(t *testing.T) { create /sub create /file remove /file - solaris: + fen: create /sub create /file write /sub @@ -471,7 +471,7 @@ func TestWatchRename(t *testing.T) { CREATE "/dir" # mkdir CREATE "/dir-renamed" # mv REMOVE|RENAME "/dir" - solaris: + fen: CREATE "/dir" # mkdir RENAME "/dir" # mv CREATE "/dir-renamed" @@ -494,7 +494,7 @@ func TestWatchRename(t *testing.T) { rename /file # mv rename rename-two # TODO: seems to lose the watch? - kqueue, solaris: + kqueue, fen: rename /file # It's actually more correct on Windows. @@ -527,7 +527,7 @@ func TestWatchRename(t *testing.T) { WRITE "" # TODO: wrong. - kqueue, solaris: + kqueue, fen: RENAME "/file" WRITE "/file" `}, @@ -583,7 +583,7 @@ func TestWatchSymlink(t *testing.T) { write /link create /link - linux, windows, solaris: + linux, windows, fen: remove /link create /link write /link @@ -715,7 +715,7 @@ func TestWatchRm(t *testing.T) { linux: remove /file remove / - solaris: + fen: remove / remove /file windows: @@ -737,7 +737,7 @@ func TestClose(t *testing.T) { // Need a small sleep as Close() on kqueue does all sorts of things, // which may take a little bit. switch runtime.GOOS { - case "freebsd", "openbsd", "netbsd", "dragonfly", "darwin", "solaris": + case "freebsd", "openbsd", "netbsd", "dragonfly", "darwin", "solaris", "illumos": time.Sleep(5 * time.Millisecond) } diff --git a/helpers_test.go b/helpers_test.go index ec1c3bcb..3edda65a 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -521,13 +521,9 @@ func newEvents(t *testing.T, s string) Events { if e, ok := events["kqueue"]; ok { return e } - // Fall back to solaris for illumos, and vice versa. - case "solaris": - if e, ok := events["illumos"]; ok { - return e - } - case "illumos": - if e, ok := events["solaris"]; ok { + // fen shortcut + case "solaris", "illumos": + if e, ok := events["fen"]; ok { return e } }