Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Solaris FEN using EventPorts from x/sys/unix #371

Merged
merged 61 commits into from Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
e374e19
Add Solaris FEN using EventPorts from x/sys/unix
nshalman May 28, 2021
96066fd
GitHub Action to run illumos tests in a VM
nshalman Aug 2, 2021
2226f96
XXX squashme: fixups
nshalman Jan 17, 2022
1fbfcc9
XXX squashme: remove doneResp channel - doesn't break tests...
nshalman Jan 17, 2022
cf84108
XXX squashme: send all fsnotify events found in the event ports event
nshalman Jan 18, 2022
546c36c
XXX squashme: select -> if
nshalman Jan 18, 2022
d3c741d
XXX squashme: handle merged MODIFIED and ATTRIB events
nshalman Jan 19, 2022
acb22e2
fix changelog
nshalman Jan 19, 2022
f10ecda
Merge remote-tracking branch 'origin/main' into fen-v2
nshalman Jan 19, 2022
e19ca77
ugprade golang.org/x/sys to latest
nshalman Jan 19, 2022
5454809
add solaris and illumos cross-compilation builds
nshalman Jan 19, 2022
7b89a96
illumos tests: run 10 times
nshalman Jan 19, 2022
04e3205
Fix FEN
nshalman Jan 20, 2022
8b33f88
Integration Tests: change 1ms sleeps to 50ms
nshalman Jan 20, 2022
03df8a8
Merge branch 'main' into fen-v2
nshalman Jan 20, 2022
bb0549e
make it a const
nshalman Jan 20, 2022
deef205
run slow tests automatically on pushes to main as a backstop of last …
nshalman Jan 20, 2022
c375f66
Revert "Integration Tests: change 1ms sleeps to 50ms"
nshalman Jan 20, 2022
e541566
Merge remote-tracking branch 'origin/main' into fen-v2
nshalman Jan 20, 2022
12c8a00
More fixups
nshalman Jan 30, 2022
2d884f3
integration tests: slight consistency improvement
nshalman Jan 30, 2022
3db872f
Merge remote-tracking branch 'origin/main' into fen-v2
nshalman Jan 30, 2022
5f7091a
bump illumos-vm action
nshalman Feb 1, 2022
6021573
Update x/sys/unix
nshalman Mar 28, 2022
62fed86
Merge branch 'main' into fen-v2
arp242 Aug 1, 2022
0da5b8d
add more t.Helper() to get to the right general area in the test files.
nshalman Aug 2, 2022
4d18cec
solaris sometimes acts like windows
nshalman Aug 2, 2022
401f603
solaris sometimes acts like linux
nshalman Aug 2, 2022
f329a9a
bug in fen? I don't know why this is happening
nshalman Aug 2, 2022
9ac8346
bug in fen? I don't know why this is happening
nshalman Aug 2, 2022
532460e
fix typo
nshalman Aug 2, 2022
f8f708c
test helper: make solaris and illumos synonyms
arp242 Aug 2, 2022
e6d3c16
Fix testcase
arp242 Aug 2, 2022
58d1748
small cleanup of eventBits and call to AssociatePath
nshalman Aug 4, 2022
bb59fed
solaris: only watch the contents of directories explicitly added
nshalman Aug 4, 2022
6f5aebd
solaris: remove races on Close
nshalman Aug 4, 2022
5350981
Merge remote-tracking branch 'origin/main' into fen-v2
nshalman Aug 4, 2022
d1b9589
Merge branch 'main' into fen-v2
arp242 Aug 6, 2022
91a0d03
Clarify that Oracle™® Solaris™® is currently untested
arp242 Aug 6, 2022
6c4e73f
Merge branch 'main' into fen-v2
arp242 Aug 6, 2022
c8f45c7
Merge remote-tracking branch 'origin/main' into fen-v2
nshalman Aug 9, 2022
2811cad
solaris test tweaks
nshalman Aug 10, 2022
da1e80a
Merge branch 'main' into fen-v2
arp242 Aug 10, 2022
6ad9eba
Fix indent (gofmt)
arp242 Aug 10, 2022
73ce130
Try to kick off the CI
arp242 Aug 10, 2022
6f261e6
"Fix" (new) testcase
arp242 Aug 10, 2022
79d9424
Add internal.Debug() for illumos too
arp242 Aug 10, 2022
33df526
Few small changes for clarity
arp242 Aug 10, 2022
96d8c26
Add TestRemoveState() for FEN too
arp242 Aug 10, 2022
b9e7edc
Merge branch 'main' into fen-v2
arp242 Aug 10, 2022
0f85e13
Run ./mkdoc.zsh
arp242 Aug 10, 2022
fbe9413
Merge remote-tracking branch 'nshalman/fen-v2' into fen-v2
nshalman Aug 11, 2022
0b29427
solaris: fixes and thoughts
nshalman Aug 11, 2022
68dc7e7
Merge remote-tracking branch 'origin/main' into fen-v2
nshalman Sep 8, 2022
a49818b
fen cleanups: ioutil, LStat vs Stat
nshalman Sep 11, 2022
bdcc473
fen: address some TODOs
nshalman Sep 11, 2022
60b7185
fen: fix handling of explicitly watched symlinks
nshalman Sep 11, 2022
cad1bd8
illumos: test on go 1.19
nshalman Sep 14, 2022
2daef9a
Merge remote-tracking branch 'origin/main' into fen-v2
nshalman Sep 16, 2022
996e478
TestWatchRename/re-add_renamed_file: fen behaves like kqueue
nshalman Sep 16, 2022
9dcb6e2
explicit alias of 'fen' for solaris and illumos similar to kqueue
nshalman Sep 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/slow-tests.yml
@@ -0,0 +1,17 @@
name: slow-tests
on: [workflow_dispatch]
nshalman marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@papertigers Do you include the extra repo in the latest images so that I could skip this part?
Alternately is there a clever way to make this more generic to survive across releases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up, @papertigers was kind enough to do this for the @r38 branch using a newer OmniOS release:
papertigers@2db24b5

