You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
5.9 KiB

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