diff --git a/src/backend/KQueue.zig b/src/backend/KQueue.zig index 47a93f7..1e94438 100644 --- a/src/backend/KQueue.zig +++ b/src/backend/KQueue.zig @@ -119,32 +119,49 @@ fn thread_fn(self: *@This(), allocator: std.mem.Allocator) void { const fd: std.posix.fd_t = @intCast(ev.ident); // Check if this is a file watch: NOTE_WRITE/NOTE_EXTEND → modified. + // Copy the path under the lock so a concurrent remove_watch cannot + // free it before we finish using it. + var file_path_buf: [std.fs.max_path_bytes]u8 = undefined; + var file_path_len: usize = 0; self.file_watches_mutex.lock(); var fwit = self.file_watches.iterator(); - const file_path: ?[]const u8 = while (fwit.next()) |entry| { - if (entry.value_ptr.* == fd) break entry.key_ptr.*; - } else null; + while (fwit.next()) |entry| { + if (entry.value_ptr.* == fd) { + @memcpy(file_path_buf[0..entry.key_ptr.*.len], entry.key_ptr.*); + file_path_len = entry.key_ptr.*.len; + break; + } + } self.file_watches_mutex.unlock(); - if (file_path) |fp| { + if (file_path_len > 0) { + const fp = file_path_buf[0..file_path_len]; if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND) != 0) self.handler.change(fp, EventType.modified, .file) catch return; continue; } // Otherwise look up the directory path for this fd. + // Same copy-under-lock pattern. + var dir_path_buf: [std.fs.max_path_bytes]u8 = undefined; + var dir_path_len: usize = 0; self.watches_mutex.lock(); var wit = self.watches.iterator(); - const dir_path: ?[]const u8 = while (wit.next()) |entry| { - if (entry.value_ptr.* == fd) break entry.key_ptr.*; - } else null; + while (wit.next()) |entry| { + if (entry.value_ptr.* == fd) { + @memcpy(dir_path_buf[0..entry.key_ptr.*.len], entry.key_ptr.*); + dir_path_len = entry.key_ptr.*.len; + break; + } + } self.watches_mutex.unlock(); - if (dir_path == null) continue; + if (dir_path_len == 0) continue; + const dir_path = dir_path_buf[0..dir_path_len]; if (ev.fflags & NOTE_DELETE != 0) { - self.handler.change(dir_path.?, EventType.deleted, .dir) catch return; + self.handler.change(dir_path, EventType.deleted, .dir) catch return; } else if (ev.fflags & NOTE_RENAME != 0) { - self.handler.change(dir_path.?, EventType.renamed, .dir) catch return; + self.handler.change(dir_path, EventType.renamed, .dir) catch return; } else if (ev.fflags & NOTE_WRITE != 0) { - self.scan_dir(allocator, dir_path.?) catch {}; + self.scan_dir(allocator, dir_path) catch {}; } } } diff --git a/src/backend/KQueueDir.zig b/src/backend/KQueueDir.zig index 94803c5..e859fe4 100644 --- a/src/backend/KQueueDir.zig +++ b/src/backend/KQueueDir.zig @@ -103,35 +103,40 @@ fn thread_fn(self: *@This(), allocator: std.mem.Allocator) void { if (ev.filter != EVFILT_VNODE) continue; const fd: std.posix.fd_t = @intCast(ev.ident); + // Copy the path under the lock so a concurrent remove_watch cannot + // free it before we finish using it. + var watch_path_buf: [std.fs.max_path_bytes]u8 = undefined; + var watch_path_len: usize = 0; + var is_file: bool = false; self.watches_mutex.lock(); var wit = self.watches.iterator(); - var watch_path: ?[]const u8 = null; - var is_file: bool = false; while (wit.next()) |entry| { if (entry.value_ptr.*.fd == fd) { - watch_path = entry.key_ptr.*; + @memcpy(watch_path_buf[0..entry.key_ptr.*.len], entry.key_ptr.*); + watch_path_len = entry.key_ptr.*.len; is_file = entry.value_ptr.*.is_file; break; } } self.watches_mutex.unlock(); - if (watch_path == null) continue; + if (watch_path_len == 0) continue; + const watch_path = watch_path_buf[0..watch_path_len]; if (is_file) { // Explicit file watch: emit events with .file type directly. if (ev.fflags & NOTE_DELETE != 0) { - self.handler.change(watch_path.?, EventType.deleted, .file) catch return; + self.handler.change(watch_path, EventType.deleted, .file) catch return; } else if (ev.fflags & NOTE_RENAME != 0) { - self.handler.change(watch_path.?, EventType.renamed, .file) catch return; + self.handler.change(watch_path, EventType.renamed, .file) catch return; } else if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND) != 0) { - self.handler.change(watch_path.?, EventType.modified, .file) catch return; + self.handler.change(watch_path, EventType.modified, .file) catch return; } } else { if (ev.fflags & NOTE_DELETE != 0) { - self.handler.change(watch_path.?, EventType.deleted, .dir) catch return; + self.handler.change(watch_path, EventType.deleted, .dir) catch return; } else if (ev.fflags & NOTE_RENAME != 0) { - self.handler.change(watch_path.?, EventType.renamed, .dir) catch return; + self.handler.change(watch_path, EventType.renamed, .dir) catch return; } else if (ev.fflags & NOTE_WRITE != 0) { - self.scan_dir(allocator, watch_path.?) catch {}; + self.scan_dir(allocator, watch_path) catch {}; } } }