diff options
Diffstat (limited to 'src/bun.js')
-rw-r--r-- | src/bun.js/event_loop.zig | 241 | ||||
-rw-r--r-- | src/bun.js/node/node_fs.zig | 2222 | ||||
-rw-r--r-- | src/bun.js/node/node_fs_binding.zig | 62 | ||||
-rw-r--r-- | src/bun.js/node/types.zig | 80 |
4 files changed, 1263 insertions, 1342 deletions
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index c7de557f4..ed9d5b195 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -303,6 +303,48 @@ const PollPendingModulesTask = JSC.ModuleLoader.AsyncModule.Queue; // const PromiseTask = JSInternalPromise.Completion.PromiseTask; const GetAddrInfoRequestTask = JSC.DNS.GetAddrInfoRequest.Task; const JSCDeferredWorkTask = JSCScheduler.JSCDeferredWorkTask; + +const Stat = JSC.Node.Async.stat; +const Lstat = JSC.Node.Async.lstat; +const Fstat = JSC.Node.Async.fstat; +const Open = JSC.Node.Async.open; +const ReadFile = JSC.Node.Async.readFile; +const WriteFile = JSC.Node.Async.writeFile; +const CopyFile = JSC.Node.Async.copyFile; +const Read = JSC.Node.Async.read; +const Write = JSC.Node.Async.write; +const Truncate = JSC.Node.Async.truncate; +const FTruncate = JSC.Node.Async.ftruncate; +const Readdir = JSC.Node.Async.readdir; +const Readv = JSC.Node.Async.readv; +const Writev = JSC.Node.Async.writev; +const Close = JSC.Node.Async.close; +const Rm = JSC.Node.Async.rm; +const Rmdir = JSC.Node.Async.rmdir; +const Chown = JSC.Node.Async.chown; +const FChown = JSC.Node.Async.fchown; +const Utimes = JSC.Node.Async.utimes; +const Lutimes = JSC.Node.Async.lutimes; +const Chmod = JSC.Node.Async.chmod; +const Fchmod = JSC.Node.Async.fchmod; +const Link = JSC.Node.Async.link; +const Symlink = JSC.Node.Async.symlink; +const Readlink = JSC.Node.Async.readlink; +const Realpath = JSC.Node.Async.realpath; +const Mkdir = JSC.Node.Async.mkdir; +const Fsync = JSC.Node.Async.fsync; +const Rename = JSC.Node.Async.rename; +const Fdatasync = JSC.Node.Async.fdatasync; +const Access = JSC.Node.Async.access; +const AppendFile = JSC.Node.Async.appendFile; +const Mkdtemp = JSC.Node.Async.mkdtemp; +const Exists = JSC.Node.Async.exists; +const Futimes = JSC.Node.Async.futimes; +const Lchmod = JSC.Node.Async.lchmod; +const Lchown = JSC.Node.Async.lchown; +const Unlink = JSC.Node.Async.unlink; + +// Task.get(ReadFileTask) -> ?ReadFileTask pub const Task = TaggedPointerUnion(.{ FetchTasklet, Microtask, @@ -321,9 +363,45 @@ pub const Task = TaggedPointerUnion(.{ GetAddrInfoRequestTask, FSWatchTask, JSCDeferredWorkTask, - - // PromiseTask, - // TimeoutTasklet, + Stat, + Lstat, + Fstat, + Open, + ReadFile, + WriteFile, + CopyFile, + Read, + Write, + Truncate, + FTruncate, + Readdir, + Close, + Rm, + Rmdir, + Chown, + FChown, + Utimes, + Lutimes, + Chmod, + Fchmod, + Link, + Symlink, + Readlink, + Realpath, + Mkdir, + Fsync, + Fdatasync, + Writev, + Readv, + Rename, + Access, + AppendFile, + Mkdtemp, + Exists, + Futimes, + Lchmod, + Lchown, + Unlink, }); const UnboundedQueue = @import("./unbounded_queue.zig").UnboundedQueue; pub const ConcurrentTask = struct { @@ -535,7 +613,6 @@ pub const EventLoop = struct { } extern fn JSC__JSGlobalObject__drainMicrotasks(*JSC.JSGlobalObject) void; fn drainMicrotasksWithGlobal(this: *EventLoop, globalObject: *JSC.JSGlobalObject) void { - JSC.markBinding(@src()); JSC__JSGlobalObject__drainMicrotasks(globalObject); this.drainDeferredTasks(); } @@ -662,6 +739,162 @@ pub const EventLoop = struct { any.runFromJS(); any.deinit(); }, + @field(Task.Tag, typeBaseName(@typeName(Stat))) => { + var any: *Stat = task.get(Stat).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Lstat))) => { + var any: *Lstat = task.get(Lstat).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Fstat))) => { + var any: *Fstat = task.get(Fstat).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Open))) => { + var any: *Open = task.get(Open).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(ReadFile))) => { + var any: *ReadFile = task.get(ReadFile).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(WriteFile))) => { + var any: *WriteFile = task.get(WriteFile).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(CopyFile))) => { + var any: *CopyFile = task.get(CopyFile).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Read))) => { + var any: *Read = task.get(Read).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Write))) => { + var any: *Write = task.get(Write).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Truncate))) => { + var any: *Truncate = task.get(Truncate).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Writev))) => { + var any: *Writev = task.get(Writev).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Readv))) => { + var any: *Readv = task.get(Readv).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Rename))) => { + var any: *Rename = task.get(Rename).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(FTruncate))) => { + var any: *FTruncate = task.get(FTruncate).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Readdir))) => { + var any: *Readdir = task.get(Readdir).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Close))) => { + var any: *Close = task.get(Close).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Rm))) => { + var any: *Rm = task.get(Rm).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Rmdir))) => { + var any: *Rmdir = task.get(Rmdir).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Chown))) => { + var any: *Chown = task.get(Chown).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(FChown))) => { + var any: *FChown = task.get(FChown).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Utimes))) => { + var any: *Utimes = task.get(Utimes).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Lutimes))) => { + var any: *Lutimes = task.get(Lutimes).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Chmod))) => { + var any: *Chmod = task.get(Chmod).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Fchmod))) => { + var any: *Fchmod = task.get(Fchmod).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Link))) => { + var any: *Link = task.get(Link).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Symlink))) => { + var any: *Symlink = task.get(Symlink).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Readlink))) => { + var any: *Readlink = task.get(Readlink).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Realpath))) => { + var any: *Realpath = task.get(Realpath).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Mkdir))) => { + var any: *Mkdir = task.get(Mkdir).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Fsync))) => { + var any: *Fsync = task.get(Fsync).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Fdatasync))) => { + var any: *Fdatasync = task.get(Fdatasync).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Access))) => { + var any: *Access = task.get(Access).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(AppendFile))) => { + var any: *AppendFile = task.get(AppendFile).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Mkdtemp))) => { + var any: *Mkdtemp = task.get(Mkdtemp).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Exists))) => { + var any: *Exists = task.get(Exists).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Futimes))) => { + var any: *Futimes = task.get(Futimes).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Lchmod))) => { + var any: *Lchmod = task.get(Lchmod).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Lchown))) => { + var any: *Lchown = task.get(Lchown).?; + any.runFromJSThread(); + }, + @field(Task.Tag, typeBaseName(@typeName(Unlink))) => { + var any: *Unlink = task.get(Unlink).?; + any.runFromJSThread(); + }, else => if (Environment.allow_assert) { bun.Output.prettyln("\nUnexpected tag: {s}\n", .{@tagName(task.tag())}); } else { diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index d9e3ed119..752d0e2fb 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -55,468 +55,147 @@ else // TODO: 0; +const SliceWithUnderlyingStringOrBuffer = JSC.Node.SliceWithUnderlyingStringOrBuffer; const ArrayBuffer = JSC.MarkedArrayBuffer; const Buffer = JSC.Buffer; const FileSystemFlags = JSC.Node.FileSystemFlags; +pub const Async = struct { + pub const access = NewAsyncFSTask(Return.Access, Arguments.Access, NodeFS.access); + pub const appendFile = NewAsyncFSTask(Return.AppendFile, Arguments.AppendFile, NodeFS.appendFile); + pub const chmod = NewAsyncFSTask(Return.Chmod, Arguments.Chmod, NodeFS.chmod); + pub const chown = NewAsyncFSTask(Return.Chown, Arguments.Chown, NodeFS.chown); + pub const close = NewAsyncFSTask(Return.Close, Arguments.Close, NodeFS.close); + pub const copyFile = NewAsyncFSTask(Return.CopyFile, Arguments.CopyFile, NodeFS.copyFile); + pub const exists = NewAsyncFSTask(Return.Exists, Arguments.Exists, NodeFS.exists); + pub const fchmod = NewAsyncFSTask(Return.Fchmod, Arguments.FChmod, NodeFS.fchmod); + pub const fchown = NewAsyncFSTask(Return.Fchown, Arguments.Fchown, NodeFS.fchown); + pub const fdatasync = NewAsyncFSTask(Return.Fdatasync, Arguments.FdataSync, NodeFS.fdatasync); + pub const fstat = NewAsyncFSTask(Return.Fstat, Arguments.Fstat, NodeFS.fstat); + pub const fsync = NewAsyncFSTask(Return.Fsync, Arguments.Fsync, NodeFS.fsync); + pub const ftruncate = NewAsyncFSTask(Return.Ftruncate, Arguments.FTruncate, NodeFS.ftruncate); + pub const futimes = NewAsyncFSTask(Return.Futimes, Arguments.Futimes, NodeFS.futimes); + pub const lchmod = NewAsyncFSTask(Return.Lchmod, Arguments.LCHmod, NodeFS.lchmod); + pub const lchown = NewAsyncFSTask(Return.Lchown, Arguments.LChown, NodeFS.lchown); + pub const link = NewAsyncFSTask(Return.Link, Arguments.Link, NodeFS.link); + pub const lstat = NewAsyncFSTask(Return.Stat, Arguments.Stat, NodeFS.lstat); + pub const lutimes = NewAsyncFSTask(Return.Lutimes, Arguments.Lutimes, NodeFS.lutimes); + pub const mkdir = NewAsyncFSTask(Return.Mkdir, Arguments.Mkdir, NodeFS.mkdir); + pub const mkdtemp = NewAsyncFSTask(Return.Mkdtemp, Arguments.MkdirTemp, NodeFS.mkdtemp); + pub const open = NewAsyncFSTask(Return.Open, Arguments.Open, NodeFS.open); + pub const read = NewAsyncFSTask(Return.Read, Arguments.Read, NodeFS.read); + pub const readdir = NewAsyncFSTask(Return.Readdir, Arguments.Readdir, NodeFS.readdir); + pub const readFile = NewAsyncFSTask(Return.ReadFile, Arguments.ReadFile, NodeFS.readFile); + pub const readlink = NewAsyncFSTask(Return.Readlink, Arguments.Readlink, NodeFS.readlink); + pub const readv = NewAsyncFSTask(Return.Readv, Arguments.Readv, NodeFS.readv); + pub const realpath = NewAsyncFSTask(Return.Realpath, Arguments.Realpath, NodeFS.realpath); + pub const rename = NewAsyncFSTask(Return.Rename, Arguments.Rename, NodeFS.rename); + pub const rm = NewAsyncFSTask(Return.Rm, Arguments.Rm, NodeFS.rm); + pub const rmdir = NewAsyncFSTask(Return.Rmdir, Arguments.RmDir, NodeFS.rmdir); + pub const stat = NewAsyncFSTask(Return.Stat, Arguments.Stat, NodeFS.stat); + pub const symlink = NewAsyncFSTask(Return.Symlink, Arguments.Symlink, NodeFS.symlink); + pub const truncate = NewAsyncFSTask(Return.Truncate, Arguments.Truncate, NodeFS.truncate); + pub const unlink = NewAsyncFSTask(Return.Unlink, Arguments.Unlink, NodeFS.unlink); + pub const utimes = NewAsyncFSTask(Return.Utimes, Arguments.Utimes, NodeFS.utimes); + pub const write = NewAsyncFSTask(Return.Write, Arguments.Write, NodeFS.write); + pub const writeFile = NewAsyncFSTask(Return.WriteFile, Arguments.WriteFile, NodeFS.writeFile); + pub const writev = NewAsyncFSTask(Return.Writev, Arguments.Writev, NodeFS.writev); + + pub const cp = AsyncCpTask; + + fn NewAsyncFSTask(comptime ReturnType: type, comptime ArgumentType: type, comptime Function: anytype) type { + return struct { + promise: JSC.JSPromise.Strong, + args: ArgumentType, + globalObject: *JSC.JSGlobalObject, + task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, + result: JSC.Maybe(ReturnType), + ref: JSC.PollRef = .{}, + tracker: JSC.AsyncTaskTracker, + + pub const Task = @This(); + + pub fn create( + globalObject: *JSC.JSGlobalObject, + args: ArgumentType, + vm: *JSC.VirtualMachine, + ) JSC.JSValue { + var task = bun.default_allocator.create(Task) catch @panic("out of memory"); + task.* = Task{ + .promise = JSC.JSPromise.Strong.init(globalObject), + .args = args, + .result = undefined, + .globalObject = globalObject, + .tracker = JSC.AsyncTaskTracker.init(vm), + }; + task.ref.ref(vm); + task.args.toThreadSafe(); + task.tracker.didSchedule(globalObject); + JSC.WorkPool.schedule(&task.task); -pub const AsyncReaddirTask = struct { - promise: JSC.JSPromise.Strong, - args: Arguments.Readdir, - globalObject: *JSC.JSGlobalObject, - task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, - result: JSC.Maybe(Return.Readdir), - ref: JSC.PollRef = .{}, - arena: bun.ArenaAllocator, - tracker: JSC.AsyncTaskTracker, - - pub fn create(globalObject: *JSC.JSGlobalObject, readdir_args: Arguments.Readdir, vm: *JSC.VirtualMachine, arena: bun.ArenaAllocator) JSC.JSValue { - var task = bun.default_allocator.create(AsyncReaddirTask) catch @panic("out of memory"); - task.* = AsyncReaddirTask{ - .promise = JSC.JSPromise.Strong.init(globalObject), - .args = readdir_args, - .result = undefined, - .globalObject = globalObject, - .arena = arena, - .tracker = JSC.AsyncTaskTracker.init(vm), - }; - task.ref.ref(vm); - task.args.path.toThreadSafe(); - task.tracker.didSchedule(globalObject); - JSC.WorkPool.schedule(&task.task); - - return task.promise.value(); - } - - fn workPoolCallback(task: *JSC.WorkPoolTask) void { - var this: *AsyncReaddirTask = @fieldParentPtr(AsyncReaddirTask, "task", task); - - var node_fs = NodeFS{}; - this.result = node_fs.readdir(this.args, .promise); - - if (this.result == .err) { - this.result.err.path = bun.default_allocator.dupe(u8, this.result.err.path) catch ""; - } - - this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, runFromJSThread)); - } - - fn runFromJSThread(this: *AsyncReaddirTask) void { - var globalObject = this.globalObject; - - var success = @as(JSC.Maybe(Return.Readdir).Tag, this.result) == .result; - const result = switch (this.result) { - .err => |err| err.toJSC(globalObject), - .result => |res| brk: { - var exceptionref: JSC.C.JSValueRef = null; - const out = JSC.JSValue.c(JSC.To.JS.withType(Return.Readdir, res, globalObject, &exceptionref)); - const exception = JSC.JSValue.c(exceptionref); - if (exception != .zero) { - success = false; - break :brk exception; - } - - break :brk out; - }, - }; - var promise_value = this.promise.value(); - var promise = this.promise.get(); - promise_value.ensureStillAlive(); - - const tracker = this.tracker; - this.deinit(); - - tracker.willDispatch(globalObject); - defer tracker.didDispatch(globalObject); - switch (success) { - false => { - promise.reject(globalObject, result); - }, - true => { - promise.resolve(globalObject, result); - }, - } - } - - pub fn deinit(this: *AsyncReaddirTask) void { - this.ref.unref(this.globalObject.bunVM()); - this.args.deinitAndUnprotect(); - this.promise.strong.deinit(); - this.arena.deinit(); - bun.default_allocator.destroy(this); - } -}; - -pub const AsyncStatTask = struct { - promise: JSC.JSPromise.Strong, - args: Arguments.Stat, - globalObject: *JSC.JSGlobalObject, - task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, - result: JSC.Maybe(Return.Stat), - ref: JSC.PollRef = .{}, - is_lstat: bool = false, - arena: bun.ArenaAllocator, - tracker: JSC.AsyncTaskTracker, - - pub fn create( - globalObject: *JSC.JSGlobalObject, - readdir_args: Arguments.Stat, - vm: *JSC.VirtualMachine, - is_lstat: bool, - arena: bun.ArenaAllocator, - ) JSC.JSValue { - var task = bun.default_allocator.create(AsyncStatTask) catch @panic("out of memory"); - task.* = AsyncStatTask{ - .promise = JSC.JSPromise.Strong.init(globalObject), - .args = readdir_args, - .result = undefined, - .globalObject = globalObject, - .is_lstat = is_lstat, - .tracker = JSC.AsyncTaskTracker.init(vm), - .arena = arena, - }; - task.ref.ref(vm); - task.args.path.toThreadSafe(); - task.tracker.didSchedule(globalObject); - - JSC.WorkPool.schedule(&task.task); - - return task.promise.value(); - } - - fn workPoolCallback(task: *JSC.WorkPoolTask) void { - var this: *AsyncStatTask = @fieldParentPtr(AsyncStatTask, "task", task); - - var node_fs = NodeFS{}; - this.result = if (this.is_lstat) - node_fs.lstat(this.args, .promise) - else - node_fs.stat(this.args, .promise); - - this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, runFromJSThread)); - } - - fn runFromJSThread(this: *AsyncStatTask) void { - var globalObject = this.globalObject; - var success = @as(JSC.Maybe(Return.Lstat).Tag, this.result) == .result; - const result = switch (this.result) { - .err => |err| err.toJSC(globalObject), - .result => |res| brk: { - var exceptionref: JSC.C.JSValueRef = null; - const out = JSC.JSValue.c(JSC.To.JS.withType(Return.Lstat, res, globalObject, &exceptionref)); - const exception = JSC.JSValue.c(exceptionref); - if (exception != .zero) { - success = false; - break :brk exception; - } - - break :brk out; - }, - }; - var promise_value = this.promise.value(); - var promise = this.promise.get(); - promise_value.ensureStillAlive(); - - const tracker = this.tracker; - tracker.willDispatch(globalObject); - defer tracker.didDispatch(globalObject); - - this.deinit(); - switch (success) { - false => { - promise.reject(globalObject, result); - }, - true => { - promise.resolve(globalObject, result); - }, - } - } - - pub fn deinit(this: *AsyncStatTask) void { - this.ref.unref(this.globalObject.bunVM()); - this.args.deinitAndUnprotect(); - this.promise.strong.deinit(); - this.arena.deinit(); - bun.default_allocator.destroy(this); - } -}; - -pub const AsyncRealpathTask = struct { - promise: JSC.JSPromise.Strong, - args: Arguments.Realpath, - globalObject: *JSC.JSGlobalObject, - task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, - result: JSC.Maybe(Return.Realpath), - ref: JSC.PollRef = .{}, - arena: bun.ArenaAllocator, - tracker: JSC.AsyncTaskTracker, - - pub fn create( - globalObject: *JSC.JSGlobalObject, - args: Arguments.Realpath, - vm: *JSC.VirtualMachine, - arena: bun.ArenaAllocator, - ) JSC.JSValue { - var task = bun.default_allocator.create(AsyncRealpathTask) catch @panic("out of memory"); - task.* = AsyncRealpathTask{ - .promise = JSC.JSPromise.Strong.init(globalObject), - .args = args, - .result = undefined, - .globalObject = globalObject, - .arena = arena, - .tracker = JSC.AsyncTaskTracker.init(vm), - }; - task.ref.ref(vm); - task.args.path.toThreadSafe(); - task.tracker.didSchedule(globalObject); - JSC.WorkPool.schedule(&task.task); - - return task.promise.value(); - } - - fn workPoolCallback(task: *JSC.WorkPoolTask) void { - var this: *AsyncRealpathTask = @fieldParentPtr(AsyncRealpathTask, "task", task); + return task.promise.value(); + } - var node_fs = NodeFS{}; - this.result = node_fs.realpath(this.args, .promise); + fn workPoolCallback(task: *JSC.WorkPoolTask) void { + var this: *Task = @fieldParentPtr(Task, "task", task); - if (this.result == .err) { - this.result.err.path = bun.default_allocator.dupe(u8, this.result.err.path) catch ""; - } + var node_fs = NodeFS{}; + this.result = Function(&node_fs, this.args, .promise); - this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, runFromJSThread)); - } - - fn runFromJSThread(this: *AsyncRealpathTask) void { - var globalObject = this.globalObject; - var success = @as(JSC.Maybe(Return.Realpath).Tag, this.result) == .result; - const result = switch (this.result) { - .err => |err| err.toJSC(globalObject), - .result => |res| brk: { - var exceptionref: JSC.C.JSValueRef = null; - const out = JSC.JSValue.c(JSC.To.JS.withType(Return.Realpath, res, globalObject, &exceptionref)); - const exception = JSC.JSValue.c(exceptionref); - if (exception != .zero) { - success = false; - break :brk exception; + if (this.result == .err) { + this.result.err.path = bun.default_allocator.dupe(u8, this.result.err.path) catch ""; + std.mem.doNotOptimizeAway(&node_fs); } - break :brk out; - }, - }; - var promise_value = this.promise.value(); - var promise = this.promise.get(); - promise_value.ensureStillAlive(); - - const tracker = this.tracker; - tracker.willDispatch(globalObject); - defer tracker.didDispatch(globalObject); - - this.deinit(); - switch (success) { - false => { - promise.reject(globalObject, result); - }, - true => { - promise.resolve(globalObject, result); - }, - } - } - - pub fn deinit(this: *AsyncRealpathTask) void { - if (this.result == .err) { - bun.default_allocator.free(this.result.err.path); - } - - this.ref.unref(this.globalObject.bunVM()); - this.args.deinitAndUnprotect(); - this.promise.strong.deinit(); - this.arena.deinit(); - bun.default_allocator.destroy(this); - } -}; - -pub const AsyncReadFileTask = struct { - promise: JSC.JSPromise.Strong, - args: Arguments.ReadFile, - globalObject: *JSC.JSGlobalObject, - task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, - result: JSC.Maybe(Return.ReadFile), - ref: JSC.PollRef = .{}, - arena: bun.ArenaAllocator, - tracker: JSC.AsyncTaskTracker, - - pub fn create( - globalObject: *JSC.JSGlobalObject, - args: Arguments.ReadFile, - vm: *JSC.VirtualMachine, - arena: bun.ArenaAllocator, - ) JSC.JSValue { - var task = bun.default_allocator.create(AsyncReadFileTask) catch @panic("out of memory"); - task.* = AsyncReadFileTask{ - .promise = JSC.JSPromise.Strong.init(globalObject), - .args = args, - .result = undefined, - .globalObject = globalObject, - .arena = arena, - .tracker = JSC.AsyncTaskTracker.init(vm), - }; - task.ref.ref(vm); - task.args.path.toThreadSafe(); - task.tracker.didSchedule(globalObject); - JSC.WorkPool.schedule(&task.task); - - return task.promise.value(); - } - - fn workPoolCallback(task: *JSC.WorkPoolTask) void { - var this: *AsyncReadFileTask = @fieldParentPtr(AsyncReadFileTask, "task", task); - - var node_fs = NodeFS{}; - this.result = node_fs.readFile(this.args, .promise); - - if (this.result == .err) { - this.result.err.path = bun.default_allocator.dupe(u8, this.result.err.path) catch ""; - } - - this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, runFromJSThread)); - } + this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.create(JSC.Task.init(this))); + } - fn runFromJSThread(this: *AsyncReadFileTask) void { - var globalObject = this.globalObject; + pub fn runFromJSThread(this: *Task) void { + var globalObject = this.globalObject; + var success = @as(JSC.Maybe(ReturnType).Tag, this.result) == .result; + const result = switch (this.result) { + .err => |err| err.toJSC(globalObject), + .result => |res| brk: { + var exceptionref: JSC.C.JSValueRef = null; + const out = JSC.JSValue.c(JSC.To.JS.withType(ReturnType, res, globalObject, &exceptionref)); + const exception = JSC.JSValue.c(exceptionref); + if (exception != .zero) { + success = false; + break :brk exception; + } - var success = @as(JSC.Maybe(Return.ReadFile).Tag, this.result) == .result; - const result = switch (this.result) { - .err => |err| err.toJSC(globalObject), - .result => |res| brk: { - var exceptionref: JSC.C.JSValueRef = null; - const out = JSC.JSValue.c(JSC.To.JS.withType(Return.ReadFile, res, globalObject, &exceptionref)); - const exception = JSC.JSValue.c(exceptionref); - if (exception != .zero) { - success = false; - break :brk exception; + break :brk out; + }, + }; + var promise_value = this.promise.value(); + var promise = this.promise.get(); + promise_value.ensureStillAlive(); + + const tracker = this.tracker; + tracker.willDispatch(globalObject); + defer tracker.didDispatch(globalObject); + + this.deinit(); + switch (success) { + false => { + promise.reject(globalObject, result); + }, + true => { + promise.resolve(globalObject, result); + }, } + } - break :brk out; - }, - }; - var promise_value = this.promise.value(); - var promise = this.promise.get(); - promise_value.ensureStillAlive(); - - const tracker = this.tracker; - tracker.willDispatch(globalObject); - defer tracker.didDispatch(globalObject); - - this.deinit(); - switch (success) { - false => { - promise.reject(globalObject, result); - }, - true => { - promise.resolve(globalObject, result); - }, - } - } - - pub fn deinit(this: *AsyncReadFileTask) void { - this.ref.unref(this.globalObject.bunVM()); - this.args.deinitAndUnprotect(); - this.promise.strong.deinit(); - this.arena.deinit(); - bun.default_allocator.destroy(this); - } -}; - -pub const AsyncCopyFileTask = struct { - promise: JSC.JSPromise.Strong, - args: Arguments.CopyFile, - globalObject: *JSC.JSGlobalObject, - task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback }, - result: JSC.Maybe(Return.CopyFile), - ref: JSC.PollRef = .{}, - arena: bun.ArenaAllocator, - tracker: JSC.AsyncTaskTracker, - - pub fn create( - globalObject: *JSC.JSGlobalObject, - copyfile_args: Arguments.CopyFile, - vm: *JSC.VirtualMachine, - arena: bun.ArenaAllocator, - ) JSC.JSValue { - var task = bun.default_allocator.create(AsyncCopyFileTask) catch @panic("out of memory"); - task.* = AsyncCopyFileTask{ - .promise = JSC.JSPromise.Strong.init(globalObject), - .args = copyfile_args, - .result = undefined, - .globalObject = globalObject, - .tracker = JSC.AsyncTaskTracker.init(vm), - .arena = arena, - }; - task.ref.ref(vm); - task.args.src.toThreadSafe(); - task.args.dest.toThreadSafe(); - task.tracker.didSchedule(globalObject); - - JSC.WorkPool.schedule(&task.task); - - return task.promise.value(); - } - - fn workPoolCallback(task: *JSC.WorkPoolTask) void { - var this: *AsyncCopyFileTask = @fieldParentPtr(AsyncCopyFileTask, "task", task); - - var node_fs = NodeFS{}; - this.result = node_fs.copyFile(this.args, .promise); - - if (this.result == .err) { - this.result.err.path = bun.default_allocator.dupe(u8, this.result.err.path) catch ""; - } - - this.globalObject.bunVMConcurrently().eventLoop().enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(this, runFromJSThread)); - } - - fn runFromJSThread(this: *AsyncCopyFileTask) void { - var globalObject = this.globalObject; - var success = @as(JSC.Maybe(Return.CopyFile).Tag, this.result) == .result; - const result = switch (this.result) { - .err => |err| err.toJSC(globalObject), - .result => |res| brk: { - var exceptionref: JSC.C.JSValueRef = null; - const out = JSC.JSValue.c(JSC.To.JS.withType(Return.CopyFile, res, globalObject, &exceptionref)); - const exception = JSC.JSValue.c(exceptionref); - if (exception != .zero) { - success = false; - break :brk exception; + pub fn deinit(this: *Task) void { + if (this.result == .err) { + bun.default_allocator.free(this.result.err.path); } - break :brk out; - }, + this.ref.unref(this.globalObject.bunVM()); + this.args.deinit(); + this.promise.strong.deinit(); + bun.default_allocator.destroy(this); + } }; - var promise_value = this.promise.value(); - var promise = this.promise.get(); - promise_value.ensureStillAlive(); - - const tracker = this.tracker; - tracker.willDispatch(globalObject); - defer tracker.didDispatch(globalObject); - - this.deinit(); - switch (success) { - false => { - promise.reject(globalObject, result); - }, - true => { - promise.resolve(globalObject, result); - }, - } - } - - pub fn deinit(this: *AsyncCopyFileTask) void { - this.ref.unref(this.globalObject.bunVM()); - this.args.deinit(); - this.promise.strong.deinit(); - this.arena.deinit(); - bun.default_allocator.destroy(this); } }; @@ -710,6 +389,16 @@ pub const Arguments = struct { this.new_path.deinit(); } + pub fn deinitAndUnprotect(this: @This()) void { + this.old_path.deinitAndUnprotect(); + this.new_path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *@This()) void { + this.old_path.toThreadSafe(); + this.new_path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Rename { const old_path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -748,8 +437,16 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn deinitAndUnprotect(this: *@This()) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *@This()) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Truncate { - const path = PathOrFileDescriptor.fromJS(ctx, arguments, arguments.arena.allocator(), exception) orelse { + const path = PathOrFileDescriptor.fromJS(ctx, arguments, bun.default_allocator, exception) orelse { if (exception.* == null) { JSC.throwInvalidArguments( "path must be a string or TypedArray", @@ -783,6 +480,21 @@ pub const Arguments = struct { pub fn deinit(_: *const @This()) void {} + pub fn deinitAndUnprotect(this: *const @This()) void { + this.buffers.value.unprotect(); + this.buffers.buffers.deinit(); + } + + pub fn toThreadSafe(this: *@This()) void { + this.buffers.value.protect(); + + var clone = bun.default_allocator.dupe(std.os.iovec, this.buffers.buffers.items) catch @panic("out of memory"); + this.buffers.buffers.deinit(); + this.buffers.buffers.items = clone; + this.buffers.buffers.capacity = clone.len; + this.buffers.buffers.allocator = bun.default_allocator; + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Writev { const fd_value = arguments.nextEat() orelse { if (exception.* == null) { @@ -855,7 +567,23 @@ pub const Arguments = struct { buffers: JSC.Node.VectorArrayBuffer, position: ?u52 = 0, - pub fn deinit(_: *const @This()) void {} + pub fn deinit(this: *const @This()) void { + _ = this; + } + + pub fn deinitAndUnprotect(this: *const @This()) void { + this.buffers.value.unprotect(); + this.buffers.buffers.deinit(); + } + + pub fn toThreadSafe(this: *@This()) void { + this.buffers.value.protect(); + var clone = bun.default_allocator.dupe(std.os.iovec, this.buffers.buffers.items) catch @panic("out of memory"); + this.buffers.buffers.deinit(); + this.buffers.buffers.items = clone; + this.buffers.buffers.capacity = clone.len; + this.buffers.buffers.allocator = bun.default_allocator; + } pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readv { const fd_value = arguments.nextEat() orelse { @@ -932,6 +660,14 @@ pub const Arguments = struct { _ = this; } + pub fn deinitAndUnprotect(this: *@This()) void { + _ = this; + } + + pub fn toThreadSafe(this: *const @This()) void { + _ = this; + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FTruncate { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -982,6 +718,14 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn deinitAndUnprotect(this: *@This()) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *@This()) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Chown { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1040,6 +784,8 @@ pub const Arguments = struct { pub fn deinit(_: @This()) void {} + pub fn toThreadSafe(_: *const @This()) void {} + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Fchown { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -1114,6 +860,14 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn deinitAndUnprotect(this: *@This()) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *@This()) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Lutimes { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1189,6 +943,14 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn toThreadSafe(this: *@This()) void { + this.path.toThreadSafe(); + } + + pub fn deinitAndUnprotect(this: *@This()) void { + this.path.deinitAndUnprotect(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Chmod { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1234,6 +996,10 @@ pub const Arguments = struct { fd: FileDescriptor, mode: Mode = 0x777, + pub fn deinit(_: *const @This()) void {} + + pub fn toThreadSafe(_: *const @This()) void {} + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FChmod { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -1303,6 +1069,10 @@ pub const Arguments = struct { this.path.deinitAndUnprotect(); } + pub fn toThreadSafe(this: *Stat) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Stat { const path = PathLike.fromJSWithAllocator(ctx, arguments, bun.default_allocator, exception) orelse { if (exception.* == null) { @@ -1356,6 +1126,8 @@ pub const Arguments = struct { pub fn deinit(_: @This()) void {} + pub fn toThreadSafe(_: *@This()) void {} + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Fstat { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -1412,6 +1184,16 @@ pub const Arguments = struct { this.new_path.deinit(); } + pub fn deinitAndUnprotect(this: *Link) void { + this.old_path.deinitAndUnprotect(); + this.new_path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *Link) void { + this.old_path.toThreadSafe(); + this.new_path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Link { const old_path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1454,6 +1236,16 @@ pub const Arguments = struct { this.new_path.deinit(); } + pub fn deinitAndUnprotect(this: Symlink) void { + this.old_path.deinitAndUnprotect(); + this.new_path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *@This()) void { + this.old_path.toThreadSafe(); + this.new_path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Symlink { const old_path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1512,6 +1304,14 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn deinitAndUnprotect(this: *Readlink) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *Readlink) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readlink { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1560,6 +1360,10 @@ pub const Arguments = struct { this.path.deinitAndUnprotect(); } + pub fn toThreadSafe(this: *Realpath) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Realpath { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1599,6 +1403,18 @@ pub const Arguments = struct { pub const Unlink = struct { path: PathLike, + pub fn deinit(this: Unlink) void { + this.path.deinit(); + } + + pub fn deinitAndUnprotect(this: *Unlink) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *Unlink) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Unlink { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1620,13 +1436,7 @@ pub const Arguments = struct { } }; - pub const Rm = struct { - path: PathLike, - force: bool = false, - max_retries: u32 = 0, - recursive: bool = false, - retry_delay: c_uint = 100, - }; + pub const Rm = RmDir; pub const RmDir = struct { path: PathLike, @@ -1637,6 +1447,14 @@ pub const Arguments = struct { recursive: bool = false, retry_delay: c_uint = 100, + pub fn deinitAndUnprotect(this: *RmDir) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *RmDir) void { + this.path.toThreadSafe(); + } + pub fn deinit(this: RmDir) void { this.path.deinit(); } @@ -1701,6 +1519,14 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn deinitAndUnprotect(this: *Mkdir) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *Mkdir) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: *JSC.JSGlobalObject, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Mkdir { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1752,10 +1578,18 @@ pub const Arguments = struct { this.prefix.deinit(); } + pub fn deinitAndUnprotect(this: *MkdirTemp) void { + this.prefix.deinit(); + } + + pub fn toThreadSafe(this: *MkdirTemp) void { + this.prefix.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?MkdirTemp { const prefix_value = arguments.next() orelse return MkdirTemp{}; - var prefix = JSC.Node.SliceOrBuffer.fromJS(ctx, arguments.arena.allocator(), prefix_value) orelse { + var prefix = JSC.Node.SliceOrBuffer.fromJS(ctx, bun.default_allocator, prefix_value) orelse { if (exception.* == null) { JSC.throwInvalidArguments( "prefix must be a string or TypedArray", @@ -1810,6 +1644,10 @@ pub const Arguments = struct { this.path.deinitAndUnprotect(); } + pub fn toThreadSafe(this: *Readdir) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readdir { const path = PathLike.fromJSWithAllocator(ctx, arguments, bun.default_allocator, exception) orelse { if (exception.* == null) { @@ -1864,6 +1702,7 @@ pub const Arguments = struct { fd: FileDescriptor, pub fn deinit(_: Close) void {} + pub fn toThreadSafe(_: Close) void {} pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Close { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { @@ -1905,6 +1744,14 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn deinitAndUnprotect(this: Open) void { + this.path.deinitAndUnprotect(); + } + + pub fn toThreadSafe(this: *Open) void { + this.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Open { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -1971,6 +1818,10 @@ pub const Arguments = struct { pub fn deinit(_: Futimes) void {} + pub fn toThreadSafe(self: *const @This()) void { + _ = self; + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Futimes { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -2052,42 +1903,6 @@ pub const Arguments = struct { } }; - pub const FSync = struct { - fd: FileDescriptor, - - pub fn deinit(_: FSync) void {} - - pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FSync { - const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { - if (exception.* == null) { - JSC.throwInvalidArguments( - "File descriptor is required", - .{}, - ctx, - exception, - ); - } - return null; - }, exception) orelse { - if (exception.* == null) { - JSC.throwInvalidArguments( - "fd must be a number", - .{}, - ctx, - exception, - ); - } - return null; - }; - - if (exception.* != null) return null; - - return FSync{ - .fd = fd, - }; - } - }; - /// Write `buffer` to the file specified by `fd`. If `buffer` is a normal object, it /// must have an own `toString` function property. /// @@ -2114,7 +1929,7 @@ pub const Arguments = struct { /// pub const Write = struct { fd: FileDescriptor, - buffer: StringOrBuffer, + buffer: JSC.Node.SliceWithUnderlyingStringOrBuffer, // buffer_val: JSC.JSValue = JSC.JSValue.zero, offset: u64 = 0, length: u64 = std.math.maxInt(u64), @@ -2123,6 +1938,10 @@ pub const Arguments = struct { pub fn deinit(_: Write) void {} + pub fn toThreadSafe(self: *@This()) void { + self.buffer.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Write { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -2150,7 +1969,7 @@ pub const Arguments = struct { if (exception.* != null) return null; - const buffer = StringOrBuffer.fromJS(ctx.ptr(), arguments.arena.allocator(), arguments.next() orelse { + const buffer = SliceWithUnderlyingStringOrBuffer.fromJS(ctx.ptr(), bun.default_allocator, arguments.next() orelse { if (exception.* == null) { JSC.throwInvalidArguments( "data is required", @@ -2177,7 +1996,7 @@ pub const Arguments = struct { .fd = fd, .buffer = buffer, .encoding = switch (buffer) { - .string => Encoding.utf8, + .SliceWithUnderlyingString => Encoding.utf8, .buffer => Encoding.buffer, }, }; @@ -2190,7 +2009,7 @@ pub const Arguments = struct { var current = current_; switch (buffer) { // fs.write(fd, string[, position[, encoding]], callback) - .string => { + .SliceWithUnderlyingString => { if (current.isNumber()) { args.position = current.to(i52); arguments.eat(); @@ -2239,6 +2058,14 @@ pub const Arguments = struct { pub fn deinit(_: Read) void {} + pub fn toThreadSafe(this: Read) void { + this.buffer.buffer.value.protect(); + } + + pub fn deinitAndUnprotect(this: *Read) void { + this.buffer.buffer.value.unprotect(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Read { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -2377,6 +2204,10 @@ pub const Arguments = struct { self.path.deinitAndUnprotect(); } + pub fn toThreadSafe(self: *ReadFile) void { + self.path.toThreadSafe(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?ReadFile { const path = PathOrFileDescriptor.fromJS(ctx, arguments, bun.default_allocator, exception) orelse { if (exception.* == null) { @@ -2463,8 +2294,16 @@ pub const Arguments = struct { self.file.deinit(); } + pub fn toThreadSafe(self: *WriteFile) void { + self.file.toThreadSafe(); + } + + pub fn deinitAndUnprotect(self: *WriteFile) void { + self.file.deinitAndUnprotect(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?WriteFile { - const file = PathOrFileDescriptor.fromJS(ctx, arguments, arguments.arena.allocator(), exception) orelse { + const file = PathOrFileDescriptor.fromJS(ctx, arguments, bun.default_allocator, exception) orelse { if (exception.* == null) { JSC.throwInvalidArguments( "path must be a string or a file descriptor", @@ -2478,7 +2317,7 @@ pub const Arguments = struct { if (exception.* != null) return null; - const data = StringOrBuffer.fromJS(ctx.ptr(), arguments.arena.allocator(), arguments.next() orelse { + const data = StringOrBuffer.fromJS(ctx.ptr(), bun.default_allocator, arguments.next() orelse { if (exception.* == null) { JSC.throwInvalidArguments( "data is required", @@ -2673,6 +2512,18 @@ pub const Arguments = struct { } } + pub fn toThreadSafe(this: *Exists) void { + if (this.path) |*path| { + path.toThreadSafe(); + } + } + + pub fn deinitAndUnprotect(this: *Exists) void { + if (this.path) |*path| { + path.deinitAndUnprotect(); + } + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Exists { return Exists{ .path = PathLike.fromJS(ctx, arguments, exception), @@ -2688,6 +2539,14 @@ pub const Arguments = struct { this.path.deinit(); } + pub fn toThreadSafe(this: *Access) void { + this.path.toThreadSafe(); + } + + pub fn deinitAndUnprotect(this: *Access) void { + this.path.deinitAndUnprotect(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Access { const path = PathLike.fromJS(ctx, arguments, exception) orelse { if (exception.* == null) { @@ -3002,6 +2861,9 @@ pub const Arguments = struct { fd: FileDescriptor, pub fn deinit(_: FdataSync) void {} + pub fn toThreadSafe(self: *const @This()) void { + _ = self; + } pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FdataSync { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { @@ -3039,11 +2901,21 @@ pub const Arguments = struct { dest: PathLike, mode: Constants.Copyfile, - fn deinit(this: CopyFile) void { + pub fn deinit(this: CopyFile) void { this.src.deinit(); this.dest.deinit(); } + pub fn toThreadSafe(this: *CopyFile) void { + this.src.toThreadSafe(); + this.dest.toThreadSafe(); + } + + pub fn deinitAndUnprotect(this: *CopyFile) void { + this.src.deinitAndUnprotect(); + this.dest.deinitAndUnprotect(); + } + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?CopyFile { const src = PathLike.fromJSWithAllocator(ctx, arguments, bun.default_allocator, exception) orelse { if (exception.* == null) { @@ -3196,6 +3068,9 @@ pub const Arguments = struct { pub const Fsync = struct { fd: FileDescriptor, + pub fn deinit(_: Fsync) void {} + pub fn toThreadSafe(_: *const @This()) void {} + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Fsync { const fd = JSC.Node.fileDescriptorFromJS(ctx, arguments.next() orelse { if (exception.* == null) { @@ -3411,69 +3286,49 @@ pub const NodeFS = struct { } pub fn appendFile(this: *NodeFS, args: Arguments.AppendFile, comptime flavor: Flavor) Maybe(Return.AppendFile) { + _ = flavor; var data = args.data.slice(); switch (args.file) { .fd => |fd| { - switch (comptime flavor) { - .sync => { - while (data.len > 0) { - const written = switch (Syscall.write(fd, data)) { - .result => |result| result, - .err => |err| return .{ .err = err }, - }; - data = data[written..]; - } - - return Maybe(Return.AppendFile).success; - }, - else => { - @compileError("Not implemented yet"); - }, + while (data.len > 0) { + const written = switch (Syscall.write(fd, data)) { + .result => |result| result, + .err => |err| return .{ .err = err }, + }; + data = data[written..]; } + + return Maybe(Return.AppendFile).success; }, .path => |path_| { const path = path_.sliceZ(&this.sync_error_buf); - switch (comptime flavor) { - .sync => { - const fd = switch (Syscall.open(path, @intFromEnum(FileSystemFlags.a), 0o000666)) { - .result => |result| result, - .err => |err| return .{ .err = err }, - }; - defer { - _ = Syscall.close(fd); - } + const fd = switch (Syscall.open(path, @intFromEnum(FileSystemFlags.a), 0o000666)) { + .result => |result| result, + .err => |err| return .{ .err = err }, + }; - while (data.len > 0) { - const written = switch (Syscall.write(fd, data)) { - .result => |result| result, - .err => |err| return .{ .err = err }, - }; - data = data[written..]; - } + defer { + _ = Syscall.close(fd); + } - return Maybe(Return.AppendFile).success; - }, - else => { - @compileError("Not implemented yet"); - }, + while (data.len > 0) { + const written = switch (Syscall.write(fd, data)) { + .result => |result| result, + .err => |err| return .{ .err = err }, + }; + data = data[written..]; } + + return Maybe(Return.AppendFile).success; }, } - - return Maybe(Return.AppendFile).todo; } pub fn close(_: *NodeFS, args: Arguments.Close, comptime flavor: Flavor) Maybe(Return.Close) { - switch (comptime flavor) { - .sync => { - return if (Syscall.close(args.fd)) |err| .{ .err = err } else Maybe(Return.Close).success; - }, - else => {}, - } - - return .{ .err = Syscall.Error.todo }; + _ = flavor; + return if (Syscall.close(args.fd)) |err| .{ .err = err } else Maybe(Return.Close).success; } // since we use a 64 KB stack buffer, we should not let this function get inlined @@ -3714,132 +3569,92 @@ pub const NodeFS = struct { } pub fn exists(this: *NodeFS, args: Arguments.Exists, comptime flavor: Flavor) Maybe(Return.Exists) { + _ = flavor; const Ret = Maybe(Return.Exists); - switch (comptime flavor) { - .sync => { - const path = args.path orelse return Ret{ .result = false }; - const slice = path.sliceZ(&this.sync_error_buf); - // access() may not work correctly on NFS file systems with UID - // mapping enabled, because UID mapping is done on the server and - // hidden from the client, which checks permissions. Similar - // problems can occur to FUSE mounts. - const rc = (system.access(slice, std.os.F_OK)); - return Ret{ .result = rc == 0 }; - }, - else => {}, - } - - return Ret.todo; + const path = args.path orelse return Ret{ .result = false }; + const slice = path.sliceZ(&this.sync_error_buf); + // access() may not work correctly on NFS file systems with UID + // mapping enabled, because UID mapping is done on the server and + // hidden from the client, which checks permissions. Similar + // problems can occur to FUSE mounts. + const rc = (system.access(slice, std.os.F_OK)); + return Ret{ .result = rc == 0 }; } pub fn chown(this: *NodeFS, args: Arguments.Chown, comptime flavor: Flavor) Maybe(Return.Chown) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Fchmod).todo; } const path = args.path.sliceZ(&this.sync_error_buf); - switch (comptime flavor) { - .sync => return Syscall.chown(path, args.uid, args.gid), - else => {}, - } - - return Maybe(Return.Chown).todo; + return Syscall.chown(path, args.uid, args.gid); } /// This should almost never be async pub fn chmod(this: *NodeFS, args: Arguments.Chmod, comptime flavor: Flavor) Maybe(Return.Chmod) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Fchmod).todo; } const path = args.path.sliceZ(&this.sync_error_buf); - switch (comptime flavor) { - .sync => { - return Maybe(Return.Chmod).errnoSysP(C.chmod(path, args.mode), .chmod, path) orelse - Maybe(Return.Chmod).success; - }, - else => {}, - } - - return Maybe(Return.Chmod).todo; + return Maybe(Return.Chmod).errnoSysP(C.chmod(path, args.mode), .chmod, path) orelse + Maybe(Return.Chmod).success; } /// This should almost never be async pub fn fchmod(_: *NodeFS, args: Arguments.FChmod, comptime flavor: Flavor) Maybe(Return.Fchmod) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Fchmod).todo; } - switch (comptime flavor) { - .sync => { - return Syscall.fchmod(args.fd, args.mode); - }, - else => {}, - } - - return Maybe(Return.Fchmod).todo; + return Syscall.fchmod(args.fd, args.mode); } pub fn fchown(_: *NodeFS, args: Arguments.Fchown, comptime flavor: Flavor) Maybe(Return.Fchown) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Fchown).todo; } - switch (comptime flavor) { - .sync => { - return Maybe(Return.Fchown).errnoSys(C.fchown(args.fd, args.uid, args.gid), .fchown) orelse - Maybe(Return.Fchown).success; - }, - else => {}, - } - - return Maybe(Return.Fchown).todo; + return Maybe(Return.Fchown).errnoSys(C.fchown(args.fd, args.uid, args.gid), .fchown) orelse + Maybe(Return.Fchown).success; } pub fn fdatasync(_: *NodeFS, args: Arguments.FdataSync, comptime flavor: Flavor) Maybe(Return.Fdatasync) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Fdatasync).todo; } - switch (comptime flavor) { - .sync => return Maybe(Return.Fdatasync).errnoSys(system.fdatasync(args.fd), .fdatasync) orelse - Maybe(Return.Fdatasync).success, - else => {}, - } - - return Maybe(Return.Fdatasync).todo; + return Maybe(Return.Fdatasync).errnoSys(system.fdatasync(args.fd), .fdatasync) orelse + Maybe(Return.Fdatasync).success; } pub fn fstat(_: *NodeFS, args: Arguments.Fstat, comptime flavor: Flavor) Maybe(Return.Fstat) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Fstat).todo; } - switch (comptime flavor) { - .sync => { - if (comptime Environment.isPosix) { - return switch (Syscall.fstat(args.fd)) { - .result => |result| Maybe(Return.Fstat){ .result = Stats.init(result, false) }, - .err => |err| Maybe(Return.Fstat){ .err = err }, - }; - } - }, - else => {}, + if (comptime Environment.isPosix) { + return switch (Syscall.fstat(args.fd)) { + .result => |result| Maybe(Return.Fstat){ .result = Stats.init(result, false) }, + .err => |err| Maybe(Return.Fstat){ .err = err }, + }; } return Maybe(Return.Fstat).todo; } pub fn fsync(_: *NodeFS, args: Arguments.Fsync, comptime flavor: Flavor) Maybe(Return.Fsync) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Fsync).todo; } - switch (comptime flavor) { - .sync => return Maybe(Return.Fsync).errnoSys(system.fsync(args.fd), .fsync) orelse - Maybe(Return.Fsync).success, - else => {}, - } - - return Maybe(Return.Fsync).todo; + return Maybe(Return.Fsync).errnoSys(system.fsync(args.fd), .fsync) orelse + Maybe(Return.Fsync).success; } pub fn ftruncateSync(args: Arguments.FTruncate) Maybe(Return.Ftruncate) { @@ -3847,14 +3662,11 @@ pub const NodeFS = struct { } pub fn ftruncate(_: *NodeFS, args: Arguments.FTruncate, comptime flavor: Flavor) Maybe(Return.Ftruncate) { - switch (comptime flavor) { - .sync => return ftruncateSync(args), - else => {}, - } - - return Maybe(Return.Ftruncate).todo; + _ = flavor; + return ftruncateSync(args); } pub fn futimes(_: *NodeFS, args: Arguments.Futimes, comptime flavor: Flavor) Maybe(Return.Futimes) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Futimes).todo; } @@ -3870,66 +3682,43 @@ pub const NodeFS = struct { }, }; - switch (comptime flavor) { - .sync => return if (Maybe(Return.Futimes).errnoSys(system.futimens(args.fd, ×), .futimens)) |err| - err - else - Maybe(Return.Futimes).success, - else => {}, - } - - return Maybe(Return.Futimes).todo; + return if (Maybe(Return.Futimes).errnoSys(system.futimens(args.fd, ×), .futimens)) |err| + err + else + Maybe(Return.Futimes).success; } pub fn lchmod(this: *NodeFS, args: Arguments.LCHmod, comptime flavor: Flavor) Maybe(Return.Lchmod) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Lchmod).todo; } const path = args.path.sliceZ(&this.sync_error_buf); - switch (comptime flavor) { - .sync => { - return Maybe(Return.Lchmod).errnoSysP(C.lchmod(path, args.mode), .lchmod, path) orelse - Maybe(Return.Lchmod).success; - }, - else => {}, - } - - return Maybe(Return.Lchmod).todo; + return Maybe(Return.Lchmod).errnoSysP(C.lchmod(path, args.mode), .lchmod, path) orelse + Maybe(Return.Lchmod).success; } pub fn lchown(this: *NodeFS, args: Arguments.LChown, comptime flavor: Flavor) Maybe(Return.Lchown) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Lchown).todo; } const path = args.path.sliceZ(&this.sync_error_buf); - switch (comptime flavor) { - .sync => { - return Maybe(Return.Lchown).errnoSysP(C.lchown(path, args.uid, args.gid), .lchown, path) orelse - Maybe(Return.Lchown).success; - }, - else => {}, - } - - return Maybe(Return.Lchown).todo; + return Maybe(Return.Lchown).errnoSysP(C.lchown(path, args.uid, args.gid), .lchown, path) orelse + Maybe(Return.Lchown).success; } pub fn link(this: *NodeFS, args: Arguments.Link, comptime flavor: Flavor) Maybe(Return.Link) { + _ = flavor; var new_path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; const from = args.old_path.sliceZ(&this.sync_error_buf); const to = args.new_path.sliceZ(&new_path_buf); - switch (comptime flavor) { - .sync => { - return Maybe(Return.Link).errnoSysP(system.link(from, to, 0), .link, from) orelse - Maybe(Return.Link).success; - }, - else => {}, - } - - return Maybe(Return.Link).todo; + return Maybe(Return.Link).errnoSysP(system.link(from, to, 0), .link, from) orelse + Maybe(Return.Link).success; } pub fn lstat(this: *NodeFS, args: Arguments.Lstat, comptime flavor: Flavor) Maybe(Return.Lstat) { if (comptime Environment.isWindows) { @@ -3957,158 +3746,146 @@ pub const NodeFS = struct { } // Node doesn't absolute the path so we don't have to either fn mkdirNonRecursive(this: *NodeFS, args: Arguments.Mkdir, comptime flavor: Flavor) Maybe(Return.Mkdir) { - switch (comptime flavor) { - .sync => { - const path = args.path.sliceZ(&this.sync_error_buf); - return switch (Syscall.mkdir(path, args.mode)) { - .result => Maybe(Return.Mkdir){ .result = bun.String.empty }, - .err => |err| Maybe(Return.Mkdir){ .err = err }, - }; - }, - else => {}, - } + _ = flavor; - return Maybe(Return.Mkdir).todo; + const path = args.path.sliceZ(&this.sync_error_buf); + return switch (Syscall.mkdir(path, args.mode)) { + .result => Maybe(Return.Mkdir){ .result = bun.String.empty }, + .err => |err| Maybe(Return.Mkdir){ .err = err }, + }; } // TODO: windows // TODO: verify this works correctly with unicode codepoints pub fn mkdirRecursive(this: *NodeFS, args: Arguments.Mkdir, comptime flavor: Flavor) Maybe(Return.Mkdir) { + _ = flavor; const Option = Maybe(Return.Mkdir); if (comptime Environment.isWindows) return Option.todo; - switch (comptime flavor) { - // The sync version does no allocation except when returning the path - .sync => { - var buf: [bun.MAX_PATH_BYTES]u8 = undefined; - const path = args.path.sliceZWithForceCopy(&buf, true); - const len = @as(u16, @truncate(path.len)); - - // First, attempt to create the desired directory - // If that fails, then walk back up the path until we have a match - switch (Syscall.mkdir(path, args.mode)) { - .err => |err| { - switch (err.getErrno()) { - else => { - @memcpy(this.sync_error_buf[0..len], path[0..len]); - return .{ .err = err.withPath(this.sync_error_buf[0..len]) }; - }, + var buf: [bun.MAX_PATH_BYTES]u8 = undefined; + const path = args.path.sliceZWithForceCopy(&buf, true); + const len = @as(u16, @truncate(path.len)); - .EXIST => { - return Option{ .result = bun.String.empty }; - }, - // continue - .NOENT => {}, - } + // First, attempt to create the desired directory + // If that fails, then walk back up the path until we have a match + switch (Syscall.mkdir(path, args.mode)) { + .err => |err| { + switch (err.getErrno()) { + else => { + @memcpy(this.sync_error_buf[0..len], path[0..len]); + return .{ .err = err.withPath(this.sync_error_buf[0..len]) }; }, - .result => { - return Option{ - .result = if (args.path == .slice_with_underlying_string) - args.path.slice_with_underlying_string.underlying - else - bun.String.create(args.path.slice()), - }; + + .EXIST => { + return Option{ .result = bun.String.empty }; }, + // continue + .NOENT => {}, } + }, + .result => { + 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[0..len], path[0..len]); + var working_mem = &this.sync_error_buf; + @memcpy(working_mem[0..len], path[0..len]); - var i: u16 = len - 1; + var i: u16 = len - 1; - // iterate backwards until creating the directory works successfully - while (i > 0) : (i -= 1) { - if (path[i] == std.fs.path.sep) { - working_mem[i] = 0; - var parent: [:0]u8 = working_mem[0..i :0]; + // iterate backwards until creating the directory works successfully + while (i > 0) : (i -= 1) { + if (path[i] == std.fs.path.sep) { + working_mem[i] = 0; + var parent: [:0]u8 = working_mem[0..i :0]; - switch (Syscall.mkdir(parent, args.mode)) { - .err => |err| { - working_mem[i] = std.fs.path.sep; - switch (err.getErrno()) { - .EXIST => { - // Handle race condition - break; - }, - .NOENT => { - continue; - }, - else => return .{ .err = err.withPath(parent) }, - } - }, - .result => { - // We found a parent that worked - working_mem[i] = std.fs.path.sep; + switch (Syscall.mkdir(parent, args.mode)) { + .err => |err| { + working_mem[i] = std.fs.path.sep; + switch (err.getErrno()) { + .EXIST => { + // Handle race condition break; }, - } - } - } - var first_match: u16 = i; - i += 1; - // after we find one that works, we go forward _after_ the first working directory - while (i < len) : (i += 1) { - if (path[i] == std.fs.path.sep) { - working_mem[i] = 0; - var parent: [:0]u8 = working_mem[0..i :0]; - - switch (Syscall.mkdir(parent, args.mode)) { - .err => |err| { - working_mem[i] = std.fs.path.sep; - switch (err.getErrno()) { - .EXIST => { - if (Environment.allow_assert) std.debug.assert(false); - continue; - }, - else => return .{ .err = err }, - } - }, - - .result => { - working_mem[i] = std.fs.path.sep; + .NOENT => { + continue; }, + else => return .{ .err = err.withPath(parent) }, } - } + }, + .result => { + // We found a parent that worked + working_mem[i] = std.fs.path.sep; + break; + }, } + } + } + var first_match: u16 = i; + i += 1; + // after we find one that works, we go forward _after_ the first working directory + while (i < len) : (i += 1) { + if (path[i] == std.fs.path.sep) { + working_mem[i] = 0; + var parent: [:0]u8 = working_mem[0..i :0]; - working_mem[len] = 0; - - // Our final directory will not have a trailing separator - // so we have to create it once again - switch (Syscall.mkdir(working_mem[0..len :0], args.mode)) { + switch (Syscall.mkdir(parent, args.mode)) { .err => |err| { + working_mem[i] = std.fs.path.sep; switch (err.getErrno()) { - // handle the race condition .EXIST => { - var display_path = bun.String.empty; - if (first_match != std.math.maxInt(u16)) { - display_path = bun.String.create(working_mem[0..first_match]); - } - return Option{ .result = display_path }; - }, - - // NOENT shouldn't happen here - else => return .{ - .err = err.withPath(path), + if (Environment.allow_assert) std.debug.assert(false); + continue; }, + else => return .{ .err = err }, } }, + .result => { - 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()), - }; + working_mem[i] = std.fs.path.sep; }, } - }, - else => {}, + } } - return Maybe(Return.Mkdir).todo; + working_mem[len] = 0; + + // Our final directory will not have a trailing separator + // so we have to create it once again + switch (Syscall.mkdir(working_mem[0..len :0], args.mode)) { + .err => |err| { + switch (err.getErrno()) { + // handle the race condition + .EXIST => { + var display_path = bun.String.empty; + if (first_match != std.math.maxInt(u16)) { + display_path = bun.String.create(working_mem[0..first_match]); + } + return Option{ .result = display_path }; + }, + + // NOENT shouldn't happen here + else => return .{ + .err = err.withPath(path), + }, + } + }, + .result => { + 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()), + }; + }, + } } pub fn mkdtemp(this: *NodeFS, args: Arguments.MkdirTemp, comptime _: Flavor) Maybe(Return.Mkdtemp) { @@ -4136,75 +3913,54 @@ pub const NodeFS = struct { return .{ .err = Syscall.Error{ .errno = @as(Syscall.Error.Int, @truncate(@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, @intFromEnum(args.flags), args.mode)) { - .err => |err| .{ - .err = err.withPath(args.path.slice()), - }, - .result => |fd| .{ .result = fd }, - }; + _ = flavor; + const path = args.path.sliceZ(&this.sync_error_buf); + return switch (Syscall.open(path, @intFromEnum(args.flags), args.mode)) { + .err => |err| .{ + .err = err.withPath(args.path.slice()), }, - else => {}, - } - - return Maybe(Return.Open).todo; + .result => |fd| .{ .result = fd }, + }; } pub fn openDir(_: *NodeFS, _: Arguments.OpenDir, comptime _: Flavor) Maybe(Return.OpenDir) { return Maybe(Return.OpenDir).todo; } fn _read(_: *NodeFS, args: Arguments.Read, comptime flavor: Flavor) Maybe(Return.Read) { + _ = flavor; if (Environment.allow_assert) std.debug.assert(args.position == null); + var buf = args.buffer.slice(); + buf = buf[@min(args.offset, buf.len)..]; + buf = buf[0..@min(buf.len, args.length)]; - switch (comptime flavor) { - // The sync version does no allocation except when returning the path - .sync => { - var buf = args.buffer.slice(); - buf = buf[@min(args.offset, buf.len)..]; - buf = buf[0..@min(buf.len, args.length)]; - - return switch (Syscall.read(args.fd, buf)) { - .err => |err| .{ - .err = err, - }, - .result => |amt| .{ - .result = .{ - .bytes_read = @as(u52, @truncate(amt)), - }, - }, - }; + return switch (Syscall.read(args.fd, buf)) { + .err => |err| .{ + .err = err, }, - else => {}, - } - - return Maybe(Return.Read).todo; + .result => |amt| .{ + .result = .{ + .bytes_read = @as(u52, @truncate(amt)), + }, + }, + }; } fn _pread(_: *NodeFS, args: Arguments.Read, comptime flavor: Flavor) Maybe(Return.Read) { - switch (comptime flavor) { - .sync => { - var buf = args.buffer.slice(); - buf = buf[@min(args.offset, buf.len)..]; - buf = buf[0..@min(buf.len, args.length)]; - - return switch (Syscall.pread(args.fd, buf, args.position.?)) { - .err => |err| .{ - .err = err, - }, - .result => |amt| .{ - .result = .{ - .bytes_read = @as(u52, @truncate(amt)), - }, - }, - }; - }, - else => {}, - } + _ = flavor; + var buf = args.buffer.slice(); + buf = buf[@min(args.offset, buf.len)..]; + buf = buf[0..@min(buf.len, args.length)]; - return Maybe(Return.Read).todo; + return switch (Syscall.pread(args.fd, buf, args.position.?)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ + .result = .{ + .bytes_read = @as(u52, @truncate(amt)), + }, + }, + }; } pub fn read(this: *NodeFS, args: Arguments.Read, comptime flavor: Flavor) Maybe(Return.Read) { @@ -4236,127 +3992,91 @@ pub const NodeFS = struct { return if (args.position != null) _pwrite(this, args, flavor) else _write(this, args, flavor); } fn _write(_: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) { - switch (comptime flavor) { - .sync => { - var buf = args.buffer.slice(); - buf = buf[@min(args.offset, buf.len)..]; - buf = buf[0..@min(buf.len, args.length)]; - - return switch (Syscall.write(args.fd, buf)) { - .err => |err| .{ - .err = err, - }, - .result => |amt| .{ - .result = .{ - .bytes_written = @as(u52, @truncate(amt)), - }, - }, - }; - }, - else => {}, - } + _ = flavor; + + var buf = args.buffer.slice(); + buf = buf[@min(args.offset, buf.len)..]; + buf = buf[0..@min(buf.len, args.length)]; - return Maybe(Return.Write).todo; + return switch (Syscall.write(args.fd, buf)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ + .result = .{ + .bytes_written = @as(u52, @truncate(amt)), + }, + }, + }; } fn _pwrite(_: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) { + _ = flavor; const position = args.position.?; - switch (comptime flavor) { - .sync => { - var buf = args.buffer.slice(); - buf = buf[@min(args.offset, buf.len)..]; - buf = buf[0..@min(args.length, buf.len)]; + var buf = args.buffer.slice(); + buf = buf[@min(args.offset, buf.len)..]; + buf = buf[0..@min(args.length, buf.len)]; - return switch (Syscall.pwrite(args.fd, buf, position)) { - .err => |err| .{ - .err = err, - }, - .result => |amt| .{ .result = .{ - .bytes_written = @as(u52, @truncate(amt)), - } }, - }; + return switch (Syscall.pwrite(args.fd, buf, position)) { + .err => |err| .{ + .err = err, }, - else => {}, - } - - return Maybe(Return.Write).todo; + .result => |amt| .{ .result = .{ + .bytes_written = @as(u52, @truncate(amt)), + } }, + }; } fn _preadv(_: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Readv) { + _ = flavor; 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 = @as(u52, @truncate(amt)), - } }, - }; + return switch (Syscall.preadv(args.fd, args.buffers.buffers.items, position)) { + .err => |err| .{ + .err = err, }, - else => {}, - } - - return Maybe(Return.Write).todo; + .result => |amt| .{ .result = .{ + .bytes_read = @as(u52, @truncate(amt)), + } }, + }; } 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 = @as(u52, @truncate(amt)), - } }, - }; + _ = flavor; + return switch (Syscall.readv(args.fd, args.buffers.buffers.items)) { + .err => |err| .{ + .err = err, }, - else => {}, - } - - return Maybe(Return.Write).todo; + .result => |amt| .{ .result = .{ + .bytes_read = @as(u52, @truncate(amt)), + } }, + }; } fn _pwritev(_: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) { + _ = flavor; 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 = @as(u52, @truncate(amt)), - } }, - }; + return switch (Syscall.pwritev(args.fd, args.buffers.buffers.items, position)) { + .err => |err| .{ + .err = err, }, - else => {}, - } - - return Maybe(Return.Write).todo; + .result => |amt| .{ .result = .{ + .bytes_written = @as(u52, @truncate(amt)), + } }, + }; } 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 = @as(u52, @truncate(amt)), - } }, - }; + _ = flavor; + return switch (Syscall.writev(args.fd, args.buffers.buffers.items)) { + .err => |err| .{ + .err = err, }, - else => {}, - } - - return Maybe(Return.Write).todo; + .result => |amt| .{ .result = .{ + .bytes_written = @as(u52, @truncate(amt)), + } }, + }; } pub fn readdir(this: *NodeFS, args: Arguments.Readdir, comptime flavor: Flavor) Maybe(Return.Readdir) { @@ -4751,50 +4471,39 @@ pub const NodeFS = struct { return Maybe(Return.WriteFile).success; } - pub fn writeFile(this: *NodeFS, args: Arguments.WriteFile, comptime flavor: Flavor) Maybe(Return.WriteFile) { - switch (comptime flavor) { - .sync => return writeFileWithPathBuffer(&this.sync_error_buf, args), - else => {}, - } - - return Maybe(Return.WriteFile).todo; + pub fn writeFile(this: *NodeFS, args: Arguments.WriteFile, comptime _: Flavor) Maybe(Return.WriteFile) { + return writeFileWithPathBuffer(&this.sync_error_buf, args); } - pub fn readlink(this: *NodeFS, args: Arguments.Readlink, comptime flavor: Flavor) Maybe(Return.Readlink) { + pub fn readlink(this: *NodeFS, args: Arguments.Readlink, comptime _: Flavor) Maybe(Return.Readlink) { var outbuf: [bun.MAX_PATH_BYTES]u8 = undefined; var inbuf = &this.sync_error_buf; - switch (comptime flavor) { - .sync => { - const path = args.path.sliceZ(inbuf); - const len = switch (Syscall.readlink(path, &outbuf)) { - .err => |err| return .{ - .err = err.withPath(args.path.slice()), - }, - .result => |buf_| buf_, - }; + const path = args.path.sliceZ(inbuf); - return .{ - .result = switch (args.encoding) { - .buffer => .{ - .buffer = Buffer.fromString(outbuf[0..len], bun.default_allocator) catch unreachable, - }, - else => if (args.path == .slice_with_underlying_string and - strings.eqlLong(args.path.slice_with_underlying_string.slice(), outbuf[0..len], true)) - .{ - .BunString = args.path.slice_with_underlying_string.underlying.dupeRef(), - } - else - .{ - .BunString = bun.String.create(outbuf[0..len]), - }, - }, - }; + const len = switch (Syscall.readlink(path, &outbuf)) { + .err => |err| return .{ + .err = err.withPath(args.path.slice()), }, - else => {}, - } + .result => |buf_| buf_, + }; - return Maybe(Return.Readlink).todo; + return .{ + .result = switch (args.encoding) { + .buffer => .{ + .buffer = Buffer.fromString(outbuf[0..len], bun.default_allocator) catch unreachable, + }, + else => if (args.path == .slice_with_underlying_string and + strings.eqlLong(args.path.slice_with_underlying_string.slice(), outbuf[0..len], true)) + .{ + .BunString = args.path.slice_with_underlying_string.underlying.dupeRef(), + } + else + .{ + .BunString = bun.String.create(outbuf[0..len]), + }, + }, + }; } pub fn realpath(this: *NodeFS, args: Arguments.Realpath, comptime _: Flavor) Maybe(Return.Realpath) { var outbuf: [bun.MAX_PATH_BYTES]u8 = undefined; @@ -4857,325 +4566,309 @@ pub const NodeFS = struct { // return error.NotImplementedYet; // } pub fn rename(this: *NodeFS, args: Arguments.Rename, comptime flavor: Flavor) Maybe(Return.Rename) { + _ = flavor; var from_buf = &this.sync_error_buf; var to_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - switch (comptime flavor) { - .sync => { - var from = args.old_path.sliceZ(from_buf); - var to = args.new_path.sliceZ(&to_buf); - return Syscall.rename(from, to); - }, - else => {}, - } - - return Maybe(Return.Rename).todo; + var from = args.old_path.sliceZ(from_buf); + var to = args.new_path.sliceZ(&to_buf); + return Syscall.rename(from, to); } pub fn rmdir(this: *NodeFS, args: Arguments.RmDir, comptime flavor: Flavor) Maybe(Return.Rmdir) { - switch (comptime flavor) { - .sync => { - if (comptime Environment.isMac) { - if (args.recursive) { - var dest = args.path.sliceZ(&this.sync_error_buf); - - var flags: u32 = bun.C.darwin.RemoveFileFlags.cross_mount | - bun.C.darwin.RemoveFileFlags.allow_long_paths | - bun.C.darwin.RemoveFileFlags.recursive; - - while (true) { - if (Maybe(Return.Rmdir).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .rmdir)) |errno| { - switch (@as(os.E, @enumFromInt(errno.err.errno))) { + _ = flavor; + + if (comptime Environment.isMac) { + if (args.recursive) { + var dest = args.path.sliceZ(&this.sync_error_buf); + + var flags: u32 = bun.C.darwin.RemoveFileFlags.cross_mount | + bun.C.darwin.RemoveFileFlags.allow_long_paths | + bun.C.darwin.RemoveFileFlags.recursive; + + while (true) { + if (Maybe(Return.Rmdir).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .rmdir)) |errno| { + switch (@as(os.E, @enumFromInt(errno.err.errno))) { + .AGAIN, .INTR => continue, + .NOENT => return Maybe(Return.Rmdir).success, + .MLINK => { + var copy: [bun.MAX_PATH_BYTES]u8 = undefined; + @memcpy(copy[0..dest.len], dest); + copy[dest.len] = 0; + var dest_copy = copy[0..dest.len :0]; + switch (Syscall.unlink(dest_copy).getErrno()) { .AGAIN, .INTR => continue, - .NOENT => return Maybe(Return.Rmdir).success, - .MLINK => { - var copy: [bun.MAX_PATH_BYTES]u8 = undefined; - @memcpy(copy[0..dest.len], dest); - copy[dest.len] = 0; - var dest_copy = copy[0..dest.len :0]; - switch (Syscall.unlink(dest_copy).getErrno()) { - .AGAIN, .INTR => continue, - .NOENT => return errno, - .SUCCESS => continue, - else => return errno, - } - }, - .SUCCESS => unreachable, + .NOENT => return errno, + .SUCCESS => continue, else => return errno, } - } - - return Maybe(Return.Rmdir).success; + }, + .SUCCESS => unreachable, + else => return errno, } } - return Maybe(Return.Rmdir).errnoSysP(system.rmdir(args.path.sliceZ(&this.sync_error_buf)), .rmdir, args.path.slice()) orelse - Maybe(Return.Rmdir).success; - } else if (comptime Environment.isLinux) { - if (args.recursive) { - std.fs.cwd().deleteTree(args.path.slice()) catch |err| { - const errno: std.os.E = switch (err) { - error.InvalidHandle => .BADF, - error.AccessDenied => .PERM, - error.FileTooBig => .FBIG, - error.SymLinkLoop => .LOOP, - error.ProcessFdQuotaExceeded => .NFILE, - error.NameTooLong => .NAMETOOLONG, - error.SystemFdQuotaExceeded => .MFILE, - error.SystemResources => .NOMEM, - error.ReadOnlyFileSystem => .ROFS, - error.FileSystem => .IO, - error.FileBusy => .BUSY, - error.DeviceBusy => .BUSY, - - // One of the path components was not a directory. - // This error is unreachable if `sub_path` does not contain a path separator. - error.NotDir => .NOTDIR, - // On Windows, file paths must be valid Unicode. - error.InvalidUtf8 => .INVAL, - - // On Windows, file paths cannot contain these characters: - // '/', '*', '?', '"', '<', '>', '|' - error.BadPathName => .INVAL, - - else => .FAULT, - }; - return Maybe(Return.Rm){ - .err = bun.sys.Error.fromCode(errno, .rmdir), - }; - }; + return Maybe(Return.Rmdir).success; + } + } - return Maybe(Return.Rmdir).success; - } + return Maybe(Return.Rmdir).errnoSysP(system.rmdir(args.path.sliceZ(&this.sync_error_buf)), .rmdir, args.path.slice()) orelse + Maybe(Return.Rmdir).success; + } else if (comptime Environment.isLinux) { + if (args.recursive) { + std.fs.cwd().deleteTree(args.path.slice()) catch |err| { + const errno: std.os.E = switch (err) { + error.InvalidHandle => .BADF, + error.AccessDenied => .PERM, + error.FileTooBig => .FBIG, + error.SymLinkLoop => .LOOP, + error.ProcessFdQuotaExceeded => .NFILE, + error.NameTooLong => .NAMETOOLONG, + error.SystemFdQuotaExceeded => .MFILE, + error.SystemResources => .NOMEM, + error.ReadOnlyFileSystem => .ROFS, + error.FileSystem => .IO, + error.FileBusy => .BUSY, + error.DeviceBusy => .BUSY, + + // One of the path components was not a directory. + // This error is unreachable if `sub_path` does not contain a path separator. + error.NotDir => .NOTDIR, + // On Windows, file paths must be valid Unicode. + error.InvalidUtf8 => .INVAL, + + // On Windows, file paths cannot contain these characters: + // '/', '*', '?', '"', '<', '>', '|' + error.BadPathName => .INVAL, + + else => .FAULT, + }; + return Maybe(Return.Rm){ + .err = bun.sys.Error.fromCode(errno, .rmdir), + }; + }; - return Maybe(Return.Rmdir).errnoSysP(system.rmdir(args.path.sliceZ(&this.sync_error_buf)), .rmdir, args.path.slice()) orelse - Maybe(Return.Rmdir).success; - } - }, - else => {}, - } + return Maybe(Return.Rmdir).success; + } - return Maybe(Return.Rmdir).todo; + return Maybe(Return.Rmdir).errnoSysP(system.rmdir(args.path.sliceZ(&this.sync_error_buf)), .rmdir, args.path.slice()) orelse + Maybe(Return.Rmdir).success; + } } pub fn rm(this: *NodeFS, args: Arguments.RmDir, comptime flavor: Flavor) Maybe(Return.Rm) { - switch (comptime flavor) { - .sync => { - if (comptime Environment.isMac) { - var dest = args.path.sliceZ(&this.sync_error_buf); - - while (true) { - var flags: u32 = 0; - if (args.recursive) { - flags |= bun.C.darwin.RemoveFileFlags.cross_mount; - flags |= bun.C.darwin.RemoveFileFlags.allow_long_paths; - flags |= bun.C.darwin.RemoveFileFlags.recursive; - } + _ = flavor; + + if (comptime Environment.isMac) { + var dest = args.path.sliceZ(&this.sync_error_buf); - if (Maybe(Return.Rm).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .unlink)) |errno| { - switch (@as(os.E, @enumFromInt(errno.err.errno))) { + while (true) { + var flags: u32 = 0; + if (args.recursive) { + flags |= bun.C.darwin.RemoveFileFlags.cross_mount; + flags |= bun.C.darwin.RemoveFileFlags.allow_long_paths; + flags |= bun.C.darwin.RemoveFileFlags.recursive; + } + + if (Maybe(Return.Rm).errnoSys(bun.C.darwin.removefileat(std.os.AT.FDCWD, dest, null, flags), .unlink)) |errno| { + switch (@as(os.E, @enumFromInt(errno.err.errno))) { + .AGAIN, .INTR => continue, + .NOENT => { + if (args.force) { + return Maybe(Return.Rm).success; + } + + return errno; + }, + + .MLINK => { + var copy: [bun.MAX_PATH_BYTES]u8 = undefined; + @memcpy(copy[0..dest.len], dest); + copy[dest.len] = 0; + var dest_copy = copy[0..dest.len :0]; + switch (Syscall.unlink(dest_copy).getErrno()) { .AGAIN, .INTR => continue, .NOENT => { if (args.force) { - return Maybe(Return.Rm).success; + continue; } return errno; }, - - .MLINK => { - var copy: [bun.MAX_PATH_BYTES]u8 = undefined; - @memcpy(copy[0..dest.len], dest); - copy[dest.len] = 0; - var dest_copy = copy[0..dest.len :0]; - switch (Syscall.unlink(dest_copy).getErrno()) { - .AGAIN, .INTR => continue, - .NOENT => { - if (args.force) { - continue; - } - - return errno; - }, - .SUCCESS => continue, - else => return errno, - } - }, - .SUCCESS => unreachable, + .SUCCESS => continue, else => return errno, } - } - - return Maybe(Return.Rm).success; - } - } else if (comptime Environment.isLinux or Environment.isWindows) { - if (args.recursive) { - std.fs.cwd().deleteTree(args.path.slice()) catch |err| { - const errno: E = switch (err) { - error.InvalidHandle => .BADF, - error.AccessDenied => .PERM, - error.FileTooBig => .FBIG, - error.SymLinkLoop => .LOOP, - error.ProcessFdQuotaExceeded => .NFILE, - error.NameTooLong => .NAMETOOLONG, - error.SystemFdQuotaExceeded => .MFILE, - error.SystemResources => .NOMEM, - error.ReadOnlyFileSystem => .ROFS, - error.FileSystem => .IO, - error.FileBusy => .BUSY, - error.DeviceBusy => .BUSY, - - // One of the path components was not a directory. - // This error is unreachable if `sub_path` does not contain a path separator. - error.NotDir => .NOTDIR, - // On Windows, file paths must be valid Unicode. - error.InvalidUtf8 => .INVAL, - - // On Windows, file paths cannot contain these characters: - // '/', '*', '?', '"', '<', '>', '|' - error.BadPathName => .INVAL, - - else => .FAULT, - }; - if (args.force) { - return Maybe(Return.Rm).success; - } - return Maybe(Return.Rm){ - .err = bun.sys.Error.fromCode(errno, .unlink), - }; - }; - return Maybe(Return.Rm).success; + }, + .SUCCESS => unreachable, + else => return errno, } } - if (comptime Environment.isPosix) { - var dest = args.path.osPath(&this.sync_error_buf); - std.os.unlinkZ(dest) catch |er| { - // empircally, it seems to return AccessDenied when the - // file is actually a directory on macOS. - if (args.recursive and - (er == error.IsDir or er == error.NotDir or er == error.AccessDenied)) - { - std.os.rmdirZ(dest) catch |err| { - if (args.force) { - return Maybe(Return.Rm).success; - } - - const code: E = switch (err) { - error.AccessDenied => .PERM, - error.SymLinkLoop => .LOOP, - error.NameTooLong => .NAMETOOLONG, - error.SystemResources => .NOMEM, - error.ReadOnlyFileSystem => .ROFS, - error.FileBusy => .BUSY, - error.FileNotFound => .NOENT, - error.InvalidUtf8 => .INVAL, - error.BadPathName => .INVAL, - else => .FAULT, - }; - - return .{ - .err = bun.sys.Error.fromCode( - code, - .rmdir, - ), - }; - }; - - return Maybe(Return.Rm).success; - } + return Maybe(Return.Rm).success; + } + } else if (comptime Environment.isLinux or Environment.isWindows) { + if (args.recursive) { + std.fs.cwd().deleteTree(args.path.slice()) catch |err| { + const errno: E = switch (err) { + error.InvalidHandle => .BADF, + error.AccessDenied => .PERM, + error.FileTooBig => .FBIG, + error.SymLinkLoop => .LOOP, + error.ProcessFdQuotaExceeded => .NFILE, + error.NameTooLong => .NAMETOOLONG, + error.SystemFdQuotaExceeded => .MFILE, + error.SystemResources => .NOMEM, + error.ReadOnlyFileSystem => .ROFS, + error.FileSystem => .IO, + error.FileBusy => .BUSY, + error.DeviceBusy => .BUSY, + + // One of the path components was not a directory. + // This error is unreachable if `sub_path` does not contain a path separator. + error.NotDir => .NOTDIR, + // On Windows, file paths must be valid Unicode. + error.InvalidUtf8 => .INVAL, + + // On Windows, file paths cannot contain these characters: + // '/', '*', '?', '"', '<', '>', '|' + error.BadPathName => .INVAL, + + else => .FAULT, + }; + if (args.force) { + return Maybe(Return.Rm).success; + } + return Maybe(Return.Rm){ + .err = bun.sys.Error.fromCode(errno, .unlink), + }; + }; + return Maybe(Return.Rm).success; + } + } + if (comptime Environment.isPosix) { + var dest = args.path.osPath(&this.sync_error_buf); + std.os.unlinkZ(dest) catch |er| { + // empircally, it seems to return AccessDenied when the + // file is actually a directory on macOS. + if (args.recursive and + (er == error.IsDir or er == error.NotDir or er == error.AccessDenied)) + { + std.os.rmdirZ(dest) catch |err| { if (args.force) { return Maybe(Return.Rm).success; } - { - const code: E = switch (er) { - error.AccessDenied => .PERM, - error.SymLinkLoop => .LOOP, - error.NameTooLong => .NAMETOOLONG, - error.SystemResources => .NOMEM, - error.ReadOnlyFileSystem => .ROFS, - error.FileBusy => .BUSY, - error.InvalidUtf8 => .INVAL, - error.BadPathName => .INVAL, - error.FileNotFound => .NOENT, - else => .FAULT, - }; + const code: E = switch (err) { + error.AccessDenied => .PERM, + error.SymLinkLoop => .LOOP, + error.NameTooLong => .NAMETOOLONG, + error.SystemResources => .NOMEM, + error.ReadOnlyFileSystem => .ROFS, + error.FileBusy => .BUSY, + error.FileNotFound => .NOENT, + error.InvalidUtf8 => .INVAL, + error.BadPathName => .INVAL, + else => .FAULT, + }; - return .{ - .err = bun.sys.Error.fromCode( - code, - .unlink, - ), - }; - } + return .{ + .err = bun.sys.Error.fromCode( + code, + .rmdir, + ), + }; }; return Maybe(Return.Rm).success; } - if (comptime Environment.isWindows) { - var dest = args.path.osPath(&this.sync_error_buf); - std.os.windows.DeleteFile(dest, .{ - .dir = null, - .remove_dir = brk: { - const file_attrs = std.os.windows.GetFileAttributesW(dest.ptr) catch |err| { - if (args.force) { - return Maybe(Return.Rm).success; - } + if (args.force) { + return Maybe(Return.Rm).success; + } - const code: E = switch (err) { - error.FileNotFound => .NOENT, - error.PermissionDenied => .PERM, - else => .INVAL, - }; + { + const code: E = switch (er) { + error.AccessDenied => .PERM, + error.SymLinkLoop => .LOOP, + error.NameTooLong => .NAMETOOLONG, + error.SystemResources => .NOMEM, + error.ReadOnlyFileSystem => .ROFS, + error.FileBusy => .BUSY, + error.InvalidUtf8 => .INVAL, + error.BadPathName => .INVAL, + error.FileNotFound => .NOENT, + else => .FAULT, + }; - return .{ - .err = bun.sys.Error.fromCode( - code, - .unlink, - ), - }; - }; - // TODO: check FILE_ATTRIBUTE_INVALID - break :brk (file_attrs & std.os.windows.FILE_ATTRIBUTE_DIRECTORY) != 0; - }, - }) catch |er| { - // empircally, it seems to return AccessDenied when the - // file is actually a directory on macOS. + return .{ + .err = bun.sys.Error.fromCode( + code, + .unlink, + ), + }; + } + }; + + return Maybe(Return.Rm).success; + } + if (comptime Environment.isWindows) { + var dest = args.path.osPath(&this.sync_error_buf); + std.os.windows.DeleteFile(dest, .{ + .dir = null, + .remove_dir = brk: { + const file_attrs = std.os.windows.GetFileAttributesW(dest.ptr) catch |err| { if (args.force) { return Maybe(Return.Rm).success; } - { - const code: E = switch (er) { - error.FileNotFound => .NOENT, - error.AccessDenied => .PERM, - error.NameTooLong => .INVAL, - error.FileBusy => .BUSY, - error.NotDir => .NOTDIR, - error.IsDir => .ISDIR, - error.DirNotEmpty => .INVAL, - error.NetworkNotFound => .NOENT, - else => .UNKNOWN, - }; + const code: E = switch (err) { + error.FileNotFound => .NOENT, + error.PermissionDenied => .PERM, + else => .INVAL, + }; - return .{ - .err = bun.sys.Error.fromCode( - code, - .unlink, - ), - }; - } + return .{ + .err = bun.sys.Error.fromCode( + code, + .unlink, + ), + }; }; + // TODO: check FILE_ATTRIBUTE_INVALID + break :brk (file_attrs & std.os.windows.FILE_ATTRIBUTE_DIRECTORY) != 0; + }, + }) catch |er| { + // empircally, it seems to return AccessDenied when the + // file is actually a directory on macOS. + if (args.force) { return Maybe(Return.Rm).success; } - }, - else => {}, - } - return Maybe(Return.Rm).todo; + { + const code: E = switch (er) { + error.FileNotFound => .NOENT, + error.AccessDenied => .PERM, + error.NameTooLong => .INVAL, + error.FileBusy => .BUSY, + error.NotDir => .NOTDIR, + error.IsDir => .ISDIR, + error.DirNotEmpty => .INVAL, + error.NetworkNotFound => .NOENT, + else => .UNKNOWN, + }; + + return .{ + .err = bun.sys.Error.fromCode( + code, + .unlink, + ), + }; + } + }; + + return Maybe(Return.Rm).success; + } } pub fn stat(this: *NodeFS, args: Arguments.Stat, comptime flavor: Flavor) Maybe(Return.Stat) { if (comptime Environment.isWindows) { @@ -5199,38 +4892,26 @@ pub const NodeFS = struct { } pub fn symlink(this: *NodeFS, args: Arguments.Symlink, comptime flavor: Flavor) Maybe(Return.Symlink) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Symlink).todo; } var to_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - switch (comptime flavor) { - .sync => { - return Syscall.symlink( - args.old_path.sliceZ(&this.sync_error_buf), - args.new_path.sliceZ(&to_buf), - ); - }, - else => {}, - } - - return Maybe(Return.Symlink).todo; + return Syscall.symlink( + args.old_path.sliceZ(&this.sync_error_buf), + args.new_path.sliceZ(&to_buf), + ); } fn _truncate(this: *NodeFS, path: PathLike, len: JSC.WebCore.Blob.SizeType, comptime flavor: Flavor) Maybe(Return.Truncate) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Truncate).todo; } - switch (comptime flavor) { - .sync => { - return Maybe(Return.Truncate).errno(C.truncate(path.sliceZ(&this.sync_error_buf), len)) orelse - Maybe(Return.Truncate).success; - }, - else => {}, - } - - return Maybe(Return.Truncate).todo; + return Maybe(Return.Truncate).errno(C.truncate(path.sliceZ(&this.sync_error_buf), len)) orelse + Maybe(Return.Truncate).success; } pub fn truncate(this: *NodeFS, args: Arguments.Truncate, comptime flavor: Flavor) Maybe(Return.Truncate) { return switch (args.path) { @@ -5246,19 +4927,13 @@ pub const NodeFS = struct { }; } pub fn unlink(this: *NodeFS, args: Arguments.Unlink, comptime flavor: Flavor) Maybe(Return.Unlink) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Unlink).todo; } - switch (comptime flavor) { - .sync => { - return Maybe(Return.Unlink).errnoSysP(system.unlink(args.path.sliceZ(&this.sync_error_buf)), .unlink, args.path.slice()) orelse - Maybe(Return.Unlink).success; - }, - else => {}, - } - - return Maybe(Return.Unlink).todo; + return Maybe(Return.Unlink).errnoSysP(system.unlink(args.path.sliceZ(&this.sync_error_buf)), .unlink, args.path.slice()) orelse + Maybe(Return.Unlink).success; } pub fn watchFile(_: *NodeFS, args: Arguments.WatchFile, comptime flavor: Flavor) Maybe(Return.WatchFile) { std.debug.assert(flavor == .sync); @@ -5283,6 +4958,7 @@ pub const NodeFS = struct { return Maybe(Return.UnwatchFile).todo; } pub fn utimes(this: *NodeFS, args: Arguments.Utimes, comptime flavor: Flavor) Maybe(Return.Utimes) { + _ = flavor; if (comptime Environment.isWindows) { return Maybe(Return.Utimes).todo; } @@ -5300,21 +4976,13 @@ pub const NodeFS = struct { }, }; - switch (comptime flavor) { - // futimes uses the syscall version - // we use libc because here, not for a good reason - // just missing from the linux syscall interface in zig and I don't want to modify that right now - .sync => return if (Maybe(Return.Utimes).errnoSysP(std.c.utimes(args.path.sliceZ(&this.sync_error_buf), ×), .utimes, args.path.slice())) |err| - err - else - Maybe(Return.Utimes).success, - else => {}, - } - - return Maybe(Return.Utimes).todo; + return if (Maybe(Return.Utimes).errnoSysP(std.c.utimes(args.path.sliceZ(&this.sync_error_buf), ×), .utimes, args.path.slice())) |err| + err + else + Maybe(Return.Utimes).success; } - pub fn lutimes(this: *NodeFS, args: Arguments.Lutimes, comptime flavor: Flavor) Maybe(Return.Lutimes) { + pub fn lutimes(this: *NodeFS, args: Arguments.Lutimes, comptime _: Flavor) Maybe(Return.Lutimes) { if (comptime Environment.isWindows) { return Maybe(Return.Lutimes).todo; } @@ -5332,18 +5000,10 @@ pub const NodeFS = struct { }, }; - switch (comptime flavor) { - // futimes uses the syscall version - // we use libc because here, not for a good reason - // just missing from the linux syscall interface in zig and I don't want to modify that right now - .sync => return if (Maybe(Return.Lutimes).errnoSysP(C.lutimes(args.path.sliceZ(&this.sync_error_buf), ×), .lutimes, args.path.slice())) |err| - err - else - Maybe(Return.Lutimes).success, - else => {}, - } - - return Maybe(Return.Lutimes).todo; + return if (Maybe(Return.Lutimes).errnoSysP(C.lutimes(args.path.sliceZ(&this.sync_error_buf), ×), .lutimes, args.path.slice())) |err| + err + else + Maybe(Return.Lutimes).success; } pub fn watch(_: *NodeFS, args: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) { if (comptime Environment.isWindows) { diff --git a/src/bun.js/node/node_fs_binding.zig b/src/bun.js/node/node_fs_binding.zig index b3866b557..db4d79fc9 100644 --- a/src/bun.js/node/node_fs_binding.zig +++ b/src/bun.js/node/node_fs_binding.zig @@ -67,6 +67,7 @@ fn callSync(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction { args, comptime Flavor.sync, ); + switch (result) { .err => |err| { globalObject.throwValue(JSC.JSValue.c(err.toJS(globalObject))); @@ -108,14 +109,6 @@ fn call(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction { globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, ) callconv(.C) JSC.JSValue { - switch (comptime FunctionEnum) { - .readdir, .lstat, .stat, .readFile, .realpath, .copyFile, .cp => {}, - else => { - globalObject.throw("Not implemented yet", .{}); - return .zero; - }, - } - var arguments = callframe.arguments(8); var slice = ArgumentsSlice.init(globalObject.bunVM(), arguments.ptr[0..arguments.len]); @@ -142,57 +135,12 @@ fn call(comptime FunctionEnum: NodeFSFunctionEnum) NodeFSFunction { // TODO: handle globalObject.throwValue - if (comptime FunctionEnum == .readdir) { - return JSC.Node.AsyncReaddirTask.create(globalObject, args, slice.vm, slice.arena); - } - - if (comptime FunctionEnum == .readFile) { - return JSC.Node.AsyncReadFileTask.create(globalObject, args, slice.vm, slice.arena); - } - - if (comptime FunctionEnum == .realpath) { - return JSC.Node.AsyncRealpathTask.create(globalObject, args, slice.vm, slice.arena); - } - - if (comptime FunctionEnum == .stat or FunctionEnum == .lstat) { - return JSC.Node.AsyncStatTask.create(globalObject, args, slice.vm, FunctionEnum == .lstat, slice.arena); - } - - if (comptime FunctionEnum == .copyFile) { - return JSC.Node.AsyncCopyFileTask.create(globalObject, args, slice.vm, slice.arena); - } - + const Task = @field(JSC.Node.Async, @tagName(FunctionEnum)); if (comptime FunctionEnum == .cp) { - return JSC.Node.AsyncCpTask.create(globalObject, args, slice.vm, slice.arena); + return Task.create(globalObject, args, globalObject.bunVM(), slice.arena); + } else { + return Task.create(globalObject, args, globalObject.bunVM()); } - - // defer { - // for (arguments.len) |arg| { - // JSC.C.JSValueUnprotect(ctx, arg); - // } - // slice.arena.deinit(); - // } - - // const args = if (comptime Arguments != void) - // Arguments.fromJS(ctx, &slice, exception) - // else - // Arguments{}; - // if (exception.* != null) return null; - - // const result: Maybe(Result) = Function(this, comptime Flavor.sync, args); - // switch (result) { - // .err => |err| { - // exception.* = err.toJS(ctx); - // return null; - // }, - // .result => |res| { - // return switch (comptime Result) { - // void => JSC.JSValue.jsUndefined().asRef(), - // else => res.toJS(ctx), - // }; - // }, - // } - // unreachable; } }; return NodeBindingClosure.bind; diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 35daea52c..84dc394d0 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -315,6 +315,68 @@ pub const StringOrBunStringOrBuffer = union(enum) { } }; +pub const SliceWithUnderlyingStringOrBuffer = union(enum) { + SliceWithUnderlyingString: bun.SliceWithUnderlyingString, + buffer: Buffer, + + pub fn toThreadSafe(this: *@This()) void { + switch (this.*) { + .SliceWithUnderlyingString => this.SliceWithUnderlyingString.toThreadSafe(), + else => {}, + } + } + + pub fn toJS(this: *SliceWithUnderlyingStringOrBuffer, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef { + return switch (this) { + .SliceWithUnderlyingStringOrBuffer => { + defer { + this.SliceWithUnderlyingString.deinit(); + this.SliceWithUnderlyingString.underlying = bun.String.empty; + this.SliceWithUnderlyingString.utf8 = .{}; + } + + return this.SliceWithUnderlyingString.underlying.toJS(ctx); + }, + .buffer => this.buffer.toJSObjectRef(ctx, exception), + }; + } + + pub fn slice(this: *const SliceWithUnderlyingStringOrBuffer) []const u8 { + return switch (this.*) { + .SliceWithUnderlyingString => this.SliceWithUnderlyingString.slice(), + .buffer => this.buffer.slice(), + }; + } + + pub fn fromJS(global: *JSC.JSGlobalObject, allocator: std.mem.Allocator, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?SliceWithUnderlyingStringOrBuffer { + _ = exception; + return switch (value.jsType()) { + JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject, JSC.JSValue.JSType.Object => { + var str = bun.String.tryFromJS(value, global) orelse return null; + return SliceWithUnderlyingStringOrBuffer{ .SliceWithUnderlyingString = str.toSlice(allocator) }; + }, + + .ArrayBuffer, + .Int8Array, + .Uint8Array, + .Uint8ClampedArray, + .Int16Array, + .Uint16Array, + .Int32Array, + .Uint32Array, + .Float32Array, + .Float64Array, + .BigInt64Array, + .BigUint64Array, + .DataView, + => SliceWithUnderlyingStringOrBuffer{ + .buffer = Buffer.fromArrayBuffer(global, value), + }, + else => null, + }; + } +}; + /// Like StringOrBuffer but actually returns a Node.js Buffer pub const StringOrNodeBuffer = union(Tag) { string: string, @@ -401,6 +463,20 @@ pub const SliceOrBuffer = union(Tag) { } } + pub fn toThreadSafe(this: *SliceOrBuffer) void { + var buffer: ?Buffer = null; + if (this.* == .buffer) { + buffer = this.buffer; + this.buffer.buffer.value.ensureStillAlive(); + } + defer { + if (buffer) |buf| { + buf.buffer.value.unprotect(); + } + } + this.ensureCloned(bun.default_allocator) catch unreachable; + } + pub const Tag = enum { string, buffer }; pub fn slice(this: SliceOrBuffer) []const u8 { @@ -650,6 +726,10 @@ pub const PathLike = union(Tag) { if (this.* == .slice_with_underlying_string) { this.slice_with_underlying_string.toThreadSafe(); } + + if (this.* == .buffer) { + this.buffer.buffer.value.protect(); + } } pub fn deinitAndUnprotect(this: *const PathLike) void { |