fix(inotify): report .closed events separately from .modified events

This commit is contained in:
CJ van den Berg 2026-03-29 14:28:23 +02:00
parent 95c7580a87
commit 99dec3f689
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
9 changed files with 59 additions and 1 deletions

View file

@ -6,6 +6,7 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = true; // FSEventStreamCreate watches the entire subtree
pub const detects_file_modifications = true;
pub const emits_close_events = false;
handler: *Handler,
stream: ?*anyopaque, // FSEventStreamRef

View file

@ -35,6 +35,7 @@ pub fn Create(comptime variant: InterfaceType) type {
pub const watches_recursively = false;
pub const detects_file_modifications = true;
pub const emits_close_events = true;
pub const polling = variant == .polling;
const WatchEntry = struct { path: []u8, is_dir: bool };
@ -342,8 +343,10 @@ pub fn Create(comptime variant: InterfaceType) type {
if (is_dir and self.has_watch_for_path(full_path))
continue;
break :blk .deleted;
} else if (ev.mask & (IN.MODIFY | IN.CLOSE_WRITE) != 0)
} else if (ev.mask & IN.MODIFY != 0)
.modified
else if (ev.mask & IN.CLOSE_WRITE != 0)
.closed
else
continue;
try self.handler.change(full_path, event_type, object_type);

View file

@ -6,6 +6,7 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = false;
pub const detects_file_modifications = true;
pub const emits_close_events = false;
handler: *Handler,
kq: std.posix.fd_t,

View file

@ -6,6 +6,7 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = false;
pub const detects_file_modifications = false;
pub const emits_close_events = false;
pub const WatchEntry = struct { fd: std.posix.fd_t, is_file: bool };
handler: *Handler,

View file

@ -6,6 +6,7 @@ const ObjectType = types.ObjectType;
pub const watches_recursively = true; // ReadDirectoryChangesW with bWatchSubtree=1
pub const detects_file_modifications = true;
pub const emits_close_events = false;
const windows = std.os.windows;

View file

@ -50,12 +50,14 @@ const CliHandler = struct {
const color: std.io.tty.Color = switch (event_type) {
.created => .green,
.modified => .blue,
.closed => .bright_black,
.deleted => .red,
.renamed => .magenta,
};
const event_label = switch (event_type) {
.created => "create ",
.modified => "modify ",
.closed => "close ",
.deleted => "delete ",
.renamed => "rename ",
};
@ -160,6 +162,7 @@ fn usage(out: std.fs.File) !void {
\\Events printed to stdout (columns: event type path):
\\ create a file or directory was created
\\ modify a file was modified
\\ close a file was closed after writing (Linux only)
\\ delete a file or directory was deleted
\\ rename a file or directory was renamed
\\

View file

@ -114,6 +114,7 @@ pub fn Create(comptime variant: Variant) type {
/// `watch()` do receive per-file `NOTE_WRITE` events and will report
/// modifications.
pub const detects_file_modifications = Backend.detects_file_modifications;
pub const emits_close_events = Backend.emits_close_events;
/// Create a new watcher.
///

View file

@ -245,6 +245,42 @@ fn testModifyFile(comptime Watcher: type, allocator: std.mem.Allocator) !void {
try std.testing.expect(th.hasChange(file_path, .modified, .file));
}
fn testCloseFile(comptime Watcher: type, allocator: std.mem.Allocator) !void {
if (comptime !Watcher.emits_close_events) return error.SkipZigTest;
const TH = MakeTestHandler(Watcher);
const tmp = try makeTempDir(allocator);
defer {
removeTempDir(tmp);
allocator.free(tmp);
}
const th = try TH.init(allocator);
defer th.deinit();
const file_path = try std.fs.path.join(allocator, &.{ tmp, "data.txt" });
defer allocator.free(file_path);
{
const f = try std.fs.createFileAbsolute(file_path, .{});
f.close();
}
var watcher = try Watcher.init(allocator, &th.handler);
defer watcher.deinit();
try watcher.watch(tmp);
{
const f = try std.fs.openFileAbsolute(file_path, .{ .mode = .write_only });
defer f.close();
try f.writeAll("hello nightwatch\n");
}
try drainEvents(Watcher, &watcher);
try std.testing.expect(th.hasChange(file_path, .modified, .file));
try std.testing.expect(th.hasChange(file_path, .closed, .file));
}
fn testDeleteFile(comptime Watcher: type, allocator: std.mem.Allocator) !void {
const TH = MakeTestHandler(Watcher);
@ -578,6 +614,12 @@ test "writing to a file emits a 'modified' event" {
}
}
test "closing a file after writing emits a 'closed' event (inotify only)" {
inline for (comptime std.enums.values(nw.Variant)) |variant| {
try testCloseFile(nw.Create(variant), std.testing.allocator);
}
}
test "deleting a file emits a 'deleted' event" {
inline for (comptime std.enums.values(nw.Variant)) |variant| {
try testDeleteFile(nw.Create(variant), std.testing.allocator);

View file

@ -7,6 +7,11 @@ pub const EventType = enum {
created,
/// A file's contents were modified.
modified,
/// A file was closed.
///
/// Only delivered by INotfiy (Linux) and only if the file was opened
/// for writing.
closed,
/// A file or directory was deleted.
deleted,
/// A file or directory was renamed or moved.