From 7ef9600e905a67423d794356ae8388f3e5e301c4 Mon Sep 17 00:00:00 2001
From: opcoder0 <110003254+opcoder0@users.noreply.github.com>
Date: Mon, 5 Dec 2022 11:20:55 +1100
Subject: [PATCH 1/5] Add test to check FileOrDirectoryAccessed event is
 triggerred

---
 fanotify_test.go | 53 +++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 46 insertions(+), 7 deletions(-)

diff --git a/fanotify_test.go b/fanotify_test.go
index 7eb234a..bca331c 100644
--- a/fanotify_test.go
+++ b/fanotify_test.go
@@ -2,7 +2,10 @@ package fanotify
 
 import (
 	"errors"
+	"fmt"
+	"os"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 	"golang.org/x/sys/unix"
@@ -31,18 +34,54 @@ func TestNewListenerInvalidFlagPreClassContent(t *testing.T) {
 func TestNewListenerValidFlags(t *testing.T) {
 	var flags uint
 	var eventFlags uint
-
 	flags = unix.FAN_CLASS_NOTIF | unix.FAN_REPORT_FID
+	eventFlags = unix.O_RDONLY | unix.O_LARGEFILE | unix.O_CLOEXEC
 	l, err := newListener("/", flags, eventFlags, 4096)
 	assert.Nil(t, err)
 	assert.NotNil(t, l)
 }
 
-// skipped
-func testKernelVersion(t *testing.T) {
-	maj, min, patch, err := kernelVersion()
-	assert.Equal(t, maj, 5)
-	assert.Equal(t, min, 15)
-	assert.Equal(t, patch, 0)
+// func testKernelVersion(t *testing.T) {
+// 	maj, min, patch, err := kernelVersion()
+// 	assert.Equal(t, maj, 5)
+// 	assert.Equal(t, min, 15)
+// 	assert.Equal(t, patch, 0)
+// 	assert.Nil(t, err)
+// }
+
+func TestFanotifyFileAccessed(t *testing.T) {
+
+	var flags uint
+	var eventFlags uint
+
+	flags = unix.FAN_CLASS_NOTIF | unix.FAN_REPORT_FID
+	eventFlags = unix.O_RDONLY | unix.O_LARGEFILE | unix.O_CLOEXEC
+	l, err := newListener("/", flags, eventFlags, 4096)
+	assert.Nil(t, err)
+	assert.NotNil(t, l)
+
+	watchDir := t.TempDir()
+	t.Logf("Watch Directory: %s", watchDir)
+	action := FileOrDirectoryAccessed
+	l.AddWatch(watchDir, action)
+	go l.Start()
+	defer l.Stop()
+	// generate event
+	data := []byte("test data...")
+	testFile := fmt.Sprintf("%s/test.dat", watchDir)
+	err = os.WriteFile(testFile, data, 0666)
+	assert.Nil(t, err)
+	t.Logf("Test file created %s", testFile)
+	f, err := os.Open(testFile)
 	assert.Nil(t, err)
+	f.Close()
+	select {
+	case <-time.After(1 * time.Second):
+		t.Error("Timeout Error: FileOrDirectoryAccessed event not received")
+	case event := <-l.Events:
+		assert.Equal(t, event.Path, testFile)
+		assert.Equal(t, event.Pid, os.Getpid())
+		hasFileAccessed := (event.Mask & FileOrDirectoryAccessed) == FileOrDirectoryAccessed
+		assert.True(t, hasFileAccessed)
+	}
 }

From 3cfe1bbda99d50cb8a1134d892b9bb65ab5aacef Mon Sep 17 00:00:00 2001
From: opcoder0 <110003254+opcoder0@users.noreply.github.com>
Date: Mon, 5 Dec 2022 12:42:25 +1100
Subject: [PATCH 2/5] Unset FAN_ONDIR from mask Simulate file access through
 cat

---
 fanotify_event.go | 12 ++++++++++--
 fanotify_test.go  | 26 ++++++++++++++------------
 2 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/fanotify_event.go b/fanotify_event.go
index cb591de..ca10323 100644
--- a/fanotify_event.go
+++ b/fanotify_event.go
@@ -353,10 +353,14 @@ func (l *Listener) readEvents() error {
 					metadata = (*unix.FanotifyEventMetadata)(unsafe.Pointer(&buf[i]))
 					continue
 				}
+				mask := metadata.Mask
+				if mask&unix.FAN_ONDIR == unix.FAN_ONDIR {
+					mask = mask ^ unix.FAN_ONDIR
+				}
 				event := Event{
 					Fd:   int(metadata.Fd),
 					Path: string(name[:n1]),
-					Mask: Action(metadata.Mask),
+					Mask: Action(mask),
 					Pid:  metadata.Pid,
 				}
 				l.Events <- event
@@ -395,11 +399,15 @@ func (l *Listener) readEvents() error {
 				fdPath := fmt.Sprintf("/proc/self/fd/%d", fd)
 				n1, _ := unix.Readlink(fdPath, name[:]) // TODO handle err case
 				pathName := string(name[:n1])
+				mask := metadata.Mask
+				if mask&unix.FAN_ONDIR == unix.FAN_ONDIR {
+					mask = mask ^ unix.FAN_ONDIR
+				}
 				event := Event{
 					Fd:       fd,
 					Path:     pathName,
 					FileName: fileName,
-					Mask:     Action(metadata.Mask),
+					Mask:     Action(mask),
 					Pid:      metadata.Pid,
 				}
 				l.Events <- event
diff --git a/fanotify_test.go b/fanotify_test.go
index bca331c..b1bb2f0 100644
--- a/fanotify_test.go
+++ b/fanotify_test.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"os/exec"
 	"testing"
 	"time"
 
@@ -49,17 +50,19 @@ func TestNewListenerValidFlags(t *testing.T) {
 // 	assert.Nil(t, err)
 // }
 
-func TestFanotifyFileAccessed(t *testing.T) {
-
-	var flags uint
-	var eventFlags uint
+func catFile(filename string) (int, error) {
+	cmd := exec.Command("cat", filename)
+	err := cmd.Run()
+	if err != nil {
+		return 0, err
+	}
+	return cmd.Process.Pid, nil
+}
 
-	flags = unix.FAN_CLASS_NOTIF | unix.FAN_REPORT_FID
-	eventFlags = unix.O_RDONLY | unix.O_LARGEFILE | unix.O_CLOEXEC
-	l, err := newListener("/", flags, eventFlags, 4096)
+func TestFanotifyFileAccessed(t *testing.T) {
+	l, err := NewListener("/", 4096, true)
 	assert.Nil(t, err)
 	assert.NotNil(t, l)
-
 	watchDir := t.TempDir()
 	t.Logf("Watch Directory: %s", watchDir)
 	action := FileOrDirectoryAccessed
@@ -72,16 +75,15 @@ func TestFanotifyFileAccessed(t *testing.T) {
 	err = os.WriteFile(testFile, data, 0666)
 	assert.Nil(t, err)
 	t.Logf("Test file created %s", testFile)
-	f, err := os.Open(testFile)
+	pid, err := catFile(testFile)
 	assert.Nil(t, err)
-	f.Close()
 	select {
 	case <-time.After(1 * time.Second):
 		t.Error("Timeout Error: FileOrDirectoryAccessed event not received")
 	case event := <-l.Events:
 		assert.Equal(t, event.Path, testFile)
-		assert.Equal(t, event.Pid, os.Getpid())
-		hasFileAccessed := (event.Mask & FileOrDirectoryAccessed) == FileOrDirectoryAccessed
+		assert.Equal(t, event.Pid, pid)
+		hasFileAccessed := (event.Mask & FileAccessed) == FileAccessed
 		assert.True(t, hasFileAccessed)
 	}
 }

From 6f2a5a3c5ab45e77db34419b8f9b589f18d98a6a Mon Sep 17 00:00:00 2001
From: opcoder0 <110003254+opcoder0@users.noreply.github.com>
Date: Mon, 5 Dec 2022 12:47:37 +1100
Subject: [PATCH 3/5] change Event.Pid to int from int32

---
 fanotify_api.go   | 2 +-
 fanotify_event.go | 4 ++--
 fanotify_test.go  | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/fanotify_api.go b/fanotify_api.go
index d812922..b571efd 100644
--- a/fanotify_api.go
+++ b/fanotify_api.go
@@ -38,7 +38,7 @@ type Event struct {
 	// Mask holds bit mask representing the operation
 	Mask Action
 	// Pid Process ID of the process that caused the event
-	Pid int32
+	Pid int
 }
 
 // Listener represents a fanotify notification group that holds a list of files,
diff --git a/fanotify_event.go b/fanotify_event.go
index ca10323..2727442 100644
--- a/fanotify_event.go
+++ b/fanotify_event.go
@@ -361,7 +361,7 @@ func (l *Listener) readEvents() error {
 					Fd:   int(metadata.Fd),
 					Path: string(name[:n1]),
 					Mask: Action(mask),
-					Pid:  metadata.Pid,
+					Pid:  int(metadata.Pid),
 				}
 				l.Events <- event
 
@@ -408,7 +408,7 @@ func (l *Listener) readEvents() error {
 					Path:     pathName,
 					FileName: fileName,
 					Mask:     Action(mask),
-					Pid:      metadata.Pid,
+					Pid:      int(metadata.Pid),
 				}
 				l.Events <- event
 				i += int(metadata.Event_len)
diff --git a/fanotify_test.go b/fanotify_test.go
index b1bb2f0..c622454 100644
--- a/fanotify_test.go
+++ b/fanotify_test.go
@@ -81,7 +81,7 @@ func TestFanotifyFileAccessed(t *testing.T) {
 	case <-time.After(1 * time.Second):
 		t.Error("Timeout Error: FileOrDirectoryAccessed event not received")
 	case event := <-l.Events:
-		assert.Equal(t, event.Path, testFile)
+		assert.Equal(t, fmt.Sprintf("%s/%s", event.Path, event.FileName), testFile)
 		assert.Equal(t, event.Pid, pid)
 		hasFileAccessed := (event.Mask & FileAccessed) == FileAccessed
 		assert.True(t, hasFileAccessed)

From 33dbb0e6778954aa90f9561c59c10111baee55a8 Mon Sep 17 00:00:00 2001
From: opcoder0 <110003254+opcoder0@users.noreply.github.com>
Date: Mon, 5 Dec 2022 14:14:28 +1100
Subject: [PATCH 4/5] add test for file modification check

---
 fanotify_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 42 insertions(+), 2 deletions(-)

diff --git a/fanotify_test.go b/fanotify_test.go
index c622454..921c131 100644
--- a/fanotify_test.go
+++ b/fanotify_test.go
@@ -59,7 +59,17 @@ func catFile(filename string) (int, error) {
 	return cmd.Process.Pid, nil
 }
 
-func TestFanotifyFileAccessed(t *testing.T) {
+func modifyFile(filename string) (int, error) {
+	cmd := exec.Command("sed", "-i", "s/\\(.*\\)/\\U\\1/", filename)
+	err := cmd.Run()
+	if err != nil {
+		return 0, err
+	}
+	return cmd.Process.Pid, nil
+}
+
+// TestWithCapSysAdmFanotifyFileAccessed requires CAP_SYS_ADM privilege.
+func TestWithCapSysAdmFanotifyFileAccessed(t *testing.T) {
 	l, err := NewListener("/", 4096, true)
 	assert.Nil(t, err)
 	assert.NotNil(t, l)
@@ -78,7 +88,7 @@ func TestFanotifyFileAccessed(t *testing.T) {
 	pid, err := catFile(testFile)
 	assert.Nil(t, err)
 	select {
-	case <-time.After(1 * time.Second):
+	case <-time.After(100 * time.Millisecond):
 		t.Error("Timeout Error: FileOrDirectoryAccessed event not received")
 	case event := <-l.Events:
 		assert.Equal(t, fmt.Sprintf("%s/%s", event.Path, event.FileName), testFile)
@@ -87,3 +97,33 @@ func TestFanotifyFileAccessed(t *testing.T) {
 		assert.True(t, hasFileAccessed)
 	}
 }
+
+// TestWithCapSysAdmFanotifyFileModified requires CAP_SYS_ADM privilege.
+func TestWithCapSysAdmFanotifyFileModified(t *testing.T) {
+	l, err := NewListener("/", 4096, true)
+	assert.Nil(t, err)
+	assert.NotNil(t, l)
+	watchDir := t.TempDir()
+	t.Logf("Watch Directory: %s", watchDir)
+	action := FileModified
+	l.AddWatch(watchDir, action)
+	go l.Start()
+	defer l.Stop()
+	// generate event
+	data := []byte("test data...")
+	testFile := fmt.Sprintf("%s/test.dat", watchDir)
+	err = os.WriteFile(testFile, data, 0666)
+	assert.Nil(t, err)
+	t.Logf("Test file created %s", testFile)
+	pid, err := modifyFile(testFile)
+	assert.Nil(t, err)
+	select {
+	case <-time.After(100 * time.Millisecond):
+		t.Error("Timeout Error: FileOrDirectoryAccessed event not received")
+	case event := <-l.Events:
+		assert.Equal(t, fmt.Sprintf("%s/%s", event.Path, event.FileName), testFile)
+		assert.Equal(t, event.Pid, pid)
+		isModifed := (event.Mask & FileModified) == FileModified
+		assert.True(t, isModifed)
+	}
+}

From 85341b2c6edcbd2ba5b6a2dd8bed50c1be2e7dda Mon Sep 17 00:00:00 2001
From: opcoder0 <110003254+opcoder0@users.noreply.github.com>
Date: Mon, 5 Dec 2022 15:57:48 +1100
Subject: [PATCH 5/5] use touch to modify file instead of sed

---
 fanotify_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fanotify_test.go b/fanotify_test.go
index 921c131..c31d851 100644
--- a/fanotify_test.go
+++ b/fanotify_test.go
@@ -60,7 +60,7 @@ func catFile(filename string) (int, error) {
 }
 
 func modifyFile(filename string) (int, error) {
-	cmd := exec.Command("sed", "-i", "s/\\(.*\\)/\\U\\1/", filename)
+	cmd := exec.Command("touch", "-m", filename)
 	err := cmd.Run()
 	if err != nil {
 		return 0, err
