github.com/nxadm/tail@v1.4.12-0.20231010141446-ba755e4d73b6/watch/polling.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 "os" 9 "runtime" 10 "time" 11 12 "github.com/nxadm/tail/util" 13 "gopkg.in/tomb.v1" 14 ) 15 16 // PollingFileWatcher polls the file for changes. 17 type PollingFileWatcher struct { 18 Filename string 19 Size int64 20 } 21 22 func NewPollingFileWatcher(filename string) *PollingFileWatcher { 23 fw := &PollingFileWatcher{filename, 0} 24 return fw 25 } 26 27 var POLL_DURATION time.Duration 28 29 func (fw *PollingFileWatcher) BlockUntilExists(t *tomb.Tomb) error { 30 for { 31 if _, err := os.Stat(fw.Filename); err == nil { 32 return nil 33 } else if !os.IsNotExist(err) { 34 return err 35 } 36 select { 37 case <-time.After(POLL_DURATION): 38 continue 39 case <-t.Dying(): 40 return tomb.ErrDying 41 } 42 } 43 panic("unreachable") 44 } 45 46 func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, pos int64) (*FileChanges, error) { 47 origFi, err := os.Stat(fw.Filename) 48 if err != nil { 49 return nil, err 50 } 51 52 changes := NewFileChanges() 53 var prevModTime time.Time 54 55 // XXX: use tomb.Tomb to cleanly manage these goroutines. replace 56 // the fatal (below) with tomb's Kill. 57 58 fw.Size = pos 59 60 go func() { 61 prevSize := fw.Size 62 for { 63 select { 64 case <-t.Dying(): 65 return 66 default: 67 } 68 69 time.Sleep(POLL_DURATION) 70 fi, err := os.Stat(fw.Filename) 71 if err != nil { 72 // Windows cannot delete a file if a handle is still open (tail keeps one open) 73 // so it gives access denied to anything trying to read it until all handles are released. 74 if os.IsNotExist(err) || (runtime.GOOS == "windows" && os.IsPermission(err)) { 75 // File does not exist (has been deleted). 76 changes.NotifyDeleted() 77 return 78 } 79 80 // XXX: report this error back to the user 81 util.Fatal("Failed to stat file %v: %v", fw.Filename, err) 82 } 83 84 // File got moved/renamed? 85 if !os.SameFile(origFi, fi) { 86 changes.NotifyDeleted() 87 return 88 } 89 90 // File got truncated? 91 fw.Size = fi.Size() 92 if prevSize > 0 && prevSize > fw.Size { 93 changes.NotifyTruncated() 94 prevSize = fw.Size 95 continue 96 } 97 // File got bigger? 98 if prevSize > 0 && prevSize < fw.Size { 99 changes.NotifyModified() 100 prevSize = fw.Size 101 continue 102 } 103 prevSize = fw.Size 104 105 // File was appended to (changed)? 106 modTime := fi.ModTime() 107 if modTime != prevModTime { 108 prevModTime = modTime 109 changes.NotifyModified() 110 } 111 } 112 }() 113 114 return changes, nil 115 } 116 117 func init() { 118 POLL_DURATION = 250 * time.Millisecond 119 }