From 2750defb63a94051f2aad56e767944a3acef5cd6 Mon Sep 17 00:00:00 2001 From: Dominic Breuker Date: Mon, 26 Feb 2018 07:41:28 +0100 Subject: [PATCH] try some more tsting --- cmd/root.go | 6 +- internal/{inotify => fswatcher}/event.go | 9 +-- internal/{inotify => fswatcher}/inotify.go | 2 +- internal/fswatcher/inotify/inotify.go | 67 +++++++++++++++++++ internal/fswatcher/inotify/inotify_test.go | 49 ++++++++++++++ internal/fswatcher/inotify/sys/syscalls.go | 36 ++++++++++ .../fswatcher/inotify/sys/syscalls_test.go | 45 +++++++++++++ internal/fswatcher/inotify/watcher.go | 30 +++++++++ internal/{inotify => fswatcher}/observer.go | 7 +- internal/{inotify => fswatcher}/setup.go | 16 +++-- .../walker/testdata/f11.txt | 0 .../walker/testdata/f12.txt | 0 .../walker/testdata/subdir/f21.txt | 0 .../walker/testdata/subdir/subsubdir/f31.txt | 0 .../{inotify => fswatcher}/walker/walker.go | 9 +-- .../walker/walker_test.go | 12 ++-- internal/{inotify => fswatcher}/watcher.go | 2 +- 17 files changed, 262 insertions(+), 28 deletions(-) rename internal/{inotify => fswatcher}/event.go (90%) rename internal/{inotify => fswatcher}/inotify.go (98%) create mode 100644 internal/fswatcher/inotify/inotify.go create mode 100644 internal/fswatcher/inotify/inotify_test.go create mode 100644 internal/fswatcher/inotify/sys/syscalls.go create mode 100644 internal/fswatcher/inotify/sys/syscalls_test.go create mode 100644 internal/fswatcher/inotify/watcher.go rename internal/{inotify => fswatcher}/observer.go (58%) rename internal/{inotify => fswatcher}/setup.go (69%) rename internal/{inotify => fswatcher}/walker/testdata/f11.txt (100%) rename internal/{inotify => fswatcher}/walker/testdata/f12.txt (100%) rename internal/{inotify => fswatcher}/walker/testdata/subdir/f21.txt (100%) rename internal/{inotify => fswatcher}/walker/testdata/subdir/subsubdir/f31.txt (100%) rename internal/{inotify => fswatcher}/walker/walker.go (77%) rename internal/{inotify => fswatcher}/walker/walker_test.go (87%) rename internal/{inotify => fswatcher}/watcher.go (97%) diff --git a/cmd/root.go b/cmd/root.go index 27d0e29..78d26bc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,7 +9,7 @@ import ( "syscall" "github.com/dominicbreuker/pspy/internal/config" - "github.com/dominicbreuker/pspy/internal/inotify" + "github.com/dominicbreuker/pspy/internal/fswatcher" "github.com/dominicbreuker/pspy/internal/logging" "github.com/dominicbreuker/pspy/internal/process" "github.com/dominicbreuker/pspy/internal/pspy" @@ -74,9 +74,9 @@ func root(cmd *cobra.Command, args []string) { LogPS: logPS, LogFS: logFS, } - iw, err := inotify.NewInotifyWatcher() + iw, err := fswatcher.NewInotifyWatcher() if err != nil { - logger.Errorf("Can't initialize inotify: %v", err) + logger.Errorf("Can't initialize fswatcher: %v", err) os.Exit(1) } defer iw.Close() diff --git a/internal/inotify/event.go b/internal/fswatcher/event.go similarity index 90% rename from internal/inotify/event.go rename to internal/fswatcher/event.go index 4599156..0cfb601 100644 --- a/internal/inotify/event.go +++ b/internal/fswatcher/event.go @@ -1,4 +1,4 @@ -package inotify +package fswatcher import ( "bytes" @@ -6,6 +6,7 @@ import ( "strconv" "unsafe" + "github.com/dominicbreuker/pspy/internal/fswatcher/inotify" "golang.org/x/sys/unix" ) @@ -35,7 +36,7 @@ var InotifyEvents = map[uint32]string{ (unix.IN_OPEN | unix.IN_ISDIR): "OPEN DIR", } -func parseEvents(i *Inotify, dataCh chan []byte, eventCh chan string, errCh chan error) { +func parseEvents(i *inotify.Inotify, dataCh chan []byte, eventCh chan string, errCh chan error) { for buf := range dataCh { n := len(buf) if n < unix.SizeofInotifyEvent { @@ -49,12 +50,12 @@ func parseEvents(i *Inotify, dataCh chan []byte, eventCh chan string, errCh chan sys := (*unix.InotifyEvent)(unsafe.Pointer(&buf[ptr])) ptr += unix.SizeofInotifyEvent - watcher, ok := i.watchers[int(sys.Wd)] + watcher, ok := i.Watchers[int(sys.Wd)] if !ok { errCh <- fmt.Errorf("Inotify event parser: unknown watcher ID: %d", sys.Wd) continue } - name = watcher.dir + "/" + name = watcher.Dir + "/" if sys.Len > 0 && len(buf) >= int(ptr+sys.Len) { name += string(bytes.TrimRight(buf[ptr:ptr+sys.Len], "\x00")) ptr += sys.Len diff --git a/internal/inotify/inotify.go b/internal/fswatcher/inotify.go similarity index 98% rename from internal/inotify/inotify.go rename to internal/fswatcher/inotify.go index 990feb6..432d25f 100644 --- a/internal/inotify/inotify.go +++ b/internal/fswatcher/inotify.go @@ -1,4 +1,4 @@ -package inotify +package fswatcher import ( "fmt" diff --git a/internal/fswatcher/inotify/inotify.go b/internal/fswatcher/inotify/inotify.go new file mode 100644 index 0000000..b5bf502 --- /dev/null +++ b/internal/fswatcher/inotify/inotify.go @@ -0,0 +1,67 @@ +package inotify + +import ( + "fmt" +) + +type InotifySyscalls interface { + Init() (int, error) + AddWatch(int, string) (int, error) + Close(int) error +} + +type Inotify struct { + FD int + Watchers map[int]*Watcher + sys InotifySyscalls +} + +func NewInotify(isys InotifySyscalls) (*Inotify, error) { + fd, err := isys.Init() + if err != nil { + return nil, fmt.Errorf("initializing inotify: %v", err) + } + + i := &Inotify{ + FD: fd, + Watchers: make(map[int]*Watcher), + sys: isys, + } + + return i, nil +} + +func (i *Inotify) Watch(dir string) error { + wd, err := i.sys.AddWatch(i.FD, dir) + if err != nil { + return fmt.Errorf("adding watcher on %s: %v", dir, err) + } + i.Watchers[wd] = &Watcher{ + WD: wd, + Dir: dir, + } + return nil +} + +func (i *Inotify) Close() error { + if err := i.sys.Close(i.FD); err != nil { + return fmt.Errorf("closing inotify file descriptor: %v", err) + } + return nil +} + +func (i *Inotify) NumWatchers() int { + return len(i.Watchers) +} + +func (i *Inotify) String() string { + if len(i.Watchers) < 20 { + dirs := make([]string, 0) + for _, w := range i.Watchers { + dirs = append(dirs, w.Dir) + } + return fmt.Sprintf("Watching: %v", dirs) + } else { + return fmt.Sprintf("Watching %d directories", len(i.Watchers)) + } +} diff --git a/internal/fswatcher/inotify/inotify_test.go b/internal/fswatcher/inotify/inotify_test.go new file mode 100644 index 0000000..dd253cc --- /dev/null +++ b/internal/fswatcher/inotify/inotify_test.go @@ -0,0 +1,49 @@ +package inotify + +import ( + "errors" + "testing" +) + +func TestNewInotify(t *testing.T) { + mis := &MockInotifySyscalls{fd: 1} + + i, err := NewInotify(mis) + if err != nil { + t.Fatalf("Unexpected error") + } + if i.FD != mis.fd { + t.Fatalf("Did not set FD of inotify object") + } +} + +func TestNewInotifyError(t *testing.T) { + mis := &MockInotifySyscalls{fd: -1} + + _, err := NewInotify(mis) + if err == nil || err.Error() != "initializing inotify: syscall error" { + t.Fatalf("Expected syscall error but did not get: %v", err) + } +} + +// mock + +type MockInotifySyscalls struct { + fd int +} + +func (mis *MockInotifySyscalls) Init() (int, error) { + if mis.fd >= 0 { + return mis.fd, nil + } else { + return -1, errors.New("syscall error") + } +} + +func (mis *MockInotifySyscalls) AddWatch(fd int, dir string) (int, error) { + return 2, nil +} + +func (mis *MockInotifySyscalls) Close(fd int) error { + return nil +} diff --git a/internal/fswatcher/inotify/sys/syscalls.go b/internal/fswatcher/inotify/sys/syscalls.go new file mode 100644 index 0000000..24f89ba --- /dev/null +++ b/internal/fswatcher/inotify/sys/syscalls.go @@ -0,0 +1,36 @@ +// +build linux + +package sys + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +const events = unix.IN_ALL_EVENTS + +type InotifySyscallsUNIX struct{} + +func (isu *InotifySyscallsUNIX) Init() (int, error) { + fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) + if fd < 0 { + return fd, fmt.Errorf("errno: %d", errno) + } + return fd, nil +} + +func (isu *InotifySyscallsUNIX) AddWatch(fd int, dir string) (int, error) { + wd, errno := unix.InotifyAddWatch(fd, dir, events) + if wd < 0 { + return wd, fmt.Errorf("errno: %d", errno) + } + return wd, nil +} + +func (isu *InotifySyscallsUNIX) Close(fd int) error { + if err := unix.Close(fd); err != nil { + return err + } + return nil +} diff --git a/internal/fswatcher/inotify/sys/syscalls_test.go b/internal/fswatcher/inotify/sys/syscalls_test.go new file mode 100644 index 0000000..aadda01 --- /dev/null +++ b/internal/fswatcher/inotify/sys/syscalls_test.go @@ -0,0 +1,45 @@ +// +build linux + +package sys + +import ( + "testing" +) + +func TestSyscalls(t *testing.T) { + is := &InotifySyscallsUNIX{} + + fd, err := is.Init() + if err != nil { + t.Fatalf("Unexpected error for inotify init: %v", err) + } + + _, err = is.AddWatch(fd, "testdata") + if err != nil { + t.Fatalf("Unexpected error adding watch to dir 'testdata': %v", err) + } + + err = is.Close(fd) + if err != nil { + t.Fatalf("Unexpected error closing inotify: %v", err) + } +} + +func TestSyscallsError(t *testing.T) { + is := &InotifySyscallsUNIX{} + + fd, err := is.Init() + if err != nil { + t.Fatalf("Unexpected error for inotify init: %v", err) + } + + _, err = is.AddWatch(fd, "non-existing-dir") + if err == nil || err.Error() != "errno: 2" { + t.Fatalf("Expected errno 2 for non-existing-dir but got: %v", err) + } + + err = is.Close(fd) + if err != nil { + t.Fatalf("Unexpected error closing inotify: %v", err) + } +} diff --git a/internal/fswatcher/inotify/watcher.go b/internal/fswatcher/inotify/watcher.go new file mode 100644 index 0000000..1ce2795 --- /dev/null +++ b/internal/fswatcher/inotify/watcher.go @@ -0,0 +1,30 @@ +package inotify + +import ( + "fmt" + "io/ioutil" + "strconv" + "strings" +) + +const maximumWatchersFile = "/proc/sys/fs/inotify/max_user_watches" + +type Watcher struct { + WD int + Dir string +} + +func GetLimit() (int, error) { + b, err := ioutil.ReadFile(maximumWatchersFile) + if err != nil { + return 0, fmt.Errorf("reading from %s: %v", maximumWatchersFile, err) + } + + s := strings.TrimSpace(string(b)) + m, err := strconv.Atoi(s) + if err != nil { + return 0, fmt.Errorf("converting to integer: %v", err) + } + + return m, nil +} diff --git a/internal/inotify/observer.go b/internal/fswatcher/observer.go similarity index 58% rename from internal/inotify/observer.go rename to internal/fswatcher/observer.go index ea5d574..3276359 100644 --- a/internal/inotify/observer.go +++ b/internal/fswatcher/observer.go @@ -1,16 +1,17 @@ -package inotify +package fswatcher import ( "fmt" + "github.com/dominicbreuker/pspy/internal/fswatcher/inotify" "golang.org/x/sys/unix" ) -func Observe(i *Inotify, triggerCh chan struct{}, dataCh chan []byte, errCh chan error) { +func Observe(i *inotify.Inotify, triggerCh chan struct{}, dataCh chan []byte, errCh chan error) { buf := make([]byte, 5*unix.SizeofInotifyEvent) for { - n, errno := unix.Read(i.fd, buf) + n, errno := unix.Read(i.FD, buf) if n == -1 { errCh <- fmt.Errorf("reading from inotify fd: errno: %d", errno) return diff --git a/internal/inotify/setup.go b/internal/fswatcher/setup.go similarity index 69% rename from internal/inotify/setup.go rename to internal/fswatcher/setup.go index 3c94e8e..4c1379e 100644 --- a/internal/inotify/setup.go +++ b/internal/fswatcher/setup.go @@ -1,13 +1,15 @@ -package inotify +package fswatcher import ( "fmt" - "github.com/dominicbreuker/pspy/internal/inotify/walker" + "github.com/dominicbreuker/pspy/internal/fswatcher/inotify" + isys "github.com/dominicbreuker/pspy/internal/fswatcher/inotify/sys" + "github.com/dominicbreuker/pspy/internal/fswatcher/walker" ) type InotifyWatcher struct { - i *Inotify + i *inotify.Inotify } func (iw *InotifyWatcher) Close() { @@ -15,7 +17,7 @@ func (iw *InotifyWatcher) Close() { } func NewInotifyWatcher() (*InotifyWatcher, error) { - i, err := NewInotify() + i, err := inotify.NewInotify(&isys.InotifySyscallsUNIX{}) if err != nil { return nil, fmt.Errorf("setting up inotify: %v", err) } @@ -48,8 +50,8 @@ func (iw *InotifyWatcher) Setup(rdirs, dirs []string, errCh chan error) (chan st return triggerCh, eventCh, nil } -func addWatchers(dir string, depth int, i *Inotify, maxWatchers int, errCh chan error) { - dirCh, doneCh := walker.Walk(dir, depth, errCh) +func addWatchers(dir string, depth int, i *inotify.Inotify, maxWatchers int, errCh chan error) { + dirCh, walkErrCh, doneCh := walker.Walk(dir, depth) loop: for { if maxWatchers > 0 && i.NumWatchers() >= maxWatchers { @@ -57,6 +59,8 @@ loop: break loop } select { + case err := <-walkErrCh: + errCh <- fmt.Errorf("adding inotift watchers: %v", err) case dir, ok := <-dirCh: if !ok { break loop diff --git a/internal/inotify/walker/testdata/f11.txt b/internal/fswatcher/walker/testdata/f11.txt similarity index 100% rename from internal/inotify/walker/testdata/f11.txt rename to internal/fswatcher/walker/testdata/f11.txt diff --git a/internal/inotify/walker/testdata/f12.txt b/internal/fswatcher/walker/testdata/f12.txt similarity index 100% rename from internal/inotify/walker/testdata/f12.txt rename to internal/fswatcher/walker/testdata/f12.txt diff --git a/internal/inotify/walker/testdata/subdir/f21.txt b/internal/fswatcher/walker/testdata/subdir/f21.txt similarity index 100% rename from internal/inotify/walker/testdata/subdir/f21.txt rename to internal/fswatcher/walker/testdata/subdir/f21.txt diff --git a/internal/inotify/walker/testdata/subdir/subsubdir/f31.txt b/internal/fswatcher/walker/testdata/subdir/subsubdir/f31.txt similarity index 100% rename from internal/inotify/walker/testdata/subdir/subsubdir/f31.txt rename to internal/fswatcher/walker/testdata/subdir/subsubdir/f31.txt diff --git a/internal/inotify/walker/walker.go b/internal/fswatcher/walker/walker.go similarity index 77% rename from internal/inotify/walker/walker.go rename to internal/fswatcher/walker/walker.go index 8e85832..93128f2 100644 --- a/internal/inotify/walker/walker.go +++ b/internal/fswatcher/walker/walker.go @@ -9,24 +9,25 @@ import ( const maxInt = int(^uint(0) >> 1) -func Walk(root string, depth int, errCh chan error) (dirCh chan string, doneCh chan struct{}) { +func Walk(root string, depth int) (dirCh chan string, errCh chan error, doneCh chan struct{}) { if depth < 0 { depth = maxInt } dirCh = make(chan string) + errCh = make(chan error) doneCh = make(chan struct{}) go func() { + defer close(dirCh) descent(root, depth-1, dirCh, errCh, doneCh) - close(dirCh) }() - return dirCh, doneCh + return dirCh, errCh, doneCh } func descent(dir string, depth int, dirCh chan string, errCh chan error, doneCh chan struct{}) { _, err := os.Stat(dir) if err != nil { - errCh <- fmt.Errorf("Can't walk directory %s: %v", dir, err) + errCh <- fmt.Errorf("visiting %s: %v", dir, err) return } select { diff --git a/internal/inotify/walker/walker_test.go b/internal/fswatcher/walker/walker_test.go similarity index 87% rename from internal/inotify/walker/walker_test.go rename to internal/fswatcher/walker/walker_test.go index 974c2c0..76419a2 100644 --- a/internal/inotify/walker/walker_test.go +++ b/internal/fswatcher/walker/walker_test.go @@ -35,12 +35,12 @@ func TestWalk(t *testing.T) { "testdata/subdir", "testdata/subdir/subsubdir", }, errs: []string{}}, - {root: "testdata/non-existing-dir", depth: 1, errCh: newErrCh(), result: []string{}, errs: []string{"Can't walk directory testdata/non-existing-dir"}}, + {root: "testdata/non-existing-dir", depth: 1, errCh: newErrCh(), result: []string{}, errs: []string{"visiting testdata/non-existing-dir"}}, } for i, tt := range tests { - dirCh, doneCh := Walk(tt.root, tt.depth, tt.errCh) - dirs, errs := getAllDirsAndErrors(dirCh, tt.errCh) + dirCh, errCh, doneCh := Walk(tt.root, tt.depth) + dirs, errs := getAllDirsAndErrors(dirCh, errCh) if !reflect.DeepEqual(dirs, tt.result) { t.Fatalf("[%d] Wrong number of dirs found: %+v", i, dirs) @@ -59,20 +59,20 @@ func getAllDirsAndErrors(dirCh chan string, errCh chan error) ([]string, []strin doneDirsCh := make(chan struct{}) go func() { + defer close(doneDirsCh) + defer close(errCh) for d := range dirCh { dirs = append(dirs, d) } - close(errCh) - close(doneDirsCh) }() doneErrsCh := make(chan struct{}) go func() { + defer close(doneErrsCh) for err := range errCh { tokens := strings.SplitN(err.Error(), ":", 2) errs = append(errs, tokens[0]) } - close(doneErrsCh) }() <-doneDirsCh <-doneErrsCh diff --git a/internal/inotify/watcher.go b/internal/fswatcher/watcher.go similarity index 97% rename from internal/inotify/watcher.go rename to internal/fswatcher/watcher.go index a055c25..c02080a 100644 --- a/internal/inotify/watcher.go +++ b/internal/fswatcher/watcher.go @@ -1,4 +1,4 @@ -package inotify +package fswatcher import ( "fmt"