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
5package watch
6
7import (
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.
17type PollingFileWatcher struct {
18 Filename string
19 Size int64
20}
21
22func NewPollingFileWatcher(filename string) *PollingFileWatcher {
23 fw := &PollingFileWatcher{filename, 0}
24 return fw
25}
26
27var POLL_DURATION time.Duration
28
29func (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
46func (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
117func init() {
118 POLL_DURATION = 250 * time.Millisecond
119}