mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
Compare commits
2 Commits
claude/fix
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a93a85500 | ||
|
|
e59937428e |
@@ -93,7 +93,10 @@ pub fn init(comptime T: type, ctx: *T, fs: *bun.fs.FileSystem, allocator: std.me
|
||||
.changed_filepaths = [_]?[:0]u8{null} ** max_count,
|
||||
};
|
||||
|
||||
try Platform.init(&watcher.platform, fs.top_level_dir);
|
||||
Platform.init(&watcher.platform, fs.top_level_dir) catch |err| {
|
||||
allocator.destroy(watcher);
|
||||
return err;
|
||||
};
|
||||
|
||||
return watcher;
|
||||
}
|
||||
@@ -132,7 +135,7 @@ pub const max_eviction_count = 8096;
|
||||
// this file instead of the platform-specific file.
|
||||
// ideally, the constants above can be inlined
|
||||
const Platform = switch (Environment.os) {
|
||||
.linux => @import("./watcher/INotifyWatcher.zig"),
|
||||
.linux => @import("./watcher/FanotifyWatcher.zig"),
|
||||
.mac => @import("./watcher/KEventWatcher.zig"),
|
||||
.windows => WindowsWatcher,
|
||||
else => @compileError("Unsupported platform"),
|
||||
|
||||
@@ -115,16 +115,21 @@ pub const PathWatcherManager = struct {
|
||||
var watchers = bun.handleOom(bun.BabyList(?*PathWatcher).initCapacity(bun.default_allocator, 1));
|
||||
errdefer watchers.deinit(bun.default_allocator);
|
||||
|
||||
const main_watcher = Watcher.init(
|
||||
PathWatcherManager,
|
||||
this,
|
||||
vm.transpiler.fs,
|
||||
bun.default_allocator,
|
||||
) catch |err| {
|
||||
watchers.deinit(bun.default_allocator);
|
||||
return err;
|
||||
};
|
||||
|
||||
const manager = PathWatcherManager{
|
||||
.file_paths = bun.StringHashMap(PathInfo).init(bun.default_allocator),
|
||||
.current_fd_task = bun.FDHashMap(*DirectoryRegisterTask).init(bun.default_allocator),
|
||||
.watchers = watchers,
|
||||
.main_watcher = try Watcher.init(
|
||||
PathWatcherManager,
|
||||
this,
|
||||
vm.transpiler.fs,
|
||||
bun.default_allocator,
|
||||
),
|
||||
.main_watcher = main_watcher,
|
||||
.vm = vm,
|
||||
.watcher_count = 0,
|
||||
.mutex = .{},
|
||||
|
||||
@@ -301,6 +301,7 @@ pub const Tag = enum(u8) {
|
||||
|
||||
pub const Error = @import("./sys/Error.zig");
|
||||
pub const PosixStat = @import("./sys/PosixStat.zig").PosixStat;
|
||||
pub const fanotify = if (Environment.isLinux) @import("./sys/fanotify.zig") else struct {};
|
||||
|
||||
pub fn Maybe(comptime ReturnTypeT: type) type {
|
||||
return bun.api.node.Maybe(ReturnTypeT, Error);
|
||||
|
||||
228
src/sys/fanotify.zig
Normal file
228
src/sys/fanotify.zig
Normal file
@@ -0,0 +1,228 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("../bun.zig");
|
||||
const Maybe = bun.sys.Maybe;
|
||||
const linux = std.os.linux;
|
||||
const posix = std.posix;
|
||||
|
||||
/// fanotify_init flags
|
||||
pub const InitFlags = packed struct(u32) {
|
||||
/// Close-on-exec flag
|
||||
cloexec: bool = false,
|
||||
/// Non-blocking flag
|
||||
nonblock: bool = false,
|
||||
_padding1: u30 = 0,
|
||||
|
||||
pub fn toInt(self: InitFlags) u32 {
|
||||
var flags: u32 = 0;
|
||||
if (self.cloexec) flags |= FAN_CLOEXEC;
|
||||
if (self.nonblock) flags |= FAN_NONBLOCK;
|
||||
return flags | FAN_CLASS_NOTIF | FAN_UNLIMITED_QUEUE | FAN_UNLIMITED_MARKS;
|
||||
}
|
||||
};
|
||||
|
||||
/// fanotify event flags (open flags for the file descriptors)
|
||||
pub const EventFlags = packed struct(u32) {
|
||||
rdonly: bool = false,
|
||||
largefile: bool = false,
|
||||
cloexec: bool = false,
|
||||
_padding: u29 = 0,
|
||||
|
||||
pub fn toInt(self: EventFlags) u32 {
|
||||
var flags: u32 = 0;
|
||||
// RDONLY is 0, so we don't need to add it
|
||||
if (self.rdonly) flags |= 0;
|
||||
if (self.largefile) flags |= 0x8000; // O_LARGEFILE on linux
|
||||
if (self.cloexec) flags |= 0x80000; // O_CLOEXEC on linux
|
||||
return flags;
|
||||
}
|
||||
};
|
||||
|
||||
/// fanotify_mark flags
|
||||
pub const MarkFlags = enum(u32) {
|
||||
add = FAN_MARK_ADD,
|
||||
remove = FAN_MARK_REMOVE,
|
||||
flush = FAN_MARK_FLUSH,
|
||||
};
|
||||
|
||||
/// fanotify event mask
|
||||
pub const EventMask = packed struct(u64) {
|
||||
access: bool = false,
|
||||
modify: bool = false,
|
||||
close_write: bool = false,
|
||||
close_nowrite: bool = false,
|
||||
open: bool = false,
|
||||
open_exec: bool = false,
|
||||
attrib: bool = false,
|
||||
create: bool = false,
|
||||
delete: bool = false,
|
||||
delete_self: bool = false,
|
||||
moved_from: bool = false,
|
||||
moved_to: bool = false,
|
||||
move_self: bool = false,
|
||||
open_perm: bool = false,
|
||||
access_perm: bool = false,
|
||||
open_exec_perm: bool = false,
|
||||
_padding1: u14 = 0,
|
||||
ondir: bool = false,
|
||||
event_on_child: bool = false,
|
||||
_padding2: u32 = 0,
|
||||
|
||||
pub fn toInt(self: EventMask) u64 {
|
||||
var mask: u64 = 0;
|
||||
if (self.access) mask |= FAN_ACCESS;
|
||||
if (self.modify) mask |= FAN_MODIFY;
|
||||
if (self.close_write) mask |= FAN_CLOSE_WRITE;
|
||||
if (self.close_nowrite) mask |= FAN_CLOSE_NOWRITE;
|
||||
if (self.open) mask |= FAN_OPEN;
|
||||
if (self.open_exec) mask |= FAN_OPEN_EXEC;
|
||||
if (self.attrib) mask |= FAN_ATTRIB;
|
||||
if (self.create) mask |= FAN_CREATE;
|
||||
if (self.delete) mask |= FAN_DELETE;
|
||||
if (self.delete_self) mask |= FAN_DELETE_SELF;
|
||||
if (self.moved_from) mask |= FAN_MOVED_FROM;
|
||||
if (self.moved_to) mask |= FAN_MOVED_TO;
|
||||
if (self.move_self) mask |= FAN_MOVE_SELF;
|
||||
if (self.open_perm) mask |= FAN_OPEN_PERM;
|
||||
if (self.access_perm) mask |= FAN_ACCESS_PERM;
|
||||
if (self.open_exec_perm) mask |= FAN_OPEN_EXEC_PERM;
|
||||
if (self.ondir) mask |= FAN_ONDIR;
|
||||
if (self.event_on_child) mask |= FAN_EVENT_ON_CHILD;
|
||||
return mask;
|
||||
}
|
||||
};
|
||||
|
||||
// fanotify_init flags
|
||||
const FAN_CLOEXEC = 0x00000001;
|
||||
const FAN_NONBLOCK = 0x00000002;
|
||||
const FAN_CLASS_NOTIF = 0x00000000;
|
||||
const FAN_UNLIMITED_QUEUE = 0x00000010;
|
||||
const FAN_UNLIMITED_MARKS = 0x00000020;
|
||||
|
||||
// fanotify_mark flags
|
||||
const FAN_MARK_ADD = 0x00000001;
|
||||
const FAN_MARK_REMOVE = 0x00000002;
|
||||
const FAN_MARK_FLUSH = 0x00000080;
|
||||
|
||||
// fanotify events
|
||||
const FAN_ACCESS = 0x00000001;
|
||||
const FAN_MODIFY = 0x00000002;
|
||||
const FAN_CLOSE_WRITE = 0x00000008;
|
||||
const FAN_CLOSE_NOWRITE = 0x00000010;
|
||||
const FAN_OPEN = 0x00000020;
|
||||
const FAN_OPEN_EXEC = 0x00001000;
|
||||
const FAN_ATTRIB = 0x00000004;
|
||||
const FAN_CREATE = 0x00000100;
|
||||
const FAN_DELETE = 0x00000200;
|
||||
const FAN_DELETE_SELF = 0x00000400;
|
||||
const FAN_MOVED_FROM = 0x00000040;
|
||||
const FAN_MOVED_TO = 0x00000080;
|
||||
const FAN_MOVE_SELF = 0x00000800;
|
||||
const FAN_OPEN_PERM = 0x00010000;
|
||||
const FAN_ACCESS_PERM = 0x00020000;
|
||||
const FAN_OPEN_EXEC_PERM = 0x00040000;
|
||||
const FAN_ONDIR = 0x40000000;
|
||||
const FAN_EVENT_ON_CHILD = 0x08000000;
|
||||
|
||||
/// fanotify event metadata structure
|
||||
pub const EventMetadata = extern struct {
|
||||
event_len: u32,
|
||||
vers: u8,
|
||||
reserved: u8,
|
||||
metadata_len: u16,
|
||||
mask: u64,
|
||||
fd: i32,
|
||||
pid: i32,
|
||||
|
||||
pub fn size(self: *align(1) const EventMetadata) u32 {
|
||||
return self.event_len;
|
||||
}
|
||||
|
||||
pub fn isDir(self: *align(1) const EventMetadata) bool {
|
||||
return (self.mask & FAN_ONDIR) != 0;
|
||||
}
|
||||
|
||||
pub fn hasValidFd(self: *align(1) const EventMetadata) bool {
|
||||
return self.fd >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// Initialize fanotify
|
||||
pub fn init(flags: InitFlags, event_flags: EventFlags) Maybe(bun.FileDescriptor) {
|
||||
const rc = linux.syscall2(
|
||||
.fanotify_init,
|
||||
@as(usize, @intCast(flags.toInt())),
|
||||
@as(usize, @intCast(event_flags.toInt())),
|
||||
);
|
||||
|
||||
const errno = posix.errno(rc);
|
||||
if (errno != .SUCCESS) {
|
||||
return .{ .err = bun.sys.Error.fromCode(errno, .open) };
|
||||
}
|
||||
|
||||
// syscall returns usize, but file descriptors are i32
|
||||
// Cast to isize first to properly handle signed values
|
||||
const fd: std.posix.fd_t = @intCast(@as(isize, @bitCast(rc)));
|
||||
return .{ .result = bun.FileDescriptor.fromNative(fd) };
|
||||
}
|
||||
|
||||
/// Add or remove a mark on a filesystem object
|
||||
pub fn mark(
|
||||
fanotify_fd: bun.FileDescriptor,
|
||||
flags: MarkFlags,
|
||||
mask: EventMask,
|
||||
dirfd: bun.FileDescriptor,
|
||||
pathname: ?[:0]const u8,
|
||||
) Maybe(void) {
|
||||
const path_ptr = if (pathname) |p| @intFromPtr(p.ptr) else 0;
|
||||
const dfd: i32 = if (pathname == null) linux.AT.FDCWD else dirfd.cast();
|
||||
|
||||
const rc = linux.syscall5(
|
||||
.fanotify_mark,
|
||||
@as(usize, @bitCast(@as(isize, fanotify_fd.cast()))),
|
||||
@as(usize, @intCast(@intFromEnum(flags))),
|
||||
@as(usize, @intCast(mask.toInt())),
|
||||
@as(usize, @bitCast(@as(isize, dfd))),
|
||||
path_ptr,
|
||||
);
|
||||
|
||||
const errno = posix.errno(rc);
|
||||
if (errno != .SUCCESS) {
|
||||
return .{ .err = bun.sys.Error.fromCode(errno, .watch) };
|
||||
}
|
||||
|
||||
return .{ .result = {} };
|
||||
}
|
||||
|
||||
/// Read events from fanotify file descriptor
|
||||
pub fn readEvents(
|
||||
fanotify_fd: bun.FileDescriptor,
|
||||
buffer: []align(@alignOf(EventMetadata)) u8,
|
||||
) Maybe([]const u8) {
|
||||
const rc = linux.read(fanotify_fd.cast(), buffer.ptr, buffer.len);
|
||||
|
||||
const errno = posix.errno(rc);
|
||||
if (errno != .SUCCESS) {
|
||||
return .{ .err = bun.sys.Error.fromCode(errno, .read) };
|
||||
}
|
||||
|
||||
return .{ .result = buffer[0..@intCast(rc)] };
|
||||
}
|
||||
|
||||
/// Iterator for fanotify events
|
||||
pub const EventIterator = struct {
|
||||
buffer: []const u8,
|
||||
offset: usize = 0,
|
||||
|
||||
pub fn next(self: *EventIterator) ?*align(1) const EventMetadata {
|
||||
if (self.offset >= self.buffer.len) return null;
|
||||
|
||||
const event: *align(1) const EventMetadata = @ptrCast(@alignCast(self.buffer[self.offset..][0..@sizeOf(EventMetadata)].ptr));
|
||||
|
||||
self.offset += event.size();
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn reset(self: *EventIterator) void {
|
||||
self.offset = 0;
|
||||
}
|
||||
};
|
||||
313
src/watcher/FanotifyWatcher.zig
Normal file
313
src/watcher/FanotifyWatcher.zig
Normal file
@@ -0,0 +1,313 @@
|
||||
//! Bun's filesystem watcher implementation for linux using fanotify
|
||||
//! https://man7.org/linux/man-pages/man7/fanotify.7.html
|
||||
//!
|
||||
//! Fanotify provides filesystem-wide monitoring with recursive capabilities.
|
||||
//! Note: fanotify requires appropriate permissions (CAP_SYS_ADMIN or similar)
|
||||
|
||||
const FanotifyWatcher = @This();
|
||||
|
||||
const log = Output.scoped(.watcher, .visible);
|
||||
const fanotify = bun.sys.fanotify;
|
||||
|
||||
// fanotify events are variable-sized, so a byte buffer is used
|
||||
const eventlist_bytes_size = 4096 * 32; // 128KB buffer for events
|
||||
const EventListBytes = [eventlist_bytes_size]u8;
|
||||
|
||||
fd: bun.FileDescriptor = bun.invalid_fd,
|
||||
loaded: bool = false,
|
||||
|
||||
// Avoid statically allocating because it increases the binary size.
|
||||
eventlist_bytes: *EventListBytes = undefined,
|
||||
allocator: std.mem.Allocator = undefined,
|
||||
|
||||
/// Store root path being monitored
|
||||
root_path: []const u8 = "",
|
||||
|
||||
watch_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
|
||||
/// nanoseconds
|
||||
coalesce_interval: isize = 100_000,
|
||||
|
||||
pub const EventListIndex = i32;
|
||||
pub const Event = fanotify.EventMetadata;
|
||||
|
||||
pub fn watchPath(this: *FanotifyWatcher, pathname: [:0]const u8) bun.sys.Maybe(EventListIndex) {
|
||||
bun.assert(this.loaded);
|
||||
const old_count = this.watch_count.fetchAdd(1, .release);
|
||||
defer if (old_count == 0) Futex.wake(&this.watch_count, 10);
|
||||
|
||||
// For files, we watch for modifications and deletions
|
||||
const mask = fanotify.EventMask{
|
||||
.modify = true,
|
||||
.close_write = true,
|
||||
.delete_self = true,
|
||||
.move_self = true,
|
||||
.event_on_child = true,
|
||||
};
|
||||
|
||||
switch (fanotify.mark(this.fd, .add, mask, bun.invalid_fd, pathname)) {
|
||||
.err => |err| {
|
||||
log("fanotify_mark({}, file, {s}) failed: {}", .{ this.fd, pathname, err });
|
||||
return .{ .err = err };
|
||||
},
|
||||
.result => {},
|
||||
}
|
||||
|
||||
log("fanotify_mark({}, file, {s}) = success", .{ this.fd, pathname });
|
||||
|
||||
// Fanotify doesn't return per-path descriptors, just return 0
|
||||
return .{ .result = 0 };
|
||||
}
|
||||
|
||||
pub fn watchDir(this: *FanotifyWatcher, pathname: [:0]const u8) bun.sys.Maybe(EventListIndex) {
|
||||
bun.assert(this.loaded);
|
||||
const old_count = this.watch_count.fetchAdd(1, .release);
|
||||
defer if (old_count == 0) Futex.wake(&this.watch_count, 10);
|
||||
|
||||
// For directories, we watch for creates, deletes, and modifications
|
||||
// event_on_child makes this apply recursively to all children
|
||||
const mask = fanotify.EventMask{
|
||||
.create = true,
|
||||
.delete = true,
|
||||
.delete_self = true,
|
||||
.move_self = true,
|
||||
.moved_from = true,
|
||||
.moved_to = true,
|
||||
.modify = true,
|
||||
.close_write = true,
|
||||
.ondir = true,
|
||||
.event_on_child = true,
|
||||
};
|
||||
|
||||
switch (fanotify.mark(this.fd, .add, mask, bun.invalid_fd, pathname)) {
|
||||
.err => |err| {
|
||||
log("fanotify_mark({}, dir, {s}) failed: {}", .{ this.fd, pathname, err });
|
||||
return .{ .err = err };
|
||||
},
|
||||
.result => {},
|
||||
}
|
||||
|
||||
log("fanotify_mark({}, dir, {s}) = success", .{ this.fd, pathname });
|
||||
|
||||
// Fanotify doesn't return per-path descriptors, just return 0
|
||||
return .{ .result = 0 };
|
||||
}
|
||||
|
||||
pub fn unwatch(this: *FanotifyWatcher, _: EventListIndex) void {
|
||||
bun.assert(this.loaded);
|
||||
_ = this.watch_count.fetchSub(1, .release);
|
||||
}
|
||||
|
||||
pub fn init(this: *FanotifyWatcher, cwd: []const u8) !void {
|
||||
bun.assert(!this.loaded);
|
||||
this.loaded = true;
|
||||
|
||||
if (bun.getenvZ("BUN_FANOTIFY_COALESCE_INTERVAL")) |env| {
|
||||
this.coalesce_interval = std.fmt.parseInt(isize, env, 10) catch 100_000;
|
||||
}
|
||||
|
||||
// Initialize fanotify with notification class
|
||||
const init_flags = fanotify.InitFlags{
|
||||
.cloexec = true,
|
||||
.nonblock = false,
|
||||
};
|
||||
const event_flags = fanotify.EventFlags{
|
||||
.rdonly = true,
|
||||
.largefile = true,
|
||||
.cloexec = true,
|
||||
};
|
||||
|
||||
switch (fanotify.init(init_flags, event_flags)) {
|
||||
.err => |err| {
|
||||
log("fanotify_init failed: {}", .{err});
|
||||
// Return Unexpected to match the error set that callers expect
|
||||
return error.Unexpected;
|
||||
},
|
||||
.result => |fd| this.fd = fd,
|
||||
}
|
||||
|
||||
this.allocator = bun.default_allocator;
|
||||
this.eventlist_bytes = try bun.default_allocator.create(EventListBytes);
|
||||
this.root_path = cwd;
|
||||
log("{} init (fanotify)", .{this.fd});
|
||||
}
|
||||
|
||||
/// Read a path from a file descriptor using /proc/self/fd/
|
||||
fn readlinkFd(fd: i32, buffer: []u8) ![]const u8 {
|
||||
var path_buf: [64]u8 = undefined;
|
||||
const proc_path = std.fmt.bufPrint(&path_buf, "/proc/self/fd/{d}", .{fd}) catch unreachable;
|
||||
|
||||
const result = std.posix.readlink(proc_path, buffer) catch |err| {
|
||||
return err;
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn stop(this: *FanotifyWatcher) void {
|
||||
log("{} stop", .{this.fd});
|
||||
if (this.fd != bun.invalid_fd) {
|
||||
this.fd.close();
|
||||
this.fd = bun.invalid_fd;
|
||||
}
|
||||
}
|
||||
|
||||
/// Repeatedly called by the main watcher until the watcher is terminated.
|
||||
pub fn watchLoopCycle(this: *bun.Watcher) bun.sys.Maybe(void) {
|
||||
defer Output.flush();
|
||||
|
||||
// Read raw fanotify events
|
||||
const read_result = std.posix.system.read(
|
||||
this.platform.fd.cast(),
|
||||
this.platform.eventlist_bytes,
|
||||
this.platform.eventlist_bytes.len,
|
||||
);
|
||||
|
||||
const errno = std.posix.errno(read_result);
|
||||
if (errno != .SUCCESS) {
|
||||
if (errno == .AGAIN or errno == .INTR) {
|
||||
return .success;
|
||||
}
|
||||
return .{ .err = bun.sys.Error.fromCode(errno, .read) };
|
||||
}
|
||||
|
||||
const bytes_read = @as(usize, @intCast(read_result));
|
||||
if (bytes_read == 0) return .success;
|
||||
|
||||
log("fanotify read {} bytes", .{bytes_read});
|
||||
|
||||
// Process fanotify events and match them against watchlist
|
||||
var offset: usize = 0;
|
||||
var event_id: usize = 0;
|
||||
var path_buffer: [bun.MAX_PATH_BYTES]u8 = undefined;
|
||||
|
||||
while (offset < bytes_read) {
|
||||
const event: *align(1) const fanotify.EventMetadata = @ptrCast(this.platform.eventlist_bytes[offset..][0..@sizeOf(fanotify.EventMetadata)].ptr);
|
||||
|
||||
offset += event.size();
|
||||
|
||||
// Close the file descriptor and get its path
|
||||
if (event.hasValidFd()) {
|
||||
defer _ = std.posix.close(event.fd);
|
||||
|
||||
// Resolve FD to path
|
||||
const event_path = readlinkFd(event.fd, &path_buffer) catch |err| {
|
||||
log("Failed to readlink fd {}: {}", .{ event.fd, err });
|
||||
continue;
|
||||
};
|
||||
|
||||
log("fanotify event on path: {s} (mask=0x{x})", .{ event_path, event.mask });
|
||||
|
||||
// Match this path against our watchlist
|
||||
const item_paths = this.watchlist.items(.file_path);
|
||||
|
||||
for (item_paths, 0..) |watch_path, idx| {
|
||||
// Check if event path matches or is within watched path
|
||||
const is_match = brk: {
|
||||
// Exact match
|
||||
if (std.mem.eql(u8, event_path, watch_path)) break :brk true;
|
||||
|
||||
// Event is within watched directory
|
||||
if (std.mem.startsWith(u8, event_path, watch_path)) {
|
||||
// Make sure it's actually within (not just prefix match)
|
||||
if (event_path.len > watch_path.len) {
|
||||
const next_char = event_path[watch_path.len];
|
||||
if (next_char == '/' or watch_path[watch_path.len - 1] == '/') {
|
||||
break :brk true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break :brk false;
|
||||
};
|
||||
|
||||
if (is_match) {
|
||||
if (event_id >= this.watch_events.len) {
|
||||
// Process current batch
|
||||
switch (processFanotifyEventBatch(this, event_id)) {
|
||||
.err => |err| return .{ .err = err },
|
||||
.result => {},
|
||||
}
|
||||
event_id = 0;
|
||||
}
|
||||
|
||||
this.watch_events[event_id] = watchEventFromFanotifyEvent(event, @intCast(idx));
|
||||
event_id += 1;
|
||||
log("Matched event to watchlist index {}", .{idx});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process any remaining events
|
||||
if (event_id > 0) {
|
||||
switch (processFanotifyEventBatch(this, event_id)) {
|
||||
.err => |err| return .{ .err = err },
|
||||
.result => {},
|
||||
}
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn processFanotifyEventBatch(this: *bun.Watcher, event_count: usize) bun.sys.Maybe(void) {
|
||||
if (event_count == 0) {
|
||||
return .success;
|
||||
}
|
||||
|
||||
var all_events = this.watch_events[0..event_count];
|
||||
std.sort.pdq(WatchEvent, all_events, {}, WatchEvent.sortByIndex);
|
||||
|
||||
var last_event_index: usize = 0;
|
||||
var last_event_id: WatchItemIndex = std.math.maxInt(WatchItemIndex);
|
||||
|
||||
for (all_events, 0..) |_, i| {
|
||||
if (all_events[i].index == last_event_id) {
|
||||
all_events[last_event_index].merge(all_events[i]);
|
||||
continue;
|
||||
}
|
||||
last_event_index = i;
|
||||
last_event_id = all_events[i].index;
|
||||
}
|
||||
|
||||
if (all_events.len == 0) return .success;
|
||||
|
||||
this.mutex.lock();
|
||||
defer this.mutex.unlock();
|
||||
|
||||
if (this.running) {
|
||||
this.onFileUpdate(this.ctx, all_events[0 .. last_event_index + 1], this.changed_filepaths[0..0], this.watchlist);
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn watchEventFromFanotifyEvent(event: *align(1) const Event, index: WatchItemIndex) WatchEvent {
|
||||
const mask = event.mask;
|
||||
const FAN_DELETE = 0x00000200;
|
||||
const FAN_DELETE_SELF = 0x00000400;
|
||||
const FAN_MOVE_SELF = 0x00000800;
|
||||
const FAN_MOVED_TO = 0x00000080;
|
||||
const FAN_MODIFY = 0x00000002;
|
||||
const FAN_CLOSE_WRITE = 0x00000008;
|
||||
|
||||
return .{
|
||||
.op = .{
|
||||
.delete = (mask & FAN_DELETE_SELF) > 0 or (mask & FAN_DELETE) > 0,
|
||||
.rename = (mask & FAN_MOVE_SELF) > 0,
|
||||
.move_to = (mask & FAN_MOVED_TO) > 0,
|
||||
.write = (mask & FAN_MODIFY) > 0 or (mask & FAN_CLOSE_WRITE) > 0,
|
||||
},
|
||||
.index = index,
|
||||
};
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const Environment = bun.Environment;
|
||||
const Futex = bun.Futex;
|
||||
const Output = bun.Output;
|
||||
|
||||
const WatchEvent = bun.Watcher.Event;
|
||||
const WatchItemIndex = bun.Watcher.WatchItemIndex;
|
||||
const max_count = bun.Watcher.max_count;
|
||||
Reference in New Issue
Block a user