Repository: fsnotify/fsnotify
Pull request #631 (2024-05-01 06:08:06)

inotify: implement WithNoFollow()
Option to not follow symlinks, but watch the links themselves.

Unexported for now, until it's supported everywhere.

Updates #227


Patch:
diff --git a/backend_inotify.go b/backend_inotify.go
index 3d79754..21d64ef 100644
--- a/backend_inotify.go
+++ b/backend_inotify.go
@@ -403,6 +403,9 @@ func (w *Watcher) AddWith(name string, opts ...addOpt) error {
 	}
 
 	var flags uint32
+	if with.noFollow {
+		flags |= unix.IN_DONT_FOLLOW
+	}
 	if with.op.Has(Create) {
 		flags |= unix.IN_CREATE
 	}
diff --git a/fsnotify.go b/fsnotify.go
index b43d524..5b4b1b1 100644
--- a/fsnotify.go
+++ b/fsnotify.go
@@ -188,8 +188,9 @@ func (e Event) String() string {
 type (
 	addOpt   func(opt *withOpts)
 	withOpts struct {
-		bufsize int
-		op      Op
+		bufsize  int
+		op       Op
+		noFollow bool
 	}
 )
 
@@ -208,7 +209,9 @@ var defaultOpts = withOpts{
 func getOptions(opts ...addOpt) withOpts {
 	with := defaultOpts
 	for _, o := range opts {
-		o(&with)
+		if o != nil {
+			o(&with)
+		}
 	}
 	return with
 }
@@ -245,6 +248,12 @@ func withOps(op Op) addOpt {
 	return func(opt *withOpts) { opt.op = op }
 }
 
+// WithNoFollow disables following symlinks, so the symlinks themselves are
+// watched.
+func withNoFollow() addOpt {
+	return func(opt *withOpts) { opt.noFollow = true }
+}
+
 var enableRecurse = false
 
 // Check if this path is recursive (ends with "/..." or "\..."), and return the


Test patch:
diff --git a/helpers_test.go b/helpers_test.go
index cc46c27..38fcfe1 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -669,6 +669,15 @@ func supportsRename() bool {
 	}
 }
 
+func supportsNofollow(t *testing.T) {
+	switch runtime.GOOS {
+	case "linux":
+		// Run test.
+	default:
+		t.Skip("withNoFollow() not yet supported on " + runtime.GOOS)
+	}
+}
+
 func tmppath(tmp, s string) string {
 	if len(s) == 0 {
 		return ""
@@ -812,6 +821,8 @@ loop:
 				supportsRecurse(t)
 			case "filter":
 				supportsFilter(t)
+			case "nofollow":
+				supportsNofollow(t)
 			case "windows":
 				if runtime.GOOS == "windows" {
 					t.Skip("Skipping on Windows")
@@ -852,6 +863,16 @@ loop:
 				do = append(do, func() { addWatch(t, w.w, tmppath(tmp, c.args[0])) })
 				continue
 			}
+
+			var follow addOpt
+			for i := range c.args {
+				if c.args[i] == "nofollow" || c.args[i] == "no-follow" {
+					c.args = append(c.args[:i], c.args[i+1:]...)
+					follow = withNoFollow()
+					break
+				}
+			}
+
 			var op Op
 			for _, o := range c.args[1:] {
 				switch strings.ToLower(o) {
@@ -878,11 +899,10 @@ loop:
 				case "close_read":
 					op |= xUnportableCloseRead
 				}
-
 			}
 			do = append(do, func() {
 				p := tmppath(tmp, c.args[0])
-				err := w.w.AddWith(p, withOps(op))
+				err := w.w.AddWith(p, withOps(op), follow)
 				if err != nil {
 					t.Fatalf("line %d: addWatch(%q): %s", c.line+1, p, err)
 				}
diff --git a/testdata/watch-dir/symlink-nofollow b/testdata/watch-dir/symlink-nofollow
new file mode 100644
index 0000000..cf05990
--- /dev/null
+++ b/testdata/watch-dir/symlink-nofollow
@@ -0,0 +1,30 @@
+# Create a new symlink to a watched file.
+require symlink
+require nofollow
+
+touch /file
+mkdir /dir
+
+watch /  default nofollow
+
+ln -s /dir /link-file
+ln -s /dir /link-dir
+
+rm -r /dir
+
+echo asd >>/file
+rm /file
+
+rm /link-file
+rm /link-dir
+
+Output:
+	create  /link-dir
+	create  /link-file
+
+	remove  /dir
+	write   /file
+	remove  /file
+
+	remove  /link-file
+	remove  /link-dir
diff --git a/testdata/watch-symlink/nofollow-dir b/testdata/watch-symlink/nofollow-dir
new file mode 100644
index 0000000..984f387
--- /dev/null
+++ b/testdata/watch-symlink/nofollow-dir
@@ -0,0 +1,17 @@
+# Watch a symlink.
+require symlink
+require nofollow
+
+mkdir /dir
+ln -s /dir /link
+watch /link  default nofollow
+
+touch /dir/file
+chmod 777 /dir
+
+rm -r /dir
+rm /link
+
+Output:
+	chmod  /link
+	remove /link
diff --git a/testdata/watch-symlink/nofollow-file b/testdata/watch-symlink/nofollow-file
new file mode 100644
index 0000000..7d0c2a9
--- /dev/null
+++ b/testdata/watch-symlink/nofollow-file
@@ -0,0 +1,20 @@
+# Watch a symlink.
+require symlink
+require nofollow
+
+touch /file
+ln -s /file /link
+
+watch /link  nofollow default
+
+chmod 777 /file
+echo asd >>/file
+rm /file
+
+rm /link
+
+touch /link
+
+Output:
+	chmod  /link
+	remove /link