1
0
Fork 0
mirror of https://github.com/Luzifer/go_helpers.git synced 2024-10-18 14:24:20 +00:00

Allow to set watcher to follow symlinks

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2023-03-19 01:42:30 +01:00
parent 98aacbd5ad
commit d67686d26f
Signed by: luzifer
GPG key ID: D91C3E91E4CAD6F5
3 changed files with 61 additions and 24 deletions

View file

@ -2,6 +2,7 @@ package file
import ( import (
"crypto/sha256" "crypto/sha256"
"os"
"sync" "sync"
"time" "time"
@ -18,10 +19,11 @@ type (
Err error Err error
FilePath string FilePath string
c chan WatcherEvent c chan WatcherEvent
checks []WatcherCheck checks []WatcherCheck
lock sync.RWMutex followSymlinks bool
stateCache map[string]any lock sync.RWMutex
stateCache map[string]any
} }
// WatcherCheck is an interface to implement own checks // WatcherCheck is an interface to implement own checks
@ -30,6 +32,14 @@ type (
// WatcherEvent is the detected change to be signeld through the // WatcherEvent is the detected change to be signeld through the
// channel within the Watcher // channel within the Watcher
WatcherEvent uint WatcherEvent uint
// WatcherOpts holds configuration for newly created watcher
WatcherOpts struct {
// FollowSymlinks switches watchers based on file metadata into
// a mode where they follow a symlink and check the real target
// file instead of the symlink
FollowSymlinks bool
}
) )
const ( const (
@ -40,6 +50,12 @@ const (
WatcherEventFileVanished WatcherEventFileVanished
) )
// DefaultWatcherOpts is used when creating the Watcher without
// giving options
var DefaultWatcherOpts = WatcherOpts{
FollowSymlinks: false,
}
// NewCryptographicWatcher is a wrapper around NewWatcher to configure // NewCryptographicWatcher is a wrapper around NewWatcher to configure
// the Watcher with presence and sha256 hash checks. // the Watcher with presence and sha256 hash checks.
func NewCryptographicWatcher(filePath string, interval time.Duration) (*Watcher, error) { func NewCryptographicWatcher(filePath string, interval time.Duration) (*Watcher, error) {
@ -53,15 +69,27 @@ func NewSimpleWatcher(filePath string, interval time.Duration) (*Watcher, error)
} }
// NewWatcher creates a new Watcher configured with the given filePath, // NewWatcher creates a new Watcher configured with the given filePath,
// interval and checks given. The checks are executed once during // default options, interval and checks given. The checks are executed
// initialization and will not cause an event to be sent. The created // once during initialization and will not cause an event to be sent. The
// Watcher will automatically start its periodic check and the C // created Watcher will automatically start its periodic check and the C
// channel should immediately be watched for changes. If the channel // channel should immediately be watched for changes. If the channel is
// is not listened on the check loop will be paused until events are // not listened on the check loop will be paused until events are
// retrieved. If during the initial checks an error is detected the // retrieved. If during the initial checks an error is detected the loop
// loop is NOT started and the watcher needs to be initialized again. // is NOT started and the watcher needs to be initialized again.
func NewWatcher(filePath string, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) { func NewWatcher(filePath string, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
w, err := newWatcher(filePath, interval, checks...) return NewWatcherWithOpts(filePath, DefaultWatcherOpts, interval, checks...)
}
// NewWatcherWithOpts creates a new Watcher configured with the given
// filePath, options, interval and checks given. The checks are executed
// once during initialization and will not cause an event to be sent. The
// created Watcher will automatically start its periodic check and the C
// channel should immediately be watched for changes. If the channel is
// not listened on the check loop will be paused until events are
// retrieved. If during the initial checks an error is detected the loop
// is NOT started and the watcher needs to be initialized again.
func NewWatcherWithOpts(filePath string, opts WatcherOpts, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
w, err := newWatcher(filePath, opts, interval, checks...)
if err == nil { if err == nil {
go w.loop() go w.loop()
@ -70,7 +98,7 @@ func NewWatcher(filePath string, interval time.Duration, checks ...WatcherCheck)
return w, err return w, err
} }
func newWatcher(filePath string, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) { func newWatcher(filePath string, opts WatcherOpts, interval time.Duration, checks ...WatcherCheck) (*Watcher, error) {
notify := make(chan WatcherEvent, 1) notify := make(chan WatcherEvent, 1)
w := &Watcher{ w := &Watcher{
@ -78,9 +106,10 @@ func newWatcher(filePath string, interval time.Duration, checks ...WatcherCheck)
CheckInterval: interval, CheckInterval: interval,
FilePath: filePath, FilePath: filePath,
c: notify, c: notify,
checks: checks, checks: checks,
stateCache: make(map[string]any), followSymlinks: opts.FollowSymlinks,
stateCache: make(map[string]any),
} }
// Initially run checks once // Initially run checks once
_, err := w.runStateChecks() _, err := w.runStateChecks()
@ -141,3 +170,11 @@ func (w *Watcher) runStateChecks() (WatcherEvent, error) {
return WatcherEventNoChange, nil return WatcherEventNoChange, nil
} }
func (w *Watcher) stat(filePath string) (os.FileInfo, error) {
if w.followSymlinks {
return os.Stat(filePath)
}
return os.Lstat(filePath)
}

View file

@ -29,7 +29,7 @@ func WatcherCheckHash(hcf func() hash.Hash) WatcherCheck {
lastHash = v lastHash = v
} }
if _, err := os.Lstat(w.FilePath); errors.Is(err, fs.ErrNotExist) { if _, err := w.stat(w.FilePath); errors.Is(err, fs.ErrNotExist) {
return WatcherEventInvalid, nil return WatcherEventInvalid, nil
} }
@ -65,7 +65,7 @@ func WatcherCheckMtime(w *Watcher) (WatcherEvent, error) {
lastChange = v lastChange = v
} }
s, err := os.Lstat(w.FilePath) s, err := w.stat(w.FilePath)
switch { switch {
case err == nil: case err == nil:
// handle size change // handle size change
@ -94,7 +94,7 @@ func WatcherCheckPresence(w *Watcher) (WatcherEvent, error) {
wasPresent = v wasPresent = v
} }
_, err := os.Lstat(w.FilePath) _, err := w.stat(w.FilePath)
if err != nil && !errors.Is(err, fs.ErrNotExist) { if err != nil && !errors.Is(err, fs.ErrNotExist) {
// Some weird error occurred // Some weird error occurred
return WatcherEventInvalid, errors.Wrap(err, "getting file stat") return WatcherEventInvalid, errors.Wrap(err, "getting file stat")
@ -124,7 +124,7 @@ func WatcherCheckSize(w *Watcher) (WatcherEvent, error) {
knownSize = v knownSize = v
} }
s, err := os.Lstat(w.FilePath) s, err := w.stat(w.FilePath)
switch { switch {
case err == nil: case err == nil:
// handle size change // handle size change

View file

@ -22,7 +22,7 @@ func TestWatcherCheckHash(t *testing.T) {
testFile := path.Join(testDir, "test.txt") testFile := path.Join(testDir, "test.txt")
w, err := newWatcher(testFile, time.Second, WatcherCheckHash(sha256.New)) w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckHash(sha256.New))
require.NoError(t, err, "initial check should not error on non existing file") require.NoError(t, err, "initial check should not error on non existing file")
evt, err := w.runStateChecks() evt, err := w.runStateChecks()
@ -73,7 +73,7 @@ func TestWatcherCheckMtime(t *testing.T) {
testFile := path.Join(testDir, "test.txt") testFile := path.Join(testDir, "test.txt")
w, err := newWatcher(testFile, time.Second, WatcherCheckMtime) w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckMtime)
require.NoError(t, err, "initial check should not error on non existing file") require.NoError(t, err, "initial check should not error on non existing file")
evt, err := w.runStateChecks() evt, err := w.runStateChecks()
@ -128,7 +128,7 @@ func TestWatcherCheckPresence(t *testing.T) {
testFile := path.Join(testDir, "test.txt") testFile := path.Join(testDir, "test.txt")
w, err := newWatcher(testFile, time.Second, WatcherCheckPresence) w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckPresence)
require.NoError(t, err, "initial check should not error on non existing file") require.NoError(t, err, "initial check should not error on non existing file")
evt, err := w.runStateChecks() evt, err := w.runStateChecks()
@ -165,7 +165,7 @@ func TestWatcherCheckSize(t *testing.T) {
testFile := path.Join(testDir, "test.txt") testFile := path.Join(testDir, "test.txt")
w, err := newWatcher(testFile, time.Second, WatcherCheckSize) w, err := newWatcher(testFile, DefaultWatcherOpts, time.Second, WatcherCheckSize)
require.NoError(t, err, "initial check should not error on non existing file") require.NoError(t, err, "initial check should not error on non existing file")
evt, err := w.runStateChecks() evt, err := w.runStateChecks()