I am trying to write a program that uses inotify to watch a file, and if the file is removed, remove the watcher and set new watcher. The code I have tried for the same is
func main() {
fsNotifyChan := make(chan fsnotify.Event)
inotify.CreateWatcher() // code included below
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
for i := range fsNotifyChan {
time.Sleep(time.Second * 5)
fmt.Println(i)
inotify.CreateWatcher()
inotify.SetNewWatcher(i.Name, fsNotifyChan)
}
}()
for k := range parsedConf{
go inotify.SetNewWatcher(k, fsNotifyChan)
}
wg.Wait()
}
Where k is a map and the keys are paths to 2 files /var/log/syslog
and /var/log/auth.log
for example.
The function that I use to create inotify watcher is
package inotify
var Watcher *fsnotify.Watcher
var err error
func CreateWatcher () {
Watcher, err = fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
}
func SetNewWatcher(filepath string, c chan fsnotify.Event) {
log.Infoln("Setting Watcher for ", filepath)
defer Watcher.Close()
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
for {
select {
case event := <-Watcher.Events:
log.Debugln("event:", event)
if event.Op&fsnotify.Rename == fsnotify.Rename {
log.Infoln(event)
removeWatcher(filepath)
c <- event
wg.Done()
runtime.Goexit()
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
log.Infoln(event)
removeWatcher(filepath)
c <- event
wg.Done()
runtime.Goexit()
}
case err := <-Watcher.Errors:
log.Errorln("error:", err)
removeWatcher(filepath)
wg.Done()
runtime.Goexit()
}
}
wg.Done()
}()
err = Watcher.Add(filepath)
if err != nil {
log.Fatal(err)
}
wg.Wait()
}
func removeWatcher(filename string) {
err := Watcher.Remove(filename)
log.Debugln("Removed watcher for", filename)
if err != nil {
log.Errorln(err)
}
}
The problem I am seeing is when I start to run the program,
First output:
iNotifier.go:18: INFO: Setting Watcher for /var/log/auth.log
iNotifier.go:18: INFO: Setting Watcher for /var/log/syslog
Then after a sudo command like echo hi | sudo tee -a /var/log/syslog
I can see
iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/syslog": WRITE
which is perfectly fine now.
Now if I try to move the syslog and put it back as
➜ bin sudo mv /var/log/syslog /var/log/syslog.bak
➜ bin sudo mv /var/log/syslog.bak /var/log/syslog
or remove the file itself and touch a new one.
The output looks like
iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/syslog": RENAME
iNotifier.go:29: INFO: "/var/log/syslog": RENAME
iNotifier.go:63: DEBUG: Removed watcher for /var/log/syslog
iNotifier.go:43: ERROR: error: <nil>
iNotifier.go:63: DEBUG: Removed watcher for /var/log/auth.log
iNotifier.go:65: ERROR: bad file descriptor
"/var/log/syslog": RENAME
iNotifier.go:18: INFO: Setting Watcher for /var/log/syslog
of which iNotifier.go:65: ERROR: bad file descriptor
could be caused because the file has been moved already, and then the goroutine will exit with runtime.Goexit()
Now if I do the same sudo command echo hi | sudo tee -a /var/log/syslog
,
I can only see one inotify output from syslog file and not from the authlog file although things are written there.
iNotifier.go:27: DEBUG: event: "/var/log/syslog": WRITE
If I move the file again and move it back one more time, I stop getting any more notifications.
iNotifier.go:27: DEBUG: event: "/var/log/syslog": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/syslog": RENAME
iNotifier.go:29: INFO: "/var/log/syslog": RENAME
iNotifier.go:63: DEBUG: Removed watcher for /var/log/syslog
and that's the last output. Any more file operations and I don't see any output. I know this could be a logical error on how I am using the way channels are supposed to be used. I am not explicitly closing the channel, and I am passing it again on further iterations. Can someone please help me understand what am I doing wrong here?
After trying a few options, I settled up with a different notify library. Apparently this library doesn't have option to individually close watcher based on path name, but can only do it by closing a channel. So I ended up creating a channel for every file that needs to be watched and a map to relate the file - to - channel, and when required, close the channel related to the file and re-open. The code snippet is
var fileMapChan map[string]chan notify.EventInfo
func main() {
fileMapChan = make(map[string]chan notify.EventInfo)
commonNotificationChan := make(chan string, 2048)
for k := range parsedConf {
c := make(chan notify.EventInfo, 2048)
fileMapChan[k] = c
}
wg := new(sync.WaitGroup)
//Create new watcher for every file
for k, v := range fileMapChan {
wg.Add(2)
poller.SetNewNotifier(k, v)
go poller.ReadChanAndFilter(v, commonNotificationChan, wg)
go poller.FileStat(k, commonNotificationChan, wg)
}
==============snip ✂ snip=============
func SetNewNotifier(filepath string, c chan notify.EventInfo) error{
log.Infoln("Setting new notifier to", filepath)
if err := notify.Watch(filepath, c, notify.All); err != nil {
log.Errorln(err)
return err
}
return nil
}
func ReadChanAndFilter(r chan notify.EventInfo, w chan<- string, wg *sync.WaitGroup) {
for i := range r {
log.Debugln(i.Event(), "on", i.Path())
if i.Event()¬ify.Rename == notify.Rename {
log.Infoln(i.Path(), "renamed")
w <- i.Path()
close(r)
notify.Stop(r)
runtime.Goexit()
}
}
defer wg.Done()
}
I don't know if this is going to be the best approach, but this is working for me. Whenever a file is moved, it will re-add the watcher.
Thank you.