diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-30 10:55:45 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-30 10:55:45 +0300 |
| commit | 271cda4e5b478a9f51ac98544e34de768a7e69ae (patch) | |
| tree | 448e920cbfc2501d4a68efc871477a7e9959247a | |
| parent | ee3a7106a724dc193589ab652ef21503ba291562 (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>
| -rw-r--r-- | internal/eventloop_exit.go | 7 | ||||
| -rw-r--r-- | internal/eventloop_test.go | 59 |
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) } }) |
