From 47f4a6bc360e889930199a9dc9ba41a85de66211 Mon Sep 17 00:00:00 2001 From: freref <35976402+freref@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:32:56 +0200 Subject: [PATCH 1/5] fix: linux watcher --- src/watchers/linux.zig | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index 82774cb..64c9208 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -73,18 +73,16 @@ pub const LinuxWatcher = struct { if (self.paths.items.len == 0) return error.NoFilesToWatch; self.running = true; - var buffer: [4096]std.os.linux.inotify_event = undefined; + var buffer: [65536]u8 = undefined; while (self.running) { const length = std.posix.read( self.inotify.fd, - std.mem.sliceAsBytes(&buffer), + &buffer, ) catch |err| switch (err) { error.WouldBlock => { - std.Thread.sleep(@as(u64, @intFromFloat(@as(f64, opts.latency) * @as( - f64, - @floatFromInt(std.time.ns_per_s), - )))); + std.Thread.sleep(@as(u64, @intFromFloat(@as(f64, opts.latency) * + @as(f64, @floatFromInt(std.time.ns_per_s))))); continue; }, else => { @@ -95,22 +93,40 @@ pub const LinuxWatcher = struct { // in bytes var i: usize = 0; while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { - const ev = buffer[i]; + if (i + @sizeOf(std.os.linux.inotify_event) > length) break; + + const ev_ptr: *align(1) std.os.linux.inotify_event = + @ptrCast(buffer[i..][0..@sizeOf(std.os.linux.inotify_event)].ptr); + const ev = ev_ptr.*; + + const step = @sizeOf(std.os.linux.inotify_event) + ev.len; + + if (i + step > length) break; if (ev.wd < self.inotify.offset) { + i += step; continue; - } else if (ev.wd > self.paths.items.len + self.inotify.offset) + } else if (ev.wd > self.paths.items.len + self.inotify.offset) { return error.InvalidWatchDescriptor; + } - if (ev.mask & std.os.linux.IN.IGNORED == 0 and ev.mask & std.os.linux.IN.MODIFY == 0) + if ((ev.mask & std.os.linux.IN.IGNORED == 0) and + (ev.mask & std.os.linux.IN.MODIFY == 0)) + { + i += step; continue; + } const index = @as(usize, @intCast(@max(0, ev.wd))) - self.inotify.offset; // Editors like vim create temporary files when saving // So we have to re-add the file to the watcher - if (ev.mask & std.os.linux.IN.IGNORED != 0) + if (ev.mask & std.os.linux.IN.IGNORED != 0) { try self.addFile(self.paths.items[index]); + } + if (self.callback) |callback| callback(self.context, .modified); + + i += step; } } } From 8ed8703d05650602a9eb5fa0695c81792ee7aca8 Mon Sep 17 00:00:00 2001 From: freref <35976402+freref@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:35:35 +0200 Subject: [PATCH 2/5] fix: field access --- src/watchers/linux.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index 64c9208..3020a9e 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -92,7 +92,7 @@ pub const LinuxWatcher = struct { // in bytes var i: usize = 0; - while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { + while (i < length) { if (i + @sizeOf(std.os.linux.inotify_event) > length) break; const ev_ptr: *align(1) std.os.linux.inotify_event = From cad3157554215a8cab05e5a1fbcbc085c92dd71d Mon Sep 17 00:00:00 2001 From: freref <35976402+freref@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:32:35 +0200 Subject: [PATCH 3/5] fix: rough working version --- src/watchers/linux.zig | 52 ++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index 3020a9e..7dc8395 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -1,5 +1,6 @@ const std = @import("std"); const interfaces = @import("interfaces.zig"); +const log = std.log.scoped(.vaxis); pub const LinuxWatcher = struct { allocator: std.mem.Allocator, @@ -14,6 +15,7 @@ pub const LinuxWatcher = struct { running: bool, pub fn init(allocator: std.mem.Allocator) !LinuxWatcher { + log.debug("init watcher", .{}); const fd = try std.posix.inotify_init1(std.os.linux.IN.NONBLOCK); errdefer std.posix.close(fd); @@ -37,12 +39,15 @@ pub const LinuxWatcher = struct { } pub fn addFile(self: *LinuxWatcher, path: []const u8) !void { - _ = try std.posix.inotify_add_watch( + try std.fs.cwd().access(path, .{}); + log.debug("file added to watcher", .{}); + const wd = try std.posix.inotify_add_watch( self.inotify.fd, path, - std.os.linux.IN.MODIFY, + std.os.linux.IN.MODIFY | std.os.linux.IN.CLOSE_WRITE | std.os.linux.IN.ATTRIB | std.os.linux.IN.MOVE_SELF | std.os.linux.IN.DELETE_SELF, ); + log.debug("added watch: wd={} for {s}", .{ wd, path }); try self.paths.append(self.allocator, path); } @@ -70,19 +75,23 @@ pub const LinuxWatcher = struct { pub fn start(self: *LinuxWatcher, opts: interfaces.Opts) !void { // TODO add polling instead of busy waiting + log.debug("start executed in the watcher", .{}); if (self.paths.items.len == 0) return error.NoFilesToWatch; + log.debug("there are files to watch", .{}); self.running = true; - var buffer: [65536]u8 = undefined; + var buffer: [4096]std.os.linux.inotify_event = undefined; while (self.running) { const length = std.posix.read( self.inotify.fd, - &buffer, + std.mem.sliceAsBytes(&buffer), ) catch |err| switch (err) { error.WouldBlock => { - std.Thread.sleep(@as(u64, @intFromFloat(@as(f64, opts.latency) * - @as(f64, @floatFromInt(std.time.ns_per_s))))); + std.Thread.sleep(@as(u64, @intFromFloat(@as(f64, opts.latency) * @as( + f64, + @floatFromInt(std.time.ns_per_s), + )))); continue; }, else => { @@ -90,43 +99,26 @@ pub const LinuxWatcher = struct { }, }; + log.debug("read {} bytes from inotify", .{length}); + // in bytes var i: usize = 0; - while (i < length) { - if (i + @sizeOf(std.os.linux.inotify_event) > length) break; - - const ev_ptr: *align(1) std.os.linux.inotify_event = - @ptrCast(buffer[i..][0..@sizeOf(std.os.linux.inotify_event)].ptr); - const ev = ev_ptr.*; - - const step = @sizeOf(std.os.linux.inotify_event) + ev.len; - - if (i + step > length) break; + while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { + const ev = buffer[i]; if (ev.wd < self.inotify.offset) { - i += step; continue; - } else if (ev.wd > self.paths.items.len + self.inotify.offset) { + } else if (ev.wd > self.paths.items.len + self.inotify.offset) return error.InvalidWatchDescriptor; - } - - if ((ev.mask & std.os.linux.IN.IGNORED == 0) and - (ev.mask & std.os.linux.IN.MODIFY == 0)) - { - i += step; - continue; - } const index = @as(usize, @intCast(@max(0, ev.wd))) - self.inotify.offset; // Editors like vim create temporary files when saving // So we have to re-add the file to the watcher - if (ev.mask & std.os.linux.IN.IGNORED != 0) { + if (ev.mask & (std.os.linux.IN.DELETE_SELF | std.os.linux.IN.MOVE_SELF | std.os.linux.IN.IGNORED) != 0) { try self.addFile(self.paths.items[index]); } - + log.debug("event: wd={}, mask={}", .{ ev.wd, ev.mask }); if (self.callback) |callback| callback(self.context, .modified); - - i += step; } } } From 7c35f162fb391525d7c7beb13e5e392fa7a92475 Mon Sep 17 00:00:00 2001 From: freref <35976402+freref@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:06:34 +0200 Subject: [PATCH 4/5] feat: custom path examples --- README.md | 2 +- examples/basic.zig | 11 ++++++++++- examples/context.zig | 16 +++++++++++++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c627200..e621735 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A lightweight and cross-platform file watcher for your Zig projects. You can run the [examples](./examples/) like so: ```sh -zig build run- +zig build run- -- ``` ### Usage diff --git a/examples/basic.zig b/examples/basic.zig index 02f2917..0c78c01 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -15,10 +15,19 @@ fn watcherThread(watcher: *fzwatch.Watcher) !void { pub fn main() !void { const allocator = std.heap.page_allocator; + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + + if (args.len < 2) { + std.debug.print("Usage: {s} \n", .{args[0]}); + return error.InvalidArgument; + } + const target_file = args[1]; + var watcher = try fzwatch.Watcher.init(allocator); defer watcher.deinit(); - try watcher.addFile("README.md"); + try watcher.addFile(target_file); watcher.setCallback(callback, null); const thread = try std.Thread.spawn(.{}, watcherThread, .{&watcher}); diff --git a/examples/context.zig b/examples/context.zig index 9e8e42d..c4a29b8 100644 --- a/examples/context.zig +++ b/examples/context.zig @@ -17,9 +17,9 @@ const Object = struct { } } - pub fn init(allocator: std.mem.Allocator) !Object { + pub fn init(allocator: std.mem.Allocator, target_file: [:0]u8) !Object { var watcher = try fzwatch.Watcher.init(allocator); - try watcher.addFile("README.md"); + try watcher.addFile(target_file); return Object{ .allocator = allocator, @@ -45,7 +45,17 @@ const Object = struct { pub fn main() !void { const allocator = std.heap.page_allocator; - var obj = try Object.init(allocator); + + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + + if (args.len < 2) { + std.debug.print("Usage: {s} \n", .{args[0]}); + return error.InvalidArgument; + } + const target_file = args[1]; + + var obj = try Object.init(allocator, target_file); defer obj.deinit(); try obj.start(); From cde3d15615d8d5e83d0b561d6ebf1a0038112ea9 Mon Sep 17 00:00:00 2001 From: freref <35976402+freref@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:04:09 +0200 Subject: [PATCH 5/5] cleanup: remove logs --- src/watchers/linux.zig | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/watchers/linux.zig b/src/watchers/linux.zig index 7dc8395..2dc646c 100644 --- a/src/watchers/linux.zig +++ b/src/watchers/linux.zig @@ -1,6 +1,5 @@ const std = @import("std"); const interfaces = @import("interfaces.zig"); -const log = std.log.scoped(.vaxis); pub const LinuxWatcher = struct { allocator: std.mem.Allocator, @@ -15,7 +14,6 @@ pub const LinuxWatcher = struct { running: bool, pub fn init(allocator: std.mem.Allocator) !LinuxWatcher { - log.debug("init watcher", .{}); const fd = try std.posix.inotify_init1(std.os.linux.IN.NONBLOCK); errdefer std.posix.close(fd); @@ -40,14 +38,12 @@ pub const LinuxWatcher = struct { pub fn addFile(self: *LinuxWatcher, path: []const u8) !void { try std.fs.cwd().access(path, .{}); - log.debug("file added to watcher", .{}); - const wd = try std.posix.inotify_add_watch( + _ = try std.posix.inotify_add_watch( self.inotify.fd, path, std.os.linux.IN.MODIFY | std.os.linux.IN.CLOSE_WRITE | std.os.linux.IN.ATTRIB | std.os.linux.IN.MOVE_SELF | std.os.linux.IN.DELETE_SELF, ); - log.debug("added watch: wd={} for {s}", .{ wd, path }); try self.paths.append(self.allocator, path); } @@ -75,9 +71,7 @@ pub const LinuxWatcher = struct { pub fn start(self: *LinuxWatcher, opts: interfaces.Opts) !void { // TODO add polling instead of busy waiting - log.debug("start executed in the watcher", .{}); if (self.paths.items.len == 0) return error.NoFilesToWatch; - log.debug("there are files to watch", .{}); self.running = true; var buffer: [4096]std.os.linux.inotify_event = undefined; @@ -99,8 +93,6 @@ pub const LinuxWatcher = struct { }, }; - log.debug("read {} bytes from inotify", .{length}); - // in bytes var i: usize = 0; while (i < length) : (i += buffer[i].len + @sizeOf(std.os.linux.inotify_event)) { @@ -117,7 +109,6 @@ pub const LinuxWatcher = struct { if (ev.mask & (std.os.linux.IN.DELETE_SELF | std.os.linux.IN.MOVE_SELF | std.os.linux.IN.IGNORED) != 0) { try self.addFile(self.paths.items[index]); } - log.debug("event: wd={}, mask={}", .{ ev.wd, ev.mask }); if (self.callback) |callback| callback(self.context, .modified); } }