}
}()
+ // if this is a link, it will follow all the links and watch the file pointed to
err = watcher.Add("/tmp/foo")
if err != nil {
log.Fatal(err)
}
+
+ // this will watch the link, rather than the file it points to
+ err = watcher.AddRaw("/tmp/link")
+ if err != nil {
+ log.Fatal(err)
+ }
+
<-done
}
```
## FAQ
+**Are symlinks resolved?**
+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).
+
+
**When a file is moved to another directory is it still being watched?**
No (it shouldn't be, unless you are watching where it was moved to).
* [notify](https://github.com/rjeczalik/notify)
* [fsevents](https://github.com/fsnotify/fsevents)
-
return nil
}
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
+// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
+func (w *Watcher) AddRaw(name string) error {
return nil
}
"bytes"
"errors"
"fmt"
+ "path/filepath"
)
// Event represents a single file system notification.
var (
ErrEventOverflow = errors.New("fsnotify queue overflow")
)
+
+// Add starts watching the named file or directory (non-recursively). Symlinks are implicitly resolved.
+func (w *Watcher) Add(name string) error {
+ rawPath, err := filepath.EvalSymlinks(name)
+ if err != nil {
+ return fmt.Errorf("error resolving %#v: %s", name, err)
+ }
+ err = w.AddRaw(rawPath)
+ if err != nil {
+ if name != rawPath {
+ return fmt.Errorf("error adding %#v for %#v: %s", rawPath, name, err)
+ }
+ return err
+ }
+ return nil
+}
name := tempMkFile(t, "")
w := newWatcher(t)
- err := w.Add(name)
+ err := w.AddRaw(name)
if err != nil {
t.Fatal(err)
}
return nil
}
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
+// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
+func (w *Watcher) AddRaw(name string) error {
name = filepath.Clean(name)
if w.isClosed() {
return errors.New("inotify instance already closed")
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
- unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
+ unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF | unix.IN_DONT_FOLLOW
var flags uint32 = agnosticEvents
if err != nil {
t.Fatalf("Failed to create watcher")
}
- w.Add(testDir)
+ w.AddRaw(testDir)
// Wait until readEvents has reached unix.Read, and Close.
<-time.After(50 * time.Millisecond)
t.Fatalf("Failed to create watcher")
}
- err = w.Add(testDir)
+ err = w.AddRaw(testDir)
if err != nil {
t.Fatalf("Failed to add .")
}
}
defer w.Close()
- err = w.Add(testDir)
+ err = w.AddRaw(testDir)
if err != nil {
t.Fatalf("Failed to add testDir: %v", err)
}
}
<-time.After(50 * time.Millisecond)
- err = w.Add(testDir)
+ err = w.AddRaw(testDir)
if err != nil {
t.Fatalf("Error adding testDir again: %v", err)
}
}
defer w.Close()
- err = w.Add(testDir)
+ err = w.AddRaw(testDir)
if err != nil {
t.Fatalf("Failed to add testDir: %v", err)
}
}
defer w.Close()
- err = w.Add(testFile)
+ err = w.AddRaw(testFile)
if err != nil {
t.Fatalf("Failed to add testFile: %v", err)
}
}
defer w.Close()
- err = w.Add(testFile)
+ err = w.AddRaw(testFile)
if err != nil {
t.Fatalf("Failed to add testFile: %v", err)
}
t.Fatalf("Cannot create subdir: %v", err)
}
- err = w.Add(testSubdir)
+ err = w.AddRaw(testSubdir)
if err != nil {
t.Fatalf("Failed to add subdir: %v", err)
}
// addWatch adds a watch for a directory
func addWatch(t *testing.T, watcher *Watcher, dir string) {
- if err := watcher.Add(dir); err != nil {
- t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
+ if err := watcher.AddRaw(dir); err != nil {
+ t.Fatalf("watcher.AddRaw(%q) failed: %s", dir, err)
}
}
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)
- if err := watcher.Add(testDir); err == nil {
+ if err := watcher.AddRaw(testDir); err == nil {
t.Fatal("expected error on Watch() after Close(), got nil")
}
}
+func TestSymlinkNotResolved(t *testing.T) {
+ testDir := tempMkdir(t)
+ file1 := filepath.Join(testDir, "file1")
+ file2 := filepath.Join(testDir, "file2")
+ link := filepath.Join(testDir, "link")
+
+ f1, err := os.Create(file1)
+ if err != nil {
+ t.Fatalf("Failed to create file1: %s", err)
+ }
+ defer f1.Close()
+ if _, err := os.Create(file2); err != nil {
+ t.Fatalf("Failed to create file2: %s", err)
+ }
+
+ // symlink works for Windows too
+ if err := os.Symlink(file1, link); err != nil {
+ t.Fatalf("Failed to create symlink: %s", err)
+ }
+
+ w, err := NewWatcher()
+ if err != nil {
+ t.Fatalf("Failed to create watcher")
+ }
+
+ err = w.AddRaw(link)
+ if err != nil {
+ t.Fatalf("Failed to add link: %s", err)
+ }
+
+ // change file 1 - no event
+ f1.Write([]byte("Hello"))
+ f1.Sync()
+ // XXX(Code0x58): doing a create here shows a CHMOD event on mac - is that an issue?
+
+ select {
+ case event := <-w.Events:
+ t.Fatalf("Event from watcher: %v", event)
+ case err := <-w.Errors:
+ t.Fatalf("Error from watcher: %v", err)
+ case <-time.After(50 * time.Millisecond):
+ }
+
+ // ~atomic link change event
+ tmpLink := filepath.Join(testDir, "tmp-link")
+ if err := os.Symlink(file2, tmpLink); err != nil {
+ t.Fatalf("Failed to create symlink: %s", err)
+ }
+
+ if err := os.Rename(tmpLink, link); err != nil {
+ t.Fatalf("Failed to replace symlink: %s", err)
+ }
+
+ select {
+ case _ = <-w.Events:
+ case err := <-w.Errors:
+ t.Fatalf("Error from watcher: %v", err)
+ case <-time.After(50 * time.Millisecond):
+ t.Fatalf("Took too long to wait for event")
+ }
+
+}
+
func TestFsnotifyFakeSymlink(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("symlinks don't work on Windows.")
defer os.RemoveAll(testDir)
watcher := newWatcher(t)
- if err := watcher.Add(testDir); err != nil {
+ if err := watcher.AddRaw(testDir); err != nil {
t.Fatalf("Expected no error on Add, got %v", err)
}
err := watcher.Close()
tempFiles = append(tempFiles, tempMkFile(t, testDir))
}
watcher := newWatcher(t)
- if err := watcher.Add(testDir); err != nil {
+ if err := watcher.AddRaw(testDir); err != nil {
t.Fatalf("Expected no error on Add, got %v", err)
}
startC, stopC := make(chan struct{}), make(chan struct{})
return nil
}
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
+// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
+func (w *Watcher) AddRaw(name string) error {
w.mu.Lock()
w.externalWatches[name] = true
w.mu.Unlock()
return "", nil
}
- // Follow Symlinks
- // Unfortunately, Linux can add bogus symlinks to watch list without
- // issue, and Windows can't do symlinks period (AFAIK). To maintain
- // consistency, we will act like everything is fine. There will simply
- // be no file events for broken symlinks.
- // Hence the returns of nil on errors.
- if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
- name, err = filepath.EvalSymlinks(name)
- if err != nil {
- return "", nil
- }
-
- w.mu.Lock()
- _, alreadyWatching = w.watches[name]
- w.mu.Unlock()
-
- if alreadyWatching {
- return name, nil
- }
-
- fi, err = os.Lstat(name)
- if err != nil {
- return "", nil
- }
- }
-
- watchfd, err = unix.Open(name, openMode, 0700)
+ watchfd, err = unix.Open(name, openMode|unix.O_SYMLINK, 0700)
if watchfd == -1 {
return "", err
}
return <-ch
}
-// Add starts watching the named file or directory (non-recursively).
-func (w *Watcher) Add(name string) error {
+// AddRaw starts watching the named file or directory (non-recursively). Symlinks are not implicitly resolved.
+func (w *Watcher) AddRaw(name string) error {
if w.isClosed {
return errors.New("watcher already closed")
}