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