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.

136 lines
2.8 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. "fmt"
  7. "os"
  8. "path/filepath"
  9. "github.com/nxadm/tail/util"
  10. "github.com/fsnotify/fsnotify"
  11. "gopkg.in/tomb.v1"
  12. )
  13. // InotifyFileWatcher uses inotify to monitor file changes.
  14. type InotifyFileWatcher struct {
  15. Filename string
  16. Size int64
  17. }
  18. func NewInotifyFileWatcher(filename string) *InotifyFileWatcher {
  19. fw := &InotifyFileWatcher{filepath.Clean(filename), 0}
  20. return fw
  21. }
  22. func (fw *InotifyFileWatcher) BlockUntilExists(t *tomb.Tomb) error {
  23. err := WatchCreate(fw.Filename)
  24. if err != nil {
  25. return err
  26. }
  27. defer RemoveWatchCreate(fw.Filename)
  28. // Do a real check now as the file might have been created before
  29. // calling `WatchFlags` above.
  30. if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) {
  31. // file exists, or stat returned an error.
  32. return err
  33. }
  34. events := Events(fw.Filename)
  35. for {
  36. select {
  37. case evt, ok := <-events:
  38. if !ok {
  39. return fmt.Errorf("inotify watcher has been closed")
  40. }
  41. evtName, err := filepath.Abs(evt.Name)
  42. if err != nil {
  43. return err
  44. }
  45. fwFilename, err := filepath.Abs(fw.Filename)
  46. if err != nil {
  47. return err
  48. }
  49. if evtName == fwFilename {
  50. return nil
  51. }
  52. case <-t.Dying():
  53. return tomb.ErrDying
  54. }
  55. }
  56. panic("unreachable")
  57. }
  58. func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) {
  59. err := Watch(fw.Filename)
  60. if err != nil {
  61. return nil, err
  62. }
  63. changes := NewFileChanges()
  64. fw.Size = pos
  65. go func() {
  66. events := Events(fw.Filename)
  67. for {
  68. prevSize := fw.Size
  69. var evt fsnotify.Event
  70. var ok bool
  71. select {
  72. case evt, ok = <-events:
  73. if !ok {
  74. RemoveWatch(fw.Filename)
  75. return
  76. }
  77. case <-t.Dying():
  78. RemoveWatch(fw.Filename)
  79. return
  80. }
  81. switch {
  82. case evt.Op&fsnotify.Remove == fsnotify.Remove:
  83. fallthrough
  84. case evt.Op&fsnotify.Rename == fsnotify.Rename:
  85. RemoveWatch(fw.Filename)
  86. changes.NotifyDeleted()
  87. return
  88. //With an open fd, unlink(fd) - inotify returns IN_ATTRIB (==fsnotify.Chmod)
  89. case evt.Op&fsnotify.Chmod == fsnotify.Chmod:
  90. fallthrough
  91. case evt.Op&fsnotify.Write == fsnotify.Write:
  92. fi, err := os.Stat(fw.Filename)
  93. if err != nil {
  94. if os.IsNotExist(err) {
  95. RemoveWatch(fw.Filename)
  96. changes.NotifyDeleted()
  97. return
  98. }
  99. // XXX: report this error back to the user
  100. util.Fatal("Failed to stat file %v: %v", fw.Filename, err)
  101. }
  102. fw.Size = fi.Size()
  103. if prevSize > 0 && prevSize > fw.Size {
  104. changes.NotifyTruncated()
  105. } else {
  106. changes.NotifyModified()
  107. }
  108. prevSize = fw.Size
  109. }
  110. }
  111. }()
  112. return changes, nil
  113. }