Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/tail/mod.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ test {
_ = @import("read_scheduler/common.zig");
_ = @import("read_scheduler/poll.zig");
_ = @import("watch_backend/poll.zig");
_ = @import("watch_backend/uring_linux.zig");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium tail/mod.zig:35

Registering watch_backend/uring_linux.zig makes its regression test fail the entire Linux suite when IoUring.init returns error.PermissionDenied or error.SystemOutdated under seccomp or older kernels. The test must skip these unavailable-environment errors instead of propagating them.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/tail/mod.zig around line 35:

Registering `watch_backend/uring_linux.zig` makes its regression test fail the entire Linux suite when `IoUring.init` returns `error.PermissionDenied` or `error.SystemOutdated` under seccomp or older kernels. The test must skip these unavailable-environment errors instead of propagating them.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 2f45578.

_ = @import("watch_backend/kqueue_macos.zig");
}
30 changes: 29 additions & 1 deletion src/tail/watch_backend/kqueue_macos.zig
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ fn ensureDirectoryWatch(kq: *State, path: []const u8) !void {
errdefer kq.allocator.free(owned_dir_path);

const dir = std.Io.Dir.cwd().openDir(kq.io, owned_dir_path, .{}) catch |err| switch (err) {
error.FileNotFound => return,
error.FileNotFound => {
kq.allocator.free(owned_dir_path);
return;
},
else => return err,
};
const dir_fd = dir.handle;
Expand All @@ -170,6 +173,7 @@ fn ensureDirectoryWatch(kq: *State, path: []const u8) !void {
}};
if (kevent(kq.fd, changes[0..], &.{}, null) < 0) {
closeFd(dir_fd);
kq.allocator.free(owned_dir_path);
return;
}
try kq.dir_watches.append(kq.allocator, .{ .path = owned_dir_path, .fd = dir_fd });
Expand Down Expand Up @@ -198,3 +202,27 @@ fn markTrackedInDirDirty(self: anytype, dir_path: []const u8) void {
}
}
}

const testing = std.testing;

test "ensureDirectoryWatch does not leak when parent directory is missing" {
if (comptime builtin.os.tag != .macos) return;
var kq = try init(std.testing.allocator, std.Options.debug_io);
defer deinit(&kq);

var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const root_abs = try tmp.dir.realPathFileAlloc(std.Options.debug_io, ".", testing.allocator);
defer testing.allocator.free(root_abs);
const target = try std.fmt.allocPrint(
testing.allocator,
"{s}/nonexistent_subdir_kqueue_leak/tail.log",
.{root_abs},
);
defer testing.allocator.free(target);

var i: usize = 0;
while (i < 50) : (i += 1) {
try ensureDirectoryWatch(&kq, target);
}
}
36 changes: 33 additions & 3 deletions src/tail/watch_backend/uring_linux.zig
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ fn ensureDirectoryWatch(u: *State, path: []const u8) !void {
const owned_dir_path = try u.allocator.dupe(u8, dir_path);
errdefer u.allocator.free(owned_dir_path);

const wd = inotifyAddWatch(u.fd, owned_dir_path, INOTIFY_MASK) catch return;
const wd = inotifyAddWatch(u.fd, owned_dir_path, INOTIFY_MASK) catch {
u.allocator.free(owned_dir_path);
return;
};
errdefer inotifyRmWatch(u.fd, wd);

try u.dir_path_to_wd.put(owned_dir_path, wd);
Expand Down Expand Up @@ -207,7 +210,7 @@ fn markTrackedInDirDirty(self: anytype, dir_path: []const u8) void {
// inotify was removed from std.posix in Zig 0.16; wrap std.os.linux directly.
fn inotifyInit1(flags: u32) !std.posix.fd_t {
const rc = std.os.linux.inotify_init1(flags);
return switch (std.posix.errno(rc)) {
return switch (std.os.linux.errno(rc)) {
.SUCCESS => @intCast(rc),
.INVAL => error.InvalidArgument,
.MFILE => error.ProcessFdQuotaExceeded,
Expand All @@ -220,7 +223,7 @@ fn inotifyInit1(flags: u32) !std.posix.fd_t {
fn inotifyAddWatch(fd: std.posix.fd_t, path: []const u8, mask: u32) !i32 {
const path_c = try std.posix.toPosixPath(path);
const rc = std.os.linux.inotify_add_watch(fd, &path_c, mask);
return switch (std.posix.errno(rc)) {
return switch (std.os.linux.errno(rc)) {
.SUCCESS => @intCast(rc),
.ACCES => error.AccessDenied,
.NOENT => error.FileNotFound,
Expand All @@ -234,3 +237,30 @@ fn inotifyAddWatch(fd: std.posix.fd_t, path: []const u8, mask: u32) !i32 {
fn inotifyRmWatch(fd: std.posix.fd_t, wd: i32) void {
_ = std.os.linux.inotify_rm_watch(fd, wd);
}

const testing = std.testing;

test "ensureDirectoryWatch does not leak when parent directory is missing" {
if (comptime builtin.os.tag != .linux) return;
var u = init(std.testing.allocator, std.Options.debug_io) catch |err| switch (err) {
error.PermissionDenied, error.SystemOutdated => return error.SkipZigTest,
else => return err,
};
defer deinit(&u);

var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const root_abs = try tmp.dir.realPathFileAlloc(std.Options.debug_io, ".", testing.allocator);
defer testing.allocator.free(root_abs);
const target = try std.fmt.allocPrint(
testing.allocator,
"{s}/nonexistent_subdir_uring_leak/tail.log",
.{root_abs},
);
defer testing.allocator.free(target);

var i: usize = 0;
while (i < 50) : (i += 1) {
try ensureDirectoryWatch(&u, target);
}
}
Loading