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