// Watch is the primary listener for this resource and it outputs events. func (obj *RecWatcher) Watch() error { if obj.watcher == nil { return fmt.Errorf("Watcher is not initialized!") } obj.wg.Add(1) defer obj.wg.Done() patharray := util.PathSplit(obj.safename) // tokenize the path var index = len(patharray) // starting index var current string // current "watcher" location var deltaDepth int // depth delta between watcher and event var send = false // send event? for { current = strings.Join(patharray[0:index], "/") if current == "" { // the empty string top is the root dir ("/") current = "/" } if obj.Flags.Debug { log.Printf("Watching: %s", current) // attempting to watch... } // initialize in the loop so that we can reset on rm-ed handles if err := obj.watcher.Add(current); err != nil { if obj.Flags.Debug { log.Printf("watcher.Add(%s): Error: %v", current, err) } if err == syscall.ENOENT { index-- // usually not found, move up one dir index = int(math.Max(1, float64(index))) continue } if err == syscall.ENOSPC { // no space left on device, out of inotify watches // TODO: consider letting the user fall back to // polling if they hit this error very often... return fmt.Errorf("Out of inotify watches: %v", err) } else if os.IsPermission(err) { return fmt.Errorf("Permission denied adding a watch: %v", err) } return fmt.Errorf("Unknown error: %v", err) } select { case event := <-obj.watcher.Events: if obj.Flags.Debug { log.Printf("Watch(%s), Event(%s): %v", current, event.Name, event.Op) } // the deeper you go, the bigger the deltaDepth is... // this is the difference between what we're watching, // and the event... doesn't mean we can't watch deeper if current == event.Name { deltaDepth = 0 // i was watching what i was looking for } else if util.HasPathPrefix(event.Name, current) { deltaDepth = len(util.PathSplit(current)) - len(util.PathSplit(event.Name)) // -1 or less } else if util.HasPathPrefix(current, event.Name) { deltaDepth = len(util.PathSplit(event.Name)) - len(util.PathSplit(current)) // +1 or more // if below me... if _, exists := obj.watches[event.Name]; exists { send = true if event.Op&fsnotify.Remove == fsnotify.Remove { obj.watcher.Remove(event.Name) delete(obj.watches, event.Name) } if (event.Op&fsnotify.Create == fsnotify.Create) && isDir(event.Name) { obj.watcher.Add(event.Name) obj.watches[event.Name] = struct{}{} if err := obj.addSubFolders(event.Name); err != nil { return err } } } } else { // TODO: different watchers get each others events! // https://github.com/go-fsnotify/fsnotify/issues/95 // this happened with two values such as: // event.Name: /tmp/mgmt/f3 and current: /tmp/mgmt/f2 continue } //log.Printf("The delta depth is: %v", deltaDepth) // if we have what we wanted, awesome, send an event... if event.Name == obj.safename { //log.Println("Event!") // FIXME: should all these below cases trigger? send = true if obj.isDir { if err := obj.addSubFolders(obj.safename); err != nil { return err } } // file removed, move the watch upwards if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) { //log.Println("Removal!") obj.watcher.Remove(current) index-- } // we must be a parent watcher, so descend in if deltaDepth < 0 { // XXX: we can block here due to: https://github.com/fsnotify/fsnotify/issues/123 obj.watcher.Remove(current) index++ } // if safename starts with event.Name, we're above, and no event should be sent } else if util.HasPathPrefix(obj.safename, event.Name) { //log.Println("Above!") if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) { log.Println("Removal!") obj.watcher.Remove(current) index-- } if deltaDepth < 0 { log.Println("Parent!") if util.PathPrefixDelta(obj.safename, event.Name) == 1 { // we're the parent dir send = true } obj.watcher.Remove(current) index++ } // if event.Name startswith safename, send event, we're already deeper } else if util.HasPathPrefix(event.Name, obj.safename) { //log.Println("Event2!") send = true } // do all our event sending all together to avoid duplicate msgs if send { send = false // only invalid state on certain types of events obj.events <- Event{Error: nil, Body: &event} } case err := <-obj.watcher.Errors: return fmt.Errorf("Unknown watcher error: %v", err) case <-obj.exit: return nil } } }
// Watch is the primary listener for this resource and it outputs events. // This one is a file watcher for files and directories. // Modify with caution, it is probably important to write some test cases first! // If the Watch returns an error, it means that something has gone wrong, and it // must be restarted. On a clean exit it returns nil. // FIXME: Also watch the source directory when using obj.Source !!! func (obj *FileRes) Watch(processChan chan event.Event) error { if obj.IsWatching() { return nil // TODO: should this be an error? } obj.SetWatching(true) defer obj.SetWatching(false) cuuid := obj.converger.Register() defer cuuid.Unregister() var startup bool Startup := func(block bool) <-chan time.Time { if block { return nil // blocks forever //return make(chan time.Time) // blocks forever } return time.After(time.Duration(500) * time.Millisecond) // 1/2 the resolution of converged timeout } var safename = path.Clean(obj.path) // no trailing slash var err error obj.watcher, err = fsnotify.NewWatcher() if err != nil { return err } defer obj.watcher.Close() patharray := util.PathSplit(safename) // tokenize the path var index = len(patharray) // starting index var current string // current "watcher" location var deltaDepth int // depth delta between watcher and event var send = false // send event? var exit = false var dirty = false isDir := func(p string) bool { finfo, err := os.Stat(p) if err != nil { return false } return finfo.IsDir() } if obj.isDir { if err := obj.addSubFolders(safename); err != nil { return err } } for { current = strings.Join(patharray[0:index], "/") if current == "" { // the empty string top is the root dir ("/") current = "/" } if global.DEBUG { log.Printf("%s[%s]: Watching: %v", obj.Kind(), obj.GetName(), current) // attempting to watch... } // initialize in the loop so that we can reset on rm-ed handles err = obj.watcher.Add(current) if err != nil { if global.DEBUG { log.Printf("%s[%s]: watcher.Add(%v): Error: %v", obj.Kind(), obj.GetName(), current, err) } if err == syscall.ENOENT { index-- // usually not found, move up one dir } else if err == syscall.ENOSPC { // no space left on device, out of inotify watches // TODO: consider letting the user fall back to // polling if they hit this error very often... return fmt.Errorf("%s[%s]: Out of inotify watches: %v", obj.Kind(), obj.GetName(), err) } else if os.IsPermission(err) { return fmt.Errorf("%s[%s]: Permission denied to add a watch: %v", obj.Kind(), obj.GetName(), err) } else { return fmt.Errorf("Unknown %s[%s] error: %v", obj.Kind(), obj.GetName(), err) } index = int(math.Max(1, float64(index))) continue } obj.SetState(ResStateWatching) // reset select { case event := <-obj.watcher.Events: if global.DEBUG { log.Printf("%s[%s]: Watch(%s), Event(%s): %v", obj.Kind(), obj.GetName(), current, event.Name, event.Op) } cuuid.SetConverged(false) // XXX: technically i can detect if the event is erroneous or not first // the deeper you go, the bigger the deltaDepth is... // this is the difference between what we're watching, // and the event... doesn't mean we can't watch deeper if current == event.Name { deltaDepth = 0 // i was watching what i was looking for } else if util.HasPathPrefix(event.Name, current) { deltaDepth = len(util.PathSplit(current)) - len(util.PathSplit(event.Name)) // -1 or less } else if util.HasPathPrefix(current, event.Name) { deltaDepth = len(util.PathSplit(event.Name)) - len(util.PathSplit(current)) // +1 or more // if below me... if _, exists := obj.watches[event.Name]; exists { send = true dirty = true if event.Op&fsnotify.Remove == fsnotify.Remove { obj.watcher.Remove(event.Name) delete(obj.watches, event.Name) } if (event.Op&fsnotify.Create == fsnotify.Create) && isDir(event.Name) { obj.watcher.Add(event.Name) obj.watches[event.Name] = struct{}{} if err := obj.addSubFolders(event.Name); err != nil { return err } } } } else { // TODO different watchers get each others events! // https://github.com/go-fsnotify/fsnotify/issues/95 // this happened with two values such as: // event.Name: /tmp/mgmt/f3 and current: /tmp/mgmt/f2 continue } //log.Printf("The delta depth is: %v", deltaDepth) // if we have what we wanted, awesome, send an event... if event.Name == safename { //log.Println("Event!") // FIXME: should all these below cases trigger? send = true dirty = true if obj.isDir { if err := obj.addSubFolders(safename); err != nil { return err } } // file removed, move the watch upwards if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) { //log.Println("Removal!") obj.watcher.Remove(current) index-- } // we must be a parent watcher, so descend in if deltaDepth < 0 { obj.watcher.Remove(current) index++ } // if safename starts with event.Name, we're above, and no event should be sent } else if util.HasPathPrefix(safename, event.Name) { //log.Println("Above!") if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) { log.Println("Removal!") obj.watcher.Remove(current) index-- } if deltaDepth < 0 { log.Println("Parent!") if util.PathPrefixDelta(safename, event.Name) == 1 { // we're the parent dir send = true dirty = true } obj.watcher.Remove(current) index++ } // if event.Name startswith safename, send event, we're already deeper } else if util.HasPathPrefix(event.Name, safename) { //log.Println("Event2!") send = true dirty = true } case err := <-obj.watcher.Errors: cuuid.SetConverged(false) return fmt.Errorf("Unknown %s[%s] watcher error: %v", obj.Kind(), obj.GetName(), err) case event := <-obj.events: cuuid.SetConverged(false) if exit, send = obj.ReadEvent(&event); exit { return nil // exit } //dirty = false // these events don't invalidate state case <-cuuid.ConvergedTimer(): cuuid.SetConverged(true) // converged! continue case <-Startup(startup): cuuid.SetConverged(false) send = true dirty = true } // do all our event sending all together to avoid duplicate msgs if send { startup = true // startup finished send = false // only invalid state on certain types of events if dirty { dirty = false obj.isStateOK = false // something made state dirty } if exit, err := obj.DoSend(processChan, ""); exit || err != nil { return err // we exit or bubble up a NACK... } } } }
// ConfigWatch writes on the channel everytime an event is seen for the path. // XXX: it would be great if we could reuse code between this and the file resource // XXX: patch this to submit it as part of go-fsnotify if they're interested... func ConfigWatch(file string) chan bool { ch := make(chan bool) go func() { var safename = path.Clean(file) // no trailing slash watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } defer watcher.Close() patharray := util.PathSplit(safename) // tokenize the path var index = len(patharray) // starting index var current string // current "watcher" location var deltaDepth int // depth delta between watcher and event var send = false // send event? for { current = strings.Join(patharray[0:index], "/") if current == "" { // the empty string top is the root dir ("/") current = "/" } if global.DEBUG { log.Printf("Watching: %v", current) // attempting to watch... } // initialize in the loop so that we can reset on rm-ed handles err = watcher.Add(current) if err != nil { if err == syscall.ENOENT { index-- // usually not found, move up one dir } else if err == syscall.ENOSPC { // XXX: occasionally: no space left on device, // XXX: probably due to lack of inotify watches log.Printf("Out of inotify watches for config(%v)", file) log.Fatal(err) } else { log.Printf("Unknown config(%v) error:", file) log.Fatal(err) } index = int(math.Max(1, float64(index))) continue } select { case event := <-watcher.Events: // the deeper you go, the bigger the deltaDepth is... // this is the difference between what we're watching, // and the event... doesn't mean we can't watch deeper if current == event.Name { deltaDepth = 0 // i was watching what i was looking for } else if util.HasPathPrefix(event.Name, current) { deltaDepth = len(util.PathSplit(current)) - len(util.PathSplit(event.Name)) // -1 or less } else if util.HasPathPrefix(current, event.Name) { deltaDepth = len(util.PathSplit(event.Name)) - len(util.PathSplit(current)) // +1 or more } else { // TODO different watchers get each others events! // https://github.com/go-fsnotify/fsnotify/issues/95 // this happened with two values such as: // event.Name: /tmp/mgmt/f3 and current: /tmp/mgmt/f2 continue } //log.Printf("The delta depth is: %v", deltaDepth) // if we have what we wanted, awesome, send an event... if event.Name == safename { //log.Println("Event!") // TODO: filter out some of the events, is Write a sufficient minimum? if event.Op&fsnotify.Write == fsnotify.Write { send = true } // file removed, move the watch upwards if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) { //log.Println("Removal!") watcher.Remove(current) index-- } // we must be a parent watcher, so descend in if deltaDepth < 0 { watcher.Remove(current) index++ } // if safename starts with event.Name, we're above, and no event should be sent } else if util.HasPathPrefix(safename, event.Name) { //log.Println("Above!") if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) { log.Println("Removal!") watcher.Remove(current) index-- } if deltaDepth < 0 { log.Println("Parent!") if util.PathPrefixDelta(safename, event.Name) == 1 { // we're the parent dir //send = true } watcher.Remove(current) index++ } // if event.Name startswith safename, send event, we're already deeper } else if util.HasPathPrefix(event.Name, safename) { //log.Println("Event2!") //send = true } case err := <-watcher.Errors: log.Printf("error: %v", err) log.Fatal(err) } // do our event sending all together to avoid duplicate msgs if send { send = false ch <- true } } //close(ch) }() return ch }