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

fsnotify-backed recursive watcher for Linux #5833

Merged
merged 4 commits into from
Dec 13, 2017

Conversation

adriansr
Copy link
Contributor

@adriansr adriansr commented Dec 7, 2017

Adds a wrapper on top of fsnotify that allows placing recursive watches on directories.

Exhibits similar behavior to FSEvents:

  • Copying a directory into a watched dir sends notifications for the whole new tree.
  • Moving a directory into a watched dir only sends a notification for the new directory.

Same for removal vs moving out of a watched dir.

Issue #5421

@adriansr adriansr added Auditbeat in progress Pull request is currently in progress. review labels Dec 7, 2017
@adriansr adriansr requested a review from andrewkroh December 7, 2017 01:29

type nonRecursiveWatcher fsnotify.Watcher

func (_ *nonRecursiveWatcher) Start(done <-chan struct{}) error {

Choose a reason for hiding this comment

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

receiver name should not be an underscore

{"/lib/libz.a", false},
}

result := make([]visitParams, 0)

Choose a reason for hiding this comment

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

can probably use "var result []visitParams" instead

{"/usr/bin/tar", false},
}

result := make([]visitParams, 0)

Choose a reason for hiding this comment

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

can probably use "var result []visitParams" instead

{"/a/b", true}}},
} {
failMsg := fmt.Sprintf("test at index %d", idx)
result := make([]visitParams, 0)

Choose a reason for hiding this comment

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

can probably use "var result []visitParams" instead

}

for _, order := range []VisitOrder{PreOrder, PostOrder} {
result := make([]visitParams, 0)

Choose a reason for hiding this comment

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

can probably use "var result []visitParams" instead


type FileTree map[string]FileTree

type VisitFunc func(path string, isDir bool) error

Choose a reason for hiding this comment

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

exported type VisitFunc should have comment or be unexported

PathSeparator = string(os.PathSeparator)
)

type FileTree map[string]FileTree

Choose a reason for hiding this comment

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

exported type FileTree should have comment or be unexported

)

const (
PathSeparator = string(os.PathSeparator)

Choose a reason for hiding this comment

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

exported const PathSeparator should have comment (or a comment on this block) or be unexported

type VisitOrder int8

const (
PreOrder VisitOrder = iota

Choose a reason for hiding this comment

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

exported const PreOrder should have comment (or a comment on this block) or be unexported

"strings"
)

type VisitOrder int8

Choose a reason for hiding this comment

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

exported type VisitOrder should have comment or be unexported

@adriansr adriansr changed the title fsnotify-backed recursive watcher fsnotify-backed recursive watcher for Linux Dec 7, 2017
)

var (
PathSeparator = string(os.PathSeparator)

Choose a reason for hiding this comment

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

exported var PathSeparator should have comment or be unexported

@adriansr adriansr added enhancement and removed in progress Pull request is currently in progress. labels Dec 8, 2017
@adriansr adriansr force-pushed the recursive/linux branch 2 times, most recently from 75f5afe to b2235a7 Compare December 11, 2017 13:29
Copy link
Member

@andrewkroh andrewkroh left a comment

Choose a reason for hiding this comment

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

Overall looks good. I like how you have wrapped fsnotify such that is works for any platform.

PathSeparator = string(os.PathSeparator)
)

// FileTree represents a directory in a filesystem-tree structure
Copy link
Member

Choose a reason for hiding this comment

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

Please close your public godoc sentences with periods. https://github.com/golang/go/wiki/CodeReviewComments#comment-sentences

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


func TestNonRecursive(t *testing.T) {
dir, err := ioutil.TempDir("", "monitor")
assert.NoError(t, err)
Copy link
Member

Choose a reason for hiding this comment

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

assert.NoError doesn't immediately fail the test case so the test will continue running after the error. In most cases this isn't what you want so you should either not use assert.NoError or you need to add in some logic like if assert.NoError { assert something is correct } . I've seen this result in lots of test case panics and I mostly use if err != nil { t.Fatal(err) } now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

func newRecursiveWatcher(inner *fsnotify.Watcher) *recursiveWatcher {
return &recursiveWatcher{
inner: inner,
tree: FileTree{},
Copy link
Member

Choose a reason for hiding this comment

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

This line is unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the map needs to be initialised otherwise I get insert to nil map panics.

Copy link
Member

Choose a reason for hiding this comment

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

Totally. I didn't realize that FileTree was a map when I made that comment.

func (watcher *recursiveWatcher) Close() error {
if watcher.done != nil {
// has been Started(), goroutine takes care of cleanup
watcher.done <- true
Copy link
Member

Choose a reason for hiding this comment

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

Closing the done channel is the most common way to signal since it works to stop an unbounded number of goroutines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return errors.Wrap(err, "file tree recursion failed")
}
if info.IsDir() {
// TODO: can be a symlink?
Copy link
Member

Choose a reason for hiding this comment

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

You could add a test case for this to find out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

switch event.Op {
case fsnotify.Create:
if err := watcher.addRecursive(event.Name); err != nil {
watcher.inner.Errors <- errors.Wrap(err, fmt.Sprintf("unable to recurse path '%s'", event.Name))
Copy link
Member

Choose a reason for hiding this comment

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

There is a Wrapf method for doing this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks

}
}

func fixPath(path string) string {
Copy link
Member

Choose a reason for hiding this comment

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

I think you could use filepath.Clean().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@andrewkroh
Copy link
Member

Can you please add a change log entry for this.

Adds a wrapper on top of fsnotify that allows to place
recursive watches on directories. Exhibits similar behavior
to FSEvents.
@adriansr
Copy link
Contributor Author

Also added a small explanation of the recursive flag to auditbeat.reference.yml

@@ -85,4 +85,7 @@
# sha224, sha256, sha384, sha512, sha512_224, sha512_256, sha3_224, sha3_256,
# sha3_384 and sha3_512. Default is sha1.
file.hash_types: [sha1]

# Detect changes to files included in subdirectories. Disabled by default.
file.recursive: false
Copy link
Member

@andrewkroh andrewkroh Dec 12, 2017

Choose a reason for hiding this comment

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

This adds an explanation to the `file.recursive` flag and also updates
the description of the underlying implementation for different OSes, to
account for recent changes.
@andrewkroh andrewkroh mentioned this pull request Dec 13, 2017
9 tasks
* macOS (Darwin) - Uses the `FSEvents` API, present since macOS 10.5. This API
coalesces multiple changes to a file into a single event. {beatname_uc} translates
this coalesced changes into a meaningful sequence of actions. However,
in rare situations the reported events may have a different ordering than what
Copy link
Member

Choose a reason for hiding this comment

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

Good explanation

@andrewkroh andrewkroh merged commit 5e66f26 into elastic:master Dec 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants