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