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