github.com/wowker/tail@v0.0.0-20210121082357-fe86e3c1032a/watch/inotify_tracker.go (about) 1 // Copyright (c) 2015 HPE Software Inc. All rights reserved. 2 // Copyright (c) 2013 ActiveState Software Inc. All rights reserved. 3 4 package watch 5 6 import ( 7 "github.com/fsnotify/fsnotify" 8 "github.com/wowker/tail/util" 9 "log" 10 "os" 11 "path/filepath" 12 "sync" 13 "syscall" 14 ) 15 16 type InotifyTracker struct { 17 mux sync.Mutex 18 watcher *fsnotify.Watcher 19 chans map[string]chan fsnotify.Event 20 done map[string]chan bool 21 watchNums map[string]int 22 watch chan *watchInfo 23 remove chan *watchInfo 24 error chan error 25 } 26 27 type watchInfo struct { 28 op fsnotify.Op 29 fname string 30 } 31 32 func (this *watchInfo) isCreate() bool { 33 return this.op == fsnotify.Create 34 } 35 36 var ( 37 // globally shared InotifyTracker; ensures only one fsnotify.Watcher is used 38 shared *InotifyTracker 39 40 // these are used to ensure the shared InotifyTracker is run exactly once 41 once = sync.Once{} 42 goRun = func() { 43 shared = &InotifyTracker{ 44 mux: sync.Mutex{}, 45 chans: make(map[string]chan fsnotify.Event), 46 done: make(map[string]chan bool), 47 watchNums: make(map[string]int), 48 watch: make(chan *watchInfo), 49 remove: make(chan *watchInfo), 50 error: make(chan error), 51 } 52 go shared.run() 53 } 54 55 logger = log.New(os.Stderr, "", log.LstdFlags) 56 ) 57 58 // Watch signals the run goroutine to begin watching the input filename 59 func Watch(fname string) error { 60 return watch(&watchInfo{ 61 fname: fname, 62 }) 63 } 64 65 // Watch create signals the run goroutine to begin watching the input filename 66 // if call the WatchCreate function, don't call the Cleanup, call the RemoveWatchCreate 67 func WatchCreate(fname string) error { 68 return watch(&watchInfo{ 69 op: fsnotify.Create, 70 fname: fname, 71 }) 72 } 73 74 func watch(winfo *watchInfo) error { 75 // start running the shared InotifyTracker if not already running 76 once.Do(goRun) 77 78 winfo.fname = filepath.Clean(winfo.fname) 79 shared.watch <- winfo 80 return <-shared.error 81 } 82 83 // RemoveWatch signals the run goroutine to remove the watch for the input filename 84 func RemoveWatch(fname string) error { 85 return remove(&watchInfo{ 86 fname: fname, 87 }) 88 } 89 90 // RemoveWatch create signals the run goroutine to remove the watch for the input filename 91 func RemoveWatchCreate(fname string) error { 92 return remove(&watchInfo{ 93 op: fsnotify.Create, 94 fname: fname, 95 }) 96 } 97 98 func remove(winfo *watchInfo) error { 99 // start running the shared InotifyTracker if not already running 100 once.Do(goRun) 101 102 winfo.fname = filepath.Clean(winfo.fname) 103 shared.mux.Lock() 104 done := shared.done[winfo.fname] 105 if done != nil { 106 delete(shared.done, winfo.fname) 107 close(done) 108 } 109 shared.mux.Unlock() 110 111 shared.remove <- winfo 112 return <-shared.error 113 } 114 115 // Events returns a channel to which FileEvents corresponding to the input filename 116 // will be sent. This channel will be closed when removeWatch is called on this 117 // filename. 118 func Events(fname string) <-chan fsnotify.Event { 119 shared.mux.Lock() 120 defer shared.mux.Unlock() 121 122 return shared.chans[fname] 123 } 124 125 // Cleanup removes the watch for the input filename if necessary. 126 func Cleanup(fname string) error { 127 return RemoveWatch(fname) 128 } 129 130 // watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating 131 // a new Watcher if the previous Watcher was closed. 132 func (shared *InotifyTracker) addWatch(winfo *watchInfo) error { 133 shared.mux.Lock() 134 defer shared.mux.Unlock() 135 136 if shared.chans[winfo.fname] == nil { 137 shared.chans[winfo.fname] = make(chan fsnotify.Event) 138 } 139 if shared.done[winfo.fname] == nil { 140 shared.done[winfo.fname] = make(chan bool) 141 } 142 143 fname := winfo.fname 144 if winfo.isCreate() { 145 // Watch for new files to be created in the parent directory. 146 fname = filepath.Dir(fname) 147 } 148 149 var err error 150 // already in inotify watch 151 if shared.watchNums[fname] == 0 { 152 err = shared.watcher.Add(fname) 153 } 154 if err == nil { 155 shared.watchNums[fname]++ 156 } 157 return err 158 } 159 160 // removeWatch calls fsnotify.RemoveWatch for the input filename and closes the 161 // corresponding events channel. 162 func (shared *InotifyTracker) removeWatch(winfo *watchInfo) error { 163 shared.mux.Lock() 164 165 ch := shared.chans[winfo.fname] 166 if ch != nil { 167 delete(shared.chans, winfo.fname) 168 close(ch) 169 } 170 171 fname := winfo.fname 172 if winfo.isCreate() { 173 // Watch for new files to be created in the parent directory. 174 fname = filepath.Dir(fname) 175 } 176 shared.watchNums[fname]-- 177 watchNum := shared.watchNums[fname] 178 if watchNum == 0 { 179 delete(shared.watchNums, fname) 180 } 181 shared.mux.Unlock() 182 183 var err error 184 // If we were the last ones to watch this file, unsubscribe from inotify. 185 // This needs to happen after releasing the lock because fsnotify waits 186 // synchronously for the kernel to acknowledge the removal of the watch 187 // for this file, which causes us to deadlock if we still held the lock. 188 if watchNum == 0 { 189 err = shared.watcher.Remove(fname) 190 } 191 192 return err 193 } 194 195 // sendEvent sends the input event to the appropriate Tail. 196 func (shared *InotifyTracker) sendEvent(event fsnotify.Event) { 197 name := filepath.Clean(event.Name) 198 199 shared.mux.Lock() 200 ch := shared.chans[name] 201 done := shared.done[name] 202 shared.mux.Unlock() 203 204 if ch != nil && done != nil { 205 select { 206 case ch <- event: 207 case <-done: 208 } 209 } 210 } 211 212 // run starts the goroutine in which the shared struct reads events from its 213 // Watcher's Event channel and sends the events to the appropriate Tail. 214 func (shared *InotifyTracker) run() { 215 watcher, err := fsnotify.NewWatcher() 216 if err != nil { 217 util.Fatal("failed to create Watcher") 218 } 219 shared.watcher = watcher 220 221 for { 222 select { 223 case winfo := <-shared.watch: 224 shared.error <- shared.addWatch(winfo) 225 226 case winfo := <-shared.remove: 227 shared.error <- shared.removeWatch(winfo) 228 229 case event, open := <-shared.watcher.Events: 230 if !open { 231 return 232 } 233 shared.sendEvent(event) 234 235 case err, open := <-shared.watcher.Errors: 236 if !open { 237 return 238 } else if err != nil { 239 sysErr, ok := err.(*os.SyscallError) 240 if !ok || sysErr.Err != syscall.EINTR { 241 logger.Printf("Error in Watcher Error channel: %s", err) 242 } 243 } 244 } 245 } 246 }