Skip to content

Commit e2e9517

Browse files
authored
Add AddRaw to not follow symlinks + Fix link folloing on Windows (#289)
1 parent 82ee6f3 commit e2e9517

9 files changed

Lines changed: 116 additions & 51 deletions

File tree

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,18 @@ func main() {
7272
}
7373
}()
7474

75+
// if this is a link, it will follow all the links and watch the file pointed to
7576
err = watcher.Add("/tmp/foo")
7677
if err != nil {
7778
log.Fatal(err)
7879
}
80+
81+
// this will watch the link, rather than the file it points to
82+
err = watcher.AddRaw("/tmp/link")
83+
if err != nil {
84+
log.Fatal(err)
85+
}
86+
7987
<-done
8088
}
8189
```
@@ -90,6 +98,10 @@ See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_t
9098

9199
## FAQ
92100

101+
**Are symlinks resolved?**
102+
Symlinks are implicitly resolved by [`filepath.EvalSymlinks(path)`](https://golang.org/pkg/path/filepath/#EvalSymlinks) when `watcher.Add(name)` is used. If that is not desired, you can use `watcher.AddRaw(name)` to not follow any symlinks before watching. See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
103+
104+
93105
**When a file is moved to another directory is it still being watched?**
94106

95107
No (it shouldn't be, unless you are watching where it was moved to).
@@ -127,4 +139,3 @@ fsnotify requires support from underlying OS to work. The current NFS protocol d
127139

128140
* [notify](https://github.com/rjeczalik/notify)
129141
* [fsevents](https://github.com/fsnotify/fsevents)
130-

fen.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ func (w *Watcher) Close() error {
2727
return nil
2828
}
2929

30-
// Add starts watching the named file or directory (non-recursively).
31-
func (w *Watcher) Add(name string) error {
30+
// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
31+
func (w *Watcher) AddRaw(name string) error {
3232
return nil
3333
}
3434

fsnotify.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"bytes"
1313
"errors"
1414
"fmt"
15+
"path/filepath"
1516
)
1617

1718
// Event represents a single file system notification.
@@ -67,3 +68,19 @@ func (e Event) String() string {
6768
var (
6869
ErrEventOverflow = errors.New("fsnotify queue overflow")
6970
)
71+
72+
// Add starts watching the named file or directory (non-recursively). Symlinks are implicitly resolved.
73+
func (w *Watcher) Add(name string) error {
74+
rawPath, err := filepath.EvalSymlinks(name)
75+
if err != nil {
76+
return fmt.Errorf("error resolving %#v: %s", name, err)
77+
}
78+
err = w.AddRaw(rawPath)
79+
if err != nil {
80+
if name != rawPath {
81+
return fmt.Errorf("error adding %#v for %#v: %s", rawPath, name, err)
82+
}
83+
return err
84+
}
85+
return nil
86+
}

fsnotify_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func TestWatcherClose(t *testing.T) {
5252

5353
name := tempMkFile(t, "")
5454
w := newWatcher(t)
55-
err := w.Add(name)
55+
err := w.AddRaw(name)
5656
if err != nil {
5757
t.Fatal(err)
5858
}

inotify.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,16 @@ func (w *Watcher) Close() error {
8888
return nil
8989
}
9090

91-
// Add starts watching the named file or directory (non-recursively).
92-
func (w *Watcher) Add(name string) error {
91+
// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
92+
func (w *Watcher) AddRaw(name string) error {
9393
name = filepath.Clean(name)
9494
if w.isClosed() {
9595
return errors.New("inotify instance already closed")
9696
}
9797

9898
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
9999
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
100-
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
100+
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF | unix.IN_DONT_FOLLOW
101101

102102
var flags uint32 = agnosticEvents
103103

inotify_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) {
5454
if err != nil {
5555
t.Fatalf("Failed to create watcher")
5656
}
57-
w.Add(testDir)
57+
w.AddRaw(testDir)
5858

5959
// Wait until readEvents has reached unix.Read, and Close.
6060
<-time.After(50 * time.Millisecond)
@@ -74,7 +74,7 @@ func TestInotifyCloseAfterRead(t *testing.T) {
7474
t.Fatalf("Failed to create watcher")
7575
}
7676

77-
err = w.Add(testDir)
77+
err = w.AddRaw(testDir)
7878
if err != nil {
7979
t.Fatalf("Failed to add .")
8080
}
@@ -121,7 +121,7 @@ func TestInotifyCloseCreate(t *testing.T) {
121121
}
122122
defer w.Close()
123123

124-
err = w.Add(testDir)
124+
err = w.AddRaw(testDir)
125125
if err != nil {
126126
t.Fatalf("Failed to add testDir: %v", err)
127127
}
@@ -149,7 +149,7 @@ func TestInotifyCloseCreate(t *testing.T) {
149149
}
150150

151151
<-time.After(50 * time.Millisecond)
152-
err = w.Add(testDir)
152+
err = w.AddRaw(testDir)
153153
if err != nil {
154154
t.Fatalf("Error adding testDir again: %v", err)
155155
}
@@ -170,7 +170,7 @@ func TestInotifyStress(t *testing.T) {
170170
}
171171
defer w.Close()
172172

173-
err = w.Add(testDir)
173+
err = w.AddRaw(testDir)
174174
if err != nil {
175175
t.Fatalf("Failed to add testDir: %v", err)
176176
}
@@ -290,7 +290,7 @@ func TestInotifyRemoveTwice(t *testing.T) {
290290
}
291291
defer w.Close()
292292

293-
err = w.Add(testFile)
293+
err = w.AddRaw(testFile)
294294
if err != nil {
295295
t.Fatalf("Failed to add testFile: %v", err)
296296
}
@@ -332,7 +332,7 @@ func TestInotifyInnerMapLength(t *testing.T) {
332332
}
333333
defer w.Close()
334334

335-
err = w.Add(testFile)
335+
err = w.AddRaw(testFile)
336336
if err != nil {
337337
t.Fatalf("Failed to add testFile: %v", err)
338338
}
@@ -384,7 +384,7 @@ func TestInotifyOverflow(t *testing.T) {
384384
t.Fatalf("Cannot create subdir: %v", err)
385385
}
386386

387-
err = w.Add(testSubdir)
387+
err = w.AddRaw(testSubdir)
388388
if err != nil {
389389
t.Fatalf("Failed to add subdir: %v", err)
390390
}

integration_test.go

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ func newWatcher(t *testing.T) *Watcher {
6666

6767
// addWatch adds a watch for a directory
6868
func addWatch(t *testing.T, watcher *Watcher, dir string) {
69-
if err := watcher.Add(dir); err != nil {
70-
t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
69+
if err := watcher.AddRaw(dir); err != nil {
70+
t.Fatalf("watcher.AddRaw(%q) failed: %s", dir, err)
7171
}
7272
}
7373

@@ -1008,11 +1008,74 @@ func TestFsnotifyClose(t *testing.T) {
10081008
testDir := tempMkdir(t)
10091009
defer os.RemoveAll(testDir)
10101010

1011-
if err := watcher.Add(testDir); err == nil {
1011+
if err := watcher.AddRaw(testDir); err == nil {
10121012
t.Fatal("expected error on Watch() after Close(), got nil")
10131013
}
10141014
}
10151015

1016+
func TestSymlinkNotResolved(t *testing.T) {
1017+
testDir := tempMkdir(t)
1018+
file1 := filepath.Join(testDir, "file1")
1019+
file2 := filepath.Join(testDir, "file2")
1020+
link := filepath.Join(testDir, "link")
1021+
1022+
f1, err := os.Create(file1)
1023+
if err != nil {
1024+
t.Fatalf("Failed to create file1: %s", err)
1025+
}
1026+
defer f1.Close()
1027+
if _, err := os.Create(file2); err != nil {
1028+
t.Fatalf("Failed to create file2: %s", err)
1029+
}
1030+
1031+
// symlink works for Windows too
1032+
if err := os.Symlink(file1, link); err != nil {
1033+
t.Fatalf("Failed to create symlink: %s", err)
1034+
}
1035+
1036+
w, err := NewWatcher()
1037+
if err != nil {
1038+
t.Fatalf("Failed to create watcher")
1039+
}
1040+
1041+
err = w.AddRaw(link)
1042+
if err != nil {
1043+
t.Fatalf("Failed to add link: %s", err)
1044+
}
1045+
1046+
// change file 1 - no event
1047+
f1.Write([]byte("Hello"))
1048+
f1.Sync()
1049+
// XXX(Code0x58): doing a create here shows a CHMOD event on mac - is that an issue?
1050+
1051+
select {
1052+
case event := <-w.Events:
1053+
t.Fatalf("Event from watcher: %v", event)
1054+
case err := <-w.Errors:
1055+
t.Fatalf("Error from watcher: %v", err)
1056+
case <-time.After(50 * time.Millisecond):
1057+
}
1058+
1059+
// ~atomic link change event
1060+
tmpLink := filepath.Join(testDir, "tmp-link")
1061+
if err := os.Symlink(file2, tmpLink); err != nil {
1062+
t.Fatalf("Failed to create symlink: %s", err)
1063+
}
1064+
1065+
if err := os.Rename(tmpLink, link); err != nil {
1066+
t.Fatalf("Failed to replace symlink: %s", err)
1067+
}
1068+
1069+
select {
1070+
case _ = <-w.Events:
1071+
case err := <-w.Errors:
1072+
t.Fatalf("Error from watcher: %v", err)
1073+
case <-time.After(50 * time.Millisecond):
1074+
t.Fatalf("Took too long to wait for event")
1075+
}
1076+
1077+
}
1078+
10161079
func TestFsnotifyFakeSymlink(t *testing.T) {
10171080
if runtime.GOOS == "windows" {
10181081
t.Skip("symlinks don't work on Windows.")
@@ -1174,7 +1237,7 @@ func TestClose(t *testing.T) {
11741237
defer os.RemoveAll(testDir)
11751238

11761239
watcher := newWatcher(t)
1177-
if err := watcher.Add(testDir); err != nil {
1240+
if err := watcher.AddRaw(testDir); err != nil {
11781241
t.Fatalf("Expected no error on Add, got %v", err)
11791242
}
11801243
err := watcher.Close()
@@ -1195,7 +1258,7 @@ func TestRemoveWithClose(t *testing.T) {
11951258
tempFiles = append(tempFiles, tempMkFile(t, testDir))
11961259
}
11971260
watcher := newWatcher(t)
1198-
if err := watcher.Add(testDir); err != nil {
1261+
if err := watcher.AddRaw(testDir); err != nil {
11991262
t.Fatalf("Expected no error on Add, got %v", err)
12001263
}
12011264
startC, stopC := make(chan struct{}), make(chan struct{})

kqueue.go

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ func (w *Watcher) Close() error {
9191
return nil
9292
}
9393

94-
// Add starts watching the named file or directory (non-recursively).
95-
func (w *Watcher) Add(name string) error {
94+
// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
95+
func (w *Watcher) AddRaw(name string) error {
9696
w.mu.Lock()
9797
w.externalWatches[name] = true
9898
w.mu.Unlock()
@@ -190,33 +190,7 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
190190
return "", nil
191191
}
192192

193-
// Follow Symlinks
194-
// Unfortunately, Linux can add bogus symlinks to watch list without
195-
// issue, and Windows can't do symlinks period (AFAIK). To maintain
196-
// consistency, we will act like everything is fine. There will simply
197-
// be no file events for broken symlinks.
198-
// Hence the returns of nil on errors.
199-
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
200-
name, err = filepath.EvalSymlinks(name)
201-
if err != nil {
202-
return "", nil
203-
}
204-
205-
w.mu.Lock()
206-
_, alreadyWatching = w.watches[name]
207-
w.mu.Unlock()
208-
209-
if alreadyWatching {
210-
return name, nil
211-
}
212-
213-
fi, err = os.Lstat(name)
214-
if err != nil {
215-
return "", nil
216-
}
217-
}
218-
219-
watchfd, err = unix.Open(name, openMode, 0700)
193+
watchfd, err = unix.Open(name, openMode|unix.O_SYMLINK, 0700)
220194
if watchfd == -1 {
221195
return "", err
222196
}

windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ func (w *Watcher) Close() error {
6464
return <-ch
6565
}
6666

67-
// Add starts watching the named file or directory (non-recursively).
68-
func (w *Watcher) Add(name string) error {
67+
// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
68+
func (w *Watcher) AddRaw(name string) error {
6969
if w.isClosed {
7070
return errors.New("watcher already closed")
7171
}

0 commit comments

Comments
 (0)