Update dependencies
This commit is contained in:
8
vendor/github.com/illarion/gonotify/v3/.gitignore
generated
vendored
Normal file
8
vendor/github.com/illarion/gonotify/v3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
21
vendor/github.com/illarion/gonotify/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/illarion/gonotify/v3/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2023 Ilarion Kovalchuk
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
71
vendor/github.com/illarion/gonotify/v3/README.md
generated
vendored
Normal file
71
vendor/github.com/illarion/gonotify/v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
## Gonotify
|
||||
|
||||
Simple Golang inotify wrapper.
|
||||
|
||||
[](https://godoc.org/github.com/illarion/gonotify/v3)
|
||||
|
||||
### Provides following primitives:
|
||||
|
||||
* Low level
|
||||
* `Inotify` - wrapper around [inotify(7)](http://man7.org/linux/man-pages/man7/inotify.7.html)
|
||||
* `InotifyEvent` - generated file/folder event. Contains `Name` (full path), `Wd` - watch descriptor and `Mask` that describes the event.
|
||||
|
||||
* Higher level
|
||||
* `FileWatcher` - higher level utility, helps to watch the list of files for changes, creation or removal
|
||||
* `DirWatcher` - higher level utility, recursively watches given root folder for added, removed or changed files.
|
||||
* `FileEvent` - embeds `InotifyEvent` and keeps additional field `Eof` to notify user that there will be no more events.
|
||||
|
||||
Use `FileWatcher` and `DirWatcher` as an example and build your own utility classes.
|
||||
|
||||
### Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/illarion/gonotify/v3"
|
||||
"time"
|
||||
"context"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
watcher, err := gonotify.NewDirWatcher(ctx, gonotify.IN_CREATE|gonotify.IN_CLOSE, "/tmp")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
main:
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.C:
|
||||
fmt.Printf("Event: %s\n", event)
|
||||
|
||||
if event.Is(gonotify.IN_CREATE) {
|
||||
fmt.Printf("File created: %s\n", event.Name)
|
||||
}
|
||||
|
||||
if event.IsAny(gonotify.IN_CLOSE, gonotify.IN_CLOSE_WRITE) {
|
||||
fmt.Printf("File closed: %s\n", event.Name)
|
||||
}
|
||||
|
||||
case <-time.After(5 * time.Second):
|
||||
fmt.Println("Good bye!")
|
||||
cancel()
|
||||
break main
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for watcher to finish all internal goroutines
|
||||
<-watcher.Done()
|
||||
fmt.Println("Watcher is done")
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
MIT. See [LICENSE](LICENSE) file for more details.
|
||||
|
||||
218
vendor/github.com/illarion/gonotify/v3/dirwatcher.go
generated
vendored
Normal file
218
vendor/github.com/illarion/gonotify/v3/dirwatcher.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
package gonotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DirWatcher recursively watches the given root folder, waiting for file events.
|
||||
// Events can be masked by providing fileMask. DirWatcher does not generate events for
|
||||
// folders or subfolders.
|
||||
type DirWatcher struct {
|
||||
C chan FileEvent
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewDirWatcher creates DirWatcher recursively waiting for events in the given root folder and
|
||||
// emitting FileEvents in channel C, that correspond to fileMask. Folder events are ignored (having IN_ISDIR set to 1)
|
||||
func NewDirWatcher(ctx context.Context, fileMask uint32, root string) (*DirWatcher, error) {
|
||||
dw := &DirWatcher{
|
||||
C: make(chan FileEvent),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
i, err := NewInotify(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queue := make([]FileEvent, 0, 100)
|
||||
|
||||
err = filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !f.IsDir() {
|
||||
|
||||
//fake event for existing files
|
||||
queue = append(queue, FileEvent{
|
||||
InotifyEvent: InotifyEvent{
|
||||
Name: path,
|
||||
Mask: IN_CREATE,
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
_, err = i.AddWatch(path, IN_ALL_EVENTS)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make(chan FileEvent)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for _, event := range queue {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
close(events)
|
||||
return
|
||||
case events <- event:
|
||||
|
||||
}
|
||||
}
|
||||
queue = nil
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
close(events)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
raw, err := i.Read()
|
||||
if err != nil {
|
||||
close(events)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
close(events)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
for _, event := range raw {
|
||||
|
||||
// Skip ignored events queued from removed watchers
|
||||
if event.Mask&IN_IGNORED == IN_IGNORED {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add watch for folders created in watched folders (recursion)
|
||||
if event.Mask&(IN_CREATE|IN_ISDIR) == IN_CREATE|IN_ISDIR {
|
||||
|
||||
// After the watch for subfolder is added, it may be already late to detect files
|
||||
// created there right after subfolder creation, so we should generate such events
|
||||
// ourselves:
|
||||
filepath.Walk(event.Name, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !f.IsDir() {
|
||||
// fake event, but there can be duplicates of this event provided by real watcher
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case events <- FileEvent{
|
||||
InotifyEvent: InotifyEvent{
|
||||
Name: path,
|
||||
Mask: IN_CREATE,
|
||||
},
|
||||
}: //noop
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Wait for further files to be added
|
||||
i.AddWatch(event.Name, IN_ALL_EVENTS)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove watch for deleted folders
|
||||
if event.Mask&IN_DELETE_SELF == IN_DELETE_SELF {
|
||||
i.RmWd(event.Wd)
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip sub-folder events
|
||||
if event.Mask&IN_ISDIR == IN_ISDIR {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case events <- FileEvent{
|
||||
InotifyEvent: event,
|
||||
}: //noop
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(dw.C)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// drain events
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
case event, ok := <-events:
|
||||
if !ok {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case dw.C <- FileEvent{
|
||||
Eof: true,
|
||||
}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Skip events not conforming with provided mask
|
||||
if event.Mask&fileMask == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case dw.C <- event:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
<-i.Done()
|
||||
close(dw.done)
|
||||
}()
|
||||
|
||||
return dw, nil
|
||||
}
|
||||
|
||||
// Done returns a channel that is closed when DirWatcher is done
|
||||
func (dw *DirWatcher) Done() <-chan struct{} {
|
||||
return dw.done
|
||||
}
|
||||
138
vendor/github.com/illarion/gonotify/v3/event.go
generated
vendored
Normal file
138
vendor/github.com/illarion/gonotify/v3/event.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package gonotify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
IN_ACCESS = uint32(syscall.IN_ACCESS) // File was accessed
|
||||
IN_ATTRIB = uint32(syscall.IN_ATTRIB) // Metadata changed
|
||||
IN_CLOSE_WRITE = uint32(syscall.IN_CLOSE_WRITE) // File opened for writing was closed.
|
||||
IN_CLOSE_NOWRITE = uint32(syscall.IN_CLOSE_NOWRITE) // File or directory not opened for writing was closed.
|
||||
IN_CREATE = uint32(syscall.IN_CREATE) // File/directory created in watched directory
|
||||
IN_DELETE = uint32(syscall.IN_DELETE) // File/directory deleted from watched directory.
|
||||
IN_DELETE_SELF = uint32(syscall.IN_DELETE_SELF) // Watched file/directory was itself deleted.
|
||||
IN_MODIFY = uint32(syscall.IN_MODIFY) // File was modified
|
||||
IN_MOVE_SELF = uint32(syscall.IN_MOVE_SELF) // Watched file/directory was itself moved.
|
||||
IN_MOVED_FROM = uint32(syscall.IN_MOVED_FROM) // Generated for the directory containing the old filename when a file is renamed.
|
||||
IN_MOVED_TO = uint32(syscall.IN_MOVED_TO) // Generated for the directory containing the new filename when a file is renamed.
|
||||
IN_OPEN = uint32(syscall.IN_OPEN) // File or directory was opened.
|
||||
|
||||
IN_ALL_EVENTS = uint32(syscall.IN_ALL_EVENTS) // bit mask of all of the above events.
|
||||
IN_MOVE = uint32(syscall.IN_MOVE) // Equates to IN_MOVED_FROM | IN_MOVED_TO.
|
||||
IN_CLOSE = uint32(syscall.IN_CLOSE) // Equates to IN_CLOSE_WRITE | IN_CLOSE_NOWRITE.
|
||||
|
||||
/* The following further bits can be specified in mask when calling Inotify.AddWatch() */
|
||||
|
||||
IN_DONT_FOLLOW = uint32(syscall.IN_DONT_FOLLOW) // Don't dereference pathname if it is a symbolic link.
|
||||
IN_EXCL_UNLINK = uint32(syscall.IN_EXCL_UNLINK) // Don't generate events for children if they have been unlinked from the directory.
|
||||
IN_MASK_ADD = uint32(syscall.IN_MASK_ADD) // Add (OR) the events in mask to the watch mask
|
||||
IN_ONESHOT = uint32(syscall.IN_ONESHOT) // Monitor the filesystem object corresponding to pathname for one event, then remove from watch list.
|
||||
IN_ONLYDIR = uint32(syscall.IN_ONLYDIR) // Watch pathname only if it is a directory.
|
||||
|
||||
/* The following bits may be set in the mask field returned by Inotify.Read() */
|
||||
|
||||
IN_IGNORED = uint32(syscall.IN_IGNORED) // Watch was removed explicitly or automatically
|
||||
IN_ISDIR = uint32(syscall.IN_ISDIR) // Subject of this event is a directory.
|
||||
IN_Q_OVERFLOW = uint32(syscall.IN_Q_OVERFLOW) // Event queue overflowed (wd is -1 for this event).
|
||||
|
||||
IN_UNMOUNT = uint32(syscall.IN_UNMOUNT) // Filesystem containing watched object was unmounted.
|
||||
)
|
||||
|
||||
var in_mapping = map[uint32]string{
|
||||
IN_ACCESS: "IN_ACCESS",
|
||||
IN_ATTRIB: "IN_ATTRIB",
|
||||
IN_CLOSE_WRITE: "IN_CLOSE_WRITE",
|
||||
IN_CLOSE_NOWRITE: "IN_CLOSE_NOWRITE",
|
||||
IN_CREATE: "IN_CREATE",
|
||||
IN_DELETE: "IN_DELETE",
|
||||
IN_DELETE_SELF: "IN_DELETE_SELF",
|
||||
IN_MODIFY: "IN_MODIFY",
|
||||
IN_MOVE_SELF: "IN_MOVE_SELF",
|
||||
IN_MOVED_FROM: "IN_MOVED_FROM",
|
||||
IN_MOVED_TO: "IN_MOVED_TO",
|
||||
IN_OPEN: "IN_OPEN",
|
||||
IN_IGNORED: "IN_IGNORED",
|
||||
IN_ISDIR: "IN_ISDIR",
|
||||
IN_Q_OVERFLOW: "IN_Q_OVERFLOW",
|
||||
IN_UNMOUNT: "IN_UNMOUNT",
|
||||
}
|
||||
|
||||
func InMaskToString(in_mask uint32) string {
|
||||
sb := &strings.Builder{}
|
||||
divide := false
|
||||
for mask, str := range in_mapping {
|
||||
if in_mask&mask == mask {
|
||||
if divide {
|
||||
sb.WriteString("|")
|
||||
}
|
||||
sb.WriteString(str)
|
||||
divide = true
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// InotifyEvent is the go representation of inotify_event found in sys/inotify.h
|
||||
type InotifyEvent struct {
|
||||
// Watch descriptor
|
||||
Wd int
|
||||
// File or directory name
|
||||
Name string
|
||||
// Contains bits that describe the event that occurred
|
||||
Mask uint32
|
||||
// Usually 0, but if events (like IN_MOVED_FROM and IN_MOVED_TO) are linked then they will have equal cookie
|
||||
Cookie uint32
|
||||
}
|
||||
|
||||
func (i InotifyEvent) GoString() string {
|
||||
return fmt.Sprintf("gonotify.InotifyEvent{Wd=%#v, Name=%s, Cookie=%#v, Mask=%#v=%s}", i.Wd, i.Name, i.Cookie, i.Mask, InMaskToString(i.Mask))
|
||||
}
|
||||
|
||||
func (i InotifyEvent) String() string {
|
||||
return fmt.Sprintf("{Wd=%d, Name=%s, Cookie=%d, Mask=%s}", i.Wd, i.Name, i.Cookie, InMaskToString(i.Mask))
|
||||
}
|
||||
|
||||
// IsAny returns true if any of the in_mask is set in the event
|
||||
func (i InotifyEvent) IsAny(in_mask ...uint32) bool {
|
||||
for _, mask := range in_mask {
|
||||
if i.Mask&mask == mask {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAll returns true if all the in_masks is set in the event
|
||||
func (i InotifyEvent) IsAll(in_mask ...uint32) bool {
|
||||
for _, mask := range in_mask {
|
||||
if i.Mask&mask != mask {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (i InotifyEvent) Is(in_mask uint32) bool {
|
||||
return i.Mask&in_mask == in_mask
|
||||
}
|
||||
|
||||
// FileEvent is the wrapper around InotifyEvent with additional Eof marker. Reading from
|
||||
// FileEvents from DirWatcher.C or FileWatcher.C may end with Eof when underlying inotify is closed
|
||||
type FileEvent struct {
|
||||
InotifyEvent
|
||||
Eof bool
|
||||
}
|
||||
|
||||
func (f FileEvent) GoString() string {
|
||||
return fmt.Sprintf("gonotify.FileEvent{InotifyEvent=%#v, Eof=%#v}", f.InotifyEvent, f.Eof)
|
||||
}
|
||||
|
||||
func (f FileEvent) String() string {
|
||||
return fmt.Sprintf("{InotifyEvent=%s, Eof=%v}", f.InotifyEvent, f.Eof)
|
||||
}
|
||||
115
vendor/github.com/illarion/gonotify/v3/filewatcher.go
generated
vendored
Normal file
115
vendor/github.com/illarion/gonotify/v3/filewatcher.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package gonotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// FileWatcher waits for events generated by filesystem for a specific list of file paths, including
|
||||
// IN_CREATE for not yet existing files and IN_DELETE for removed.
|
||||
type FileWatcher struct {
|
||||
C chan FileEvent
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewFileWatcher creates FileWatcher with provided inotify mask and list of files to wait events for.
|
||||
func NewFileWatcher(ctx context.Context, mask uint32, files ...string) (*FileWatcher, error) {
|
||||
|
||||
f := &FileWatcher{
|
||||
C: make(chan FileEvent),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
inotify, err := NewInotify(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expectedPaths := make(map[string]bool)
|
||||
|
||||
for _, file := range files {
|
||||
_, err := inotify.AddWatch(filepath.Dir(file), mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expectedPaths[file] = true
|
||||
}
|
||||
|
||||
events := make(chan FileEvent)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
close(events)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
raw, err := inotify.Read()
|
||||
if err != nil {
|
||||
close(events)
|
||||
return
|
||||
}
|
||||
|
||||
for _, event := range raw {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case events <- FileEvent{
|
||||
InotifyEvent: event,
|
||||
}: //noop
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(f.C)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case event, ok := <-events:
|
||||
|
||||
if !ok {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case f.C <- FileEvent{Eof: true}:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !expectedPaths[event.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case f.C <- event:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-inotify.Done()
|
||||
wg.Wait()
|
||||
close(f.done)
|
||||
}()
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Done returns a channel that is closed when the FileWatcher is done.
|
||||
func (f *FileWatcher) Done() <-chan struct{} {
|
||||
return f.done
|
||||
}
|
||||
527
vendor/github.com/illarion/gonotify/v3/inotify.go
generated
vendored
Normal file
527
vendor/github.com/illarion/gonotify/v3/inotify.go
generated
vendored
Normal file
@@ -0,0 +1,527 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package gonotify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/illarion/gonotify/v3/syscallf"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxEvents is the maximum number of events to read in one syscall
|
||||
maxEvents = 1024
|
||||
)
|
||||
|
||||
type addWatchResult struct {
|
||||
wd int
|
||||
err error
|
||||
}
|
||||
|
||||
type addWatchRequest struct {
|
||||
pathName string
|
||||
mask uint32
|
||||
result chan addWatchResult
|
||||
}
|
||||
|
||||
type rmWdRequest struct {
|
||||
wd int
|
||||
ignored bool // if true, the watch was removed automatically
|
||||
result chan error
|
||||
}
|
||||
|
||||
type rmPathRequest struct {
|
||||
pathName string
|
||||
result chan error
|
||||
}
|
||||
|
||||
type eventItem struct {
|
||||
InotifyEvent
|
||||
err error
|
||||
}
|
||||
|
||||
// Inotify is the low level wrapper around inotify_init(), inotify_add_watch() and inotify_rm_watch()
|
||||
type Inotify struct {
|
||||
ctx context.Context
|
||||
done chan struct{}
|
||||
addWatchIn chan addWatchRequest
|
||||
rmByWdIn chan rmWdRequest
|
||||
rmByPathIn chan rmPathRequest
|
||||
eventsOut chan eventItem
|
||||
|
||||
readMutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewInotify creates new inotify instance
|
||||
func NewInotify(ctx context.Context) (*Inotify, error) {
|
||||
fd, err := syscall.InotifyInit1(syscall.IN_CLOEXEC | syscall.IN_NONBLOCK)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "inotify")
|
||||
|
||||
inotify := &Inotify{
|
||||
ctx: ctx,
|
||||
done: make(chan struct{}),
|
||||
addWatchIn: make(chan addWatchRequest),
|
||||
rmByWdIn: make(chan rmWdRequest),
|
||||
rmByPathIn: make(chan rmPathRequest),
|
||||
eventsOut: make(chan eventItem, maxEvents),
|
||||
}
|
||||
|
||||
type getPathRequest struct {
|
||||
wd int
|
||||
result chan string
|
||||
}
|
||||
|
||||
getPathIn := make(chan getPathRequest)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
//defer cancel()
|
||||
<-ctx.Done()
|
||||
//file.Close()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
// read events goroutine. Only this goroutine can read or close the inotify file descriptor
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(inotify.eventsOut)
|
||||
|
||||
// reusable buffers for reading inotify events. Make sure they're not
|
||||
// leaked into other goroutines, as they're not thread safe
|
||||
buf := make([]byte, maxEvents*(syscall.SizeofInotifyEvent+syscall.NAME_MAX+1))
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
var n int
|
||||
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
n, err = file.Read(buf)
|
||||
if err != nil {
|
||||
|
||||
// if we got an error, we should return
|
||||
select {
|
||||
case inotify.eventsOut <- eventItem{
|
||||
InotifyEvent: InotifyEvent{},
|
||||
err: err,
|
||||
}:
|
||||
default:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if n < syscall.SizeofInotifyEvent {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
for offset+syscall.SizeofInotifyEvent <= n {
|
||||
event := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
var name string
|
||||
{
|
||||
nameStart := offset + syscall.SizeofInotifyEvent
|
||||
nameEnd := offset + syscall.SizeofInotifyEvent + int(event.Len)
|
||||
|
||||
if nameEnd > n {
|
||||
continue
|
||||
}
|
||||
|
||||
name = strings.TrimRight(string(buf[nameStart:nameEnd]), "\x00")
|
||||
offset = nameEnd
|
||||
}
|
||||
|
||||
req := getPathRequest{wd: int(event.Wd), result: make(chan string)}
|
||||
var watchName string
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case getPathIn <- req:
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case watchName = <-req.result:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if watchName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
name = filepath.Join(watchName, name)
|
||||
|
||||
inotifyEvent := InotifyEvent{
|
||||
Wd: int(event.Wd),
|
||||
Name: name,
|
||||
Mask: event.Mask,
|
||||
Cookie: event.Cookie,
|
||||
}
|
||||
|
||||
// watch was removed explicitly or automatically
|
||||
if inotifyEvent.Is(IN_IGNORED) {
|
||||
|
||||
// remove watch
|
||||
|
||||
result := make(chan error)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case inotify.rmByWdIn <- rmWdRequest{
|
||||
wd: int(event.Wd),
|
||||
ignored: true,
|
||||
result: result,
|
||||
}:
|
||||
case <-time.After(1 * time.Second):
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-result:
|
||||
if err != nil {
|
||||
// TODO log error
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case inotify.eventsOut <- eventItem{
|
||||
InotifyEvent: inotifyEvent,
|
||||
err: nil,
|
||||
}:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
// main goroutine (handle channels)
|
||||
go func() {
|
||||
//defer cancel()
|
||||
defer wg.Done()
|
||||
|
||||
watches := make(map[string]int)
|
||||
paths := make(map[int]string)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
// Handle pending requests
|
||||
draining := true
|
||||
|
||||
for draining {
|
||||
select {
|
||||
case req := <-inotify.addWatchIn:
|
||||
// Send error to addWatch requests
|
||||
select {
|
||||
case req.result <- addWatchResult{
|
||||
wd: 0,
|
||||
err: errors.New("Inotify instance closed"),
|
||||
}:
|
||||
default:
|
||||
}
|
||||
case <-inotify.rmByWdIn:
|
||||
case <-inotify.addWatchIn:
|
||||
case <-inotify.rmByPathIn:
|
||||
case <-getPathIn:
|
||||
|
||||
default:
|
||||
draining = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, w := range watches {
|
||||
_, err := syscallf.InotifyRmWatch(fd, w)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
case req := <-inotify.addWatchIn:
|
||||
wd, err := syscall.InotifyAddWatch(fd, req.pathName, req.mask)
|
||||
if err == nil {
|
||||
watches[req.pathName] = wd
|
||||
paths[wd] = req.pathName
|
||||
}
|
||||
select {
|
||||
case req.result <- addWatchResult{wd: wd, err: err}:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
case req := <-inotify.rmByWdIn:
|
||||
pathName, ok := paths[req.wd]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !req.ignored {
|
||||
_, err = syscallf.InotifyRmWatch(fd, req.wd)
|
||||
}
|
||||
|
||||
delete(watches, pathName)
|
||||
delete(paths, req.wd)
|
||||
|
||||
select {
|
||||
case req.result <- err:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
case req := <-inotify.rmByPathIn:
|
||||
wd, ok := watches[req.pathName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
_, err := syscallf.InotifyRmWatch(fd, wd)
|
||||
|
||||
delete(watches, req.pathName)
|
||||
delete(paths, wd)
|
||||
|
||||
select {
|
||||
case req.result <- err:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
case req := <-getPathIn:
|
||||
wd := paths[req.wd]
|
||||
select {
|
||||
case req.result <- wd:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
//defer cancel()
|
||||
wg.Wait()
|
||||
close(inotify.done)
|
||||
}()
|
||||
|
||||
return inotify, nil
|
||||
}
|
||||
|
||||
// Done returns a channel that is closed when Inotify is done
|
||||
func (i *Inotify) Done() <-chan struct{} {
|
||||
return i.done
|
||||
}
|
||||
|
||||
// AddWatch adds given path to list of watched files / folders
|
||||
func (i *Inotify) AddWatch(pathName string, mask uint32) (int, error) {
|
||||
|
||||
req := addWatchRequest{
|
||||
pathName: pathName,
|
||||
mask: mask,
|
||||
result: make(chan addWatchResult),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return 0, i.ctx.Err()
|
||||
case i.addWatchIn <- req:
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return 0, i.ctx.Err()
|
||||
case result := <-req.result:
|
||||
return result.wd, result.err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RmWd removes watch by watch descriptor
|
||||
func (i *Inotify) RmWd(wd int) error {
|
||||
|
||||
req := rmWdRequest{
|
||||
wd: wd,
|
||||
ignored: false,
|
||||
result: make(chan error),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return i.ctx.Err()
|
||||
case i.rmByWdIn <- req:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return i.ctx.Err()
|
||||
case err := <-req.result:
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RmWatch removes watch by pathName
|
||||
func (i *Inotify) RmWatch(pathName string) error {
|
||||
|
||||
req := rmPathRequest{
|
||||
pathName: pathName,
|
||||
result: make(chan error),
|
||||
}
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return i.ctx.Err()
|
||||
case i.rmByPathIn <- req:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return i.ctx.Err()
|
||||
case err := <-req.result:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads portion of InotifyEvents and may fail with an error. If no events are available, it will
|
||||
// wait forever, until context is cancelled.
|
||||
func (i *Inotify) Read() ([]InotifyEvent, error) {
|
||||
i.readMutex.Lock()
|
||||
defer i.readMutex.Unlock()
|
||||
|
||||
events := make([]InotifyEvent, 0, maxEvents)
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return events, i.ctx.Err()
|
||||
case <-i.Done():
|
||||
return events, errors.New("inotify closed")
|
||||
case evt, ok := <-i.eventsOut:
|
||||
|
||||
if !ok {
|
||||
return events, errors.New("inotify closed")
|
||||
}
|
||||
if evt.err != nil {
|
||||
return events, evt.err
|
||||
}
|
||||
|
||||
if evt.InotifyEvent.Wd != 0 {
|
||||
// append first event
|
||||
events = append(events, evt.InotifyEvent)
|
||||
}
|
||||
|
||||
if len(events) >= maxEvents {
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// read all available events
|
||||
read:
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return events, i.ctx.Err()
|
||||
case <-i.Done():
|
||||
return events, errors.New("inotify closed")
|
||||
case evt, ok := <-i.eventsOut:
|
||||
if !ok {
|
||||
return events, errors.New("inotify closed")
|
||||
}
|
||||
if evt.err != nil {
|
||||
return events, evt.err
|
||||
}
|
||||
|
||||
if evt.InotifyEvent.Wd != 0 {
|
||||
// append event
|
||||
events = append(events, evt.InotifyEvent)
|
||||
}
|
||||
|
||||
if len(events) >= maxEvents {
|
||||
return events, nil
|
||||
}
|
||||
|
||||
default:
|
||||
break read
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// ReadDeadline waits for InotifyEvents until deadline is reached, or context is cancelled. If
|
||||
// deadline is reached, it will return all events read until that point.
|
||||
func (i *Inotify) ReadDeadline(deadline time.Time) ([]InotifyEvent, error) {
|
||||
i.readMutex.Lock()
|
||||
defer i.readMutex.Unlock()
|
||||
|
||||
events := make([]InotifyEvent, 0, maxEvents)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-i.ctx.Done():
|
||||
return events, i.ctx.Err()
|
||||
case <-i.Done():
|
||||
return events, errors.New("Inotify closed")
|
||||
case <-time.After(time.Until(deadline)):
|
||||
return events, nil
|
||||
case evt, ok := <-i.eventsOut:
|
||||
if !ok {
|
||||
return events, errors.New("Inotify closed")
|
||||
}
|
||||
if evt.err != nil {
|
||||
return events, evt.err
|
||||
}
|
||||
|
||||
events = append(events, evt.InotifyEvent)
|
||||
|
||||
if len(events) >= maxEvents {
|
||||
return events, nil
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
17
vendor/github.com/illarion/gonotify/v3/syscallf/rm_watch.go
generated
vendored
Normal file
17
vendor/github.com/illarion/gonotify/v3/syscallf/rm_watch.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build linux
|
||||
|
||||
package syscallf
|
||||
|
||||
import "syscall"
|
||||
|
||||
func InotifyRmWatch(fd int, watchdesc int) (int, error) {
|
||||
var success int
|
||||
var err error
|
||||
|
||||
r0, _, e1 := syscall.RawSyscall(syscall.SYS_INOTIFY_RM_WATCH, uintptr(fd), uintptr(watchdesc), 0)
|
||||
success = int(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return success, err
|
||||
}
|
||||
Reference in New Issue
Block a user