summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-30 10:55:45 +0300
committerPaul Buetow <paul@buetow.org>2026-05-30 10:55:45 +0300
commit271cda4e5b478a9f51ac98544e34de768a7e69ae (patch)
tree448e920cbfc2501d4a68efc871477a7e9959247a /internal
parentee3a7106a724dc193589ab652ef21503ba291562 (diff)
creat: keep pathname on failed creat; lock in fd->path mapping
creat(pathname, mode) is equivalent to open(pathname, O_CREAT|O_WRONLY|O_TRUNC, mode): on success it returns a new fd, on failure -1. handlePathExit already special-cased creat to register the returned fd->path mapping in fdState (matching handleOpenExit for open/openat/openat2), but on failure (ret<0) it left ep.File unset, silently dropping the path. handleOpenExit keeps the path via NewPathname for failed opens so error scenarios stay observable; align the creat branch with that behavior. Strengthen CreatEventTest to assert the returned fd is registered with the correct path and synthesized open flags, and add a negative FailedCreatEventTest covering the ret<0 path (no fd registered, path retained). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/eventloop_exit.go7
-rw-r--r--internal/eventloop_test.go59
2 files changed, 59 insertions, 7 deletions
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index a5b38d4..c1465fc 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -105,10 +105,17 @@ func (e *eventLoop) handlePathExit(ep *event.Pair, pathEv *types.PathEvent) bool
return false
}
if fd := int32(retEvent.Ret); fd >= 0 {
+ // creat(pathname, mode) == open(pathname, O_CREAT|O_WRONLY|O_TRUNC,
+ // mode): on success it returns a new fd, so register the fd->path
+ // mapping just like handleOpenExit does for open/openat/openat2.
fdFile := file.NewFd(fd, types.StringValue(pathEv.Pathname[:]),
syscall.O_CREAT|syscall.O_WRONLY|syscall.O_TRUNC)
e.fdState().set(fd, fdFile)
ep.File = fdFile
+ } else {
+ // Failed creat (-1): keep the path so error scenarios stay
+ // observable, mirroring handleOpenExit's failed-open branch.
+ ep.File = file.NewPathname(pathEv.Pathname[:])
}
} else {
ep.File = file.NewPathname(pathEv.Pathname[:])
diff --git a/internal/eventloop_test.go b/internal/eventloop_test.go
index 79b8677..98d7696 100644
--- a/internal/eventloop_test.go
+++ b/internal/eventloop_test.go
@@ -53,11 +53,12 @@ func TestEventloop(t *testing.T) {
"MsyncEventTest": makeMsyncEventTestData(t),
"FtruncateEventTest": makeFtruncateEventTestData(t),
// PathEvent tests
- "MkdirEventTest": makeMkdirEventTestData(t),
- "UnlinkEventTest": makeUnlinkEventTestData(t),
- "CreatEventTest": makeCreatEventTestData(t),
- "StatEventTest": makeStatEventTestData(t),
- "AccessEventTest": makeAccessEventTestData(t),
+ "MkdirEventTest": makeMkdirEventTestData(t),
+ "UnlinkEventTest": makeUnlinkEventTestData(t),
+ "CreatEventTest": makeCreatEventTestData(t),
+ "FailedCreatEventTest": makeFailedCreatEventTestData(t),
+ "StatEventTest": makeStatEventTestData(t),
+ "AccessEventTest": makeAccessEventTestData(t),
// NameEvent tests
"RenameEventTest": makeRenameEventTestData(t),
"LinkEventTest": makeLinkEventTestData(t),
@@ -1363,9 +1364,53 @@ func makeCreatEventTestData(t *testing.T) (td testData) {
if !exitEv.Equals(ep.ExitEv) {
t.Errorf("Expected '%v' but got '%v'", exitEv, ep.ExitEv)
}
- // For creat, we expect the file to be tracked with the returned fd
+ // creat(pathname, mode) == open(pathname, O_CREAT|O_WRONLY|O_TRUNC,
+ // mode): on success the returned fd must be registered in fdState and
+ // mapped to the path, exactly like open/openat. Lock in both the
+ // fd->path mapping and the synthesized open flags.
if ep.File == nil {
- t.Errorf("Expected file to be set")
+ t.Fatalf("Expected file to be set for successful creat")
+ }
+ verifyFileDescriptor(t, el, 47, pathname)
+ fdFile, ok := el.fdState().files[47].(*file.FdFile)
+ if !ok {
+ t.Fatalf("Expected creat fd 47 to be an *file.FdFile")
+ }
+ wantFlags := file.Flags(syscall.O_CREAT | syscall.O_WRONLY | syscall.O_TRUNC)
+ if fdFile.Flags() != wantFlags {
+ t.Errorf("creat fd flags = %v, want %v", fdFile.Flags(), wantFlags)
+ }
+ })
+
+ return td
+}
+
+// makeFailedCreatEventTestData locks in that a failed creat (return -1) does
+// NOT register an fd but still keeps the pathname so error scenarios stay
+// observable — mirroring handleOpenExit's failed-open branch.
+func makeFailedCreatEventTestData(t *testing.T) (td testData) {
+ pathname := "/tmp/cannot-create.txt"
+ enterEv, enterEvBytes := makeEnterPathEvent(t, defaulTime, defaultPid, defaultTid, pathname, types.SYS_ENTER_CREAT)
+ td.rawTracepoints = append(td.rawTracepoints, enterEvBytes)
+
+ exitEv, exitEvBytes := makeExitRetEvent(t, defaulTime+100, defaultPid, defaultTid, types.SYS_EXIT_CREAT, -1) // EACCES etc.
+ td.rawTracepoints = append(td.rawTracepoints, exitEvBytes)
+
+ td.validates = append(td.validates, func(t *testing.T, el *eventLoop, ep *event.Pair) {
+ if !enterEv.Equals(ep.EnterEv) {
+ t.Errorf("Expected '%v' but got '%v'", enterEv, ep.EnterEv)
+ }
+ if !exitEv.Equals(ep.ExitEv) {
+ t.Errorf("Expected '%v' but got '%v'", exitEv, ep.ExitEv)
+ }
+ // No fd was returned, so nothing must be registered. -1 is not a valid
+ // fd key; assert the path-only file carries the name instead.
+ verifyFdNotTracked(t, el, -1)
+ if ep.File == nil {
+ t.Fatalf("Expected path file to be set for failed creat")
+ }
+ if ep.File.Name() != pathname {
+ t.Errorf("failed creat path = %q, want %q", ep.File.Name(), pathname)
}
})