I will pull in those changes soon.

pkg install go-117
run: |
/opt/ooce/go-1.17/bin/go test
3 changes: 3 additions & 0 deletions AUTHORS
Expand Up @@ -26,14 +26,17 @@ Eric Lin <linxiulei@gmail.com>
Evan Phoenix <evan@fallingsnow.net>
Francisco Souza <f@souza.cc>
Gautam Dey <gautam.dey77@gmail.com>
Gereon Frey <gereon.frey@gmail.com>
Hari haran <hariharan.uno@gmail.com>
Ichinose Shogo <shogo82148@gmail.com>
Isaac Davis <isaac.davis@joyent.com>
Johannes Ebke <johannes@ebke.org>
John C Barstow <jbowtie@amathaine.com>
Kelvin Fo <vmirage@gmail.com>
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
Matt Layher <mdlayher@gmail.com>
Matthias Stone <matthias@bellstone.ca>
Nahum Shalman <nahamu@gmail.com>
Nathan Youngman <git@nathany.com>
Nickolai Zeldovich <nickolai@csail.mit.edu>
Oliver Bristow <evilumbrella+github@gmail.com>
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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
nshalman marked this conversation as resolved.
Show resolved Hide resolved

* 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
Expand Down
2 changes: 1 addition & 1 deletion 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-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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -6,15 +6,15 @@

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 |
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| inotify | Linux 2.6.27 or later, Android\* | Supported |
| 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) |
Expand Down
273 changes: 271 additions & 2 deletions fen.go
Expand Up @@ -9,30 +9,299 @@ 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
}

// 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{})

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()
return nil
}

// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
nshalman marked this conversation as resolved.
Show resolved Hide resolved
return nil
if w.isClosed() {
return errors.New("FEN watcher already closed")
}
if w.port.PathIsWatched(name) {
return nil
}
stat, err := os.Stat(name)
nshalman marked this conversation as resolved.
Show resolved Hide resolved
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.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)
}

// 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
fmode := event.Cookie.(os.FileMode)

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{
toSend = &Event{path, Remove}
if !w.sendEvent(*toSend) {
return nil
}
reRegister = false
}
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
}
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
// 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}
if !w.sendEvent(*toSend) {
return nil
}
// Don't keep watching the file that was removed
reRegister = false
}

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)
}
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -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
nshalman marked this conversation as resolved.
Show resolved Hide resolved

retract v1.5.0
4 changes: 2 additions & 2 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions integration_test.go
Expand Up @@ -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

Expand Down