add documentation to watch

This commit is contained in:
Emil Lerch 2023-05-09 10:08:23 -07:00
parent 5e09b735c6
commit 1dd2a49daf
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 69 additions and 36 deletions

View File

@ -10,8 +10,7 @@ const Self = @This();
fileChanged: *const fn (usize) void,
inotify_fd: ?std.os.fd_t = null,
// sizeof(std.os.pollfd) == 8, so this is 8k
// fds: [MAX_FDS]std.os.pollfd = [_]std.os.pollfd{.{ .fd = 0, .events = 0, .revents = 0 }} ** MAX_FDS,
nfds_t: usize = 0,
wds: [MAX_FDS]i32 = [_]i32{0} ** MAX_FDS,
modified: [MAX_FDS]bool = [_]bool{false} ** MAX_FDS,
@ -35,7 +34,15 @@ pub fn deinit(self: *Self) void {
}
}
pub fn watchFds(self: *Self) void {
/// starts the file watch. This function will not return, so it is best
/// to put this function in its own thread:
///
/// const watcher_thread = try std.Thread.spawn(.{}, Watch.startWatch, .{&watcher});
///
/// Due to the nature of the poll(), behavior will almost definitely not work
/// well if files are added after the watch begins. A method for doing this
/// is intended later
pub fn startWatch(self: *Self) void {
while (true) {
if (self.nfds_t == 0) {
std.time.sleep(250);
@ -53,6 +60,9 @@ pub fn watchFds(self: *Self) void {
fds,
-1, // Infinite timeout
) catch @panic("poll error")) > 0) {
// fds[0] is inotify, so if we have data in that file descriptor,
// we can force the data into an inotify_event structure and act on it
if (fds[0].revents & c.POLLIN == c.POLLIN) { // POLLIN means "there is data to read"
var event_buf: [4096]u8 align(@alignOf(std.os.linux.inotify_event)) = undefined;
// "borrowed" from https://ziglang.org/documentation/master/std/src/std/fs/watch.zig.html#L588
@ -66,32 +76,7 @@ pub fn watchFds(self: *Self) void {
@alignCast(@alignOf(*const std.os.linux.inotify_event), ptr),
);
if (ev.mask & std.os.linux.IN.MODIFY == std.os.linux.IN.MODIFY) {
for (self.wds, 0..) |wd, inx| {
if (ev.wd == wd)
self.modified[inx] = true;
}
}
// attrib added as build process moves in place and modifies attributes
// TODO: Also watch MOVED_TO, which is on the directory...
if (ev.mask & std.os.linux.IN.CLOSE_WRITE == std.os.linux.IN.CLOSE_WRITE or
ev.mask & std.os.linux.IN.ATTRIB == std.os.linux.IN.ATTRIB)
{
for (self.wds, 0..) |wd, inx| {
if (ev.wd == wd)
self.fileChanged(inx);
}
}
if (ev.mask & std.os.linux.IN.CLOSE_NOWRITE == std.os.linux.IN.CLOSE_NOWRITE) {
for (self.wds, 0..) |wd, inx| {
if (ev.wd == wd and self.modified[inx]) {
self.modified[inx] = false;
self.fileChanged(inx);
}
}
}
// see man 2 poll
self.handleFile(fds[0]);
// Read next event from inotify
ptr = @alignCast(
@alignOf(std.os.linux.inotify_event),
ptr + @sizeOf(std.os.linux.inotify_event) + ev.len,
@ -102,13 +87,66 @@ pub fn watchFds(self: *Self) void {
}
}
/// This will determine whether the inotify event indicates an actionable
/// change to the file, and if so, will call self.fileChanged
fn processInotifyEvent(self: *Self, ev: *const std.os.linux.inotify_event) void {
// If the file was modified, it is good to know, but not
// actionable at this time. We can set a modification flag
// for later use. This flag and process may be unnecessary...
// how can we have a modify followed by CLOSE_NOWRITE?
//
// TODO: Delete the following
if (ev.mask & std.os.linux.IN.MODIFY == std.os.linux.IN.MODIFY) {
for (self.wds, 0..) |wd, inx| {
if (ev.wd == wd)
self.modified[inx] = true;
}
}
if (ev.mask & std.os.linux.IN.CLOSE_NOWRITE == std.os.linux.IN.CLOSE_NOWRITE) {
for (self.wds, 0..) |wd, inx| {
if (ev.wd == wd and self.modified[inx]) {
self.modified[inx] = false;
self.fileChanged(inx);
}
}
}
// There's a couple ways a file can be modified. The simplest
// way is to write(), then close(). For a variety of reasons
// due to safety, a lot of programs will write some temporary
// file, then copy or move it in place. This will fail to
// trigger IN_CLOSE_WRITE, so we need to detect it another
// way. The best is to watch for events on the parent directory
// to find move events. Note that using copy will trigger
// a IN_CLOSE_WRITE. Without building directory watching in,
// we can use IN_ATTRIB to satisfy the `zig build` use case,
// which modifies attributes after moving the file.
//
// THIS WILL NOT WORK in the generic sense, and ultimately
// we're going to have to watch the directory as well
// attrib added as build process moves in place and modifies attributes
//
// TODO: Also watch MOVED_TO, which is on the directory...
if (ev.mask & std.os.linux.IN.CLOSE_WRITE == std.os.linux.IN.CLOSE_WRITE or
ev.mask & std.os.linux.IN.ATTRIB == std.os.linux.IN.ATTRIB)
{
for (self.wds, 0..) |wd, inx| {
if (ev.wd == wd)
self.fileChanged(inx);
}
}
}
/// adds a file to watch. The return will be a handle that will be returned
/// in the fileChanged event triffered from startWatch
pub fn addFileWatch(self: *Self, path: [:0]const u8) !usize {
self.inotify_fd = self.inotify_fd orelse try std.os.inotify_init1(std.os.linux.IN.NONBLOCK);
errdefer {
std.os.close(self.inotify_fd.?);
self.inotify_fd = null;
}
// open 20, close_norite 10, attrib 4
// zig build modification pattern: open 20, close_nowrite 10, MOVED_TO (on the directory), attrib 4
// unix cp: OPEN, MODIFY, CLOSE_WRITE, ATTRIB
// unix mv: MOVED_TO (on the directory)
self.wds[self.nfds_t] = try std.os.inotify_add_watchZ(
self.inotify_fd.?,
path,
@ -119,8 +157,3 @@ pub fn addFileWatch(self: *Self, path: [:0]const u8) !usize {
self.nfds_t += 1;
return self.nfds_t - 1;
}
fn handleFile(self: Self, fd: std.os.pollfd) void {
_ = fd;
_ = self;
}

View File

@ -135,7 +135,7 @@ pub fn main() !void {
try stdout.print("Run `zig build test` to run the tests.\n", .{});
try bw.flush(); // don't forget to flush!
const watcher_thread = try std.Thread.spawn(.{}, Watch.watchFds, .{&watcher});
const watcher_thread = try std.Thread.spawn(.{}, Watch.startWatch, .{&watcher});
while (true) {
std.time.sleep(std.time.ns_per_s * 2);