diff options
Diffstat (limited to 'src/bun.js/node/node_fs.zig')
-rw-r--r-- | src/bun.js/node/node_fs.zig | 3657 |
1 files changed, 3657 insertions, 0 deletions
diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig new file mode 100644 index 000000000..3cacb57b2 --- /dev/null +++ b/src/bun.js/node/node_fs.zig @@ -0,0 +1,3657 @@ +// This file contains the underlying implementation for sync & async functions +// for interacting with the filesystem from JavaScript. +// The top-level functions assume the arguments are already validated +const std = @import("std"); +const bun = @import("../../global.zig"); +const strings = bun.strings; +const string = bun.string; +const AsyncIO = @import("io"); +const JSC = @import("../../jsc.zig"); +const PathString = JSC.PathString; +const Environment = bun.Environment; +const C = bun.C; +const Flavor = JSC.Node.Flavor; +const system = std.os.system; +const Maybe = JSC.Maybe; +const Encoding = JSC.Node.Encoding; +const Syscall = @import("./syscall.zig"); +const Constants = @import("./node_fs_constant.zig").Constants; +const builtin = @import("builtin"); +const os = @import("std").os; +const darwin = os.darwin; +const linux = os.linux; +const PathOrBuffer = JSC.Node.PathOrBuffer; +const PathLike = JSC.Node.PathLike; +const PathOrFileDescriptor = JSC.Node.PathOrFileDescriptor; +const FileDescriptor = JSC.Node.FileDescriptor; +const DirIterator = @import("./dir_iterator.zig"); +const Path = @import("../../resolver/resolve_path.zig"); +const FileSystem = @import("../../fs.zig").FileSystem; +const StringOrBuffer = JSC.Node.StringOrBuffer; +const ArgumentsSlice = JSC.Node.ArgumentsSlice; +const TimeLike = JSC.Node.TimeLike; +const Mode = JSC.Node.Mode; + +const uid_t = std.os.uid_t; +const gid_t = std.os.gid_t; + +/// u63 to allow one null bit +const ReadPosition = u63; + +const Stats = JSC.Node.Stats; +const BigIntStats = JSC.Node.BigIntStats; +const DirEnt = JSC.Node.DirEnt; + +pub const FlavoredIO = struct { + io: *AsyncIO, +}; + +pub const default_permission = Syscall.S.IRUSR | + Syscall.S.IWUSR | + Syscall.S.IRGRP | + Syscall.S.IWGRP | + Syscall.S.IROTH | + Syscall.S.IWOTH; + +const ArrayBuffer = JSC.MarkedArrayBuffer; +const Buffer = JSC.Buffer; +const FileSystemFlags = JSC.Node.FileSystemFlags; + +// TODO: to improve performance for all of these +// The tagged unions for each type should become regular unions +// and the tags should be passed in as comptime arguments to the functions performing the syscalls +// This would reduce stack size, at the cost of instruction cache misses +const Arguments = struct { + pub const Rename = struct { + old_path: PathLike, + new_path: PathLike, + + 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) { + JSC.throwInvalidArguments( + "oldPath must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const new_path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "newPath must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + return Rename{ .old_path = old_path, .new_path = new_path }; + } + }; + + pub const Truncate = struct { + /// Passing a file descriptor is deprecated and may result in an error being thrown in the future. + path: PathOrFileDescriptor, + len: JSC.WebCore.Blob.SizeType = 0, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Truncate { + const path = PathOrFileDescriptor.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const len: JSC.WebCore.Blob.SizeType = brk: { + const len_value = arguments.next() orelse break :brk 0; + + if (len_value.isNumber()) { + arguments.eat(); + break :brk len_value.to(JSC.WebCore.Blob.SizeType); + } + + break :brk 0; + }; + + return Truncate{ .path = path, .len = len }; + } + }; + + pub const FTruncate = struct { + fd: FileDescriptor, + len: ?JSC.WebCore.Blob.SizeType = null, + + 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) { + JSC.throwInvalidArguments( + "file descriptor is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor must be a number", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + + if (exception.* != null) return null; + + const len: JSC.WebCore.Blob.SizeType = brk: { + const len_value = arguments.next() orelse break :brk 0; + if (len_value.isNumber()) { + arguments.eat(); + break :brk len_value.to(JSC.WebCore.Blob.SizeType); + } + + break :brk 0; + }; + + return FTruncate{ .fd = fd, .len = len }; + } + }; + + pub const Chown = struct { + path: PathLike, + uid: uid_t = 0, + gid: gid_t = 0, + + 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) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const uid: uid_t = brk: { + const uid_value = arguments.next() orelse break :brk { + if (exception.* == null) { + JSC.throwInvalidArguments( + "uid is required", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + break :brk @intCast(uid_t, uid_value.toInt32()); + }; + + const gid: gid_t = brk: { + const gid_value = arguments.next() orelse break :brk { + if (exception.* == null) { + JSC.throwInvalidArguments( + "gid is required", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + break :brk @intCast(gid_t, gid_value.toInt32()); + }; + + return Chown{ .path = path, .uid = uid, .gid = gid }; + } + }; + + pub const Fchown = struct { + fd: FileDescriptor, + uid: uid_t, + gid: gid_t, + + 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) { + JSC.throwInvalidArguments( + "file descriptor is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor must be a number", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const uid: uid_t = brk: { + const uid_value = arguments.next() orelse break :brk { + if (exception.* == null) { + JSC.throwInvalidArguments( + "uid is required", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + break :brk @intCast(uid_t, uid_value.toInt32()); + }; + + const gid: gid_t = brk: { + const gid_value = arguments.next() orelse break :brk { + if (exception.* == null) { + JSC.throwInvalidArguments( + "gid is required", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + break :brk @intCast(gid_t, gid_value.toInt32()); + }; + + return Fchown{ .fd = fd, .uid = uid, .gid = gid }; + } + }; + + pub const LChown = Chown; + + pub const Lutimes = struct { + path: PathLike, + atime: TimeLike, + mtime: TimeLike, + + 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) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const atime = JSC.Node.timeLikeFromJS(ctx, arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "atime is required", + .{}, + ctx, + exception, + ); + } + + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "atime must be a number or a Date", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + + const mtime = JSC.Node.timeLikeFromJS(ctx, arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mtime is required", + .{}, + ctx, + exception, + ); + } + + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mtime must be a number or a Date", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + + return Lutimes{ .path = path, .atime = atime, .mtime = mtime }; + } + }; + + pub const Chmod = struct { + path: PathLike, + mode: Mode = 0x777, + + 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) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + const mode: Mode = JSC.Node.modeFromJS(ctx, arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mode is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mode must be a string or integer", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + + return Chmod{ .path = path, .mode = mode }; + } + }; + + pub const FChmod = struct { + fd: FileDescriptor, + mode: Mode = 0x777, + + 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) { + JSC.throwInvalidArguments( + "file descriptor is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor must be a number", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + arguments.eat(); + + const mode: Mode = JSC.Node.modeFromJS(ctx, arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mode is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mode must be a string or integer", + .{}, + ctx, + exception, + ); + } + return null; + }; + + arguments.eat(); + + return FChmod{ .fd = fd, .mode = mode }; + } + }; + + pub const LCHmod = Chmod; + + pub const Stat = struct { + path: PathLike, + big_int: bool = false, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Stat { + const path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const big_int = brk: { + if (arguments.next()) |next_val| { + if (next_val.isObject()) { + if (next_val.isCallable(ctx.ptr().vm())) break :brk false; + arguments.eat(); + + if (next_val.getIfPropertyExists(ctx.ptr(), "bigint")) |big_int| { + break :brk big_int.toBoolean(); + } + } + } + break :brk false; + }; + + if (exception.* != null) return null; + + return Stat{ .path = path, .big_int = big_int }; + } + }; + + pub const Fstat = struct { + fd: FileDescriptor, + big_int: bool = false, + + 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) { + JSC.throwInvalidArguments( + "file descriptor is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "file descriptor must be a number", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const big_int = brk: { + if (arguments.next()) |next_val| { + if (next_val.isObject()) { + if (next_val.isCallable(ctx.ptr().vm())) break :brk false; + arguments.eat(); + + if (next_val.getIfPropertyExists(ctx.ptr(), "bigint")) |big_int| { + break :brk big_int.toBoolean(); + } + } + } + break :brk false; + }; + + if (exception.* != null) return null; + + return Fstat{ .fd = fd, .big_int = big_int }; + } + }; + + pub const Lstat = Stat; + + pub const Link = struct { + old_path: PathLike, + new_path: PathLike, + + 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) { + JSC.throwInvalidArguments( + "oldPath must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const new_path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "newPath must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + return Link{ .old_path = old_path, .new_path = new_path }; + } + }; + + pub const Symlink = struct { + old_path: PathLike, + new_path: PathLike, + + 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) { + JSC.throwInvalidArguments( + "target must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const new_path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + if (arguments.next()) |next_val| { + // The type argument is only available on Windows and + // ignored on other platforms. It can be set to 'dir', + // 'file', or 'junction'. If the type argument is not set, + // Node.js will autodetect target type and use 'file' or + // 'dir'. If the target does not exist, 'file' will be used. + // Windows junction points require the destination path to + // be absolute. When using 'junction', the target argument + // will automatically be normalized to absolute path. + if (next_val.isString()) { + comptime if (Environment.isWindows) @compileError("Add support for type argument on Windows"); + arguments.eat(); + } + } + + return Symlink{ .old_path = old_path, .new_path = new_path }; + } + }; + + pub const Readlink = struct { + path: PathLike, + encoding: Encoding = Encoding.utf8, + + 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) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + var encoding = Encoding.utf8; + if (arguments.next()) |val| { + arguments.eat(); + + switch (val.jsType()) { + JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => { + encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8; + }, + else => { + if (val.isObject()) { + if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| { + encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8; + } + } + }, + } + } + + return Readlink{ .path = path, .encoding = encoding }; + } + }; + + pub const Realpath = struct { + path: PathLike, + encoding: Encoding = Encoding.utf8, + + 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) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + var encoding = Encoding.utf8; + if (arguments.next()) |val| { + arguments.eat(); + + switch (val.jsType()) { + JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => { + encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8; + }, + else => { + if (val.isObject()) { + if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| { + encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8; + } + } + }, + } + } + + return Realpath{ .path = path, .encoding = encoding }; + } + }; + + pub const Unlink = struct { + path: PathLike, + + 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) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + return Unlink{ + .path = path, + }; + } + }; + + pub const Rm = struct { + path: PathLike, + force: bool = false, + max_retries: u32 = 0, + recursive: bool = false, + retry_delay: c_uint = 100, + }; + + pub const RmDir = struct { + path: PathLike, + + max_retries: u32 = 0, + recursive: bool = false, + retry_delay: c_uint = 100, + }; + + /// https://github.com/nodejs/node/blob/master/lib/fs.js#L1285 + pub const Mkdir = struct { + path: PathLike, + /// Indicates whether parent folders should be created. + /// If a folder was created, the path to the first created folder will be returned. + /// @default false + recursive: bool = false, + /// A file mode. If a string is passed, it is parsed as an octal integer. If not specified + /// @default + mode: Mode = 0o777, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Mkdir { + const path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var recursive = false; + var mode: Mode = 0o777; + + if (arguments.next()) |val| { + arguments.eat(); + + if (val.isObject()) { + if (val.getIfPropertyExists(ctx.ptr(), "recursive")) |recursive_| { + recursive = recursive_.toBoolean(); + } + + if (val.getIfPropertyExists(ctx.ptr(), "mode")) |mode_| { + mode = JSC.Node.modeFromJS(ctx, mode_, exception) orelse mode; + } + } + } + + return Mkdir{ + .path = path, + .recursive = recursive, + .mode = mode, + }; + } + }; + + const MkdirTemp = struct { + prefix: string = "", + encoding: Encoding = Encoding.utf8, + + 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.ZigString.Empty; + prefix_value.toZigString(&prefix, ctx.ptr()); + + if (exception.* != null) return null; + + arguments.eat(); + + var encoding = Encoding.utf8; + + if (arguments.next()) |val| { + arguments.eat(); + + switch (val.jsType()) { + JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => { + encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8; + }, + else => { + if (val.isObject()) { + if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| { + encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8; + } + } + }, + } + } + + return MkdirTemp{ + .prefix = prefix.slice(), + .encoding = encoding, + }; + } + }; + + pub const Readdir = struct { + path: PathLike, + encoding: Encoding = Encoding.utf8, + with_file_types: bool = false, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Readdir { + const path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var encoding = Encoding.utf8; + var with_file_types = false; + + if (arguments.next()) |val| { + arguments.eat(); + + switch (val.jsType()) { + JSC.JSValue.JSType.String, JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject => { + encoding = Encoding.fromStringValue(val, ctx.ptr()) orelse Encoding.utf8; + }, + else => { + if (val.isObject()) { + if (val.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| { + encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse Encoding.utf8; + } + + if (val.getIfPropertyExists(ctx.ptr(), "withFileTypes")) |with_file_types_| { + with_file_types = with_file_types_.toBoolean(); + } + } + }, + } + } + + return Readdir{ + .path = path, + .encoding = encoding, + .with_file_types = with_file_types, + }; + } + }; + + pub const Close = struct { + fd: FileDescriptor, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Close { + 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 Close{ + .fd = fd, + }; + } + }; + + pub const Open = struct { + path: PathLike, + flags: FileSystemFlags = FileSystemFlags.@"r", + mode: Mode = default_permission, + + 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) { + JSC.throwInvalidArguments( + "path must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var flags = FileSystemFlags.@"r"; + var mode: Mode = default_permission; + + if (arguments.next()) |val| { + arguments.eat(); + + if (val.isObject()) { + if (val.getIfPropertyExists(ctx.ptr(), "flags")) |flags_| { + flags = FileSystemFlags.fromJS(ctx, flags_, exception) orelse flags; + } + + if (val.getIfPropertyExists(ctx.ptr(), "mode")) |mode_| { + mode = JSC.Node.modeFromJS(ctx, mode_, exception) orelse mode; + } + } else if (!val.isEmpty()) { + flags = FileSystemFlags.fromJS(ctx, val, exception) orelse flags; + + if (arguments.nextEat()) |next| { + mode = JSC.Node.modeFromJS(ctx, next, exception) orelse mode; + } + } + } + + if (exception.* != null) return null; + + return Open{ + .path = path, + .flags = flags, + .mode = mode, + }; + } + }; + + /// Change the file system timestamps of the object referenced by `path`. + /// + /// The `atime` and `mtime` arguments follow these rules: + /// + /// * Values can be either numbers representing Unix epoch time in seconds,`Date`s, or a numeric string like `'123456789.0'`. + /// * If the value can not be converted to a number, or is `NaN`, `Infinity` or`-Infinity`, an `Error` will be thrown. + /// @since v0.4.2 + pub const Utimes = Lutimes; + + pub const Futimes = struct { + fd: FileDescriptor, + atime: TimeLike, + mtime: TimeLike, + + 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) { + 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; + }; + arguments.eat(); + if (exception.* != null) return null; + + const atime = JSC.Node.timeLikeFromJS(ctx, arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "atime is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "atime must be a number, Date or string", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const mtime = JSC.Node.timeLikeFromJS(ctx, arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mtime is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "mtime must be a number, Date or string", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + return Futimes{ + .fd = fd, + .atime = atime, + .mtime = mtime, + }; + } + }; + + pub const FSync = struct { + fd: FileDescriptor, + + 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. + /// + /// `offset` determines the part of the buffer to be written, and `length` is + /// an integer specifying the number of bytes to write. + /// + /// `position` refers to the offset from the beginning of the file where this data + /// should be written. If `typeof position !== 'number'`, the data will be written + /// at the current position. See [`pwrite(2)`](http://man7.org/linux/man-pages/man2/pwrite.2.html). + /// + /// The callback will be given three arguments `(err, bytesWritten, buffer)` where`bytesWritten` specifies how many _bytes_ were written from `buffer`. + /// + /// If this method is invoked as its `util.promisify()` ed version, it returns + /// a promise for an `Object` with `bytesWritten` and `buffer` properties. + /// + /// It is unsafe to use `fs.write()` multiple times on the same file without waiting + /// for the callback. For this scenario, {@link createWriteStream} is + /// recommended. + /// + /// On Linux, positional writes don't work when the file is opened in append mode. + /// The kernel ignores the position argument and always appends the data to + /// the end of the file. + /// @since v0.0.2 + /// + pub const Write = struct { + fd: FileDescriptor, + buffer: StringOrBuffer, + // buffer_val: JSC.JSValue = JSC.JSValue.zero, + offset: u64 = 0, + length: u64 = std.math.maxInt(u64), + position: ?ReadPosition = null, + encoding: Encoding = Encoding.buffer, + + 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) { + 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; + }; + + arguments.eat(); + + if (exception.* != null) return null; + + const buffer = StringOrBuffer.fromJS(ctx.ptr(), arguments.arena.allocator(), arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "data is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "data must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + if (exception.* != null) return null; + + var args = Write{ + .fd = fd, + .buffer = buffer, + .encoding = switch (buffer) { + .string => Encoding.utf8, + .buffer => Encoding.buffer, + }, + }; + + arguments.eat(); + + // TODO: make this faster by passing argument count at comptime + if (arguments.next()) |current_| { + parse: { + var current = current_; + switch (buffer) { + // fs.write(fd, string[, position[, encoding]], callback) + .string => { + if (current.isNumber()) { + args.position = current.toU32(); + arguments.eat(); + current = arguments.next() orelse break :parse; + } + + if (current.isString()) { + args.encoding = Encoding.fromStringValue(current, ctx.ptr()) orelse Encoding.utf8; + arguments.eat(); + } + }, + // fs.write(fd, buffer[, offset[, length[, position]]], callback) + .buffer => { + if (!current.isNumber()) { + break :parse; + } + + if (!current.isNumber()) break :parse; + args.offset = current.toU32(); + arguments.eat(); + current = arguments.next() orelse break :parse; + + if (!current.isNumber()) break :parse; + args.length = current.toU32(); + arguments.eat(); + current = arguments.next() orelse break :parse; + + if (!current.isNumber()) break :parse; + args.position = current.toU32(); + arguments.eat(); + }, + } + } + } + + return args; + } + }; + + pub const Read = struct { + fd: FileDescriptor, + buffer: Buffer, + offset: u64 = 0, + length: u64 = std.math.maxInt(u64), + position: ?ReadPosition = null, + + 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) { + 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; + }; + + arguments.eat(); + + if (exception.* != null) return null; + + const buffer = Buffer.fromJS(ctx.ptr(), arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "buffer is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "buffer must be a TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + arguments.eat(); + + var args = Read{ + .fd = fd, + .buffer = buffer, + }; + + if (arguments.next()) |current| { + arguments.eat(); + if (current.isNumber()) { + args.offset = current.toU32(); + + if (arguments.remaining.len < 2) { + JSC.throwInvalidArguments( + "length and position are required", + .{}, + ctx, + exception, + ); + + return null; + } + + args.length = arguments.remaining[0].toU32(); + + if (args.length == 0) { + JSC.throwInvalidArguments( + "length must be greater than 0", + .{}, + ctx, + exception, + ); + + return null; + } + + const position: i32 = if (arguments.remaining[1].isNumber()) + arguments.remaining[1].toInt32() + else + -1; + + args.position = if (position > -1) @intCast(ReadPosition, position) else null; + arguments.remaining = arguments.remaining[2..]; + } else if (current.isObject()) { + if (current.getIfPropertyExists(ctx.ptr(), "offset")) |num| { + args.offset = num.toU32(); + } + + if (current.getIfPropertyExists(ctx.ptr(), "length")) |num| { + args.length = num.toU32(); + } + + if (current.getIfPropertyExists(ctx.ptr(), "position")) |num| { + const position: i32 = if (num.isEmptyOrUndefinedOrNull()) -1 else num.toInt32(); + if (position > -1) { + args.position = @intCast(ReadPosition, position); + } + } + } + } + + return args; + } + }; + + /// Asynchronously reads the entire contents of a file. + /// @param path A path to a file. If a URL is provided, it must use the `file:` protocol. + /// If a file descriptor is provided, the underlying file will _not_ be closed automatically. + /// @param options Either the encoding for the result, or an object that contains the encoding and an optional flag. + /// If a flag is not provided, it defaults to `'r'`. + pub const ReadFile = struct { + path: PathOrFileDescriptor, + encoding: Encoding = Encoding.utf8, + + flag: FileSystemFlags = FileSystemFlags.@"r", + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?ReadFile { + const path = PathOrFileDescriptor.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or a file descriptor", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var encoding = Encoding.buffer; + var flag = FileSystemFlags.@"r"; + + if (arguments.next()) |arg| { + arguments.eat(); + if (arg.isString()) { + encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } else if (arg.isObject()) { + if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| { + if (!encoding_.isUndefinedOrNull()) { + encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + } + + if (arg.getIfPropertyExists(ctx.ptr(), "flag")) |flag_| { + flag = FileSystemFlags.fromJS(ctx, flag_, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid flag", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + } + } + + // Note: Signal is not implemented + return ReadFile{ + .path = path, + .encoding = encoding, + .flag = flag, + }; + } + }; + + pub const WriteFile = struct { + encoding: Encoding = Encoding.utf8, + flag: FileSystemFlags = FileSystemFlags.@"w", + mode: Mode = 0666, + file: PathOrFileDescriptor, + data: StringOrBuffer, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?WriteFile { + const file = PathOrFileDescriptor.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or a file descriptor", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const data = StringOrBuffer.fromJS(ctx.ptr(), arguments.arena.allocator(), arguments.next() orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "data is required", + .{}, + ctx, + exception, + ); + } + return null; + }, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "data must be a string or TypedArray", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + arguments.eat(); + + var encoding = Encoding.buffer; + var flag = FileSystemFlags.@"w"; + var mode: Mode = default_permission; + + if (arguments.next()) |arg| { + arguments.eat(); + if (arg.isString()) { + encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } else if (arg.isObject()) { + if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| { + if (!encoding_.isUndefinedOrNull()) { + encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + } + + if (arg.getIfPropertyExists(ctx.ptr(), "flag")) |flag_| { + flag = FileSystemFlags.fromJS(ctx, flag_, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid flag", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "mode")) |mode_| { + mode = JSC.Node.modeFromJS(ctx, mode_, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid flag", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + } + } + + // Note: Signal is not implemented + return WriteFile{ + .file = file, + .encoding = encoding, + .flag = flag, + .mode = mode, + .data = data, + }; + } + }; + + pub const AppendFile = WriteFile; + + pub const OpenDir = struct { + path: PathLike, + encoding: Encoding = Encoding.utf8, + + /// Number of directory entries that are buffered internally when reading from the directory. Higher values lead to better performance but higher memory usage. Default: 32 + buffer_size: c_int = 32, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?OpenDir { + const path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or a file descriptor", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var encoding = Encoding.buffer; + var buffer_size: c_int = 32; + + if (arguments.next()) |arg| { + arguments.eat(); + if (arg.isString()) { + encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } else if (arg.isObject()) { + if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding_| { + if (!encoding_.isUndefinedOrNull()) { + encoding = Encoding.fromStringValue(encoding_, ctx.ptr()) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + } + + if (arg.getIfPropertyExists(ctx.ptr(), "bufferSize")) |buffer_size_| { + buffer_size = buffer_size_.toInt32(); + if (buffer_size < 0) { + if (exception.* == null) { + JSC.throwInvalidArguments( + "bufferSize must be > 0", + .{}, + ctx, + exception, + ); + } + return null; + } + } + } + } + + return OpenDir{ + .path = path, + .encoding = encoding, + .buffer_size = buffer_size, + }; + } + }; + pub const Exists = struct { + path: PathLike, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?Exists { + const path = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "path must be a string or buffer", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + return Exists{ + .path = path, + }; + } + }; + + pub const Access = struct { + path: PathLike, + mode: FileSystemFlags = FileSystemFlags.@"r", + + 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) { + JSC.throwInvalidArguments( + "path must be a string or buffer", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var mode = FileSystemFlags.@"r"; + + if (arguments.next()) |arg| { + arguments.eat(); + if (arg.isString()) { + mode = FileSystemFlags.fromJS(ctx, arg, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "Invalid mode", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + } + + return Access{ + .path = path, + .mode = mode, + }; + } + }; + + pub const CreateReadStream = struct { + file: PathOrFileDescriptor, + flags: FileSystemFlags = FileSystemFlags.@"r", + encoding: Encoding = Encoding.utf8, + mode: Mode = default_permission, + autoClose: bool = true, + emitClose: bool = true, + start: i32 = 0, + end: i32 = std.math.maxInt(i32), + highwater_mark: u32 = 64 * 1024, + global_object: *JSC.JSGlobalObject, + + pub fn copyToState(this: CreateReadStream, state: *JSC.Node.Readable.State) void { + state.encoding = this.encoding; + state.highwater_mark = this.highwater_mark; + state.start = this.start; + state.end = this.end; + } + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?CreateReadStream { + var path = PathLike.fromJS(ctx, arguments, exception); + if (exception.* != null) return null; + if (path == null) arguments.eat(); + + var stream = CreateReadStream{ + .file = undefined, + .global_object = ctx.ptr(), + }; + var fd: FileDescriptor = std.math.maxInt(FileDescriptor); + + if (arguments.next()) |arg| { + arguments.eat(); + if (arg.isString()) { + stream.encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } else if (arg.isObject()) { + if (arg.getIfPropertyExists(ctx.ptr(), "mode")) |mode_| { + stream.mode = JSC.Node.modeFromJS(ctx, mode_, exception) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid mode", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding| { + stream.encoding = Encoding.fromStringValue(encoding, ctx.ptr()) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "flags")) |flags| { + stream.flags = FileSystemFlags.fromJS(ctx, flags, exception) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid flags", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "fd")) |flags| { + fd = JSC.Node.fileDescriptorFromJS(ctx, flags, exception) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid file descriptor", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "autoClose")) |autoClose| { + stream.autoClose = autoClose.toBoolean(); + } + + if (arg.getIfPropertyExists(ctx.ptr(), "emitClose")) |emitClose| { + stream.emitClose = emitClose.toBoolean(); + } + + if (arg.getIfPropertyExists(ctx.ptr(), "start")) |start| { + stream.start = start.toInt32(); + } + + if (arg.getIfPropertyExists(ctx.ptr(), "end")) |end| { + stream.end = end.toInt32(); + } + + if (arg.getIfPropertyExists(ctx.ptr(), "highWaterMark")) |highwaterMark| { + stream.highwater_mark = highwaterMark.toU32(); + } + } + } + + if (fd != std.math.maxInt(FileDescriptor)) { + stream.file = .{ .fd = fd }; + } else if (path) |path_| { + stream.file = .{ .path = path_ }; + } else { + JSC.throwInvalidArguments("Missing path or file descriptor", .{}, ctx, exception); + return null; + } + return stream; + } + }; + + pub const CreateWriteStream = struct { + file: PathOrFileDescriptor, + flags: FileSystemFlags = FileSystemFlags.@"w", + encoding: Encoding = Encoding.utf8, + mode: Mode = default_permission, + autoClose: bool = true, + emitClose: bool = true, + start: i32 = 0, + highwater_mark: u32 = 256 * 1024, + global_object: *JSC.JSGlobalObject, + + pub fn copyToState(this: CreateWriteStream, state: *JSC.Node.Writable.State) void { + state.encoding = this.encoding; + state.highwater_mark = this.highwater_mark; + state.start = this.start; + state.emit_close = this.emitClose; + } + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?CreateWriteStream { + var path = PathLike.fromJS(ctx, arguments, exception); + if (exception.* != null) return null; + if (path == null) arguments.eat(); + + var stream = CreateWriteStream{ + .file = undefined, + .global_object = ctx.ptr(), + }; + var fd: FileDescriptor = std.math.maxInt(FileDescriptor); + + if (arguments.next()) |arg| { + arguments.eat(); + if (arg.isString()) { + stream.encoding = Encoding.fromStringValue(arg, ctx.ptr()) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } else if (arg.isObject()) { + if (arg.getIfPropertyExists(ctx.ptr(), "mode")) |mode_| { + stream.mode = JSC.Node.modeFromJS(ctx, mode_, exception) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid mode", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "encoding")) |encoding| { + stream.encoding = Encoding.fromStringValue(encoding, ctx.ptr()) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid encoding", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "flags")) |flags| { + stream.flags = FileSystemFlags.fromJS(ctx, flags, exception) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid flags", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "fd")) |flags| { + fd = JSC.Node.fileDescriptorFromJS(ctx, flags, exception) orelse { + if (exception.* != null) { + JSC.throwInvalidArguments( + "Invalid file descriptor", + .{}, + ctx, + exception, + ); + } + return null; + }; + } + + if (arg.getIfPropertyExists(ctx.ptr(), "autoClose")) |autoClose| { + stream.autoClose = autoClose.toBoolean(); + } + + if (arg.getIfPropertyExists(ctx.ptr(), "emitClose")) |emitClose| { + stream.emitClose = emitClose.toBoolean(); + } + + if (arg.getIfPropertyExists(ctx.ptr(), "start")) |start| { + stream.start = start.toInt32(); + } + } + } + + if (fd != std.math.maxInt(FileDescriptor)) { + stream.file = .{ .fd = fd }; + } else if (path) |path_| { + stream.file = .{ .path = path_ }; + } else { + JSC.throwInvalidArguments("Missing path or file descriptor", .{}, ctx, exception); + return null; + } + return stream; + } + }; + + pub const FdataSync = struct { + fd: FileDescriptor, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?FdataSync { + 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 FdataSync{ + .fd = fd, + }; + } + }; + + pub const CopyFile = struct { + src: PathLike, + dest: PathLike, + mode: Constants.Copyfile, + + pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?CopyFile { + const src = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "src must be a string or buffer", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + const dest = PathLike.fromJS(ctx, arguments, exception) orelse { + if (exception.* == null) { + JSC.throwInvalidArguments( + "dest must be a string or buffer", + .{}, + ctx, + exception, + ); + } + return null; + }; + + if (exception.* != null) return null; + + var mode: i32 = 0; + if (arguments.next()) |arg| { + arguments.eat(); + if (arg.isNumber()) { + mode = arg.toInt32(); + } + } + + return CopyFile{ + .src = src, + .dest = dest, + .mode = @intToEnum(Constants.Copyfile, mode), + }; + } + }; + + pub const WriteEv = struct { + fd: FileDescriptor, + buffers: []const ArrayBuffer, + position: ReadPosition, + }; + + pub const ReadEv = struct { + fd: FileDescriptor, + buffers: []ArrayBuffer, + position: ReadPosition, + }; + + pub const Copy = struct { + pub const FilterCallback = fn (source: string, destination: string) bool; + /// Dereference symlinks + /// @default false + dereference: bool = false, + + /// When `force` is `false`, and the destination + /// exists, throw an error. + /// @default false + errorOnExist: bool = false, + + /// Function to filter copied files/directories. Return + /// `true` to copy the item, `false` to ignore it. + filter: ?FilterCallback = null, + + /// Overwrite existing file or directory. _The copy + /// operation will ignore errors if you set this to false and the destination + /// exists. Use the `errorOnExist` option to change this behavior. + /// @default true + force: bool = true, + + /// When `true` timestamps from `src` will + /// be preserved. + /// @default false + preserve_timestamps: bool = false, + + /// Copy directories recursively. + /// @default false + recursive: bool = false, + }; + + pub const UnwatchFile = void; + pub const Watch = void; + pub const WatchFile = void; + pub const Fsync = struct { + fd: FileDescriptor, + + 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, + }; + } + }; +}; + +const Return = struct { + pub const Access = void; + pub const AppendFile = void; + pub const Close = void; + pub const CopyFile = void; + pub const Exists = bool; + pub const Fchmod = void; + pub const Chmod = void; + pub const Fchown = void; + pub const Fdatasync = void; + pub const Fstat = Stats; + pub const Rm = void; + pub const Fsync = void; + pub const Ftruncate = void; + pub const Futimes = void; + pub const Lchmod = void; + pub const Lchown = void; + pub const Link = void; + pub const Lstat = Stats; + pub const Mkdir = string; + pub const Mkdtemp = PathString; + pub const Open = FileDescriptor; + pub const WriteFile = void; + pub const Read = struct { + bytes_read: u52, + + pub fn toJS(this: Read, _: JSC.C.JSContextRef, _: JSC.C.ExceptionRef) JSC.C.JSValueRef { + return JSC.JSValue.jsNumberFromUint64(this.bytes_read).asObjectRef(); + } + }; + pub const ReadPromise = struct { + bytes_read: u52, + buffer_val: JSC.JSValue = JSC.JSValue.zero, + const fields = .{ + .@"bytesRead" = JSC.ZigString.init("bytesRead"), + .@"buffer" = JSC.ZigString.init("buffer"), + }; + pub fn toJS(this: Read, ctx: JSC.C.JSContextRef, _: JSC.C.ExceptionRef) JSC.C.JSValueRef { + defer if (!this.buffer_val.isEmptyOrUndefinedOrNull()) + JSC.C.JSValueUnprotect(ctx, this.buffer_val.asObjectRef()); + + return JSC.JSValue.createObject2( + ctx.ptr(), + &fields.bytesRead, + &fields.buffer, + JSC.JSValue.jsNumberFromUint64(@intCast(u52, @minimum(std.math.maxInt(u52), this.bytes_read))), + this.buffer_val, + ).asObjectRef(); + } + }; + + pub const WritePromise = struct { + bytes_written: u52, + buffer: StringOrBuffer, + buffer_val: JSC.JSValue = JSC.JSValue.zero, + const fields = .{ + .@"bytesWritten" = JSC.ZigString.init("bytesWritten"), + .@"buffer" = JSC.ZigString.init("buffer"), + }; + + // Excited for the issue that's like "cannot read file bigger than 2 GB" + pub fn toJS(this: Write, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef { + defer if (!this.buffer_val.isEmptyOrUndefinedOrNull() and this.buffer == .buffer) + JSC.C.JSValueUnprotect(ctx, this.buffer_val.asObjectRef()); + + return JSC.JSValue.createObject2( + ctx.ptr(), + &fields.bytesWritten, + &fields.buffer, + JSC.JSValue.jsNumberFromUint64(@intCast(u52, @minimum(std.math.maxInt(u52), this.bytes_written))), + if (this.buffer == .buffer) + this.buffer_val + else + JSC.JSValue.fromRef(this.buffer.toJS(ctx, exception)), + ).asObjectRef(); + } + }; + pub const Write = struct { + bytes_written: u52, + const fields = .{ + .@"bytesWritten" = JSC.ZigString.init("bytesWritten"), + }; + + // Excited for the issue that's like "cannot read file bigger than 2 GB" + pub fn toJS(this: Write, _: JSC.C.JSContextRef, _: JSC.C.ExceptionRef) JSC.C.JSValueRef { + return JSC.JSValue.jsNumberFromUint64(this.bytes_written).asObjectRef(); + } + }; + + pub const Readdir = union(Tag) { + with_file_types: []const DirEnt, + buffers: []const Buffer, + files: []const PathString, + + pub const Tag = enum { + with_file_types, + buffers, + files, + }; + + pub fn toJS(this: Readdir, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.C.JSValueRef { + return switch (this) { + .with_file_types => JSC.To.JS.withType([]const DirEnt, this.with_file_types, ctx, exception), + .buffers => JSC.To.JS.withType([]const Buffer, this.buffers, ctx, exception), + .files => JSC.To.JS.withTypeClone([]const PathString, this.files, ctx, exception, true), + }; + } + }; + pub const ReadFile = StringOrBuffer; + pub const Readlink = StringOrBuffer; + pub const Realpath = StringOrBuffer; + pub const RealpathNative = Realpath; + pub const Rename = void; + pub const Rmdir = void; + pub const Stat = Stats; + + pub const Symlink = void; + pub const Truncate = void; + pub const Unlink = void; + pub const UnwatchFile = void; + pub const Watch = void; + pub const WatchFile = void; + pub const Utimes = void; + + pub const CreateReadStream = *JSC.Node.Stream; + pub const CreateWriteStream = *JSC.Node.Stream; + pub const Chown = void; + pub const Lutimes = void; +}; + +/// Bun's implementation of the Node.js "fs" module +/// https://nodejs.org/api/fs.html +/// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/fs.d.ts +pub const NodeFS = struct { + async_io: *AsyncIO, + + /// Buffer to store a temporary file path that might appear in a returned error message. + /// + /// We want to avoid allocating a new path buffer for every error message so that JSC can clone + GC it. + /// That means a stack-allocated buffer won't suffice. Instead, we re-use + /// the heap allocated buffer on the NodefS struct + sync_error_buf: [bun.MAX_PATH_BYTES]u8 = undefined, + + pub const ReturnType = Return; + + pub fn access(this: *NodeFS, args: Arguments.Access, comptime _: Flavor) Maybe(Return.Access) { + var path = args.path.sliceZ(&this.sync_error_buf); + const rc = Syscall.system.access(path, @enumToInt(args.mode)); + return Maybe(Return.Access).errnoSysP(rc, .access, path) orelse Maybe(Return.Access).success; + } + + pub fn appendFile(this: *NodeFS, args: Arguments.AppendFile, comptime flavor: Flavor) Maybe(Return.AppendFile) { + 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 => { + _ = this; + @compileError("Not implemented yet"); + }, + } + }, + .path => |path_| { + const path = path_.sliceZ(&this.sync_error_buf); + switch (comptime flavor) { + .sync => { + const fd = switch (Syscall.open(path, @enumToInt(FileSystemFlags.@"a"), 000666)) { + .result => |result| result, + .err => |err| return .{ .err = err }, + }; + + defer { + _ = Syscall.close(fd); + } + + 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 => { + _ = this; + @compileError("Not implemented yet"); + }, + } + }, + } + + return Maybe(Return.AppendFile).todo; + } + + pub fn close(this: *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 => { + _ = this; + }, + } + + return .{ .err = Syscall.Error.todo }; + } + + /// https://github.com/libuv/libuv/pull/2233 + /// https://github.com/pnpm/pnpm/issues/2761 + /// https://github.com/libuv/libuv/pull/2578 + /// https://github.com/nodejs/node/issues/34624 + pub fn copyFile(this: *NodeFS, args: Arguments.CopyFile, comptime flavor: Flavor) Maybe(Return.CopyFile) { + const ret = Maybe(Return.CopyFile); + + switch (comptime flavor) { + .sync => { + var src_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + var dest_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + var src = args.src.sliceZ(&src_buf); + var dest = args.dest.sliceZ(&dest_buf); + + if (comptime Environment.isMac) { + if (args.mode.isForceClone()) { + // https://www.manpagez.com/man/2/clonefile/ + return ret.errnoSysP(C.clonefile(src, dest, 0), .clonefile, src) orelse ret.success; + } + + var mode: Mode = C.darwin.COPYFILE_ACL | C.darwin.COPYFILE_DATA; + if (args.mode.shouldntOverwrite()) { + mode |= C.darwin.COPYFILE_EXCL; + } + + return ret.errnoSysP(C.copyfile(src, dest, null, mode), .copyfile, src) orelse ret.success; + } + + if (comptime Environment.isLinux) { + const src_fd = switch (Syscall.open(src, std.os.O.RDONLY, 0644)) { + .result => |result| result, + .err => |err| return .{ .err = err }, + }; + defer { + _ = Syscall.close(src_fd); + } + + const stat_: linux.Stat = switch (Syscall.fstat(src_fd)) { + .result => |result| result, + .err => |err| return Maybe(Return.CopyFile){ .err = err }, + }; + + if (!os.S.ISREG(stat_.mode)) { + return Maybe(Return.CopyFile){ .err = .{ .errno = @enumToInt(C.SystemErrno.ENOTSUP) } }; + } + + var flags: Mode = std.os.O.CREAT | std.os.O.WRONLY | std.os.O.TRUNC; + if (args.mode.shouldntOverwrite()) { + flags |= std.os.O.EXCL; + } + + const dest_fd = switch (Syscall.open(dest, flags, flags)) { + .result => |result| result, + .err => |err| return Maybe(Return.CopyFile){ .err = err }, + }; + defer { + _ = Syscall.close(dest_fd); + } + + var off_in_copy = @bitCast(i64, @as(u64, 0)); + var off_out_copy = @bitCast(i64, @as(u64, 0)); + + // https://manpages.debian.org/testing/manpages-dev/ioctl_ficlone.2.en.html + if (args.mode.isForceClone()) { + return Maybe(Return.CopyFile).todo; + } + + var size = @intCast(usize, @maximum(stat_.size, 0)); + + if (size == 0) { + // copy until EOF + size = std.mem.page_size; + while (true) { + // Linux Kernel 5.3 or later + const written = linux.copy_file_range(src_fd, &off_in_copy, dest_fd, &off_out_copy, size, 0); + if (ret.errnoSysP(written, .copy_file_range, dest)) |err| return err; + // wrote zero bytes means EOF + if (written == 0) break; + size -|= written; + } + } else { + while (size > 0) { + // Linux Kernel 5.3 or later + const written = linux.copy_file_range(src_fd, &off_in_copy, dest_fd, &off_out_copy, size, 0); + if (ret.errnoSysP(written, .copy_file_range, dest)) |err| return err; + // wrote zero bytes means EOF + if (written == 0) break; + size -|= written; + } + } + + return ret.success; + } + }, + else => { + _ = args; + _ = this; + _ = flavor; + }, + } + + return Maybe(Return.CopyFile).todo; + } + pub fn exists(this: *NodeFS, args: Arguments.Exists, comptime flavor: Flavor) Maybe(Return.Exists) { + const Ret = Maybe(Return.Exists); + const path = args.path.sliceZ(&this.sync_error_buf); + switch (comptime flavor) { + .sync => { + // 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(path, std.os.F_OK)); + return Ret{ .result = rc == 0 }; + }, + else => {}, + } + _ = args; + _ = this; + _ = flavor; + return Ret.todo; + } + + pub fn chown(this: *NodeFS, args: Arguments.Chown, comptime flavor: Flavor) Maybe(Return.Chown) { + const path = args.path.sliceZ(&this.sync_error_buf); + + switch (comptime flavor) { + .sync => return Syscall.chown(path, args.uid, args.gid), + else => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Chown).todo; + } + + /// This should almost never be async + pub fn chmod(this: *NodeFS, args: Arguments.Chmod, comptime flavor: Flavor) Maybe(Return.Chmod) { + 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 => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Chmod).todo; + } + + /// This should almost never be async + pub fn fchmod(this: *NodeFS, args: Arguments.FChmod, comptime flavor: Flavor) Maybe(Return.Fchmod) { + switch (comptime flavor) { + .sync => { + return Syscall.fchmod(args.fd, args.mode); + }, + else => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Fchmod).todo; + } + pub fn fchown(this: *NodeFS, args: Arguments.Fchown, comptime flavor: Flavor) Maybe(Return.Fchown) { + switch (comptime flavor) { + .sync => { + return Maybe(Return.Fchown).errnoSys(C.fchown(args.fd, args.uid, args.gid), .fchown) orelse + Maybe(Return.Fchown).success; + }, + else => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Fchown).todo; + } + pub fn fdatasync(this: *NodeFS, args: Arguments.FdataSync, comptime flavor: Flavor) Maybe(Return.Fdatasync) { + switch (comptime flavor) { + .sync => return Maybe(Return.Fdatasync).errnoSys(system.fdatasync(args.fd), .fdatasync) orelse + Maybe(Return.Fdatasync).success, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Fdatasync).todo; + } + pub fn fstat(this: *NodeFS, args: Arguments.Fstat, comptime flavor: Flavor) Maybe(Return.Fstat) { + if (args.big_int) return Maybe(Return.Fstat).todo; + + switch (comptime flavor) { + .sync => { + return switch (Syscall.fstat(args.fd)) { + .result => |result| Maybe(Return.Fstat){ .result = Stats.init(result) }, + .err => |err| Maybe(Return.Fstat){ .err = err }, + }; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Fstat).todo; + } + + pub fn fsync(this: *NodeFS, args: Arguments.Fsync, comptime flavor: Flavor) Maybe(Return.Fsync) { + switch (comptime flavor) { + .sync => return Maybe(Return.Fsync).errnoSys(system.fsync(args.fd), .fsync) orelse + Maybe(Return.Fsync).success, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Fsync).todo; + } + + pub fn ftruncate(this: *NodeFS, args: Arguments.FTruncate, comptime flavor: Flavor) Maybe(Return.Ftruncate) { + switch (comptime flavor) { + .sync => return Maybe(Return.Ftruncate).errnoSys(system.ftruncate(args.fd, args.len orelse 0), .ftruncate) orelse + Maybe(Return.Ftruncate).success, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Ftruncate).todo; + } + pub fn futimes(this: *NodeFS, args: Arguments.Futimes, comptime flavor: Flavor) Maybe(Return.Futimes) { + var times = [2]std.os.timespec{ + .{ + .tv_sec = args.mtime, + .tv_nsec = 0, + }, + .{ + .tv_sec = args.atime, + .tv_nsec = 0, + }, + }; + + switch (comptime flavor) { + .sync => return if (Maybe(Return.Futimes).errnoSys(system.futimens(args.fd, ×), .futimens)) |err| + err + else + Maybe(Return.Futimes).success, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Futimes).todo; + } + + pub fn lchmod(this: *NodeFS, args: Arguments.LCHmod, comptime flavor: Flavor) Maybe(Return.Lchmod) { + 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 => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Lchmod).todo; + } + + pub fn lchown(this: *NodeFS, args: Arguments.LChown, comptime flavor: Flavor) Maybe(Return.Lchown) { + 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 => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Lchown).todo; + } + pub fn link(this: *NodeFS, args: Arguments.Link, comptime flavor: Flavor) Maybe(Return.Link) { + 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 => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Link).todo; + } + pub fn lstat(this: *NodeFS, args: Arguments.Lstat, comptime flavor: Flavor) Maybe(Return.Lstat) { + if (args.big_int) return Maybe(Return.Lstat).todo; + + switch (comptime flavor) { + .sync => { + return switch (Syscall.lstat( + args.path.sliceZ( + &this.sync_error_buf, + ), + )) { + .result => |result| Maybe(Return.Lstat){ .result = Return.Lstat.init(result) }, + .err => |err| Maybe(Return.Lstat){ .err = err }, + }; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Lstat).todo; + } + + pub fn mkdir(this: *NodeFS, args: Arguments.Mkdir, comptime flavor: Flavor) Maybe(Return.Mkdir) { + return if (args.recursive) mkdirRecursive(this, args, flavor) else mkdirNonRecursive(this, args, flavor); + } + // 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 = "" }, + .err => |err| Maybe(Return.Mkdir){ .err = err }, + }; + }, + else => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Mkdir).todo; + } + + // TODO: windows + // TODO: verify this works correctly with unicode codepoints + fn mkdirRecursive(this: *NodeFS, args: Arguments.Mkdir, comptime flavor: Flavor) Maybe(Return.Mkdir) { + const Option = Maybe(Return.Mkdir); + if (comptime Environment.isWindows) @compileError("This needs to be implemented on Windows."); + + 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 = @truncate(u16, 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, path.ptr, len); + return .{ .err = err.withPath(this.sync_error_buf[0..len]) }; + }, + + .EXIST => { + return Option{ .result = "" }; + }, + // continue + .NOENT => {}, + } + }, + .result => { + return Option{ .result = args.path.slice() }; + }, + } + + var working_mem = &this.sync_error_buf; + @memcpy(working_mem, path.ptr, len); + + 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]; + + 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; + 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; + }, + } + } + } + + 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: []const u8 = ""; + if (first_match != std.math.maxInt(u16)) { + // TODO: this leaks memory + display_path = bun.default_allocator.dupe(u8, display_path[0..first_match]) catch unreachable; + } + return Option{ .result = display_path }; + }, + + // NOENT shouldn't happen here + else => return .{ + .err = err.withPath(path), + }, + } + }, + .result => { + var display_path = args.path.slice(); + if (first_match != std.math.maxInt(u16)) { + // TODO: this leaks memory + display_path = bun.default_allocator.dupe(u8, display_path[0..first_match]) catch unreachable; + } + return Option{ .result = display_path }; + }, + } + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Mkdir).todo; + } + + pub fn mkdtemp(this: *NodeFS, args: Arguments.MkdirTemp, comptime flavor: Flavor) Maybe(Return.Mkdtemp) { + var prefix_buf = &this.sync_error_buf; + prefix_buf[0] = 0; + const len = args.prefix.len; + if (len > 0) { + @memcpy(prefix_buf, args.prefix.ptr, len); + prefix_buf[len] = 0; + } + + const rc = C.mkdtemp(prefix_buf); + switch (std.c.getErrno(@ptrToInt(rc))) { + .SUCCESS => {}, + else => |errno| return .{ .err = Syscall.Error{ .errno = @truncate(Syscall.Error.Int, @enumToInt(errno)), .syscall = .mkdtemp } }, + } + + _ = this; + _ = flavor; + return .{ + .result = PathString.init(bun.default_allocator.dupe(u8, std.mem.span(rc.?)) catch unreachable), + }; + } + pub fn open(this: *NodeFS, args: Arguments.Open, comptime flavor: Flavor) Maybe(Return.Open) { + switch (comptime flavor) { + // The sync version does no allocation except when returning the path + .sync => { + const path = args.path.sliceZ(&this.sync_error_buf); + return switch (Syscall.open(path, @enumToInt(args.flags), args.mode)) { + .err => |err| .{ + .err = err.withPath(args.path.slice()), + }, + .result => |fd| .{ .result = fd }, + }; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Open).todo; + } + pub fn openDir(this: *NodeFS, args: Arguments.OpenDir, comptime flavor: Flavor) Maybe(Return.OpenDir) { + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.OpenDir).todo; + } + + fn _read(this: *NodeFS, args: Arguments.Read, comptime flavor: Flavor) Maybe(Return.Read) { + _ = args; + _ = this; + _ = flavor; + if (Environment.allow_assert) std.debug.assert(args.position == null); + + switch (comptime flavor) { + // The sync version does no allocation except when returning the path + .sync => { + var buf = args.buffer.slice(); + buf = buf[@minimum(args.offset, buf.len)..]; + buf = buf[0..@minimum(buf.len, args.length)]; + + return switch (Syscall.read(args.fd, buf)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ + .result = .{ + .bytes_read = @truncate(u52, amt), + }, + }, + }; + }, + else => {}, + } + + return Maybe(Return.Read).todo; + } + + fn _pread(this: *NodeFS, args: Arguments.Read, comptime flavor: Flavor) Maybe(Return.Read) { + _ = this; + + switch (comptime flavor) { + .sync => { + var buf = args.buffer.slice(); + buf = buf[@minimum(args.offset, buf.len)..]; + buf = buf[0..@minimum(buf.len, args.length)]; + + return switch (Syscall.pread(args.fd, buf, args.position.?)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ + .result = .{ + .bytes_read = @truncate(u52, amt), + }, + }, + }; + }, + else => {}, + } + + return Maybe(Return.Read).todo; + } + + pub fn read(this: *NodeFS, args: Arguments.Read, comptime flavor: Flavor) Maybe(Return.Read) { + return if (args.position != null) + this._pread( + args, + comptime flavor, + ) + else + this._read( + args, + comptime flavor, + ); + } + + pub fn write(this: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) { + return if (args.position != null) _pwrite(this, args, flavor) else _write(this, args, flavor); + } + fn _write(this: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) { + _ = args; + _ = this; + _ = flavor; + + switch (comptime flavor) { + .sync => { + var buf = args.buffer.slice(); + buf = buf[@minimum(args.offset, buf.len)..]; + buf = buf[0..@minimum(buf.len, args.length)]; + + return switch (Syscall.write(args.fd, buf)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ + .result = .{ + .bytes_written = @truncate(u52, amt), + }, + }, + }; + }, + else => {}, + } + + return Maybe(Return.Write).todo; + } + + fn _pwrite(this: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) { + _ = args; + _ = this; + _ = flavor; + + const position = args.position.?; + + switch (comptime flavor) { + .sync => { + var buf = args.buffer.slice(); + buf = buf[@minimum(args.offset, buf.len)..]; + buf = buf[0..@minimum(args.length, buf.len)]; + + return switch (Syscall.pwrite(args.fd, buf, position)) { + .err => |err| .{ + .err = err, + }, + .result => |amt| .{ .result = .{ + .bytes_written = @truncate(u52, amt), + } }, + }; + }, + else => {}, + } + + return Maybe(Return.Write).todo; + } + + pub fn readdir(this: *NodeFS, args: Arguments.Readdir, comptime flavor: Flavor) Maybe(Return.Readdir) { + return switch (args.encoding) { + .buffer => _readdir( + this, + args, + Buffer, + flavor, + ), + else => { + if (!args.with_file_types) { + return _readdir( + this, + args, + PathString, + flavor, + ); + } + + return _readdir( + this, + args, + DirEnt, + flavor, + ); + }, + }; + } + + pub fn _readdir( + this: *NodeFS, + args: Arguments.Readdir, + comptime ExpectedType: type, + comptime flavor: Flavor, + ) Maybe(Return.Readdir) { + const file_type = comptime switch (ExpectedType) { + DirEnt => "with_file_types", + PathString => "files", + Buffer => "buffers", + else => unreachable, + }; + + switch (comptime flavor) { + .sync => { + var path = args.path.sliceZ(&this.sync_error_buf); + const flags = os.O.DIRECTORY | os.O.RDONLY; + const fd = switch (Syscall.open(path, flags, 0)) { + .err => |err| return .{ + .err = err.withPath(args.path.slice()), + }, + .result => |fd_| fd_, + }; + defer { + _ = Syscall.close(fd); + } + + var entries = std.ArrayList(ExpectedType).init(bun.default_allocator); + var dir = std.fs.Dir{ .fd = fd }; + var iterator = DirIterator.iterate(dir); + var entry = iterator.next(); + while (switch (entry) { + .err => |err| { + for (entries.items) |*item| { + switch (comptime ExpectedType) { + DirEnt => { + bun.default_allocator.free(item.name.slice()); + }, + Buffer => { + item.destroy(); + }, + PathString => { + bun.default_allocator.free(item.slice()); + }, + else => unreachable, + } + } + + entries.deinit(); + + return .{ + .err = err.withPath(args.path.slice()), + }; + }, + .result => |ent| ent, + }) |current| : (entry = iterator.next()) { + switch (comptime ExpectedType) { + DirEnt => { + entries.append(.{ + .name = PathString.init(bun.default_allocator.dupe(u8, current.name.slice()) catch unreachable), + .kind = current.kind, + }) catch unreachable; + }, + Buffer => { + const slice = current.name.slice(); + entries.append(Buffer.fromString(slice, bun.default_allocator) catch unreachable) catch unreachable; + }, + PathString => { + entries.append( + PathString.init(bun.default_allocator.dupe(u8, current.name.slice()) catch unreachable), + ) catch unreachable; + }, + else => unreachable, + } + } + + return .{ .result = @unionInit(Return.Readdir, file_type, entries.items) }; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Readdir).todo; + } + pub fn readFile(this: *NodeFS, args: Arguments.ReadFile, comptime flavor: Flavor) Maybe(Return.ReadFile) { + var path: [:0]const u8 = undefined; + switch (comptime flavor) { + .sync => { + const fd = switch (args.path) { + .path => brk: { + path = args.path.path.sliceZ(&this.sync_error_buf); + break :brk switch (Syscall.open( + path, + os.O.RDONLY | os.O.NOCTTY, + 0, + )) { + .err => |err| return .{ + .err = err.withPath(if (args.path == .path) args.path.path.slice() else ""), + }, + .result => |fd_| fd_, + }; + }, + .fd => |_fd| _fd, + }; + + defer { + if (args.path == .path) + _ = Syscall.close(fd); + } + + const stat_ = switch (Syscall.fstat(fd)) { + .err => |err| return .{ + .err = err, + }, + .result => |stat_| stat_, + }; + + const size = @intCast(u64, @maximum(stat_.size, 0)); + var buf = std.ArrayList(u8).init(bun.default_allocator); + buf.ensureTotalCapacityPrecise(size + 16) catch unreachable; + buf.expandToCapacity(); + var total: usize = 0; + while (total < size) { + switch (Syscall.read(fd, buf.items.ptr[total..buf.capacity])) { + .err => |err| return .{ + .err = err, + }, + .result => |amt| { + total += amt; + // There are cases where stat()'s size is wrong or out of date + if (total > size and amt != 0) { + buf.ensureUnusedCapacity(8096) catch unreachable; + buf.expandToCapacity(); + continue; + } + + if (amt == 0) { + break; + } + }, + } + } + buf.items.len = total; + return switch (args.encoding) { + .buffer => .{ + .result = .{ + .buffer = Buffer.fromBytes(buf.items, bun.default_allocator, .Uint8Array), + }, + }, + else => .{ + .result = .{ + .string = buf.items, + }, + }, + }; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.ReadFile).todo; + } + + pub fn writeFile(this: *NodeFS, args: Arguments.WriteFile, comptime flavor: Flavor) Maybe(Return.WriteFile) { + var path: [:0]const u8 = undefined; + + switch (comptime flavor) { + .sync => { + const fd = switch (args.file) { + .path => brk: { + path = args.file.path.sliceZ(&this.sync_error_buf); + break :brk switch (Syscall.open( + path, + @enumToInt(args.flag) | os.O.NOCTTY, + args.mode, + )) { + .err => |err| return .{ + .err = err.withPath(path), + }, + .result => |fd_| fd_, + }; + }, + .fd => |_fd| _fd, + }; + + defer { + if (args.file == .path) + _ = Syscall.close(fd); + } + + var buf = args.data.slice(); + var written: usize = 0; + + while (buf.len > 0) { + switch (Syscall.write(fd, buf)) { + .err => |err| return .{ + .err = err, + }, + .result => |amt| { + buf = buf[amt..]; + written += amt; + if (amt == 0) { + break; + } + }, + } + } + + _ = this.ftruncate(.{ .fd = fd, .len = @truncate(JSC.WebCore.Blob.SizeType, written) }, .sync); + + return Maybe(Return.WriteFile).success; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.WriteFile).todo; + } + + pub fn readlink(this: *NodeFS, args: Arguments.Readlink, comptime flavor: 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_, + }; + + return .{ + .result = switch (args.encoding) { + .buffer => .{ + .buffer = Buffer.fromString(outbuf[0..len], bun.default_allocator) catch unreachable, + }, + else => .{ + .string = bun.default_allocator.dupe(u8, outbuf[0..len]) catch unreachable, + }, + }, + }; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Readlink).todo; + } + pub fn realpath(this: *NodeFS, args: Arguments.Realpath, comptime flavor: Flavor) Maybe(Return.Realpath) { + var outbuf: [bun.MAX_PATH_BYTES]u8 = undefined; + var inbuf = &this.sync_error_buf; + if (comptime Environment.allow_assert) std.debug.assert(FileSystem.instance_loaded); + + switch (comptime flavor) { + .sync => { + var path_slice = args.path.slice(); + + var parts = [_]string{ FileSystem.instance.top_level_dir, path_slice }; + var path_ = FileSystem.instance.absBuf(&parts, inbuf); + inbuf[path_.len] = 0; + var path: [:0]u8 = inbuf[0..path_.len :0]; + + const flags = if (comptime Environment.isLinux) + // O_PATH is faster + std.os.O.PATH + else + std.os.O.RDONLY; + + const fd = switch (Syscall.open(path, flags, 0)) { + .err => |err| return .{ + .err = err.withPath(path), + }, + .result => |fd_| fd_, + }; + + defer { + _ = Syscall.close(fd); + } + + const buf = switch (Syscall.getFdPath(fd, &outbuf)) { + .err => |err| return .{ + .err = err.withPath(path), + }, + .result => |buf_| buf_, + }; + + return .{ + .result = switch (args.encoding) { + .buffer => .{ + .buffer = Buffer.fromString(buf, bun.default_allocator) catch unreachable, + }, + else => .{ + .string = bun.default_allocator.dupe(u8, buf) catch unreachable, + }, + }, + }; + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Realpath).todo; + } + pub const realpathNative = realpath; + // pub fn realpathNative(this: *NodeFS, args: Arguments.Realpath, comptime flavor: Flavor) Maybe(Return.Realpath) { + // _ = args; + // _ = this; + // _ = flavor; + // return error.NotImplementedYet; + // } + pub fn rename(this: *NodeFS, args: Arguments.Rename, comptime flavor: Flavor) Maybe(Return.Rename) { + 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 => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Rename).todo; + } + pub fn rmdir(this: *NodeFS, args: Arguments.RmDir, comptime flavor: Flavor) Maybe(Return.Rmdir) { + switch (comptime flavor) { + .sync => { + var dir = args.old_path.sliceZ(&this.sync_error_buf); + _ = dir; + }, + else => {}, + } + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Rmdir).todo; + } + pub fn rm(this: *NodeFS, args: Arguments.RmDir, comptime flavor: Flavor) Maybe(Return.Rm) { + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Rm).todo; + } + pub fn stat(this: *NodeFS, args: Arguments.Stat, comptime flavor: Flavor) Maybe(Return.Stat) { + if (args.big_int) return Maybe(Return.Stat).todo; + + switch (comptime flavor) { + .sync => { + return @as(Maybe(Return.Stat), switch (Syscall.stat( + args.path.sliceZ( + &this.sync_error_buf, + ), + )) { + .result => |result| Maybe(Return.Stat){ .result = Return.Stat.init(result) }, + .err => |err| Maybe(Return.Stat){ .err = err }, + }); + }, + else => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Stat).todo; + } + + pub fn symlink(this: *NodeFS, args: Arguments.Symlink, comptime flavor: Flavor) Maybe(Return.Symlink) { + 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 => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Symlink).todo; + } + fn _truncate(this: *NodeFS, path: PathLike, len: JSC.WebCore.Blob.SizeType, comptime flavor: Flavor) Maybe(Return.Truncate) { + switch (comptime flavor) { + .sync => { + return Maybe(Return.Truncate).errno(C.truncate(path.sliceZ(&this.sync_error_buf), len)) orelse + Maybe(Return.Truncate).success; + }, + else => {}, + } + + _ = this; + _ = flavor; + return Maybe(Return.Truncate).todo; + } + pub fn truncate(this: *NodeFS, args: Arguments.Truncate, comptime flavor: Flavor) Maybe(Return.Truncate) { + return switch (args.path) { + .fd => |fd| this.ftruncate( + Arguments.FTruncate{ .fd = fd, .len = args.len }, + flavor, + ), + .path => this._truncate( + args.path.path, + args.len, + flavor, + ), + }; + } + pub fn unlink(this: *NodeFS, args: Arguments.Unlink, comptime flavor: Flavor) Maybe(Return.Unlink) { + 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 => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Unlink).todo; + } + pub fn unwatchFile(this: *NodeFS, args: Arguments.UnwatchFile, comptime flavor: Flavor) Maybe(Return.UnwatchFile) { + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.UnwatchFile).todo; + } + pub fn utimes(this: *NodeFS, args: Arguments.Utimes, comptime flavor: Flavor) Maybe(Return.Utimes) { + var times = [2]std.c.timeval{ + .{ + .tv_sec = args.mtime, + // TODO: is this correct? + .tv_usec = 0, + }, + .{ + .tv_sec = args.atime, + // TODO: is this correct? + .tv_usec = 0, + }, + }; + + 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 => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Utimes).todo; + } + + pub fn lutimes(this: *NodeFS, args: Arguments.Lutimes, comptime flavor: Flavor) Maybe(Return.Lutimes) { + var times = [2]std.c.timeval{ + .{ + .tv_sec = args.mtime, + // TODO: is this correct? + .tv_usec = 0, + }, + .{ + .tv_sec = args.atime, + // TODO: is this correct? + .tv_usec = 0, + }, + }; + + 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 => {}, + } + + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Lutimes).todo; + } + pub fn watch(this: *NodeFS, args: Arguments.Watch, comptime flavor: Flavor) Maybe(Return.Watch) { + _ = args; + _ = this; + _ = flavor; + return Maybe(Return.Watch).todo; + } + pub fn createReadStream(this: *NodeFS, args: Arguments.CreateReadStream, comptime flavor: Flavor) Maybe(Return.CreateReadStream) { + _ = args; + _ = this; + _ = flavor; + var stream = bun.default_allocator.create(JSC.Node.Stream) catch unreachable; + stream.* = JSC.Node.Stream{ + .sink = .{ + .readable = JSC.Node.Readable{ + .stream = stream, + .globalObject = args.global_object, + }, + }, + .sink_type = .readable, + .content = undefined, + .content_type = undefined, + .allocator = bun.default_allocator, + }; + + args.file.copyToStream(args.flags, args.autoClose, args.mode, bun.default_allocator, stream) catch unreachable; + args.copyToState(&stream.sink.readable.state); + return Maybe(Return.CreateReadStream){ .result = stream }; + } + pub fn createWriteStream(this: *NodeFS, args: Arguments.CreateWriteStream, comptime flavor: Flavor) Maybe(Return.CreateWriteStream) { + _ = args; + _ = this; + _ = flavor; + var stream = bun.default_allocator.create(JSC.Node.Stream) catch unreachable; + stream.* = JSC.Node.Stream{ + .sink = .{ + .writable = JSC.Node.Writable{ + .stream = stream, + .globalObject = args.global_object, + }, + }, + .sink_type = .writable, + .content = undefined, + .content_type = undefined, + .allocator = bun.default_allocator, + }; + args.file.copyToStream(args.flags, args.autoClose, args.mode, bun.default_allocator, stream) catch unreachable; + args.copyToState(&stream.sink.writable.state); + return Maybe(Return.CreateWriteStream){ .result = stream }; + } +}; |