fsnotify_bsd.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. // Copyright 2010 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build freebsd openbsd netbsd dragonfly darwin
  5. package fsnotify
  6. import (
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "os"
  11. "path/filepath"
  12. "sync"
  13. "syscall"
  14. )
  15. const (
  16. // Flags (from <sys/event.h>)
  17. sys_NOTE_DELETE = 0x0001 /* vnode was removed */
  18. sys_NOTE_WRITE = 0x0002 /* data contents changed */
  19. sys_NOTE_EXTEND = 0x0004 /* size increased */
  20. sys_NOTE_ATTRIB = 0x0008 /* attributes changed */
  21. sys_NOTE_LINK = 0x0010 /* link count changed */
  22. sys_NOTE_RENAME = 0x0020 /* vnode was renamed */
  23. sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */
  24. // Watch all events
  25. sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME
  26. // Block for 100 ms on each call to kevent
  27. keventWaitTime = 100e6
  28. )
  29. type FileEvent struct {
  30. mask uint32 // Mask of events
  31. Name string // File name (optional)
  32. create bool // set by fsnotify package if found new file
  33. }
  34. // IsCreate reports whether the FileEvent was triggered by a creation
  35. func (e *FileEvent) IsCreate() bool { return e.create }
  36. // IsDelete reports whether the FileEvent was triggered by a delete
  37. func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE }
  38. // IsModify reports whether the FileEvent was triggered by a file modification
  39. func (e *FileEvent) IsModify() bool {
  40. return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB)
  41. }
  42. // IsRename reports whether the FileEvent was triggered by a change name
  43. func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME }
  44. // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
  45. func (e *FileEvent) IsAttrib() bool {
  46. return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB
  47. }
  48. type Watcher struct {
  49. mu sync.Mutex // Mutex for the Watcher itself.
  50. kq int // File descriptor (as returned by the kqueue() syscall)
  51. watches map[string]int // Map of watched file descriptors (key: path)
  52. wmut sync.Mutex // Protects access to watches.
  53. fsnFlags map[string]uint32 // Map of watched files to flags used for filter
  54. fsnmut sync.Mutex // Protects access to fsnFlags.
  55. enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue
  56. enmut sync.Mutex // Protects access to enFlags.
  57. paths map[int]string // Map of watched paths (key: watch descriptor)
  58. finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor)
  59. pmut sync.Mutex // Protects access to paths and finfo.
  60. fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events)
  61. femut sync.Mutex // Protects access to fileExists.
  62. externalWatches map[string]bool // Map of watches added by user of the library.
  63. ewmut sync.Mutex // Protects access to externalWatches.
  64. Error chan error // Errors are sent on this channel
  65. internalEvent chan *FileEvent // Events are queued on this channel
  66. Event chan *FileEvent // Events are returned on this channel
  67. done chan bool // Channel for sending a "quit message" to the reader goroutine
  68. isClosed bool // Set to true when Close() is first called
  69. }
  70. // NewWatcher creates and returns a new kevent instance using kqueue(2)
  71. func NewWatcher() (*Watcher, error) {
  72. fd, errno := syscall.Kqueue()
  73. if fd == -1 {
  74. return nil, os.NewSyscallError("kqueue", errno)
  75. }
  76. w := &Watcher{
  77. kq: fd,
  78. watches: make(map[string]int),
  79. fsnFlags: make(map[string]uint32),
  80. enFlags: make(map[string]uint32),
  81. paths: make(map[int]string),
  82. finfo: make(map[int]os.FileInfo),
  83. fileExists: make(map[string]bool),
  84. externalWatches: make(map[string]bool),
  85. internalEvent: make(chan *FileEvent),
  86. Event: make(chan *FileEvent),
  87. Error: make(chan error),
  88. done: make(chan bool, 1),
  89. }
  90. go w.readEvents()
  91. go w.purgeEvents()
  92. return w, nil
  93. }
  94. // Close closes a kevent watcher instance
  95. // It sends a message to the reader goroutine to quit and removes all watches
  96. // associated with the kevent instance
  97. func (w *Watcher) Close() error {
  98. w.mu.Lock()
  99. if w.isClosed {
  100. w.mu.Unlock()
  101. return nil
  102. }
  103. w.isClosed = true
  104. w.mu.Unlock()
  105. // Send "quit" message to the reader goroutine
  106. w.done <- true
  107. w.wmut.Lock()
  108. ws := w.watches
  109. w.wmut.Unlock()
  110. for path := range ws {
  111. w.removeWatch(path)
  112. }
  113. return nil
  114. }
  115. // AddWatch adds path to the watched file set.
  116. // The flags are interpreted as described in kevent(2).
  117. func (w *Watcher) addWatch(path string, flags uint32) error {
  118. w.mu.Lock()
  119. if w.isClosed {
  120. w.mu.Unlock()
  121. return errors.New("kevent instance already closed")
  122. }
  123. w.mu.Unlock()
  124. watchDir := false
  125. w.wmut.Lock()
  126. watchfd, found := w.watches[path]
  127. w.wmut.Unlock()
  128. if !found {
  129. fi, errstat := os.Lstat(path)
  130. if errstat != nil {
  131. return errstat
  132. }
  133. // don't watch socket
  134. if fi.Mode()&os.ModeSocket == os.ModeSocket {
  135. return nil
  136. }
  137. // Follow Symlinks
  138. // Unfortunately, Linux can add bogus symlinks to watch list without
  139. // issue, and Windows can't do symlinks period (AFAIK). To maintain
  140. // consistency, we will act like everything is fine. There will simply
  141. // be no file events for broken symlinks.
  142. // Hence the returns of nil on errors.
  143. if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
  144. path, err := filepath.EvalSymlinks(path)
  145. if err != nil {
  146. return nil
  147. }
  148. fi, errstat = os.Lstat(path)
  149. if errstat != nil {
  150. return nil
  151. }
  152. }
  153. fd, errno := syscall.Open(path, open_FLAGS, 0700)
  154. if fd == -1 {
  155. return errno
  156. }
  157. watchfd = fd
  158. w.wmut.Lock()
  159. w.watches[path] = watchfd
  160. w.wmut.Unlock()
  161. w.pmut.Lock()
  162. w.paths[watchfd] = path
  163. w.finfo[watchfd] = fi
  164. w.pmut.Unlock()
  165. }
  166. // Watch the directory if it has not been watched before.
  167. w.pmut.Lock()
  168. w.enmut.Lock()
  169. if w.finfo[watchfd].IsDir() &&
  170. (flags&sys_NOTE_WRITE) == sys_NOTE_WRITE &&
  171. (!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_NOTE_WRITE) {
  172. watchDir = true
  173. }
  174. w.enmut.Unlock()
  175. w.pmut.Unlock()
  176. w.enmut.Lock()
  177. w.enFlags[path] = flags
  178. w.enmut.Unlock()
  179. var kbuf [1]syscall.Kevent_t
  180. watchEntry := &kbuf[0]
  181. watchEntry.Fflags = flags
  182. syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR)
  183. entryFlags := watchEntry.Flags
  184. success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
  185. if success == -1 {
  186. return errno
  187. } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
  188. return errors.New("kevent add error")
  189. }
  190. if watchDir {
  191. errdir := w.watchDirectoryFiles(path)
  192. if errdir != nil {
  193. return errdir
  194. }
  195. }
  196. return nil
  197. }
  198. // Watch adds path to the watched file set, watching all events.
  199. func (w *Watcher) watch(path string) error {
  200. w.ewmut.Lock()
  201. w.externalWatches[path] = true
  202. w.ewmut.Unlock()
  203. return w.addWatch(path, sys_NOTE_ALLEVENTS)
  204. }
  205. // RemoveWatch removes path from the watched file set.
  206. func (w *Watcher) removeWatch(path string) error {
  207. w.wmut.Lock()
  208. watchfd, ok := w.watches[path]
  209. w.wmut.Unlock()
  210. if !ok {
  211. return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path))
  212. }
  213. var kbuf [1]syscall.Kevent_t
  214. watchEntry := &kbuf[0]
  215. syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
  216. entryFlags := watchEntry.Flags
  217. success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
  218. if success == -1 {
  219. return os.NewSyscallError("kevent_rm_watch", errno)
  220. } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
  221. return errors.New("kevent rm error")
  222. }
  223. syscall.Close(watchfd)
  224. w.wmut.Lock()
  225. delete(w.watches, path)
  226. w.wmut.Unlock()
  227. w.enmut.Lock()
  228. delete(w.enFlags, path)
  229. w.enmut.Unlock()
  230. w.pmut.Lock()
  231. delete(w.paths, watchfd)
  232. fInfo := w.finfo[watchfd]
  233. delete(w.finfo, watchfd)
  234. w.pmut.Unlock()
  235. // Find all watched paths that are in this directory that are not external.
  236. if fInfo.IsDir() {
  237. var pathsToRemove []string
  238. w.pmut.Lock()
  239. for _, wpath := range w.paths {
  240. wdir, _ := filepath.Split(wpath)
  241. if filepath.Clean(wdir) == filepath.Clean(path) {
  242. w.ewmut.Lock()
  243. if !w.externalWatches[wpath] {
  244. pathsToRemove = append(pathsToRemove, wpath)
  245. }
  246. w.ewmut.Unlock()
  247. }
  248. }
  249. w.pmut.Unlock()
  250. for _, p := range pathsToRemove {
  251. // Since these are internal, not much sense in propagating error
  252. // to the user, as that will just confuse them with an error about
  253. // a path they did not explicitly watch themselves.
  254. w.removeWatch(p)
  255. }
  256. }
  257. return nil
  258. }
  259. // readEvents reads from the kqueue file descriptor, converts the
  260. // received events into Event objects and sends them via the Event channel
  261. func (w *Watcher) readEvents() {
  262. var (
  263. eventbuf [10]syscall.Kevent_t // Event buffer
  264. events []syscall.Kevent_t // Received events
  265. twait *syscall.Timespec // Time to block waiting for events
  266. n int // Number of events returned from kevent
  267. errno error // Syscall errno
  268. )
  269. events = eventbuf[0:0]
  270. twait = new(syscall.Timespec)
  271. *twait = syscall.NsecToTimespec(keventWaitTime)
  272. for {
  273. // See if there is a message on the "done" channel
  274. var done bool
  275. select {
  276. case done = <-w.done:
  277. default:
  278. }
  279. // If "done" message is received
  280. if done {
  281. errno := syscall.Close(w.kq)
  282. if errno != nil {
  283. w.Error <- os.NewSyscallError("close", errno)
  284. }
  285. close(w.internalEvent)
  286. close(w.Error)
  287. return
  288. }
  289. // Get new events
  290. if len(events) == 0 {
  291. n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait)
  292. // EINTR is okay, basically the syscall was interrupted before
  293. // timeout expired.
  294. if errno != nil && errno != syscall.EINTR {
  295. w.Error <- os.NewSyscallError("kevent", errno)
  296. continue
  297. }
  298. // Received some events
  299. if n > 0 {
  300. events = eventbuf[0:n]
  301. }
  302. }
  303. // Flush the events we received to the events channel
  304. for len(events) > 0 {
  305. fileEvent := new(FileEvent)
  306. watchEvent := &events[0]
  307. fileEvent.mask = uint32(watchEvent.Fflags)
  308. w.pmut.Lock()
  309. fileEvent.Name = w.paths[int(watchEvent.Ident)]
  310. fileInfo := w.finfo[int(watchEvent.Ident)]
  311. w.pmut.Unlock()
  312. if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() {
  313. // Double check to make sure the directory exist. This can happen when
  314. // we do a rm -fr on a recursively watched folders and we receive a
  315. // modification event first but the folder has been deleted and later
  316. // receive the delete event
  317. if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) {
  318. // mark is as delete event
  319. fileEvent.mask |= sys_NOTE_DELETE
  320. }
  321. }
  322. if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() {
  323. w.sendDirectoryChangeEvents(fileEvent.Name)
  324. } else {
  325. // Send the event on the events channel
  326. w.internalEvent <- fileEvent
  327. }
  328. // Move to next event
  329. events = events[1:]
  330. if fileEvent.IsRename() {
  331. w.removeWatch(fileEvent.Name)
  332. w.femut.Lock()
  333. delete(w.fileExists, fileEvent.Name)
  334. w.femut.Unlock()
  335. }
  336. if fileEvent.IsDelete() {
  337. w.removeWatch(fileEvent.Name)
  338. w.femut.Lock()
  339. delete(w.fileExists, fileEvent.Name)
  340. w.femut.Unlock()
  341. // Look for a file that may have overwritten this
  342. // (ie mv f1 f2 will delete f2 then create f2)
  343. fileDir, _ := filepath.Split(fileEvent.Name)
  344. fileDir = filepath.Clean(fileDir)
  345. w.wmut.Lock()
  346. _, found := w.watches[fileDir]
  347. w.wmut.Unlock()
  348. if found {
  349. // make sure the directory exist before we watch for changes. When we
  350. // do a recursive watch and perform rm -fr, the parent directory might
  351. // have gone missing, ignore the missing directory and let the
  352. // upcoming delete event remove the watch form the parent folder
  353. if _, err := os.Lstat(fileDir); !os.IsNotExist(err) {
  354. w.sendDirectoryChangeEvents(fileDir)
  355. }
  356. }
  357. }
  358. }
  359. }
  360. }
  361. func (w *Watcher) watchDirectoryFiles(dirPath string) error {
  362. // Get all files
  363. files, err := ioutil.ReadDir(dirPath)
  364. if err != nil {
  365. return err
  366. }
  367. // Search for new files
  368. for _, fileInfo := range files {
  369. filePath := filepath.Join(dirPath, fileInfo.Name())
  370. // Inherit fsnFlags from parent directory
  371. w.fsnmut.Lock()
  372. if flags, found := w.fsnFlags[dirPath]; found {
  373. w.fsnFlags[filePath] = flags
  374. } else {
  375. w.fsnFlags[filePath] = FSN_ALL
  376. }
  377. w.fsnmut.Unlock()
  378. if fileInfo.IsDir() == false {
  379. // Watch file to mimic linux fsnotify
  380. e := w.addWatch(filePath, sys_NOTE_ALLEVENTS)
  381. if e != nil {
  382. return e
  383. }
  384. } else {
  385. // If the user is currently watching directory
  386. // we want to preserve the flags used
  387. w.enmut.Lock()
  388. currFlags, found := w.enFlags[filePath]
  389. w.enmut.Unlock()
  390. var newFlags uint32 = sys_NOTE_DELETE
  391. if found {
  392. newFlags |= currFlags
  393. }
  394. // Linux gives deletes if not explicitly watching
  395. e := w.addWatch(filePath, newFlags)
  396. if e != nil {
  397. return e
  398. }
  399. }
  400. w.femut.Lock()
  401. w.fileExists[filePath] = true
  402. w.femut.Unlock()
  403. }
  404. return nil
  405. }
  406. // sendDirectoryEvents searches the directory for newly created files
  407. // and sends them over the event channel. This functionality is to have
  408. // the BSD version of fsnotify match linux fsnotify which provides a
  409. // create event for files created in a watched directory.
  410. func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
  411. // Get all files
  412. files, err := ioutil.ReadDir(dirPath)
  413. if err != nil {
  414. w.Error <- err
  415. }
  416. // Search for new files
  417. for _, fileInfo := range files {
  418. filePath := filepath.Join(dirPath, fileInfo.Name())
  419. w.femut.Lock()
  420. _, doesExist := w.fileExists[filePath]
  421. w.femut.Unlock()
  422. if !doesExist {
  423. // Inherit fsnFlags from parent directory
  424. w.fsnmut.Lock()
  425. if flags, found := w.fsnFlags[dirPath]; found {
  426. w.fsnFlags[filePath] = flags
  427. } else {
  428. w.fsnFlags[filePath] = FSN_ALL
  429. }
  430. w.fsnmut.Unlock()
  431. // Send create event
  432. fileEvent := new(FileEvent)
  433. fileEvent.Name = filePath
  434. fileEvent.create = true
  435. w.internalEvent <- fileEvent
  436. }
  437. w.femut.Lock()
  438. w.fileExists[filePath] = true
  439. w.femut.Unlock()
  440. }
  441. w.watchDirectoryFiles(dirPath)
  442. }