File watching is platform-specific. The following examples wait for one real file-system event instead of polling modification times.
macOS and FreeBSD: kqueue
This example uses kqueue and EVFILT_VNODE to watch build.zig. Run it on macOS or FreeBSD, then modify or rename the file from another terminal.
const builtin = @import("builtin");
const std = @import("std");
pub fn main(init: std.process.Init) !void {
if (comptime builtin.os.tag == .macos or builtin.os.tag == .freebsd) {
try watchWithKqueue(init);
} else {
std.debug.print("This example requires macOS or a BSD system.\n", .{});
}
}
fn watchWithKqueue(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "build.zig", .{});
defer file.close(io);
const queue = std.c.kqueue();
if (queue < 0) return error.KqueueUnavailable;
defer std.Io.Threaded.closeFd(queue);
const change = std.c.Kevent{
.ident = @intCast(file.handle),
.filter = std.c.EVFILT.VNODE,
.flags = std.c.EV.ADD | std.c.EV.ENABLE | std.c.EV.CLEAR,
.fflags = std.c.NOTE.WRITE | std.c.NOTE.DELETE | std.c.NOTE.RENAME,
.data = 0,
.udata = 0,
};
var changes = [_]std.c.Kevent{change};
var events: [1]std.c.Kevent = undefined;
std.debug.print("Waiting for a change to build.zig...\n", .{});
const count = std.c.kevent(queue, &changes, 1, &events, 1, null);
if (count < 0) return error.KeventFailed;
if (count == 0) return error.NoEvent;
std.debug.print("build.zig changed (flags: 0x{x})\n", .{events[0].fflags});
}
Linux: inotify
This example uses inotify to watch the current directory. Run it on Linux, then create, modify, move, or delete a file from another terminal.
const builtin = @import("builtin");
const std = @import("std");
pub fn main(init: std.process.Init) !void {
_ = init;
if (comptime builtin.os.tag == .linux) {
try watchWithInotify();
} else {
std.debug.print("This example requires Linux.\n", .{});
}
}
fn watchWithInotify() !void {
const linux = std.os.linux;
const descriptor_result = linux.inotify_init1(linux.IN.CLOEXEC);
if (linux.errno(descriptor_result) != .SUCCESS) {
return std.posix.unexpectedErrno(linux.errno(descriptor_result));
}
const descriptor: std.posix.fd_t = @intCast(descriptor_result);
defer std.Io.Threaded.closeFd(descriptor);
const mask = linux.IN.MODIFY | linux.IN.CREATE | linux.IN.DELETE | linux.IN.MOVE;
const watch_result = linux.inotify_add_watch(descriptor, ".", mask);
if (linux.errno(watch_result) != .SUCCESS) {
return std.posix.unexpectedErrno(linux.errno(watch_result));
}
const watch: i32 = @intCast(watch_result);
var buffer: [4096]u8 align(@alignOf(linux.inotify_event)) = undefined;
std.debug.print("Waiting for a change in the current directory...\n", .{});
const bytes_read = try std.posix.read(descriptor, &buffer);
if (bytes_read < @sizeOf(linux.inotify_event)) return error.InvalidEvent;
const event: *const linux.inotify_event = @ptrCast(&buffer);
std.debug.print("directory event (mask: 0x{x})", .{event.mask});
if (event.getName()) |name| {
std.debug.print(": {s}", .{name});
}
std.debug.print("\n", .{});
const remove_result = linux.inotify_rm_watch(descriptor, watch);
if (linux.errno(remove_result) != .SUCCESS) {
return std.posix.unexpectedErrno(linux.errno(remove_result));
}
}