github.com/liumingmin/tail@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/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) error { 87 return 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) error { 94 return remove(&watchInfo{ 95 op: fsnotify.Create, 96 fname: fname, 97 }) 98 } 99 100 func remove(winfo *watchInfo) error { 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 shared.mux.Unlock() 112 113 shared.remove <- winfo 114 return <-shared.error 115 } 116 117 // Events returns a channel to which FileEvents corresponding to the input filename 118 // will be sent. This channel will be closed when removeWatch is called on this 119 // filename. 120 func Events(fname string) <-chan fsnotify.Event { 121 shared.mux.Lock() 122 defer shared.mux.Unlock() 123 124 return shared.chans[fname] 125 } 126 127 // Cleanup removes the watch for the input filename if necessary. 128 func Cleanup(fname string) error { 129 return RemoveWatch(fname) 130 } 131 132 // watchFlags calls fsnotify.WatchFlags for the input filename and flags, creating 133 // a new Watcher if the previous Watcher was closed. 134 func (shared *InotifyTracker) addWatch(winfo *watchInfo) error { 135 shared.mux.Lock() 136 defer shared.mux.Unlock() 137 138 if shared.chans[winfo.fname] == nil { 139 shared.chans[winfo.fname] = make(chan fsnotify.Event) 140 } 141 if shared.done[winfo.fname] == nil { 142 shared.done[winfo.fname] = make(chan bool) 143 } 144 145 fname := winfo.fname 146 if winfo.isCreate() { 147 // Watch for new files to be created in the parent directory. 148 fname = filepath.Dir(fname) 149 } 150 151 var err error 152 // already in inotify watch 153 if shared.watchNums[fname] == 0 { 154 err = shared.watcher.Add(fname) 155 } 156 if err == nil { 157 shared.watchNums[fname]++ 158 } 159 return err 160 } 161 162 // removeWatch calls fsnotify.RemoveWatch for the input filename and closes the 163 // corresponding events channel. 164 func (shared *InotifyTracker) removeWatch(winfo *watchInfo) error { 165 shared.mux.Lock() 166 167 ch := shared.chans[winfo.fname] 168 if ch != nil { 169 delete(shared.chans, winfo.fname) 170 close(ch) 171 } 172 173 fname := winfo.fname 174 if winfo.isCreate() { 175 // Watch for new files to be created in the parent directory. 176 fname = filepath.Dir(fname) 177 } 178 shared.watchNums[fname]-- 179 watchNum := shared.watchNums[fname] 180 if watchNum == 0 { 181 delete(shared.watchNums, fname) 182 } 183 shared.mux.Unlock() 184 185 var err error 186 // If we were the last ones to watch this file, unsubscribe from inotify. 187 // This needs to happen after releasing the lock because fsnotify waits 188 // synchronously for the kernel to acknowledge the removal of the watch 189 // for this file, which causes us to deadlock if we still held the lock. 190 if watchNum == 0 { 191 err = shared.watcher.Remove(fname) 192 } 193 194 return err 195 } 196 197 // sendEvent sends the input event to the appropriate Tail. 198 func (shared *InotifyTracker) sendEvent(event fsnotify.Event) { 199 name := filepath.Clean(event.Name) 200 201 shared.mux.Lock() 202 ch := shared.chans[name] 203 done := shared.done[name] 204 shared.mux.Unlock() 205 206 if ch != nil && done != nil { 207 select { 208 case ch <- event: 209 case <-done: 210 } 211 } 212 } 213 214 // run starts the goroutine in which the shared struct reads events from its 215 // Watcher's Event channel and sends the events to the appropriate Tail. 216 func (shared *InotifyTracker) run() { 217 defer func() { 218 recover() 219 }() 220 221 watcher, err := fsnotify.NewWatcher() 222 if err != nil { 223 util.Fatal("failed to create Watcher") 224 } 225 shared.watcher = watcher 226 227 for { 228 select { 229 case winfo := <-shared.watch: 230 shared.error <- shared.addWatch(winfo) 231 232 case winfo := <-shared.remove: 233 shared.error <- shared.removeWatch(winfo) 234 235 case event, open := <-shared.watcher.Events: 236 if !open { 237 return 238 } 239 shared.sendEvent(event) 240 241 case err, open := <-shared.watcher.Errors: 242 if !open { 243 return 244 } else if err != nil { 245 sysErr, ok := err.(*os.SyscallError) 246 if !ok || sysErr.Err != syscall.EINTR { 247 logger.Printf("Error in Watcher Error channel: %s", err) 248 } 249 } 250 } 251 } 252 }