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