diff options
Diffstat (limited to 'src/bun.js/node')
-rw-r--r-- | src/bun.js/node/buffer.zig | 2 | ||||
-rw-r--r-- | src/bun.js/node/dir_iterator.zig | 54 | ||||
-rw-r--r-- | src/bun.js/node/fs_events.zig | 609 | ||||
-rw-r--r-- | src/bun.js/node/node.classes.ts | 31 | ||||
-rw-r--r-- | src/bun.js/node/node_fs.zig | 445 | ||||
-rw-r--r-- | src/bun.js/node/node_fs_binding.zig | 10 | ||||
-rw-r--r-- | src/bun.js/node/node_fs_constant.zig | 6 | ||||
-rw-r--r-- | src/bun.js/node/node_fs_watcher.zig | 919 | ||||
-rw-r--r-- | src/bun.js/node/node_os.zig | 66 | ||||
-rw-r--r-- | src/bun.js/node/os/constants.zig | 4 | ||||
-rw-r--r-- | src/bun.js/node/syscall.zig | 190 | ||||
-rw-r--r-- | src/bun.js/node/types.zig | 148 |
12 files changed, 2274 insertions, 210 deletions
diff --git a/src/bun.js/node/buffer.zig b/src/bun.js/node/buffer.zig index f73498069..3a0750f05 100644 --- a/src/bun.js/node/buffer.zig +++ b/src/bun.js/node/buffer.zig @@ -50,7 +50,7 @@ pub const BufferVectorized = struct { switch (written) { 0 => {}, - 1 => @memset(buf.ptr, buf[0], buf.len), + 1 => @memset(buf, buf[0]), else => { var contents = buf[0..written]; buf = buf[written..]; diff --git a/src/bun.js/node/dir_iterator.zig b/src/bun.js/node/dir_iterator.zig index aa939679c..dac78e5e2 100644 --- a/src/bun.js/node/dir_iterator.zig +++ b/src/bun.js/node/dir_iterator.zig @@ -78,15 +78,15 @@ pub const Iterator = switch (builtin.os.tag) { } const entry_kind = switch (darwin_entry.d_type) { - os.DT.BLK => Entry.Kind.BlockDevice, - os.DT.CHR => Entry.Kind.CharacterDevice, - os.DT.DIR => Entry.Kind.Directory, - os.DT.FIFO => Entry.Kind.NamedPipe, - os.DT.LNK => Entry.Kind.SymLink, - os.DT.REG => Entry.Kind.File, - os.DT.SOCK => Entry.Kind.UnixDomainSocket, - os.DT.WHT => Entry.Kind.Whiteout, - else => Entry.Kind.Unknown, + os.DT.BLK => Entry.Kind.block_device, + os.DT.CHR => Entry.Kind.character_device, + os.DT.DIR => Entry.Kind.directory, + os.DT.FIFO => Entry.Kind.named_pipe, + os.DT.LNK => Entry.Kind.sym_link, + os.DT.REG => Entry.Kind.file, + os.DT.SOCK => Entry.Kind.unix_domain_socket, + os.DT.WHT => Entry.Kind.whiteout, + else => Entry.Kind.unknown, }; return .{ .result = IteratorResult{ @@ -134,14 +134,14 @@ pub const Iterator = switch (builtin.os.tag) { } const entry_kind = switch (linux_entry.d_type) { - linux.DT.BLK => Entry.Kind.BlockDevice, - linux.DT.CHR => Entry.Kind.CharacterDevice, - linux.DT.DIR => Entry.Kind.Directory, - linux.DT.FIFO => Entry.Kind.NamedPipe, - linux.DT.LNK => Entry.Kind.SymLink, - linux.DT.REG => Entry.Kind.File, - linux.DT.SOCK => Entry.Kind.UnixDomainSocket, - else => Entry.Kind.Unknown, + linux.DT.BLK => Entry.Kind.block_device, + linux.DT.CHR => Entry.Kind.character_device, + linux.DT.DIR => Entry.Kind.directory, + linux.DT.FIFO => Entry.Kind.named_pipe, + linux.DT.LNK => Entry.Kind.sym_link, + linux.DT.REG => Entry.Kind.file, + linux.DT.SOCK => Entry.Kind.unix_domain_socket, + else => Entry.Kind.unknown, }; return .{ .result = IteratorResult{ @@ -213,9 +213,9 @@ pub const Iterator = switch (builtin.os.tag) { const name_utf8 = self.name_data[0..name_utf8_len]; const kind = blk: { const attrs = dir_info.FileAttributes; - if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; - if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; - break :blk Entry.Kind.File; + if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.directory; + if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.sym_link; + break :blk Entry.Kind.file; }; return .{ .result = IteratorResult{ @@ -275,13 +275,13 @@ pub const Iterator = switch (builtin.os.tag) { } const entry_kind = switch (entry.d_type) { - .BLOCK_DEVICE => Entry.Kind.BlockDevice, - .CHARACTER_DEVICE => Entry.Kind.CharacterDevice, - .DIRECTORY => Entry.Kind.Directory, - .SYMBOLIC_LINK => Entry.Kind.SymLink, - .REGULAR_FILE => Entry.Kind.File, - .SOCKET_STREAM, .SOCKET_DGRAM => Entry.Kind.UnixDomainSocket, - else => Entry.Kind.Unknown, + .BLOCK_DEVICE => Entry.Kind.block_device, + .CHARACTER_DEVICE => Entry.Kind.character_device, + .DIRECTORY => Entry.Kind.directory, + .SYMBOLIC_LINK => Entry.Kind.sym_link, + .REGULAR_FILE => Entry.Kind.file, + .SOCKET_STREAM, .SOCKET_DGRAM => Entry.Kind.unix_domain_socket, + else => Entry.Kind.unknown, }; return IteratorResult{ .name = name, diff --git a/src/bun.js/node/fs_events.zig b/src/bun.js/node/fs_events.zig new file mode 100644 index 000000000..a3fba5441 --- /dev/null +++ b/src/bun.js/node/fs_events.zig @@ -0,0 +1,609 @@ +const std = @import("std"); +const bun = @import("root").bun; +const Environment = bun.Environment; +const Mutex = @import("../../lock.zig").Lock; +const sync = @import("../../sync.zig"); +const Semaphore = sync.Semaphore; +const UnboundedQueue = @import("../unbounded_queue.zig").UnboundedQueue; +const TaggedPointerUnion = @import("../../tagged_pointer.zig").TaggedPointerUnion; +const string = bun.string; + +pub const CFAbsoluteTime = f64; +pub const CFTimeInterval = f64; +pub const CFArrayCallBacks = anyopaque; + +pub const FSEventStreamEventFlags = c_int; +pub const OSStatus = c_int; +pub const CFIndex = c_long; + +pub const FSEventStreamCreateFlags = u32; +pub const FSEventStreamEventId = u64; + +pub const CFStringEncoding = c_uint; + +pub const CFArrayRef = ?*anyopaque; +pub const CFAllocatorRef = ?*anyopaque; +pub const CFBundleRef = ?*anyopaque; +pub const CFDictionaryRef = ?*anyopaque; +pub const CFRunLoopRef = ?*anyopaque; +pub const CFRunLoopSourceRef = ?*anyopaque; +pub const CFStringRef = ?*anyopaque; +pub const CFTypeRef = ?*anyopaque; +pub const FSEventStreamRef = ?*anyopaque; +pub const FSEventStreamCallback = *const fn (FSEventStreamRef, ?*anyopaque, usize, ?*anyopaque, *FSEventStreamEventFlags, *FSEventStreamEventId) callconv(.C) void; + +// we only care about info and perform +pub const CFRunLoopSourceContext = extern struct { + version: CFIndex = 0, + info: *anyopaque, + retain: ?*anyopaque = null, + release: ?*anyopaque = null, + copyDescription: ?*anyopaque = null, + equal: ?*anyopaque = null, + hash: ?*anyopaque = null, + schedule: ?*anyopaque = null, + cancel: ?*anyopaque = null, + perform: *const fn (?*anyopaque) callconv(.C) void, +}; + +pub const FSEventStreamContext = extern struct { + version: CFIndex = 0, + info: ?*anyopaque = null, + pad: [3]?*anyopaque = .{ null, null, null }, +}; + +pub const kCFStringEncodingUTF8: CFStringEncoding = 0x8000100; +pub const noErr: OSStatus = 0; + +pub const kFSEventStreamCreateFlagNoDefer: c_int = 2; +pub const kFSEventStreamCreateFlagFileEvents: c_int = 16; + +pub const kFSEventStreamEventFlagEventIdsWrapped: c_int = 8; +pub const kFSEventStreamEventFlagHistoryDone: c_int = 16; +pub const kFSEventStreamEventFlagItemChangeOwner: c_int = 0x4000; +pub const kFSEventStreamEventFlagItemCreated: c_int = 0x100; +pub const kFSEventStreamEventFlagItemFinderInfoMod: c_int = 0x2000; +pub const kFSEventStreamEventFlagItemInodeMetaMod: c_int = 0x400; +pub const kFSEventStreamEventFlagItemIsDir: c_int = 0x20000; +pub const kFSEventStreamEventFlagItemModified: c_int = 0x1000; +pub const kFSEventStreamEventFlagItemRemoved: c_int = 0x200; +pub const kFSEventStreamEventFlagItemRenamed: c_int = 0x800; +pub const kFSEventStreamEventFlagItemXattrMod: c_int = 0x8000; +pub const kFSEventStreamEventFlagKernelDropped: c_int = 4; +pub const kFSEventStreamEventFlagMount: c_int = 64; +pub const kFSEventStreamEventFlagRootChanged: c_int = 32; +pub const kFSEventStreamEventFlagUnmount: c_int = 128; +pub const kFSEventStreamEventFlagUserDropped: c_int = 2; + +// Lazy function call binding. +const RTLD_LAZY = 0x1; +// Symbols exported from this image (dynamic library or bundle) +// are generally hidden and only availble to dlsym() when +// directly using the handle returned by this call to dlopen(). +const RTLD_LOCAL = 0x4; + +pub const kFSEventsModified: c_int = + kFSEventStreamEventFlagItemChangeOwner | + kFSEventStreamEventFlagItemFinderInfoMod | + kFSEventStreamEventFlagItemInodeMetaMod | + kFSEventStreamEventFlagItemModified | + kFSEventStreamEventFlagItemXattrMod; + +pub const kFSEventsRenamed: c_int = + kFSEventStreamEventFlagItemCreated | + kFSEventStreamEventFlagItemRemoved | + kFSEventStreamEventFlagItemRenamed; + +pub const kFSEventsSystem: c_int = + kFSEventStreamEventFlagUserDropped | + kFSEventStreamEventFlagKernelDropped | + kFSEventStreamEventFlagEventIdsWrapped | + kFSEventStreamEventFlagHistoryDone | + kFSEventStreamEventFlagMount | + kFSEventStreamEventFlagUnmount | + kFSEventStreamEventFlagRootChanged; + +var fsevents_mutex: Mutex = Mutex.init(); +var fsevents_default_loop_mutex: Mutex = Mutex.init(); +var fsevents_default_loop: ?*FSEventsLoop = null; + +fn dlsym(handle: ?*anyopaque, comptime Type: type, comptime symbol: [:0]const u8) ?Type { + if (std.c.dlsym(handle, symbol)) |ptr| { + return bun.cast(Type, ptr); + } + return null; +} + +pub const CoreFoundation = struct { + handle: ?*anyopaque, + ArrayCreate: *fn (CFAllocatorRef, [*]?*anyopaque, CFIndex, ?*CFArrayCallBacks) callconv(.C) CFArrayRef, + Release: *fn (CFTypeRef) callconv(.C) void, + + RunLoopAddSource: *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, + RunLoopGetCurrent: *fn () callconv(.C) CFRunLoopRef, + RunLoopRemoveSource: *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, + RunLoopRun: *fn () callconv(.C) void, + RunLoopSourceCreate: *fn (CFAllocatorRef, CFIndex, *CFRunLoopSourceContext) callconv(.C) CFRunLoopSourceRef, + RunLoopSourceSignal: *fn (CFRunLoopSourceRef) callconv(.C) void, + RunLoopStop: *fn (CFRunLoopRef) callconv(.C) void, + RunLoopWakeUp: *fn (CFRunLoopRef) callconv(.C) void, + StringCreateWithFileSystemRepresentation: *fn (CFAllocatorRef, [*]const u8) callconv(.C) CFStringRef, + RunLoopDefaultMode: *CFStringRef, + + pub fn get() CoreFoundation { + if (fsevents_cf) |cf| return cf; + fsevents_mutex.lock(); + defer fsevents_mutex.unlock(); + if (fsevents_cf) |cf| return cf; + + InitLibrary(); + + return fsevents_cf.?; + } + + // We Actually never deinit it + // pub fn deinit(this: *CoreFoundation) void { + // if(this.handle) | ptr| { + // this.handle = null; + // _ = std.c.dlclose(this.handle); + // } + // } + +}; + +pub const CoreServices = struct { + handle: ?*anyopaque, + FSEventStreamCreate: *fn (CFAllocatorRef, FSEventStreamCallback, *FSEventStreamContext, CFArrayRef, FSEventStreamEventId, CFTimeInterval, FSEventStreamCreateFlags) callconv(.C) FSEventStreamRef, + FSEventStreamInvalidate: *fn (FSEventStreamRef) callconv(.C) void, + FSEventStreamRelease: *fn (FSEventStreamRef) callconv(.C) void, + FSEventStreamScheduleWithRunLoop: *fn (FSEventStreamRef, CFRunLoopRef, CFStringRef) callconv(.C) void, + FSEventStreamStart: *fn (FSEventStreamRef) callconv(.C) c_int, + FSEventStreamStop: *fn (FSEventStreamRef) callconv(.C) void, + // libuv set it to -1 so the actual value is this + kFSEventStreamEventIdSinceNow: FSEventStreamEventId = 18446744073709551615, + + pub fn get() CoreServices { + if (fsevents_cs) |cs| return cs; + fsevents_mutex.lock(); + defer fsevents_mutex.unlock(); + if (fsevents_cs) |cs| return cs; + + InitLibrary(); + + return fsevents_cs.?; + } + + // We Actually never deinit it + // pub fn deinit(this: *CoreServices) void { + // if(this.handle) | ptr| { + // this.handle = null; + // _ = std.c.dlclose(this.handle); + // } + // } + +}; + +var fsevents_cf: ?CoreFoundation = null; +var fsevents_cs: ?CoreServices = null; + +fn InitLibrary() void { + const fsevents_cf_handle = std.c.dlopen("/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", RTLD_LAZY | RTLD_LOCAL); + if (fsevents_cf_handle == null) @panic("Cannot Load CoreFoundation"); + + fsevents_cf = CoreFoundation{ + .handle = fsevents_cf_handle, + .ArrayCreate = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, [*]?*anyopaque, CFIndex, ?*CFArrayCallBacks) callconv(.C) CFArrayRef, "CFArrayCreate") orelse @panic("Cannot Load CoreFoundation"), + .Release = dlsym(fsevents_cf_handle, *fn (CFTypeRef) callconv(.C) void, "CFRelease") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopAddSource = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, "CFRunLoopAddSource") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopGetCurrent = dlsym(fsevents_cf_handle, *fn () callconv(.C) CFRunLoopRef, "CFRunLoopGetCurrent") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopRemoveSource = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef, CFRunLoopSourceRef, CFStringRef) callconv(.C) void, "CFRunLoopRemoveSource") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopRun = dlsym(fsevents_cf_handle, *fn () callconv(.C) void, "CFRunLoopRun") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopSourceCreate = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, CFIndex, *CFRunLoopSourceContext) callconv(.C) CFRunLoopSourceRef, "CFRunLoopSourceCreate") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopSourceSignal = dlsym(fsevents_cf_handle, *fn (CFRunLoopSourceRef) callconv(.C) void, "CFRunLoopSourceSignal") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopStop = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef) callconv(.C) void, "CFRunLoopStop") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopWakeUp = dlsym(fsevents_cf_handle, *fn (CFRunLoopRef) callconv(.C) void, "CFRunLoopWakeUp") orelse @panic("Cannot Load CoreFoundation"), + .StringCreateWithFileSystemRepresentation = dlsym(fsevents_cf_handle, *fn (CFAllocatorRef, [*]const u8) callconv(.C) CFStringRef, "CFStringCreateWithFileSystemRepresentation") orelse @panic("Cannot Load CoreFoundation"), + .RunLoopDefaultMode = dlsym(fsevents_cf_handle, *CFStringRef, "kCFRunLoopDefaultMode") orelse @panic("Cannot Load CoreFoundation"), + }; + + const fsevents_cs_handle = std.c.dlopen("/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices", RTLD_LAZY | RTLD_LOCAL); + if (fsevents_cs_handle == null) @panic("Cannot Load CoreServices"); + + fsevents_cs = CoreServices{ + .handle = fsevents_cs_handle, + .FSEventStreamCreate = dlsym(fsevents_cs_handle, *fn (CFAllocatorRef, FSEventStreamCallback, *FSEventStreamContext, CFArrayRef, FSEventStreamEventId, CFTimeInterval, FSEventStreamCreateFlags) callconv(.C) FSEventStreamRef, "FSEventStreamCreate") orelse @panic("Cannot Load CoreServices"), + .FSEventStreamInvalidate = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamInvalidate") orelse @panic("Cannot Load CoreServices"), + .FSEventStreamRelease = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamRelease") orelse @panic("Cannot Load CoreServices"), + .FSEventStreamScheduleWithRunLoop = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef, CFRunLoopRef, CFStringRef) callconv(.C) void, "FSEventStreamScheduleWithRunLoop") orelse @panic("Cannot Load CoreServices"), + .FSEventStreamStart = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) c_int, "FSEventStreamStart") orelse @panic("Cannot Load CoreServices"), + .FSEventStreamStop = dlsym(fsevents_cs_handle, *fn (FSEventStreamRef) callconv(.C) void, "FSEventStreamStop") orelse @panic("Cannot Load CoreServices"), + }; +} + +pub const FSEventsLoop = struct { + signal_source: CFRunLoopSourceRef, + mutex: Mutex, + loop: CFRunLoopRef = null, + sem: Semaphore, + thread: std.Thread = undefined, + tasks: ConcurrentTask.Queue = ConcurrentTask.Queue{}, + watchers: bun.BabyList(?*FSEventsWatcher) = .{}, + watcher_count: u32 = 0, + fsevent_stream: FSEventStreamRef = null, + paths: ?[]?*anyopaque = null, + cf_paths: CFArrayRef = null, + has_scheduled_watchers: bool = false, + + pub const Task = struct { + ctx: ?*anyopaque, + callback: *const (fn (*anyopaque) void), + + pub fn run(this: *Task) void { + var callback = this.callback; + var ctx = this.ctx; + callback(ctx.?); + } + + pub fn New(comptime Type: type, comptime Callback: anytype) type { + return struct { + pub fn init(ctx: *Type) Task { + return Task{ + .callback = wrap, + .ctx = ctx, + }; + } + + pub fn wrap(this: ?*anyopaque) void { + @call(.always_inline, Callback, .{@ptrCast(*Type, @alignCast(@alignOf(Type), this.?))}); + } + }; + } + }; + + pub const ConcurrentTask = struct { + task: Task = undefined, + next: ?*ConcurrentTask = null, + auto_delete: bool = false, + + pub const Queue = UnboundedQueue(ConcurrentTask, .next); + + pub fn from(this: *ConcurrentTask, task: Task) *ConcurrentTask { + this.* = .{ + .task = task, + .next = null, + }; + return this; + } + }; + + pub fn CFThreadLoop(this: *FSEventsLoop) void { + bun.Output.Source.configureNamedThread("CFThreadLoop"); + + const CF = CoreFoundation.get(); + + this.loop = CF.RunLoopGetCurrent(); + + CF.RunLoopAddSource(this.loop, this.signal_source, CF.RunLoopDefaultMode.*); + + this.sem.post(); + + CF.RunLoopRun(); + CF.RunLoopRemoveSource(this.loop, this.signal_source, CF.RunLoopDefaultMode.*); + + this.loop = null; + } + + // Runs in CF thread, executed after `enqueueTaskConcurrent()` + fn CFLoopCallback(arg: ?*anyopaque) callconv(.C) void { + if (arg) |self| { + const this = bun.cast(*FSEventsLoop, self); + + var concurrent = this.tasks.popBatch(); + const count = concurrent.count; + if (count == 0) + return; + + var iter = concurrent.iterator(); + while (iter.next()) |task| { + task.task.run(); + if (task.auto_delete) bun.default_allocator.destroy(task); + } + } + } + + pub fn init() !*FSEventsLoop { + const this = bun.default_allocator.create(FSEventsLoop) catch unreachable; + + const CF = CoreFoundation.get(); + + var ctx = CFRunLoopSourceContext{ + .info = this, + .perform = CFLoopCallback, + }; + + const signal_source = CF.RunLoopSourceCreate(null, 0, &ctx); + if (signal_source == null) { + return error.FailedToCreateCoreFoudationSourceLoop; + } + + var fs_loop = FSEventsLoop{ .sem = Semaphore.init(0), .mutex = Mutex.init(), .signal_source = signal_source }; + + this.* = fs_loop; + this.thread = try std.Thread.spawn(.{}, FSEventsLoop.CFThreadLoop, .{this}); + + // sync threads + this.sem.wait(); + return this; + } + + fn enqueueTaskConcurrent(this: *FSEventsLoop, task: Task) void { + const CF = CoreFoundation.get(); + var concurrent = bun.default_allocator.create(ConcurrentTask) catch unreachable; + concurrent.auto_delete = true; + this.tasks.push(concurrent.from(task)); + CF.RunLoopSourceSignal(this.signal_source); + CF.RunLoopWakeUp(this.loop); + } + + // Runs in CF thread, when there're events in FSEventStream + fn _events_cb(_: FSEventStreamRef, info: ?*anyopaque, numEvents: usize, eventPaths: ?*anyopaque, eventFlags: *FSEventStreamEventFlags, _: *FSEventStreamEventId) callconv(.C) void { + const paths_ptr = bun.cast([*][*:0]const u8, eventPaths); + const paths = paths_ptr[0..numEvents]; + var loop = bun.cast(*FSEventsLoop, info); + const event_flags = bun.cast([*]FSEventStreamEventFlags, eventFlags); + + for (loop.watchers.slice()) |watcher| { + if (watcher) |handle| { + for (paths, 0..) |path_ptr, i| { + var flags = event_flags[i]; + var path = path_ptr[0..bun.len(path_ptr)]; + // Filter out paths that are outside handle's request + if (path.len < handle.path.len or !bun.strings.startsWith(path, handle.path)) { + continue; + } + const is_file = (flags & kFSEventStreamEventFlagItemIsDir) == 0; + + // Remove common prefix, unless the watched folder is "/" + if (!(handle.path.len == 1 and handle.path[0] == '/')) { + path = path[handle.path.len..]; + + // Ignore events with path equal to directory itself + if (path.len <= 1 and is_file) { + continue; + } + if (path.len == 0) { + // Since we're using fsevents to watch the file itself, path == handle.path, and we now need to get the basename of the file back + while (path.len > 0) { + if (bun.strings.startsWithChar(path, '/')) { + path = path[1..]; + break; + } else { + path = path[1..]; + } + } + + // Created and Removed seem to be always set, but don't make sense + flags &= ~kFSEventsRenamed; + } else { + // Skip forward slash + path = path[1..]; + } + } + + // Do not emit events from subdirectories (without option set) + if (path.len == 0 or (bun.strings.containsChar(path, '/') and !handle.recursive)) { + continue; + } + + var is_rename = true; + + if ((flags & kFSEventsRenamed) == 0) { + if ((flags & kFSEventsModified) != 0 or is_file) { + is_rename = false; + } + } + + handle.callback(handle.ctx, path, is_file, is_rename); + } + } + } + } + + // Runs on CF Thread + pub fn _schedule(this: *FSEventsLoop) void { + this.mutex.lock(); + defer this.mutex.unlock(); + this.has_scheduled_watchers = false; + + var watchers = this.watchers.slice(); + + const CF = CoreFoundation.get(); + const CS = CoreServices.get(); + + if (this.fsevent_stream) |stream| { + // Stop emitting events + CS.FSEventStreamStop(stream); + + // Release stream + CS.FSEventStreamInvalidate(stream); + CS.FSEventStreamRelease(stream); + this.fsevent_stream = null; + } + // clean old paths + if (this.paths) |p| { + this.paths = null; + bun.default_allocator.destroy(p); + } + if (this.cf_paths) |cf| { + this.cf_paths = null; + CF.Release(cf); + } + + const paths = bun.default_allocator.alloc(?*anyopaque, this.watcher_count) catch unreachable; + var count: u32 = 0; + for (watchers) |w| { + if (w) |watcher| { + const path = CF.StringCreateWithFileSystemRepresentation(null, watcher.path.ptr); + paths[count] = path; + count += 1; + } + } + + const cf_paths = CF.ArrayCreate(null, paths.ptr, count, null); + var ctx: FSEventStreamContext = .{ + .info = this, + }; + + const latency: CFAbsoluteTime = 0.05; + // Explanation of selected flags: + // 1. NoDefer - without this flag, events that are happening continuously + // (i.e. each event is happening after time interval less than `latency`, + // counted from previous event), will be deferred and passed to callback + // once they'll either fill whole OS buffer, or when this continuous stream + // will stop (i.e. there'll be delay between events, bigger than + // `latency`). + // Specifying this flag will invoke callback after `latency` time passed + // since event. + // 2. FileEvents - fire callback for file changes too (by default it is firing + // it only for directory changes). + // + const flags: FSEventStreamCreateFlags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents; + + // + // NOTE: It might sound like a good idea to remember last seen StreamEventId, + // but in reality one dir might have last StreamEventId less than, the other, + // that is being watched now. Which will cause FSEventStream API to report + // changes to files from the past. + // + const ref = CS.FSEventStreamCreate(null, _events_cb, &ctx, cf_paths, CS.kFSEventStreamEventIdSinceNow, latency, flags); + + CS.FSEventStreamScheduleWithRunLoop(ref, this.loop, CF.RunLoopDefaultMode.*); + if (CS.FSEventStreamStart(ref) == 0) { + //clean in case of failure + bun.default_allocator.destroy(paths); + CF.Release(cf_paths); + CS.FSEventStreamInvalidate(ref); + CS.FSEventStreamRelease(ref); + return; + } + this.fsevent_stream = ref; + this.paths = paths; + this.cf_paths = cf_paths; + } + + fn registerWatcher(this: *FSEventsLoop, watcher: *FSEventsWatcher) void { + this.mutex.lock(); + defer this.mutex.unlock(); + if (this.watcher_count == this.watchers.len) { + this.watcher_count += 1; + this.watchers.push(bun.default_allocator, watcher) catch unreachable; + } else { + var watchers = this.watchers.slice(); + for (watchers, 0..) |w, i| { + if (w == null) { + watchers[i] = watcher; + this.watcher_count += 1; + break; + } + } + } + + if (this.has_scheduled_watchers == false) { + this.has_scheduled_watchers = true; + this.enqueueTaskConcurrent(Task.New(FSEventsLoop, _schedule).init(this)); + } + } + + fn unregisterWatcher(this: *FSEventsLoop, watcher: *FSEventsWatcher) void { + this.mutex.lock(); + defer this.mutex.unlock(); + var watchers = this.watchers.slice(); + for (watchers, 0..) |w, i| { + if (w) |item| { + if (item == watcher) { + watchers[i] = null; + // if is the last one just pop + if (i == watchers.len - 1) { + this.watchers.len -= 1; + } + this.watcher_count -= 1; + break; + } + } + } + } + + // Runs on CF loop to close the loop + fn _stop(this: *FSEventsLoop) void { + const CF = CoreFoundation.get(); + CF.RunLoopStop(this.loop); + } + fn deinit(this: *FSEventsLoop) void { + // signal close and wait + this.enqueueTaskConcurrent(Task.New(FSEventsLoop, FSEventsLoop._stop).init(this)); + this.thread.join(); + const CF = CoreFoundation.get(); + + CF.Release(this.signal_source); + this.signal_source = null; + + this.sem.deinit(); + this.mutex.deinit(); + if (this.watcher_count > 0) { + while (this.watchers.popOrNull()) |watcher| { + if (watcher) |w| { + // unlink watcher + w.loop = null; + } + } + } + + this.watchers.deinitWithAllocator(bun.default_allocator); + + bun.default_allocator.destroy(this); + } +}; + +pub const FSEventsWatcher = struct { + path: string, + callback: Callback, + loop: ?*FSEventsLoop, + recursive: bool, + ctx: ?*anyopaque, + + const Callback = *const fn (ctx: ?*anyopaque, path: string, is_file: bool, is_rename: bool) void; + + pub fn init(loop: *FSEventsLoop, path: string, recursive: bool, callback: Callback, ctx: ?*anyopaque) *FSEventsWatcher { + var this = bun.default_allocator.create(FSEventsWatcher) catch unreachable; + this.* = FSEventsWatcher{ + .path = path, + .callback = callback, + .loop = loop, + .recursive = recursive, + .ctx = ctx, + }; + + loop.registerWatcher(this); + return this; + } + + pub fn deinit(this: *FSEventsWatcher) void { + if (this.loop) |loop| { + loop.unregisterWatcher(this); + } + bun.default_allocator.destroy(this); + } +}; + +pub fn watch(path: string, recursive: bool, callback: FSEventsWatcher.Callback, ctx: ?*anyopaque) !*FSEventsWatcher { + if (fsevents_default_loop) |loop| { + return FSEventsWatcher.init(loop, path, recursive, callback, ctx); + } else { + fsevents_default_loop_mutex.lock(); + defer fsevents_default_loop_mutex.unlock(); + if (fsevents_default_loop == null) { + fsevents_default_loop = try FSEventsLoop.init(); + } + return FSEventsWatcher.init(fsevents_default_loop.?, path, recursive, callback, ctx); + } +} diff --git a/src/bun.js/node/node.classes.ts b/src/bun.js/node/node.classes.ts index f984077e4..2efad5245 100644 --- a/src/bun.js/node/node.classes.ts +++ b/src/bun.js/node/node.classes.ts @@ -2,6 +2,35 @@ import { define } from "../scripts/class-definitions"; export default [ define({ + name: "FSWatcher", + construct: false, + noConstructor: true, + finalize: true, + configurable: false, + hasPendingActivity: true, + klass: {}, + JSType: "0b11101110", + proto: { + ref: { + fn: "doRef", + length: 0, + }, + unref: { + fn: "doUnref", + length: 0, + }, + hasRef: { + fn: "hasRef", + length: 0, + }, + close: { + fn: "doClose", + length: 0, + }, + }, + values: ["listener"], + }), + define({ name: "Timeout", construct: false, noConstructor: true, @@ -300,7 +329,7 @@ export default [ utimes: { fn: "utimes", length: 4 }, utimesSync: { fn: "utimesSync", length: 3 }, // TODO: - // watch: { fn: "watch", length: 3 }, + watch: { fn: "watch", length: 3 }, // watchFile: { fn: "watchFile", length: 3 }, writeFile: { fn: "writeFile", length: 4 }, writeFileSync: { fn: "writeFileSync", length: 3 }, diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 254d58455..3f298c5c7 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -34,9 +34,8 @@ const Mode = JSC.Node.Mode; const uid_t = std.os.uid_t; const gid_t = std.os.gid_t; - /// u63 to allow one null bit -const ReadPosition = u63; +const ReadPosition = i64; const Stats = JSC.Node.Stats; const Dirent = JSC.Node.Dirent; @@ -136,6 +135,154 @@ pub const Arguments = struct { } }; + pub const Writev = struct { + fd: FileDescriptor, + buffers: JSC.Node.VectorArrayBuffer, + position: ?u52 = 0, + + pub fn deinit(_: *const @This()) void {} + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Writev { + const fd_value = arguments.nextEat() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor is required", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const fd = JSC.Node.fileDescriptorFromJS(ctx, fd_value, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor must be a number", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const buffers = JSC.Node.VectorArrayBuffer.fromJS( + ctx, + arguments.protectEatNext() orelse { + JSC.throwInvalidArguments("Expected an ArrayBufferView[]", .{}, ctx, exception); + return null; + }, + exception, + arguments.arena.allocator(), + ) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "buffers must be an array of TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + var position: ?u52 = null; + + if (arguments.nextEat()) |pos_value| { + if (!pos_value.isUndefinedOrNull()) { + if (pos_value.isNumber()) { + position = pos_value.to(u52); + } else { + JSC.throwInvalidArguments( + "position must be a number", + .{}, + ctx, + exception, + ); + return null; + } + } + } + + return Writev{ .fd = fd, .buffers = buffers, .position = position }; + } + }; + + pub const Readv = struct { + fd: FileDescriptor, + buffers: JSC.Node.VectorArrayBuffer, + position: ?u52 = 0, + + pub fn deinit(_: *const @This()) void {} + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readv { + const fd_value = arguments.nextEat() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor is required", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const fd = JSC.Node.fileDescriptorFromJS(ctx, fd_value, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor must be a number", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const buffers = JSC.Node.VectorArrayBuffer.fromJS( + ctx, + arguments.protectEatNext() orelse { + JSC.throwInvalidArguments("Expected an ArrayBufferView[]", .{}, ctx, exception); + return null; + }, + exception, + arguments.arena.allocator(), + ) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "buffers must be an array of TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + var position: ?u52 = null; + + if (arguments.nextEat()) |pos_value| { + if (!pos_value.isUndefinedOrNull()) { + if (pos_value.isNumber()) { + position = pos_value.to(u52); + } else { + JSC.throwInvalidArguments( + "position must be a number", + .{}, + ctx, + exception, + ); + return null; + } + } + } + + return Readv{ .fd = fd, .buffers = buffers, .position = position }; + } + }; + pub const FTruncate = struct { fd: FileDescriptor, len: ?JSC.WebCore.Blob.SizeType = null, @@ -505,6 +652,7 @@ pub const Arguments = struct { pub const Stat = struct { path: PathLike, big_int: bool = false, + throw_if_no_entry: bool = true, pub fn deinit(this: Stat) void { this.path.deinit(); @@ -525,13 +673,25 @@ pub const Arguments = struct { if (exception.* != null) return null; + var throw_if_no_entry = true; + const big_int = brk: { if (arguments.next()) |next_val| { if (next_val.isObject()) { if (next_val.isCallable(ctx.ptr().vm())) break :brk false; arguments.eat(); - if (next_val.getOptional(ctx.ptr(), "bigint", bool) catch false) |big_int| { + if (next_val.getOptional(ctx.ptr(), "throwIfNoEntry", bool) catch { + path.deinit(); + return null; + }) |throw_if_no_entry_val| { + throw_if_no_entry = throw_if_no_entry_val; + } + + if (next_val.getOptional(ctx.ptr(), "bigint", bool) catch { + path.deinit(); + return null; + }) |big_int| { break :brk big_int; } } @@ -541,7 +701,7 @@ pub const Arguments = struct { if (exception.* != null) return null; - return Stat{ .path = path, .big_int = big_int }; + return Stat{ .path = path, .big_int = big_int, .throw_if_no_entry = throw_if_no_entry }; } }; @@ -1377,7 +1537,7 @@ pub const Arguments = struct { // fs.write(fd, string[, position[, encoding]], callback) .string => { if (current.isNumber()) { - args.position = current.toU32(); + args.position = current.to(i52); arguments.eat(); current = arguments.next() orelse break :parse; } @@ -1393,18 +1553,18 @@ pub const Arguments = struct { break :parse; } - if (!current.isNumber()) break :parse; - args.offset = current.toU32(); + if (!(current.isNumber() or current.isBigInt())) break :parse; + args.offset = current.to(u52); arguments.eat(); current = arguments.next() orelse break :parse; - if (!current.isNumber()) break :parse; - args.length = current.toU32(); + if (!(current.isNumber() or current.isBigInt())) break :parse; + args.length = current.to(u52); arguments.eat(); current = arguments.next() orelse break :parse; - if (!current.isNumber()) break :parse; - args.position = current.toU32(); + if (!(current.isNumber() or current.isBigInt())) break :parse; + args.position = current.to(i52); arguments.eat(); }, } @@ -1484,8 +1644,8 @@ pub const Arguments = struct { if (arguments.next()) |current| { arguments.eat(); - if (current.isNumber()) { - args.offset = current.toU32(); + if (current.isNumber() or current.isBigInt()) { + args.offset = current.to(u52); if (arguments.remaining.len < 2) { JSC.throwInvalidArguments( @@ -1497,8 +1657,8 @@ pub const Arguments = struct { return null; } - - args.length = arguments.remaining[0].toU32(); + if (arguments.remaining[0].isNumber() or arguments.remaining[0].isBigInt()) + args.length = arguments.remaining[0].to(u52); if (args.length == 0) { JSC.throwInvalidArguments( @@ -1511,26 +1671,26 @@ pub const Arguments = struct { return null; } - const position: i32 = if (arguments.remaining[1].isNumber()) - arguments.remaining[1].toInt32() - else - -1; + if (arguments.remaining[1].isNumber() or arguments.remaining[1].isBigInt()) + args.position = @intCast(ReadPosition, arguments.remaining[1].to(i52)); - args.position = if (position > -1) @intCast(ReadPosition, position) else null; arguments.remaining = arguments.remaining[2..]; } else if (current.isObject()) { - if (current.getIfPropertyExists(ctx.ptr(), "offset")) |num| { - args.offset = num.toU32(); + if (current.getTruthy(ctx.ptr(), "offset")) |num| { + if (num.isNumber() or num.isBigInt()) { + args.offset = num.to(u52); + } } - if (current.getIfPropertyExists(ctx.ptr(), "length")) |num| { - args.length = num.toU32(); + if (current.getTruthy(ctx.ptr(), "length")) |num| { + if (num.isNumber() or num.isBigInt()) { + args.length = num.to(u52); + } } - if (current.getIfPropertyExists(ctx.ptr(), "position")) |num| { - const position: i32 = if (num.isEmptyOrUndefinedOrNull()) -1 else num.coerce(i32, ctx); - if (position > -1) { - args.position = @intCast(ReadPosition, position); + if (current.getTruthy(ctx.ptr(), "position")) |num| { + if (num.isNumber() or num.isBigInt()) { + args.position = num.to(i52); } } } @@ -2264,7 +2424,7 @@ pub const Arguments = struct { return CopyFile{ .src = src, .dest = dest, - .mode = @intToEnum(Constants.Copyfile, mode), + .mode = @enumFromInt(Constants.Copyfile, mode), }; } }; @@ -2313,7 +2473,7 @@ pub const Arguments = struct { }; pub const UnwatchFile = void; - pub const Watch = void; + pub const Watch = JSC.Node.FSWatcher.Arguments; pub const WatchFile = void; pub const Fsync = struct { fd: FileDescriptor, @@ -2350,6 +2510,18 @@ pub const Arguments = struct { }; }; +pub const StatOrNotFound = union(enum) { + stats: Stats, + not_found: void, + + pub fn toJS(this: *StatOrNotFound, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + return switch (this.*) { + .stats => this.stats.toJS(globalObject), + .not_found => JSC.JSValue.undefined, + }; + } +}; + const Return = struct { pub const Access = void; pub const AppendFile = void; @@ -2368,11 +2540,12 @@ const Return = struct { pub const Lchmod = void; pub const Lchown = void; pub const Link = void; - pub const Lstat = Stats; - pub const Mkdir = string; + pub const Lstat = StatOrNotFound; + pub const Mkdir = bun.String; pub const Mkdtemp = JSC.ZigString; pub const Open = FileDescriptor; pub const WriteFile = void; + pub const Readv = Read; pub const Read = struct { bytes_read: u52, @@ -2469,18 +2642,20 @@ const Return = struct { pub const RealpathNative = Realpath; pub const Rename = void; pub const Rmdir = void; - pub const Stat = Stats; + pub const Stat = StatOrNotFound; pub const Symlink = void; pub const Truncate = void; pub const Unlink = void; pub const UnwatchFile = void; - pub const Watch = void; + pub const Watch = JSC.JSValue; pub const WatchFile = void; pub const Utimes = void; pub const Chown = void; pub const Lutimes = void; + + pub const Writev = Write; }; /// Bun's implementation of the Node.js "fs" module @@ -2493,12 +2668,13 @@ pub const NodeFS = struct { /// That means a stack-allocated buffer won't suffice. Instead, we re-use /// the heap allocated buffer on the NodefS struct sync_error_buf: [bun.MAX_PATH_BYTES]u8 = undefined, + vm: ?*JSC.VirtualMachine = null, pub const ReturnType = Return; pub fn access(this: *NodeFS, args: Arguments.Access, comptime _: Flavor) Maybe(Return.Access) { var path = args.path.sliceZ(&this.sync_error_buf); - const rc = Syscall.system.access(path, @enumToInt(args.mode)); + const rc = Syscall.system.access(path, @intFromEnum(args.mode)); return Maybe(Return.Access).errnoSysP(rc, .access, path) orelse Maybe(Return.Access).success; } @@ -2528,7 +2704,7 @@ pub const NodeFS = struct { const path = path_.sliceZ(&this.sync_error_buf); switch (comptime flavor) { .sync => { - const fd = switch (Syscall.open(path, @enumToInt(FileSystemFlags.a), 0o000666)) { + const fd = switch (Syscall.open(path, @intFromEnum(FileSystemFlags.a), 0o000666)) { .result => |result| result, .err => |err| return .{ .err = err }, }; @@ -2594,7 +2770,7 @@ pub const NodeFS = struct { }; if (!os.S.ISREG(stat_.mode)) { - return Maybe(Return.CopyFile){ .err = .{ .errno = @enumToInt(C.SystemErrno.ENOTSUP) } }; + return Maybe(Return.CopyFile){ .err = .{ .errno = @intFromEnum(C.SystemErrno.ENOTSUP) } }; } // 64 KB is about the break-even point for clonefile() to be worth it @@ -2723,7 +2899,7 @@ pub const NodeFS = struct { }; if (!os.S.ISREG(stat_.mode)) { - return Maybe(Return.CopyFile){ .err = .{ .errno = @enumToInt(C.SystemErrno.ENOTSUP) } }; + return Maybe(Return.CopyFile){ .err = .{ .errno = @intFromEnum(C.SystemErrno.ENOTSUP) } }; } var flags: Mode = std.os.O.CREAT | std.os.O.WRONLY; @@ -2978,8 +3154,13 @@ pub const NodeFS = struct { &this.sync_error_buf, ), )) { - .result => |result| Maybe(Return.Lstat){ .result = Return.Lstat.init(result, false) }, - .err => |err| Maybe(Return.Lstat){ .err = err }, + .result => |result| Maybe(Return.Lstat){ .result = .{ .stats = Stats.init(result, args.big_int) } }, + .err => |err| brk: { + if (!args.throw_if_no_entry and err.getErrno() == .NOENT) { + return Maybe(Return.Lstat){ .result = .{ .not_found = {} } }; + } + break :brk Maybe(Return.Lstat){ .err = err }; + }, }; }, else => {}, @@ -2997,7 +3178,7 @@ pub const NodeFS = struct { .sync => { const path = args.path.sliceZ(&this.sync_error_buf); return switch (Syscall.mkdir(path, args.mode)) { - .result => Maybe(Return.Mkdir){ .result = "" }, + .result => Maybe(Return.Mkdir){ .result = bun.String.empty }, .err => |err| Maybe(Return.Mkdir){ .err = err }, }; }, @@ -3026,24 +3207,29 @@ pub const NodeFS = struct { .err => |err| { switch (err.getErrno()) { else => { - @memcpy(&this.sync_error_buf, path.ptr, len); + @memcpy(this.sync_error_buf[0..len], path[0..len]); return .{ .err = err.withPath(this.sync_error_buf[0..len]) }; }, .EXIST => { - return Option{ .result = "" }; + return Option{ .result = bun.String.empty }; }, // continue .NOENT => {}, } }, .result => { - return Option{ .result = args.path.slice() }; + return Option{ + .result = if (args.path == .slice_with_underlying_string) + args.path.slice_with_underlying_string.underlying + else + bun.String.create(args.path.slice()), + }; }, } var working_mem = &this.sync_error_buf; - @memcpy(working_mem, path.ptr, len); + @memcpy(working_mem[0..len], path[0..len]); var i: u16 = len - 1; @@ -3111,10 +3297,9 @@ pub const NodeFS = struct { switch (err.getErrno()) { // handle the race condition .EXIST => { - var display_path: []const u8 = ""; + var display_path = bun.String.empty; if (first_match != std.math.maxInt(u16)) { - // TODO: this leaks memory - display_path = bun.default_allocator.dupe(u8, display_path[0..first_match]) catch unreachable; + display_path = bun.String.create(working_mem[0..first_match]); } return Option{ .result = display_path }; }, @@ -3126,12 +3311,14 @@ pub const NodeFS = struct { } }, .result => { - var display_path = args.path.slice(); - if (first_match != std.math.maxInt(u16)) { - // TODO: this leaks memory - display_path = bun.default_allocator.dupe(u8, display_path[0..first_match]) catch unreachable; - } - return Option{ .result = display_path }; + return Option{ + .result = if (first_match != std.math.maxInt(u16)) + bun.String.create(working_mem[0..first_match]) + else if (args.path == .slice_with_underlying_string) + args.path.slice_with_underlying_string.underlying + else + bun.String.create(args.path.slice()), + }; }, } }, @@ -3146,7 +3333,7 @@ pub const NodeFS = struct { const prefix_slice = args.prefix.slice(); const len = @min(prefix_slice.len, prefix_buf.len -| 7); if (len > 0) { - @memcpy(prefix_buf, prefix_slice.ptr, len); + @memcpy(prefix_buf[0..len], prefix_slice[0..len]); } prefix_buf[len..][0..6].* = "XXXXXX".*; prefix_buf[len..][6] = 0; @@ -3162,15 +3349,15 @@ pub const NodeFS = struct { }; } // std.c.getErrno(rc) returns SUCCESS if rc is null so we call std.c._errno() directly - const errno = @intToEnum(std.c.E, std.c._errno().*); - return .{ .err = Syscall.Error{ .errno = @truncate(Syscall.Error.Int, @enumToInt(errno)), .syscall = .mkdtemp } }; + const errno = @enumFromInt(std.c.E, std.c._errno().*); + return .{ .err = Syscall.Error{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(errno)), .syscall = .mkdtemp } }; } pub fn open(this: *NodeFS, args: Arguments.Open, comptime flavor: Flavor) Maybe(Return.Open) { switch (comptime flavor) { // The sync version does no allocation except when returning the path .sync => { const path = args.path.sliceZ(&this.sync_error_buf); - return switch (Syscall.open(path, @enumToInt(args.flags), args.mode)) { + return switch (Syscall.open(path, @intFromEnum(args.flags), args.mode)) { .err => |err| .{ .err = err.withPath(args.path.slice()), }, @@ -3250,6 +3437,14 @@ pub const NodeFS = struct { ); } + pub fn readv(this: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Read) { + return if (args.position != null) _preadv(this, args, flavor) else _readv(this, args, flavor); + } + + pub fn writev(this: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) { + return if (args.position != null) _pwritev(this, args, flavor) else _writev(this, args, flavor); + } + pub fn write(this: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) { return if (args.position != null) _pwrite(this, args, flavor) else _write(this, args, flavor); } @@ -3301,6 +3496,82 @@ pub const NodeFS = struct { return Maybe(Return.Write).todo; } + fn _preadv(_: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Readv) { + const position = args.position.?; + + switch (comptime flavor) { + .sync => { + return switch (Syscall.preadv(args.fd, args.buffers.buffers.items, position)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ .result = .{ + .bytes_read = @truncate(u52, amt), + } }, + }; + }, + else => {}, + } + + return Maybe(Return.Write).todo; + } + + fn _readv(_: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Readv) { + switch (comptime flavor) { + .sync => { + return switch (Syscall.readv(args.fd, args.buffers.buffers.items)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ .result = .{ + .bytes_read = @truncate(u52, amt), + } }, + }; + }, + else => {}, + } + + return Maybe(Return.Write).todo; + } + + fn _pwritev(_: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) { + const position = args.position.?; + + switch (comptime flavor) { + .sync => { + return switch (Syscall.pwritev(args.fd, args.buffers.buffers.items, position)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ .result = .{ + .bytes_written = @truncate(u52, amt), + } }, + }; + }, + else => {}, + } + + return Maybe(Return.Write).todo; + } + + fn _writev(_: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) { + switch (comptime flavor) { + .sync => { + return switch (Syscall.writev(args.fd, args.buffers.buffers.items)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ .result = .{ + .bytes_written = @truncate(u52, amt), + } }, + }; + }, + else => {}, + } + + return Maybe(Return.Write).todo; + } + pub fn readdir(this: *NodeFS, args: Arguments.Readdir, comptime flavor: Flavor) Maybe(Return.Readdir) { return switch (args.encoding) { .buffer => _readdir( @@ -3443,6 +3714,35 @@ pub const NodeFS = struct { const fd = switch (args.path) { .path => brk: { path = args.path.path.sliceZ(&this.sync_error_buf); + if (this.vm) |vm| { + if (vm.standalone_module_graph) |graph| { + if (graph.find(path)) |file| { + if (args.encoding == .buffer) { + return .{ + .result = .{ + .buffer = Buffer.fromBytes( + bun.default_allocator.dupe(u8, file.contents) catch @panic("out of memory"), + bun.default_allocator, + .Uint8Array, + ), + }, + }; + } else if (comptime string_type == .default) + return .{ + .result = .{ + .string = bun.default_allocator.dupe(u8, file.contents) catch @panic("out of memory"), + }, + } + else + return .{ + .result = .{ + .null_terminated = bun.default_allocator.dupeZ(u8, file.contents) catch @panic("out of memory"), + }, + }; + } + } + } + break :brk switch (Syscall.open( path, os.O.RDONLY | os.O.NOCTTY, @@ -3605,7 +3905,7 @@ pub const NodeFS = struct { break :brk switch (Syscall.openat( args.dirfd, path, - @enumToInt(args.flag) | os.O.NOCTTY, + @intFromEnum(args.flag) | os.O.NOCTTY, args.mode, )) { .err => |err| return .{ @@ -3672,7 +3972,7 @@ pub const NodeFS = struct { } // https://github.com/oven-sh/bun/issues/2931 - if ((@enumToInt(args.flag) & std.os.O.APPEND) == 0) { + if ((@intFromEnum(args.flag) & std.os.O.APPEND) == 0) { _ = ftruncateSync(.{ .fd = fd, .len = @truncate(JSC.WebCore.Blob.SizeType, written) }); } @@ -3819,12 +4119,12 @@ pub const NodeFS = struct { while (true) { if (Maybe(Return.Rmdir).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .rmdir)) |errno| { - switch (@intToEnum(os.E, errno.err.errno)) { + switch (@enumFromInt(os.E, errno.err.errno)) { .AGAIN, .INTR => continue, .NOENT => return Maybe(Return.Rmdir).success, .MLINK => { var copy: [bun.MAX_PATH_BYTES]u8 = undefined; - @memcpy(©, dest.ptr, dest.len); + @memcpy(copy[0..dest.len], dest); copy[dest.len] = 0; var dest_copy = copy[0..dest.len :0]; switch (Syscall.unlink(dest_copy).getErrno()) { @@ -3906,7 +4206,7 @@ pub const NodeFS = struct { } if (Maybe(Return.Rm).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .unlink)) |errno| { - switch (@intToEnum(os.E, errno.err.errno)) { + switch (@enumFromInt(os.E, errno.err.errno)) { .AGAIN, .INTR => continue, .NOENT => { if (args.force) { @@ -3918,7 +4218,7 @@ pub const NodeFS = struct { .MLINK => { var copy: [bun.MAX_PATH_BYTES]u8 = undefined; - @memcpy(©, dest.ptr, dest.len); + @memcpy(copy[0..dest.len], dest); copy[dest.len] = 0; var dest_copy = copy[0..dest.len :0]; switch (Syscall.unlink(dest_copy).getErrno()) { @@ -4063,8 +4363,13 @@ pub const NodeFS = struct { &this.sync_error_buf, ), )) { - .result => |result| Maybe(Return.Stat){ .result = Return.Stat.init(result, false) }, - .err => |err| Maybe(Return.Stat){ .err = err }, + .result => |result| Maybe(Return.Stat){ .result = .{ .stats = Stats.init(result, args.big_int) } }, + .err => |err| brk: { + if (!args.throw_if_no_entry and err.getErrno() == .NOENT) { + return Maybe(Return.Stat){ .result = .{ .not_found = {} } }; + } + break :brk Maybe(Return.Stat){ .err = err }; + }, }); }, else => {}, @@ -4181,8 +4486,12 @@ pub const NodeFS = struct { return Maybe(Return.Lutimes).todo; } - pub fn watch(_: *NodeFS, _: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) { - return Maybe(Return.Watch).todo; + pub fn watch(_: *NodeFS, args: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) { + const watcher = args.createFSWatcher() catch |err| { + args.global_this.throwError(err, "Failed to watch filename"); + return Maybe(Return.Watch){ .result = JSC.JSValue.jsUndefined() }; + }; + return Maybe(Return.Watch){ .result = watcher }; } pub fn createReadStream(_: *NodeFS, _: Arguments.CreateReadStream, comptime _: Flavor) Maybe(Return.CreateReadStream) { return Maybe(Return.CreateReadStream).todo; diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig index 74b769bf6..a4cc62cd3 100644 --- a/src/bun.js/node/node_fs_binding.zig +++ b/src/bun.js/node/node_fs_binding.zig @@ -229,6 +229,10 @@ pub const NodeJSFS = struct { pub const lutimesSync = callSync(.lutimes); pub const rmSync = callSync(.rm); pub const rmdirSync = callSync(.rmdir); + pub const writev = call(.writev); + pub const writevSync = callSync(.writev); + pub const readv = call(.readv); + pub const readvSync = callSync(.readv); pub const fdatasyncSync = callSync(.fdatasync); pub const fdatasync = call(.fdatasync); @@ -241,12 +245,10 @@ pub const NodeJSFS = struct { return JSC.Node.Stats.getConstructor(globalThis); } + pub const watch = callSync(.watch); + // Not implemented yet: const notimpl = fdatasync; pub const opendir = notimpl; pub const opendirSync = notimpl; - pub const readv = notimpl; - pub const readvSync = notimpl; - pub const writev = notimpl; - pub const writevSync = notimpl; }; diff --git a/src/bun.js/node/node_fs_constant.zig b/src/bun.js/node/node_fs_constant.zig index 378f332c6..8e642ebad 100644 --- a/src/bun.js/node/node_fs_constant.zig +++ b/src/bun.js/node/node_fs_constant.zig @@ -26,17 +26,17 @@ pub const Constants = struct { pub const force = 4; pub inline fn isForceClone(this: Copyfile) bool { - return (@enumToInt(this) & COPYFILE_FICLONE_FORCE) != 0; + return (@intFromEnum(this) & COPYFILE_FICLONE_FORCE) != 0; } pub inline fn shouldntOverwrite(this: Copyfile) bool { - return (@enumToInt(this) & COPYFILE_EXCL) != 0; + return (@intFromEnum(this) & COPYFILE_EXCL) != 0; } pub inline fn canUseClone(this: Copyfile) bool { _ = this; return Environment.isMac; - // return (@enumToInt(this) | COPYFILE_FICLONE) != 0; + // return (@intFromEnum(this) | COPYFILE_FICLONE) != 0; } }; diff --git a/src/bun.js/node/node_fs_watcher.zig b/src/bun.js/node/node_fs_watcher.zig new file mode 100644 index 000000000..d0af350c0 --- /dev/null +++ b/src/bun.js/node/node_fs_watcher.zig @@ -0,0 +1,919 @@ +const std = @import("std"); +const JSC = @import("root").bun.JSC; +const bun = @import("root").bun; +const Fs = @import("../../fs.zig"); +const Path = @import("../../resolver/resolve_path.zig"); +const Encoder = JSC.WebCore.Encoder; +const Mutex = @import("../../lock.zig").Lock; + +const FSEvents = @import("./fs_events.zig"); + +const VirtualMachine = JSC.VirtualMachine; +const EventLoop = JSC.EventLoop; +const PathLike = JSC.Node.PathLike; +const ArgumentsSlice = JSC.Node.ArgumentsSlice; +const Output = bun.Output; +const string = bun.string; +const StoredFileDescriptorType = bun.StoredFileDescriptorType; +const Environment = bun.Environment; + +pub const FSWatcher = struct { + const watcher = @import("../../watcher.zig"); + const options = @import("../../options.zig"); + pub const Watcher = watcher.NewWatcher(*FSWatcher); + const log = Output.scoped(.FSWatcher, false); + + pub const ChangeEvent = struct { + hash: Watcher.HashType = 0, + event_type: FSWatchTask.EventType = .change, + time_stamp: i64 = 0, + }; + + onAccept: std.ArrayHashMapUnmanaged(FSWatcher.Watcher.HashType, bun.BabyList(OnAcceptCallback), bun.ArrayIdentityContext, false) = .{}, + ctx: *VirtualMachine, + verbose: bool = false, + file_paths: bun.BabyList(string) = .{}, + entry_path: ?string = null, + entry_dir: string = "", + last_change_event: ChangeEvent = .{}, + + // JSObject + mutex: Mutex, + signal: ?*JSC.AbortSignal, + persistent: bool, + default_watcher: ?*FSWatcher.Watcher, + fsevents_watcher: ?*FSEvents.FSEventsWatcher, + poll_ref: JSC.PollRef = .{}, + globalThis: *JSC.JSGlobalObject, + js_this: JSC.JSValue, + encoding: JSC.Node.Encoding, + // user can call close and pre-detach so we need to track this + closed: bool, + // counts pending tasks so we only deinit after all tasks are done + task_count: u32, + has_pending_activity: std.atomic.Atomic(bool), + pub usingnamespace JSC.Codegen.JSFSWatcher; + + pub fn eventLoop(this: FSWatcher) *EventLoop { + return this.ctx.eventLoop(); + } + + pub fn enqueueTaskConcurrent(this: FSWatcher, task: *JSC.ConcurrentTask) void { + this.eventLoop().enqueueTaskConcurrent(task); + } + + pub fn deinit(this: *FSWatcher) void { + // stop all managers and signals + this.detach(); + + while (this.file_paths.popOrNull()) |file_path| { + bun.default_allocator.destroy(file_path); + } + this.file_paths.deinitWithAllocator(bun.default_allocator); + if (this.entry_path) |path| { + this.entry_path = null; + bun.default_allocator.destroy(path); + } + bun.default_allocator.destroy(this); + } + + pub const FSWatchTask = struct { + ctx: *FSWatcher, + count: u8 = 0, + + entries: [8]Entry = undefined, + concurrent_task: JSC.ConcurrentTask = undefined, + + pub const EventType = enum { + rename, + change, + @"error", + abort, + }; + + pub const EventFreeType = enum { + destroy, + free, + none, + }; + + pub const Entry = struct { + file_path: string, + event_type: EventType, + free_type: EventFreeType, + }; + + pub fn append(this: *FSWatchTask, file_path: string, event_type: EventType, free_type: EventFreeType) void { + if (this.count == 8) { + this.enqueue(); + var ctx = this.ctx; + this.* = .{ + .ctx = ctx, + .count = 0, + }; + } + + this.entries[this.count] = .{ + .file_path = file_path, + .event_type = event_type, + .free_type = free_type, + }; + this.count += 1; + } + + pub fn run(this: *FSWatchTask) void { + // this runs on JS Context Thread + + for (this.entries[0..this.count]) |entry| { + switch (entry.event_type) { + .rename => { + this.ctx.emit(entry.file_path, "rename"); + }, + .change => { + this.ctx.emit(entry.file_path, "change"); + }, + .@"error" => { + // file_path is the error message in this case + this.ctx.emitError(entry.file_path); + }, + .abort => { + this.ctx.emitIfAborted(); + }, + } + } + + this.ctx.unrefTask(); + } + + pub fn enqueue(this: *FSWatchTask) void { + if (this.count == 0) + return; + + // if false is closed or detached (can still contain valid refs but will not create a new one) + if (this.ctx.refTask()) { + var that = bun.default_allocator.create(FSWatchTask) catch unreachable; + + that.* = this.*; + this.count = 0; + that.concurrent_task.task = JSC.Task.init(that); + this.ctx.enqueueTaskConcurrent(&that.concurrent_task); + return; + } + // closed or detached so just cleanEntries + this.cleanEntries(); + } + pub fn cleanEntries(this: *FSWatchTask) void { + while (this.count > 0) { + this.count -= 1; + switch (this.entries[this.count].free_type) { + .destroy => bun.default_allocator.destroy(this.entries[this.count].file_path), + .free => bun.default_allocator.free(this.entries[this.count].file_path), + else => {}, + } + } + } + + pub fn deinit(this: *FSWatchTask) void { + this.cleanEntries(); + bun.default_allocator.destroy(this); + } + }; + + fn NewCallback(comptime FunctionSignature: type) type { + return union(enum) { + javascript_callback: JSC.Strong, + zig_callback: struct { + ptr: *anyopaque, + function: *const FunctionSignature, + }, + }; + } + + pub const OnAcceptCallback = NewCallback(fn ( + vm: *JSC.VirtualMachine, + specifier: []const u8, + ) void); + + fn addDirectory(ctx: *FSWatcher, fs_watcher: *FSWatcher.Watcher, fd: StoredFileDescriptorType, file_path: string, recursive: bool, buf: *[bun.MAX_PATH_BYTES + 1]u8, is_entry_path: bool) !void { + var dir_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable; + + if (is_entry_path) { + ctx.entry_path = dir_path_clone; + ctx.entry_dir = dir_path_clone; + } else { + ctx.file_paths.push(bun.default_allocator, dir_path_clone) catch unreachable; + } + fs_watcher.addDirectory(fd, dir_path_clone, FSWatcher.Watcher.getHash(file_path), false) catch |err| { + ctx.deinit(); + fs_watcher.deinit(true); + return err; + }; + + var iter = (std.fs.IterableDir{ .dir = std.fs.Dir{ + .fd = fd, + } }).iterate(); + + while (iter.next() catch |err| { + ctx.deinit(); + fs_watcher.deinit(true); + return err; + }) |entry| { + var parts = [2]string{ dir_path_clone, entry.name }; + var entry_path = Path.joinAbsStringBuf( + Fs.FileSystem.instance.topLevelDirWithoutTrailingSlash(), + buf, + &parts, + .auto, + ); + + buf[entry_path.len] = 0; + var entry_path_z = buf[0..entry_path.len :0]; + + var fs_info = fdFromAbsolutePathZ(entry_path_z) catch |err| { + ctx.deinit(); + fs_watcher.deinit(true); + return err; + }; + + if (fs_info.is_file) { + const file_path_clone = bun.default_allocator.dupeZ(u8, entry_path) catch unreachable; + + ctx.file_paths.push(bun.default_allocator, file_path_clone) catch unreachable; + + fs_watcher.addFile(fs_info.fd, file_path_clone, FSWatcher.Watcher.getHash(entry_path), options.Loader.file, 0, null, false) catch |err| { + ctx.deinit(); + fs_watcher.deinit(true); + return err; + }; + } else { + if (recursive) { + addDirectory(ctx, fs_watcher, fs_info.fd, entry_path, recursive, buf, false) catch |err| { + ctx.deinit(); + fs_watcher.deinit(true); + return err; + }; + } + } + } + } + + pub fn onError( + this: *FSWatcher, + err: anyerror, + ) void { + var current_task: FSWatchTask = .{ + .ctx = this, + }; + current_task.append(@errorName(err), .@"error", .none); + current_task.enqueue(); + } + + pub fn onFSEventUpdate( + ctx: ?*anyopaque, + path: string, + _: bool, + is_rename: bool, + ) void { + const this = bun.cast(*FSWatcher, ctx.?); + + var current_task: FSWatchTask = .{ + .ctx = this, + }; + defer current_task.enqueue(); + + const relative_path = bun.default_allocator.dupe(u8, path) catch unreachable; + const event_type: FSWatchTask.EventType = if (is_rename) .rename else .change; + + current_task.append(relative_path, event_type, .destroy); + } + + pub fn onFileUpdate( + this: *FSWatcher, + events: []watcher.WatchEvent, + changed_files: []?[:0]u8, + watchlist: watcher.Watchlist, + ) void { + var slice = watchlist.slice(); + const file_paths = slice.items(.file_path); + + var counts = slice.items(.count); + const kinds = slice.items(.kind); + var _on_file_update_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + + var ctx = this.default_watcher.?; + defer ctx.flushEvictions(); + defer Output.flush(); + + var bundler = if (@TypeOf(this.ctx.bundler) == *bun.Bundler) + this.ctx.bundler + else + &this.ctx.bundler; + + var fs: *Fs.FileSystem = bundler.fs; + + var current_task: FSWatchTask = .{ + .ctx = this, + }; + defer current_task.enqueue(); + + const time_stamp = std.time.milliTimestamp(); + const time_diff = time_stamp - this.last_change_event.time_stamp; + + for (events) |event| { + const file_path = file_paths[event.index]; + const update_count = counts[event.index] + 1; + counts[event.index] = update_count; + const kind = kinds[event.index]; + + if (comptime Environment.isDebug) { + if (this.verbose) { + Output.prettyErrorln("[watch] {s} ({s}, {})", .{ file_path, @tagName(kind), event.op }); + } + } + + switch (kind) { + .file => { + if (event.op.delete) { + ctx.removeAtIndex( + event.index, + 0, + &.{}, + .file, + ); + } + + var file_hash: FSWatcher.Watcher.HashType = FSWatcher.Watcher.getHash(file_path); + + if (event.op.write or event.op.delete or event.op.rename) { + const event_type: FSWatchTask.EventType = if (event.op.delete or event.op.rename or event.op.move_to) .rename else .change; + // skip consecutive duplicates + if ((this.last_change_event.time_stamp == 0 or time_diff > 1) or this.last_change_event.event_type != event_type and this.last_change_event.hash != file_hash) { + this.last_change_event.time_stamp = time_stamp; + this.last_change_event.event_type = event_type; + this.last_change_event.hash = file_hash; + + const relative_slice = fs.relative(this.entry_dir, file_path); + + if (this.verbose) + Output.prettyErrorln("<r><d>File changed: {s}<r>", .{relative_slice}); + + const relative_path = bun.default_allocator.dupe(u8, relative_slice) catch unreachable; + + current_task.append(relative_path, event_type, .destroy); + } + } + }, + .directory => { + // macOS should use FSEvents for directories + if (comptime Environment.isMac) { + @panic("Unexpected directory watch"); + } + + const affected = event.names(changed_files); + + for (affected) |changed_name_| { + const changed_name: []const u8 = bun.asByteSlice(changed_name_.?); + if (changed_name.len == 0 or changed_name[0] == '~' or changed_name[0] == '.') continue; + + var file_hash: FSWatcher.Watcher.HashType = 0; + const relative_slice: string = brk: { + var file_path_without_trailing_slash = std.mem.trimRight(u8, file_path, std.fs.path.sep_str); + + @memcpy(_on_file_update_path_buf[0..file_path_without_trailing_slash.len], file_path_without_trailing_slash); + + _on_file_update_path_buf[file_path_without_trailing_slash.len] = std.fs.path.sep; + + @memcpy(_on_file_update_path_buf[file_path_without_trailing_slash.len + 1 ..][0..changed_name.len], changed_name); + const path_slice = _on_file_update_path_buf[0 .. file_path_without_trailing_slash.len + changed_name.len + 1]; + file_hash = FSWatcher.Watcher.getHash(path_slice); + + const relative = fs.relative(this.entry_dir, path_slice); + + break :brk relative; + }; + + // skip consecutive duplicates + const event_type: FSWatchTask.EventType = .rename; // renaming folders, creating folder or files will be always be rename + if ((this.last_change_event.time_stamp == 0 or time_diff > 1) or this.last_change_event.event_type != event_type and this.last_change_event.hash != file_hash) { + const relative_path = bun.default_allocator.dupe(u8, relative_slice) catch unreachable; + + this.last_change_event.time_stamp = time_stamp; + this.last_change_event.event_type = event_type; + this.last_change_event.hash = file_hash; + + current_task.append(relative_path, event_type, .destroy); + + if (this.verbose) + Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{relative_path}); + } + } + + if (this.verbose and affected.len == 0) { + Output.prettyErrorln("<r> <d>Dir change: {s}<r>", .{fs.relative(this.entry_dir, file_path)}); + } + }, + } + } + } + + pub const Arguments = struct { + path: PathLike, + listener: JSC.JSValue, + global_this: JSC.C.JSContextRef, + signal: ?*JSC.AbortSignal, + persistent: bool, + recursive: bool, + encoding: JSC.Node.Encoding, + verbose: bool, + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Arguments { + const vm = ctx.vm(); + const path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "filename must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + var listener: JSC.JSValue = .zero; + var signal: ?*JSC.AbortSignal = null; + var persistent: bool = true; + var recursive: bool = false; + var encoding: JSC.Node.Encoding = .utf8; + var verbose = false; + if (arguments.nextEat()) |options_or_callable| { + + // options + if (options_or_callable.isObject()) { + if (options_or_callable.get(ctx, "persistent")) |persistent_| { + if (!persistent_.isBoolean()) { + JSC.throwInvalidArguments( + "persistent must be a boolean.", + .{}, + ctx, + exception, + ); + return null; + } + persistent = persistent_.toBoolean(); + } + + if (options_or_callable.get(ctx, "verbose")) |verbose_| { + if (!verbose_.isBoolean()) { + JSC.throwInvalidArguments( + "verbose must be a boolean.", + .{}, + ctx, + exception, + ); + return null; + } + verbose = verbose_.toBoolean(); + } + + if (options_or_callable.get(ctx, "encoding")) |encoding_| { + if (!encoding_.isString()) { + JSC.throwInvalidArguments( + "encoding must be a string.", + .{}, + ctx, + exception, + ); + return null; + } + if (JSC.Node.Encoding.fromJS(encoding_, ctx.ptr())) |node_encoding| { + encoding = node_encoding; + } else { + JSC.throwInvalidArguments( + "invalid encoding.", + .{}, + ctx, + exception, + ); + return null; + } + } + + if (options_or_callable.get(ctx, "recursive")) |recursive_| { + if (!recursive_.isBoolean()) { + JSC.throwInvalidArguments( + "recursive must be a boolean.", + .{}, + ctx, + exception, + ); + return null; + } + recursive = recursive_.toBoolean(); + } + + // abort signal + if (options_or_callable.get(ctx, "signal")) |signal_| { + if (JSC.AbortSignal.fromJS(signal_)) |signal_obj| { + //Keep it alive + signal_.ensureStillAlive(); + signal = signal_obj; + } else { + JSC.throwInvalidArguments( + "signal is not of type AbortSignal.", + .{}, + ctx, + exception, + ); + + return null; + } + } + + // listener + if (arguments.nextEat()) |callable| { + if (!callable.isCell() or !callable.isCallable(vm)) { + exception.* = JSC.toInvalidArguments("Expected \"listener\" callback to be a function", .{}, ctx).asObjectRef(); + return null; + } + listener = callable; + } + } else { + if (!options_or_callable.isCell() or !options_or_callable.isCallable(vm)) { + exception.* = JSC.toInvalidArguments("Expected \"listener\" callback to be a function", .{}, ctx).asObjectRef(); + return null; + } + listener = options_or_callable; + } + } + if (listener == .zero) { + exception.* = JSC.toInvalidArguments("Expected \"listener\" callback", .{}, ctx).asObjectRef(); + return null; + } + + return Arguments{ + .path = path, + .listener = listener, + .global_this = ctx, + .signal = signal, + .persistent = persistent, + .recursive = recursive, + .encoding = encoding, + .verbose = verbose, + }; + } + + pub fn createFSWatcher(this: Arguments) !JSC.JSValue { + const obj = try FSWatcher.init(this); + if (obj.js_this != .zero) { + return obj.js_this; + } + return JSC.JSValue.jsUndefined(); + } + }; + + pub fn initJS(this: *FSWatcher, listener: JSC.JSValue) void { + if (this.persistent) { + this.poll_ref.ref(this.ctx); + } + + const js_this = FSWatcher.toJS(this, this.globalThis); + js_this.ensureStillAlive(); + this.js_this = js_this; + FSWatcher.listenerSetCached(js_this, this.globalThis, listener); + + if (this.signal) |s| { + // already aborted? + if (s.aborted()) { + // safely abort next tick + var current_task: FSWatchTask = .{ + .ctx = this, + }; + current_task.append("", .abort, .none); + current_task.enqueue(); + } else { + // watch for abortion + this.signal = s.listen(FSWatcher, this, FSWatcher.emitAbort); + } + } + } + + pub fn emitIfAborted(this: *FSWatcher) void { + if (this.signal) |s| { + if (s.aborted()) { + const err = s.abortReason(); + this.emitAbort(err); + } + } + } + + pub fn emitAbort(this: *FSWatcher, err: JSC.JSValue) void { + if (this.closed) return; + defer this.close(); + + err.ensureStillAlive(); + if (this.js_this != .zero) { + const js_this = this.js_this; + js_this.ensureStillAlive(); + if (FSWatcher.listenerGetCached(js_this)) |listener| { + listener.ensureStillAlive(); + var args = [_]JSC.JSValue{ + JSC.ZigString.static("error").toValue(this.globalThis), + if (err.isEmptyOrUndefinedOrNull()) JSC.WebCore.AbortSignal.createAbortError(JSC.ZigString.static("The user aborted a request"), &JSC.ZigString.Empty, this.globalThis) else err, + }; + _ = listener.callWithGlobalThis( + this.globalThis, + &args, + ); + } + } + } + pub fn emitError(this: *FSWatcher, err: string) void { + if (this.closed) return; + defer this.close(); + + if (this.js_this != .zero) { + const js_this = this.js_this; + js_this.ensureStillAlive(); + if (FSWatcher.listenerGetCached(js_this)) |listener| { + listener.ensureStillAlive(); + var args = [_]JSC.JSValue{ + JSC.ZigString.static("error").toValue(this.globalThis), + JSC.ZigString.fromUTF8(err).toErrorInstance(this.globalThis), + }; + _ = listener.callWithGlobalThis( + this.globalThis, + &args, + ); + } + } + } + + pub fn emit(this: *FSWatcher, file_name: string, comptime eventType: string) void { + if (this.js_this != .zero) { + const js_this = this.js_this; + js_this.ensureStillAlive(); + if (FSWatcher.listenerGetCached(js_this)) |listener| { + listener.ensureStillAlive(); + var filename: JSC.JSValue = JSC.JSValue.jsUndefined(); + if (file_name.len > 0) { + if (this.encoding == .buffer) + filename = JSC.ArrayBuffer.createBuffer(this.globalThis, file_name) + else if (this.encoding == .utf8) { + filename = JSC.ZigString.fromUTF8(file_name).toValueGC(this.globalThis); + } else { + // convert to desired encoding + filename = Encoder.toStringAtRuntime(file_name.ptr, file_name.len, this.globalThis, this.encoding); + } + } + var args = [_]JSC.JSValue{ + JSC.ZigString.static(eventType).toValue(this.globalThis), + filename, + }; + _ = listener.callWithGlobalThis( + this.globalThis, + &args, + ); + } + } + } + + pub fn doRef(this: *FSWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + if (!this.closed and !this.persistent) { + this.persistent = true; + this.poll_ref.ref(this.ctx); + } + return JSC.JSValue.jsUndefined(); + } + + pub fn doUnref(this: *FSWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + if (this.persistent) { + this.persistent = false; + this.poll_ref.unref(this.ctx); + } + return JSC.JSValue.jsUndefined(); + } + + pub fn hasRef(this: *FSWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + return JSC.JSValue.jsBoolean(this.persistent); + } + + // this can be called from Watcher Thread or JS Context Thread + pub fn refTask(this: *FSWatcher) bool { + this.mutex.lock(); + defer this.mutex.unlock(); + // stop new references + if (this.closed) return false; + this.task_count += 1; + return true; + } + + pub fn hasPendingActivity(this: *FSWatcher) callconv(.C) bool { + @fence(.Acquire); + return this.has_pending_activity.load(.Acquire); + } + // only called from Main Thread + pub fn updateHasPendingActivity(this: *FSWatcher) void { + @fence(.Release); + this.has_pending_activity.store(false, .Release); + } + + // unref is always called on main JS Context Thread + pub fn unrefTask(this: *FSWatcher) void { + this.mutex.lock(); + defer this.mutex.unlock(); + this.task_count -= 1; + if (this.closed and this.task_count == 0) { + this.updateHasPendingActivity(); + } + } + + pub fn close( + this: *FSWatcher, + ) void { + this.mutex.lock(); + if (!this.closed) { + this.closed = true; + + // emit should only be called unlocked + this.mutex.unlock(); + + this.emit("", "close"); + // we immediately detach here + this.detach(); + + // no need to lock again, because ref checks closed and unref is only called on main thread + if (this.task_count == 0) { + this.updateHasPendingActivity(); + } + } else { + this.mutex.unlock(); + } + } + + // this can be called multiple times + pub fn detach(this: *FSWatcher) void { + if (this.persistent) { + this.persistent = false; + this.poll_ref.unref(this.ctx); + } + + if (this.signal) |signal| { + this.signal = null; + signal.detach(this); + } + + if (this.default_watcher) |default_watcher| { + this.default_watcher = null; + default_watcher.deinit(true); + } + + if (this.fsevents_watcher) |fsevents_watcher| { + this.fsevents_watcher = null; + fsevents_watcher.deinit(); + } + + this.js_this = .zero; + } + + pub fn doClose(this: *FSWatcher, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + this.close(); + return JSC.JSValue.jsUndefined(); + } + + pub fn finalize(this: *FSWatcher) callconv(.C) void { + this.deinit(); + } + + const PathResult = struct { + fd: StoredFileDescriptorType = 0, + is_file: bool = true, + }; + + fn fdFromAbsolutePathZ( + absolute_path_z: [:0]const u8, + ) !PathResult { + var stat = try bun.C.lstat_absolute(absolute_path_z); + var result = PathResult{}; + + switch (stat.kind) { + .sym_link => { + var file = try std.fs.openFileAbsoluteZ(absolute_path_z, .{ .mode = .read_only }); + result.fd = file.handle; + const _stat = try file.stat(); + + result.is_file = _stat.kind != .directory; + }, + .directory => { + const dir = (try std.fs.openIterableDirAbsoluteZ(absolute_path_z, .{ + .access_sub_paths = true, + })).dir; + result.fd = dir.fd; + result.is_file = false; + }, + else => { + const file = try std.fs.openFileAbsoluteZ(absolute_path_z, .{ .mode = .read_only }); + result.fd = file.handle; + result.is_file = true; + }, + } + return result; + } + + pub fn init(args: Arguments) !*FSWatcher { + var buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined; + var slice = args.path.slice(); + if (bun.strings.startsWith(slice, "file://")) { + slice = slice[6..]; + } + var parts = [_]string{ + slice, + }; + + var file_path = Path.joinAbsStringBuf( + Fs.FileSystem.instance.top_level_dir, + &buf, + &parts, + .auto, + ); + + buf[file_path.len] = 0; + var file_path_z = buf[0..file_path.len :0]; + + var fs_type = try fdFromAbsolutePathZ(file_path_z); + + var ctx = try bun.default_allocator.create(FSWatcher); + const vm = args.global_this.bunVM(); + ctx.* = .{ + .ctx = vm, + .mutex = Mutex.init(), + .signal = if (args.signal) |s| s.ref() else null, + .persistent = args.persistent, + .default_watcher = null, + .fsevents_watcher = null, + .globalThis = args.global_this, + .js_this = .zero, + .encoding = args.encoding, + .closed = false, + .task_count = 0, + .has_pending_activity = std.atomic.Atomic(bool).init(true), + .verbose = args.verbose, + .file_paths = bun.BabyList(string).initCapacity(bun.default_allocator, 1) catch |err| { + ctx.deinit(); + return err; + }, + }; + + if (comptime Environment.isMac) { + if (!fs_type.is_file) { + var dir_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable; + ctx.entry_path = dir_path_clone; + ctx.entry_dir = dir_path_clone; + + ctx.fsevents_watcher = FSEvents.watch(dir_path_clone, args.recursive, onFSEventUpdate, bun.cast(*anyopaque, ctx)) catch |err| { + ctx.deinit(); + return err; + }; + + ctx.initJS(args.listener); + return ctx; + } + } + + var default_watcher = FSWatcher.Watcher.init( + ctx, + vm.bundler.fs, + bun.default_allocator, + ) catch |err| { + ctx.deinit(); + return err; + }; + + ctx.default_watcher = default_watcher; + + if (fs_type.is_file) { + var file_path_clone = bun.default_allocator.dupeZ(u8, file_path) catch unreachable; + + ctx.entry_path = file_path_clone; + ctx.entry_dir = std.fs.path.dirname(file_path_clone) orelse file_path_clone; + + default_watcher.addFile(fs_type.fd, file_path_clone, FSWatcher.Watcher.getHash(file_path), options.Loader.file, 0, null, false) catch |err| { + ctx.deinit(); + return err; + }; + } else { + addDirectory(ctx, default_watcher, fs_type.fd, file_path, args.recursive, &buf, true) catch |err| { + ctx.deinit(); + return err; + }; + } + + default_watcher.start() catch |err| { + ctx.deinit(); + return err; + }; + + ctx.initJS(args.listener); + return ctx; + } +}; diff --git a/src/bun.js/node/node_os.zig b/src/bun.js/node/node_os.zig index 4b37640cf..483acb3e2 100644 --- a/src/bun.js/node/node_os.zig +++ b/src/bun.js/node/node_os.zig @@ -16,7 +16,7 @@ pub const Os = struct { pub const code = @embedFile("../os.exports.js"); pub fn create(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { - const module = JSC.JSValue.createEmptyObject(globalObject, 20); + const module = JSC.JSValue.createEmptyObject(globalObject, 22); module.put(globalObject, JSC.ZigString.static("arch"), JSC.NewFunction(globalObject, JSC.ZigString.static("arch"), 0, arch, true)); module.put(globalObject, JSC.ZigString.static("cpus"), JSC.NewFunction(globalObject, JSC.ZigString.static("cpus"), 0, cpus, true)); @@ -31,7 +31,6 @@ pub const Os = struct { module.put(globalObject, JSC.ZigString.static("platform"), JSC.NewFunction(globalObject, JSC.ZigString.static("platform"), 0, platform, true)); module.put(globalObject, JSC.ZigString.static("release"), JSC.NewFunction(globalObject, JSC.ZigString.static("release"), 0, release, true)); module.put(globalObject, JSC.ZigString.static("setPriority"), JSC.NewFunction(globalObject, JSC.ZigString.static("setPriority"), 2, setPriority, true)); - module.put(globalObject, JSC.ZigString.static("tmpdir"), JSC.NewFunction(globalObject, JSC.ZigString.static("tmpdir"), 0, tmpdir, true)); module.put(globalObject, JSC.ZigString.static("totalmem"), JSC.NewFunction(globalObject, JSC.ZigString.static("totalmem"), 0, totalmem, true)); module.put(globalObject, JSC.ZigString.static("type"), JSC.NewFunction(globalObject, JSC.ZigString.static("type"), 0, Os.type, true)); module.put(globalObject, JSC.ZigString.static("uptime"), JSC.NewFunction(globalObject, JSC.ZigString.static("uptime"), 0, uptime, true)); @@ -79,8 +78,8 @@ pub const Os = struct { return if (comptime Environment.isLinux) cpusImplLinux(globalThis) catch { const err = JSC.SystemError{ - .message = JSC.ZigString.init("Failed to get cpu information"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + .message = bun.String.static("Failed to get cpu information"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), }; globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); @@ -89,8 +88,8 @@ pub const Os = struct { else if (comptime Environment.isMac) cpusImplDarwin(globalThis) catch { const err = JSC.SystemError{ - .message = JSC.ZigString.init("Failed to get cpu information"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + .message = bun.String.static("Failed to get cpu information"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), }; globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); @@ -211,7 +210,7 @@ pub const Os = struct { if (local_bindings.host_processor_info(std.c.mach_host_self(), local_bindings.PROCESSOR_CPU_LOAD_INFO, &num_cpus, @ptrCast(*local_bindings.processor_info_array_t, &info), &info_size) != .SUCCESS) { return error.no_processor_info; } - defer _ = std.c.vm_deallocate(std.c.mach_task_self(), @ptrToInt(info), info_size); + defer _ = std.c.vm_deallocate(std.c.mach_task_self(), @intFromPtr(info), info_size); // Ensure we got the amount of data we expected to guard against buffer overruns if (info_size != C.PROCESSOR_CPU_LOAD_INFO_COUNT * num_cpus) { @@ -319,11 +318,11 @@ pub const Os = struct { //info.put(globalThis, JSC.ZigString.static("syscall"), JSC.ZigString.init("uv_os_getpriority").withEncoding().toValueGC(globalThis)); const err = JSC.SystemError{ - .message = JSC.ZigString.init("A system error occurred: uv_os_getpriority returned ESRCH (no such process)"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + .message = bun.String.static("A system error occurred: uv_os_getpriority returned ESRCH (no such process)"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), //.info = info, .errno = -3, - .syscall = JSC.ZigString.init("uv_os_getpriority"), + .syscall = bun.String.static("uv_os_getpriority"), }; globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); @@ -378,10 +377,10 @@ pub const Os = struct { const rc = C.getifaddrs(&interface_start); if (rc != 0) { const err = JSC.SystemError{ - .message = JSC.ZigString.init("A system error occurred: getifaddrs returned an error"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), - .errno = @enumToInt(std.os.errno(rc)), - .syscall = JSC.ZigString.init("getifaddrs"), + .message = bun.String.static("A system error occurred: getifaddrs returned an error"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + .errno = @intFromEnum(std.os.errno(rc)), + .syscall = bun.String.static("getifaddrs"), }; globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); @@ -461,7 +460,7 @@ pub const Os = struct { var cidr = JSC.JSValue.null; if (maybe_suffix) |suffix| { //NOTE addr_str might not start at buf[0] due to slicing in formatIp - const start = @ptrToInt(addr_str.ptr) - @ptrToInt(&buf[0]); + const start = @intFromPtr(addr_str.ptr) - @intFromPtr(&buf[0]); // Start writing the suffix immediately after the address const suffix_str = std.fmt.bufPrint(buf[start + addr_str.len ..], "/{}", .{suffix}) catch unreachable; // The full cidr value is the address + the suffix @@ -485,7 +484,7 @@ pub const Os = struct { std.os.AF.INET => JSC.ZigString.static("IPv4"), std.os.AF.INET6 => JSC.ZigString.static("IPv6"), else => JSC.ZigString.static("unknown"), - }).toValue(globalThis)); + }).toValueGC(globalThis)); // mac <string> The MAC address of the network interface { @@ -592,11 +591,11 @@ pub const Os = struct { switch (errcode) { .SRCH => { const err = JSC.SystemError{ - .message = JSC.ZigString.init("A system error occurred: uv_os_setpriority returned ESRCH (no such process)"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + .message = bun.String.static("A system error occurred: uv_os_setpriority returned ESRCH (no such process)"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), //.info = info, .errno = -3, - .syscall = JSC.ZigString.init("uv_os_setpriority"), + .syscall = bun.String.static("uv_os_setpriority"), }; globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); @@ -604,11 +603,11 @@ pub const Os = struct { }, .ACCES => { const err = JSC.SystemError{ - .message = JSC.ZigString.init("A system error occurred: uv_os_setpriority returned EACCESS (permission denied)"), - .code = JSC.ZigString.init(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), + .message = bun.String.static("A system error occurred: uv_os_setpriority returned EACCESS (permission denied)"), + .code = bun.String.static(@as(string, @tagName(JSC.Node.ErrorCode.ERR_SYSTEM_ERROR))), //.info = info, .errno = -13, - .syscall = JSC.ZigString.init("uv_os_setpriority"), + .syscall = bun.String.static("uv_os_setpriority"), }; globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); @@ -620,29 +619,6 @@ pub const Os = struct { return JSC.JSValue.jsUndefined(); } - pub fn tmpdir(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - JSC.markBinding(@src()); - - const dir: []const u8 = brk: { - if (comptime Environment.isWindows) { - if (bun.getenvZ("TEMP") orelse bun.getenvZ("TMP")) |tmpdir_| { - break :brk tmpdir_; - } - - if (bun.getenvZ("SYSTEMROOT") orelse bun.getenvZ("WINDIR")) |systemdir_| { - break :brk systemdir_ ++ "\\temp"; - } - } else { - const dir = bun.asByteSlice(bun.getenvZ("TMPDIR") orelse bun.getenvZ("TMP") orelse bun.getenvZ("TEMP") orelse "/tmp"); - break :brk strings.withoutTrailingSlash(dir); - } - - break :brk "unknown"; - }; - - return JSC.ZigString.init(dir).withEncoding().toValueGC(globalThis); - } - pub fn totalmem(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { JSC.markBinding(@src()); diff --git a/src/bun.js/node/os/constants.zig b/src/bun.js/node/os/constants.zig index a3508d383..9cf754e03 100644 --- a/src/bun.js/node/os/constants.zig +++ b/src/bun.js/node/os/constants.zig @@ -8,14 +8,14 @@ const ConstantType = enum { ERRNO, ERRNO_WIN, SIG, DLOPEN, OTHER }; fn getErrnoConstant(comptime name: []const u8) ?comptime_int { return if (@hasField(std.os.E, name)) - return @enumToInt(@field(std.os.E, name)) + return @intFromEnum(@field(std.os.E, name)) else return null; } fn getWindowsErrnoConstant(comptime name: []const u8) ?comptime_int { return if (@hasField(std.os.E, name)) - return @enumToInt(@field(std.os.windows.ws2_32.WinsockError, name)) + return @intFromEnum(@field(std.os.windows.ws2_32.WinsockError, name)) else return null; } diff --git a/src/bun.js/node/syscall.zig b/src/bun.js/node/syscall.zig index 7b10a3028..5ff0b2f44 100644 --- a/src/bun.js/node/syscall.zig +++ b/src/bun.js/node/syscall.zig @@ -106,6 +106,10 @@ pub const Tag = enum(u8) { waitpid, posix_spawn, getaddrinfo, + writev, + pwritev, + readv, + preadv, pub var strings = std.EnumMap(Tag, JSC.C.JSStringRef).initFull(null); }; const PathString = @import("root").bun.PathString; @@ -202,7 +206,7 @@ pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Nod .SUCCESS => .{ .result = @intCast(bun.FileDescriptor, rc) }, else => |err| .{ .err = .{ - .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), + .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)), .syscall = .open, }, }, @@ -218,7 +222,7 @@ pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Nod else => |err| { return Maybe(std.os.fd_t){ .err = .{ - .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), + .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)), .syscall = .open, }, }; @@ -253,14 +257,14 @@ pub fn closeAllowingStdoutAndStderr(fd: std.os.fd_t) ?Syscall.Error { if (comptime Environment.isMac) { // This avoids the EINTR problem. return switch (system.getErrno(system.@"close$NOCANCEL"(fd))) { - .BADF => Syscall.Error{ .errno = @enumToInt(os.E.BADF), .syscall = .close }, + .BADF => Syscall.Error{ .errno = @intFromEnum(os.E.BADF), .syscall = .close }, else => null, }; } if (comptime Environment.isLinux) { return switch (linux.getErrno(linux.close(fd))) { - .BADF => Syscall.Error{ .errno = @enumToInt(os.E.BADF), .syscall = .close }, + .BADF => Syscall.Error{ .errno = @intFromEnum(os.E.BADF), .syscall = .close }, else => null, }; } @@ -302,6 +306,154 @@ pub fn write(fd: os.fd_t, bytes: []const u8) Maybe(usize) { } } +fn veclen(buffers: anytype) usize { + var len: usize = 0; + for (buffers) |buffer| { + len += buffer.iov_len; + } + return len; +} + +pub fn writev(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) { + if (comptime Environment.isMac) { + const rc = writev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), @intCast(i32, buffers.len)); + if (comptime Environment.allow_assert) + log("writev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .writev, fd)) |err| { + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } else { + while (true) { + const rc = writev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), buffers.len); + if (comptime Environment.allow_assert) + log("writev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .writev, fd)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; + } +} + +pub fn pwritev(fd: os.fd_t, buffers: []std.os.iovec, position: isize) Maybe(usize) { + if (comptime Environment.isMac) { + const rc = pwritev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), @intCast(i32, buffers.len), position); + if (comptime Environment.allow_assert) + log("pwritev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .pwritev, fd)) |err| { + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } else { + while (true) { + const rc = pwritev_sym(fd, @ptrCast([*]std.os.iovec_const, buffers.ptr), buffers.len, position); + if (comptime Environment.allow_assert) + log("pwritev({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .pwritev, fd)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; + } +} + +pub fn readv(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) { + if (comptime Environment.isMac) { + const rc = readv_sym(fd, buffers.ptr, @intCast(i32, buffers.len)); + if (comptime Environment.allow_assert) + log("readv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .readv, fd)) |err| { + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } else { + while (true) { + const rc = readv_sym(fd, buffers.ptr, buffers.len); + if (comptime Environment.allow_assert) + log("readv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .readv, fd)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; + } +} + +pub fn preadv(fd: os.fd_t, buffers: []std.os.iovec, position: isize) Maybe(usize) { + if (comptime Environment.isMac) { + const rc = preadv_sym(fd, buffers.ptr, @intCast(i32, buffers.len), position); + if (comptime Environment.allow_assert) + log("preadv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .preadv, fd)) |err| { + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } else { + while (true) { + const rc = preadv_sym(fd, buffers.ptr, buffers.len, position); + if (comptime Environment.allow_assert) + log("preadv({d}, {d}) = {d}", .{ fd, veclen(buffers), rc }); + + if (Maybe(usize).errnoSysFd(rc, .preadv, fd)) |err| { + if (err.getErrno() == .INTR) continue; + return err; + } + + return Maybe(usize){ .result = @intCast(usize, rc) }; + } + unreachable; + } +} + +const preadv_sym = if (builtin.os.tag == .linux and builtin.link_libc) + std.os.linux.preadv +else if (builtin.os.tag.isDarwin()) + system.@"preadv$NOCANCEL" +else + system.preadv; + +const readv_sym = if (builtin.os.tag == .linux and builtin.link_libc) + std.os.linux.readv +else if (builtin.os.tag.isDarwin()) + system.@"readv$NOCANCEL" +else + system.readv; + +const pwritev_sym = if (builtin.os.tag == .linux and builtin.link_libc) + std.os.linux.pwritev +else if (builtin.os.tag.isDarwin()) + system.@"pwritev$NOCANCEL" +else + system.pwritev; + +const writev_sym = if (builtin.os.tag == .linux and builtin.link_libc) + std.os.linux.writev +else if (builtin.os.tag.isDarwin()) + system.@"writev$NOCANCEL" +else + system.writev; + const pread_sym = if (builtin.os.tag == .linux and builtin.link_libc) sys.pread64 else if (builtin.os.tag.isDarwin()) @@ -546,7 +698,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) Maybe([]u8) { .macos, .ios, .watchos, .tvos => { // On macOS, we can use F.GETPATH fcntl command to query the OS for // the path to the file descriptor. - @memset(out_buffer, 0, MAX_PATH_BYTES); + @memset(out_buffer[0..MAX_PATH_BYTES], 0); if (Maybe([]u8).errnoSys(system.fcntl(fd, os.F.GETPATH, out_buffer), .fcntl)) |err| { return err; } @@ -594,7 +746,7 @@ fn mmap( const fail = std.c.MAP.FAILED; if (rc == fail) { return Maybe([]align(mem.page_size) u8){ - .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(std.c.getErrno(@bitCast(i64, @ptrToInt(fail))))), .syscall = .mmap }, + .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(std.c.getErrno(@bitCast(i64, @intFromPtr(fail))))), .syscall = .mmap }, }; } @@ -643,16 +795,16 @@ pub fn munmap(memory: []align(mem.page_size) const u8) Maybe(void) { pub const Error = struct { const max_errno_value = brk: { const errno_values = std.enums.values(os.E); - var err = @enumToInt(os.E.SUCCESS); + var err = @intFromEnum(os.E.SUCCESS); for (errno_values) |errn| { - err = @max(err, @enumToInt(errn)); + err = @max(err, @intFromEnum(errn)); } break :brk err; }; pub const Int: type = std.math.IntFittingRange(0, max_errno_value + 5); errno: Int, - syscall: Syscall.Tag = @intToEnum(Syscall.Tag, 0), + syscall: Syscall.Tag = @enumFromInt(Syscall.Tag, 0), path: []const u8 = "", fd: i32 = -1, @@ -661,7 +813,7 @@ pub const Error = struct { } pub fn fromCode(errno: os.E, syscall: Syscall.Tag) Error { - return .{ .errno = @truncate(Int, @enumToInt(errno)), .syscall = syscall }; + return .{ .errno = @truncate(Int, @intFromEnum(errno)), .syscall = syscall }; } pub fn format(self: Error, comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { @@ -672,16 +824,16 @@ pub const Error = struct { pub const retry = Error{ .errno = if (Environment.isLinux) - @intCast(Int, @enumToInt(os.E.AGAIN)) + @intCast(Int, @intFromEnum(os.E.AGAIN)) else if (Environment.isMac) - @intCast(Int, @enumToInt(os.E.WOULDBLOCK)) + @intCast(Int, @intFromEnum(os.E.WOULDBLOCK)) else - @intCast(Int, @enumToInt(os.E.INTR)), + @intCast(Int, @intFromEnum(os.E.INTR)), .syscall = .retry, }; pub inline fn getErrno(this: Error) os.E { - return @intToEnum(os.E, this.errno); + return @enumFromInt(os.E, this.errno); } pub inline fn withPath(this: Error, path: anytype) Error { @@ -721,20 +873,20 @@ pub const Error = struct { pub fn toSystemError(this: Error) SystemError { var err = SystemError{ .errno = @as(c_int, this.errno) * -1, - .syscall = JSC.ZigString.init(@tagName(this.syscall)), + .syscall = bun.String.static(@tagName(this.syscall)), }; // errno label if (this.errno > 0 and this.errno < C.SystemErrno.max) { - const system_errno = @intToEnum(C.SystemErrno, this.errno); - err.code = JSC.ZigString.init(@tagName(system_errno)); + const system_errno = @enumFromInt(C.SystemErrno, this.errno); + err.code = bun.String.static(@tagName(system_errno)); if (C.SystemErrno.labels.get(system_errno)) |label| { - err.message = JSC.ZigString.init(label); + err.message = bun.String.static(label); } } if (this.path.len > 0) { - err.path = JSC.ZigString.init(this.path); + err.path = bun.String.create(this.path); } if (this.fd != -1) { diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 1fe378a84..642039ba5 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -93,6 +93,10 @@ pub fn Maybe(comptime ResultType: type) type { return JSC.JSValue.jsUndefined(); } + if (comptime ReturnType == JSC.JSValue) { + return r; + } + if (comptime ReturnType == JSC.ArrayBuffer) { return r.toJS(globalThis, null); } @@ -135,7 +139,7 @@ pub fn Maybe(comptime ResultType: type) type { pub inline fn getErrno(this: @This()) os.E { return switch (this) { .result => os.E.SUCCESS, - .err => |err| @intToEnum(os.E, err.errno), + .err => |err| @enumFromInt(os.E, err.errno), }; } @@ -144,7 +148,7 @@ pub fn Maybe(comptime ResultType: type) type { .SUCCESS => null, else => |err| @This(){ // always truncate - .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(err)) }, + .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)) }, }, }; } @@ -154,7 +158,7 @@ pub fn Maybe(comptime ResultType: type) type { .SUCCESS => null, else => |err| @This(){ // always truncate - .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), .syscall = syscall }, + .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)), .syscall = syscall }, }, }; } @@ -165,7 +169,7 @@ pub fn Maybe(comptime ResultType: type) type { else => |err| @This(){ // always truncate .err = .{ - .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), + .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)), .syscall = syscall, .fd = @intCast(i32, fd), }, @@ -178,7 +182,7 @@ pub fn Maybe(comptime ResultType: type) type { .SUCCESS => null, else => |err| @This(){ // always truncate - .err = .{ .errno = @truncate(Syscall.Error.Int, @enumToInt(err)), .syscall = syscall, .path = bun.asByteSlice(path) }, + .err = .{ .errno = @truncate(Syscall.Error.Int, @intFromEnum(err)), .syscall = syscall, .path = bun.asByteSlice(path) }, }, }; } @@ -520,8 +524,8 @@ pub const Encoding = enum(u8) { switch (encoding) { .base64 => { var base64: [std.base64.standard.Encoder.calcSize(size)]u8 = undefined; - const result = JSC.ZigString.init(std.base64.standard.Encoder.encode(&base64, input)).toValueGC(globalThis); - return result; + const len = bun.base64.encode(&base64, input); + return JSC.ZigString.init(base64[0..len]).toValueGC(globalThis); }, .base64url => { var buf: [std.base64.url_safe.Encoder.calcSize(size) + "data:;base64,".len]u8 = undefined; @@ -537,9 +541,18 @@ pub const Encoding = enum(u8) { const result = JSC.ZigString.init(out).toValueGC(globalThis); return result; }, - else => { - globalThis.throwInvalidArguments("Unexpected encoding", .{}); - return JSC.JSValue.zero; + .buffer => { + return JSC.ArrayBuffer.createBuffer(globalThis, input); + }, + + inline else => |enc| { + const res = JSC.WebCore.Encoder.toString(input.ptr, size, globalThis, enc); + if (res.isError()) { + globalThis.throwValue(res); + return .zero; + } + + return res; }, } } @@ -567,9 +580,18 @@ pub const Encoding = enum(u8) { const result = JSC.ZigString.init(out).toValueGC(globalThis); return result; }, - else => { - globalThis.throwInvalidArguments("Unexpected encoding", .{}); - return JSC.JSValue.zero; + .buffer => { + return JSC.ArrayBuffer.createBuffer(globalThis, input); + }, + inline else => |enc| { + const res = JSC.WebCore.Encoder.toString(input.ptr, input.len, globalThis, enc); + + if (res.isError()) { + globalThis.throwValue(res); + return .zero; + } + + return res; }, } } @@ -632,7 +654,7 @@ pub const PathLike = union(Tag) { } } - @memcpy(buf, sliced.ptr, sliced.len); + @memcpy(buf[0..sliced.len], sliced); buf[sliced.len] = 0; return buf[0..sliced.len :0]; } @@ -809,6 +831,50 @@ pub const Valid = struct { } }; +pub const VectorArrayBuffer = struct { + value: JSC.JSValue, + buffers: std.ArrayList(std.os.iovec), + + pub fn toJS(this: VectorArrayBuffer, _: *JSC.JSGlobalObject) JSC.JSValue { + return this.value; + } + + pub fn fromJS(globalObject: *JSC.JSGlobalObject, val: JSC.JSValue, exception: JSC.C.ExceptionRef, allocator: std.mem.Allocator) ?VectorArrayBuffer { + if (!val.jsType().isArrayLike()) { + JSC.throwInvalidArguments("Expected ArrayBufferView[]", .{}, globalObject, exception); + return null; + } + + var bufferlist = std.ArrayList(std.os.iovec).init(allocator); + var i: usize = 0; + const len = val.getLength(globalObject); + bufferlist.ensureTotalCapacityPrecise(len) catch @panic("Failed to allocate memory for ArrayBuffer[]"); + + while (i < len) { + const element = val.getIndex(globalObject, @truncate(u32, i)); + + if (!element.isCell()) { + JSC.throwInvalidArguments("Expected ArrayBufferView[]", .{}, globalObject, exception); + return null; + } + + const array_buffer = element.asArrayBuffer(globalObject) orelse { + JSC.throwInvalidArguments("Expected ArrayBufferView[]", .{}, globalObject, exception); + return null; + }; + + var buf = array_buffer.byteSlice(); + bufferlist.append(std.os.iovec{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }) catch @panic("Failed to allocate memory for ArrayBuffer[]"); + i += 1; + } + + return VectorArrayBuffer{ .value = val, .buffers = bufferlist }; + } +}; + pub const ArgumentsSlice = struct { remaining: []const JSC.JSValue, vm: *JSC.VirtualMachine, @@ -904,7 +970,7 @@ pub fn timeLikeFromJS(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, _: JS return null; } - return @truncate(TimeLike, @floatToInt(i64, milliseconds / @as(f64, std.time.ms_per_s))); + return @truncate(TimeLike, @intFromFloat(i64, milliseconds / @as(f64, std.time.ms_per_s))); } if (!value.isNumber() and !value.isString()) { @@ -916,7 +982,7 @@ pub fn timeLikeFromJS(globalThis: *JSC.JSGlobalObject, value: JSC.JSValue, _: JS return null; } - return @truncate(TimeLike, @floatToInt(i64, seconds)); + return @truncate(TimeLike, @intFromFloat(i64, seconds)); } pub fn modeFromJS(ctx: JSC.C.JSContextRef, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?Mode { @@ -968,8 +1034,8 @@ pub const PathOrFileDescriptor = union(Tag) { pub fn hash(this: JSC.Node.PathOrFileDescriptor) u64 { return switch (this) { - .path => std.hash.Wyhash.hash(0, this.path.slice()), - .fd => std.hash.Wyhash.hash(0, std.mem.asBytes(&this.fd)), + .path => bun.hash(this.path.slice()), + .fd => bun.hash(std.mem.asBytes(&this.fd)), }; } @@ -1104,7 +1170,7 @@ pub const FileSystemFlags = enum(Mode) { pub fn fromJS(ctx: JSC.C.JSContextRef, val: JSC.JSValue, exception: JSC.C.ExceptionRef) ?FileSystemFlags { if (val.isNumber()) { const number = val.coerce(i32, ctx); - return @intToEnum(FileSystemFlags, @intCast(Mode, @max(number, 0))); + return @enumFromInt(FileSystemFlags, @intCast(Mode, @max(number, 0))); } const jsType = val.jsType(); @@ -1160,7 +1226,7 @@ pub const FileSystemFlags = enum(Mode) { return null; }; - return @intToEnum(FileSystemFlags, @intCast(Mode, flags)); + return @enumFromInt(FileSystemFlags, @intCast(Mode, flags)); } return null; @@ -1172,7 +1238,7 @@ pub const Date = enum(u64) { _, pub fn toJS(this: Date, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef { - const seconds = @floatCast(f64, @intToFloat(f64, @enumToInt(this)) * 1000.0); + const seconds = @floatCast(f64, @floatFromInt(f64, @intFromEnum(this)) * 1000.0); const unix_timestamp = JSC.JSValue.jsNumber(seconds); const array: [1]JSC.C.JSValueRef = .{unix_timestamp.asObjectRef()}; const obj = JSC.C.JSObjectMakeDate(ctx, 1, &array, exception); @@ -1219,12 +1285,12 @@ fn StatsDataType(comptime T: type) type { .size = @truncate(T, @intCast(i64, stat_.size)), .blksize = @truncate(T, @intCast(i64, stat_.blksize)), .blocks = @truncate(T, @intCast(i64, stat_.blocks)), - .atime_ms = (@intToFloat(f64, @max(atime.tv_sec, 0)) * std.time.ms_per_s) + (@intToFloat(f64, @intCast(usize, @max(atime.tv_nsec, 0))) / std.time.ns_per_ms), - .mtime_ms = (@intToFloat(f64, @max(mtime.tv_sec, 0)) * std.time.ms_per_s) + (@intToFloat(f64, @intCast(usize, @max(mtime.tv_nsec, 0))) / std.time.ns_per_ms), - .ctime_ms = (@intToFloat(f64, @max(ctime.tv_sec, 0)) * std.time.ms_per_s) + (@intToFloat(f64, @intCast(usize, @max(ctime.tv_nsec, 0))) / std.time.ns_per_ms), - .atime = @intToEnum(Date, @intCast(u64, @max(atime.tv_sec, 0))), - .mtime = @intToEnum(Date, @intCast(u64, @max(mtime.tv_sec, 0))), - .ctime = @intToEnum(Date, @intCast(u64, @max(ctime.tv_sec, 0))), + .atime_ms = (@floatFromInt(f64, @max(atime.tv_sec, 0)) * std.time.ms_per_s) + (@floatFromInt(f64, @intCast(usize, @max(atime.tv_nsec, 0))) / std.time.ns_per_ms), + .mtime_ms = (@floatFromInt(f64, @max(mtime.tv_sec, 0)) * std.time.ms_per_s) + (@floatFromInt(f64, @intCast(usize, @max(mtime.tv_nsec, 0))) / std.time.ns_per_ms), + .ctime_ms = (@floatFromInt(f64, @max(ctime.tv_sec, 0)) * std.time.ms_per_s) + (@floatFromInt(f64, @intCast(usize, @max(ctime.tv_nsec, 0))) / std.time.ns_per_ms), + .atime = @enumFromInt(Date, @intCast(u64, @max(atime.tv_sec, 0))), + .mtime = @enumFromInt(Date, @intCast(u64, @max(mtime.tv_sec, 0))), + .ctime = @enumFromInt(Date, @intCast(u64, @max(ctime.tv_sec, 0))), // Linux doesn't include this info in stat // maybe it does in statx, but do you really need birthtime? If you do please file an issue. @@ -1234,9 +1300,9 @@ fn StatsDataType(comptime T: type) type { @truncate(T, @intCast(i64, if (stat_.birthtime().tv_nsec > 0) (@intCast(usize, stat_.birthtime().tv_nsec) / std.time.ns_per_ms) else 0)), .birthtime = if (Environment.isLinux) - @intToEnum(Date, 0) + @enumFromInt(Date, 0) else - @intToEnum(Date, @intCast(u64, @max(stat_.birthtime().tv_sec, 0))), + @enumFromInt(Date, @intCast(u64, @max(stat_.birthtime().tv_sec, 0))), }; } }; @@ -1426,49 +1492,49 @@ pub const Dirent = struct { _: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.BlockDevice); + return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.block_device); } pub fn isCharacterDevice( this: *Dirent, _: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.CharacterDevice); + return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.character_device); } pub fn isDirectory( this: *Dirent, _: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.Directory); + return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.directory); } pub fn isFIFO( this: *Dirent, _: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.NamedPipe or this.kind == std.fs.File.Kind.EventPort); + return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.named_pipe or this.kind == std.fs.File.Kind.event_port); } pub fn isFile( this: *Dirent, _: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.File); + return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.file); } pub fn isSocket( this: *Dirent, _: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.UnixDomainSocket); + return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.unix_domain_socket); } pub fn isSymbolicLink( this: *Dirent, _: *JSC.JSGlobalObject, _: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.SymLink); + return JSC.JSValue.jsBoolean(this.kind == std.fs.File.Kind.sym_link); } pub fn finalize(this: *Dirent) callconv(.C) void { @@ -1490,14 +1556,14 @@ pub const Emitter = struct { pub fn append(this: *List, allocator: std.mem.Allocator, ctx: JSC.C.JSContextRef, listener: Listener) !void { JSC.C.JSValueProtect(ctx, listener.callback.asObjectRef()); try this.list.append(allocator, listener); - this.once_count +|= @as(u32, @boolToInt(listener.once)); + this.once_count +|= @as(u32, @intFromBool(listener.once)); } pub fn prepend(this: *List, allocator: std.mem.Allocator, ctx: JSC.C.JSContextRef, listener: Listener) !void { JSC.C.JSValueProtect(ctx, listener.callback.asObjectRef()); try this.list.ensureUnusedCapacity(allocator, 1); this.list.insertAssumeCapacity(0, listener); - this.once_count +|= @as(u32, @boolToInt(listener.once)); + this.once_count +|= @as(u32, @intFromBool(listener.once)); } // removeListener() will remove, at most, one instance of a listener from the @@ -1510,7 +1576,7 @@ pub const Emitter = struct { for (callbacks, 0..) |item, i| { if (callback.eqlValue(item)) { JSC.C.JSValueUnprotect(ctx, callback.asObjectRef()); - this.once_count -|= @as(u32, @boolToInt(this.list.items(.once)[i])); + this.once_count -|= @as(u32, @intFromBool(this.list.items(.once)[i])); this.list.orderedRemove(i); return true; } @@ -2154,7 +2220,9 @@ pub const Process = struct { } } - pub fn exit(_: *JSC.JSGlobalObject, code: i32) callconv(.C) void { + pub fn exit(globalObject: *JSC.JSGlobalObject, code: i32) callconv(.C) void { + globalObject.bunVM().onExit(); + std.os.exit(@truncate(u8, @intCast(u32, @max(code, 0)))); } |