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 "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.
19type InotifyFileWatcher struct {
20 Filename string
21 Size int64
22}
23
24func NewInotifyFileWatcher(filename string) *InotifyFileWatcher {
25 fw := &InotifyFileWatcher{filepath.Clean(filename), 0}
26 return fw
27}
28
29func (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
69func (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}