diff options
Diffstat (limited to 'src/bun.js/node')
-rw-r--r-- | src/bun.js/node/dir_iterator.zig | 38 | ||||
-rw-r--r-- | src/bun.js/node/node_fs.zig | 211 | ||||
-rw-r--r-- | src/bun.js/node/node_os.zig | 21 | ||||
-rw-r--r-- | src/bun.js/node/path_watcher.zig | 10 | ||||
-rw-r--r-- | src/bun.js/node/syscall.zig | 487 | ||||
-rw-r--r-- | src/bun.js/node/types.zig | 66 |
6 files changed, 714 insertions, 119 deletions
diff --git a/src/bun.js/node/dir_iterator.zig b/src/bun.js/node/dir_iterator.zig index 968fde001..994ddaa31 100644 --- a/src/bun.js/node/dir_iterator.zig +++ b/src/bun.js/node/dir_iterator.zig @@ -11,6 +11,7 @@ const os = std.os; const Dir = std.fs.Dir; const JSC = @import("root").bun.JSC; const PathString = JSC.PathString; +const bun = @import("root").bun; const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError; const mem = std.mem; @@ -188,11 +189,37 @@ pub const Iterator = switch (builtin.os.tag) { if (io.Information == 0) return .{ .result = null }; self.index = 0; self.end_index = io.Information; - switch (rc) { - .SUCCESS => {}, - .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability + // If the handle is not a directory, we'll get STATUS_INVALID_PARAMETER. + if (rc == .INVALID_PARAMETER) { + return .{ + .err = .{ + .errno = @as(bun.sys.Error.Int, @truncate(@intFromEnum(bun.C.SystemErrno.ENOTDIR))), + .syscall = .NtQueryDirectoryFile, + }, + }; + } + + if (rc == .NO_MORE_FILES) { + self.end_index = self.index; + return .{ .result = null }; + } + + if (rc != .SUCCESS) { + if ((bun.windows.Win32Error.fromNTStatus(rc).toSystemErrno())) |errno| { + return .{ + .err = .{ + .errno = @truncate(@intFromEnum(errno)), + .syscall = .NtQueryDirectoryFile, + }, + }; + } - else => return w.unexpectedStatus(rc), + return .{ + .err = .{ + .errno = @truncate(@intFromEnum(bun.C.SystemErrno.EUNKNOWN)), + .syscall = .NtQueryDirectoryFile, + }, + }; } } @@ -208,8 +235,7 @@ pub const Iterator = switch (builtin.os.tag) { if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' })) continue; // Trust that Windows gives us valid UTF-16LE - const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable; - const name_utf8 = self.name_data[0..name_utf8_len]; + const name_utf8 = strings.fromWPath(self.name_data[0..], name_utf16le); const kind = blk: { const attrs = dir_info.FileAttributes; if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.directory; diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 130ab8cdc..01952dc68 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -31,9 +31,9 @@ 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; +const E = C.E; +const uid_t = if (Environment.isPosix) std.os.uid_t else i32; +const gid_t = if (Environment.isPosix) std.os.gid_t else i32; /// u63 to allow one null bit const ReadPosition = i64; @@ -44,12 +44,16 @@ 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; +pub const default_permission = if (Environment.isPosix) + Syscall.S.IRUSR | + Syscall.S.IWUSR | + Syscall.S.IRGRP | + Syscall.S.IWGRP | + Syscall.S.IROTH | + Syscall.S.IWOTH +else + // TODO: + 0; const ArrayBuffer = JSC.MarkedArrayBuffer; const Buffer = JSC.Buffer; @@ -1212,7 +1216,9 @@ pub const Arguments = struct { // 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"); + if (comptime Environment.isWindows) { + bun.todo(@src(), {}); + } arguments.eat(); } } @@ -2174,7 +2180,7 @@ pub const Arguments = struct { mode: Mode = 0o666, file: PathOrFileDescriptor, data: StringOrBuffer, - dirfd: FileDescriptor = @as(FileDescriptor, @intCast(std.fs.cwd().fd)), + dirfd: FileDescriptor, pub fn deinit(self: WriteFile) void { self.file.deinit(); @@ -2290,6 +2296,7 @@ pub const Arguments = struct { .flag = flag, .mode = mode, .data = data, + .dirfd = bun.toFD(std.fs.cwd().fd), }; } }; @@ -3058,6 +3065,10 @@ pub const NodeFS = struct { pub const ReturnType = Return; pub fn access(this: *NodeFS, args: Arguments.Access, comptime _: Flavor) Maybe(Return.Access) { + if (comptime Environment.isWindows) { + return Maybe(Return.Access).todo; + } + var path = args.path.sliceZ(&this.sync_error_buf); const rc = Syscall.system.access(path, @intFromEnum(args.mode)); return Maybe(Return.Access).errnoSysP(rc, .access, path) orelse Maybe(Return.Access).success; @@ -3391,6 +3402,10 @@ pub const NodeFS = struct { } pub fn chown(this: *NodeFS, args: Arguments.Chown, comptime flavor: Flavor) Maybe(Return.Chown) { + if (comptime Environment.isWindows) { + return Maybe(Return.Fchmod).todo; + } + const path = args.path.sliceZ(&this.sync_error_buf); switch (comptime flavor) { @@ -3403,6 +3418,10 @@ pub const NodeFS = struct { /// This should almost never be async pub fn chmod(this: *NodeFS, args: Arguments.Chmod, comptime flavor: Flavor) Maybe(Return.Chmod) { + if (comptime Environment.isWindows) { + return Maybe(Return.Fchmod).todo; + } + const path = args.path.sliceZ(&this.sync_error_buf); switch (comptime flavor) { @@ -3418,6 +3437,10 @@ pub const NodeFS = struct { /// This should almost never be async pub fn fchmod(_: *NodeFS, args: Arguments.FChmod, comptime flavor: Flavor) Maybe(Return.Fchmod) { + if (comptime Environment.isWindows) { + return Maybe(Return.Fchmod).todo; + } + switch (comptime flavor) { .sync => { return Syscall.fchmod(args.fd, args.mode); @@ -3428,6 +3451,10 @@ pub const NodeFS = struct { return Maybe(Return.Fchmod).todo; } pub fn fchown(_: *NodeFS, args: Arguments.Fchown, comptime flavor: Flavor) Maybe(Return.Fchown) { + 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 @@ -3439,6 +3466,9 @@ pub const NodeFS = struct { return Maybe(Return.Fchown).todo; } pub fn fdatasync(_: *NodeFS, args: Arguments.FdataSync, comptime flavor: Flavor) Maybe(Return.Fdatasync) { + 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, @@ -3448,12 +3478,18 @@ pub const NodeFS = struct { return Maybe(Return.Fdatasync).todo; } pub fn fstat(_: *NodeFS, args: Arguments.Fstat, comptime flavor: Flavor) Maybe(Return.Fstat) { + if (comptime Environment.isWindows) { + return Maybe(Return.Fstat).todo; + } + switch (comptime flavor) { .sync => { - return switch (Syscall.fstat(args.fd)) { - .result => |result| Maybe(Return.Fstat){ .result = Stats.init(result, false) }, - .err => |err| Maybe(Return.Fstat){ .err = err }, - }; + 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 => {}, } @@ -3462,6 +3498,10 @@ pub const NodeFS = struct { } pub fn fsync(_: *NodeFS, args: Arguments.Fsync, comptime flavor: Flavor) Maybe(Return.Fsync) { + 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, @@ -3472,8 +3512,7 @@ pub const NodeFS = struct { } pub fn ftruncateSync(args: Arguments.FTruncate) Maybe(Return.Ftruncate) { - return Maybe(Return.Ftruncate).errnoSys(system.ftruncate(args.fd, args.len orelse 0), .ftruncate) orelse - Maybe(Return.Ftruncate).success; + return Syscall.ftruncate(args.fd, args.len orelse 0); } pub fn ftruncate(_: *NodeFS, args: Arguments.FTruncate, comptime flavor: Flavor) Maybe(Return.Ftruncate) { @@ -3485,6 +3524,10 @@ pub const NodeFS = struct { return Maybe(Return.Ftruncate).todo; } pub fn futimes(_: *NodeFS, args: Arguments.Futimes, comptime flavor: Flavor) Maybe(Return.Futimes) { + if (comptime Environment.isWindows) { + return Maybe(Return.Futimes).todo; + } + var times = [2]std.os.timespec{ .{ .tv_sec = args.mtime, @@ -3508,6 +3551,10 @@ pub const NodeFS = struct { } pub fn lchmod(this: *NodeFS, args: Arguments.LCHmod, comptime flavor: Flavor) Maybe(Return.Lchmod) { + if (comptime Environment.isWindows) { + return Maybe(Return.Lchmod).todo; + } + const path = args.path.sliceZ(&this.sync_error_buf); switch (comptime flavor) { @@ -3522,6 +3569,10 @@ pub const NodeFS = struct { } pub fn lchown(this: *NodeFS, args: Arguments.LChown, comptime flavor: Flavor) Maybe(Return.Lchown) { + if (comptime Environment.isWindows) { + return Maybe(Return.Lchown).todo; + } + const path = args.path.sliceZ(&this.sync_error_buf); switch (comptime flavor) { @@ -3550,6 +3601,10 @@ pub const NodeFS = struct { return Maybe(Return.Link).todo; } pub fn lstat(this: *NodeFS, args: Arguments.Lstat, comptime flavor: Flavor) Maybe(Return.Lstat) { + if (comptime Environment.isWindows) { + return Maybe(Return.Lstat).todo; + } + _ = flavor; return switch (Syscall.lstat( args.path.sliceZ( @@ -3589,7 +3644,7 @@ pub const NodeFS = struct { // TODO: verify this works correctly with unicode codepoints pub 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."); + if (comptime Environment.isWindows) return Option.todo; switch (comptime flavor) { // The sync version does no allocation except when returning the path @@ -3822,6 +3877,7 @@ pub const NodeFS = struct { } pub fn read(this: *NodeFS, args: Arguments.Read, comptime flavor: Flavor) Maybe(Return.Read) { + if (comptime Environment.isWindows) return Maybe(Return.Read).todo; return if (args.position != null) this._pread( args, @@ -3835,14 +3891,17 @@ pub const NodeFS = struct { } pub fn readv(this: *NodeFS, args: Arguments.Readv, comptime flavor: Flavor) Maybe(Return.Read) { + if (comptime Environment.isWindows) return Maybe(Return.Read).todo; return if (args.position != null) _preadv(this, args, flavor) else _readv(this, args, flavor); } pub fn writev(this: *NodeFS, args: Arguments.Writev, comptime flavor: Flavor) Maybe(Return.Write) { + if (comptime Environment.isWindows) return Maybe(Return.Write).todo; return if (args.position != null) _pwritev(this, args, flavor) else _writev(this, args, flavor); } pub fn write(this: *NodeFS, args: Arguments.Write, comptime flavor: Flavor) Maybe(Return.Write) { + if (comptime Environment.isWindows) return Maybe(Return.Write).todo; 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) { @@ -4023,7 +4082,7 @@ pub const NodeFS = struct { } var entries = std.ArrayList(ExpectedType).init(bun.default_allocator); - var dir = std.fs.Dir{ .fd = fd }; + var dir = std.fs.Dir{ .fd = bun.fdcast(fd) }; var iterator = DirIterator.iterate(dir); var entry = iterator.next(); while (switch (entry) { @@ -4159,9 +4218,8 @@ pub const NodeFS = struct { // Only used in DOMFormData if (args.offset > 0) { - std.os.lseek_SET(fd, args.offset) catch {}; + _ = Syscall.setFileOffset(fd, args.offset); } - // For certain files, the size might be 0 but the file might still have contents. const size = @as( u64, @@ -4318,13 +4376,13 @@ pub const NodeFS = struct { 0 else brk: { // on linux, it's absolutely positioned - const pos = JSC.Node.Syscall.system.lseek( + const pos = bun.sys.system.lseek( fd, @as(std.os.off_t, @intCast(0)), std.os.linux.SEEK.CUR, ); - switch (JSC.Node.Syscall.getErrno(pos)) { + switch (bun.sys.getErrno(pos)) { .SUCCESS => break :brk @as(usize, @intCast(pos)), else => break :preallocate, } @@ -4551,7 +4609,7 @@ pub const NodeFS = struct { else => .FAULT, }; return Maybe(Return.Rm){ - .err = JSC.Node.Syscall.Error.fromCode(errno, .rmdir), + .err = bun.sys.Error.fromCode(errno, .rmdir), }; }; @@ -4617,10 +4675,10 @@ pub const NodeFS = struct { return Maybe(Return.Rm).success; } - } else if (comptime Environment.isLinux) { + } else if (comptime Environment.isLinux or Environment.isWindows) { if (args.recursive) { std.fs.cwd().deleteTree(args.path.slice()) catch |err| { - const errno: std.os.E = switch (err) { + const errno: E = switch (err) { error.InvalidHandle => .BADF, error.AccessDenied => .PERM, error.FileTooBig => .FBIG, @@ -4650,15 +4708,15 @@ pub const NodeFS = struct { return Maybe(Return.Rm).success; } return Maybe(Return.Rm){ - .err = JSC.Node.Syscall.Error.fromCode(errno, .unlink), + .err = bun.sys.Error.fromCode(errno, .unlink), }; }; return Maybe(Return.Rm).success; } } - { - var dest = args.path.sliceZ(&this.sync_error_buf); + 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. @@ -4670,7 +4728,7 @@ pub const NodeFS = struct { return Maybe(Return.Rm).success; } - const code: std.os.E = switch (err) { + const code: E = switch (err) { error.AccessDenied => .PERM, error.SymLinkLoop => .LOOP, error.NameTooLong => .NAMETOOLONG, @@ -4684,7 +4742,7 @@ pub const NodeFS = struct { }; return .{ - .err = JSC.Node.Syscall.Error.fromCode( + .err = bun.sys.Error.fromCode( code, .rmdir, ), @@ -4699,7 +4757,7 @@ pub const NodeFS = struct { } { - const code: std.os.E = switch (er) { + const code: E = switch (er) { error.AccessDenied => .PERM, error.SymLinkLoop => .LOOP, error.NameTooLong => .NAMETOOLONG, @@ -4713,7 +4771,66 @@ pub const NodeFS = struct { }; return .{ - .err = JSC.Node.Syscall.Error.fromCode( + .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 (err) { + error.FileNotFound => .NOENT, + error.PermissionDenied => .PERM, + else => .INVAL, + }; + + 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; + } + + { + 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, ), @@ -4730,6 +4847,9 @@ pub const NodeFS = struct { return Maybe(Return.Rm).todo; } pub fn stat(this: *NodeFS, args: Arguments.Stat, comptime flavor: Flavor) Maybe(Return.Stat) { + if (comptime Environment.isWindows) { + return Maybe(Return.Stat).todo; + } _ = flavor; return @as(Maybe(Return.Stat), switch (Syscall.stat( @@ -4748,6 +4868,10 @@ pub const NodeFS = struct { } pub fn symlink(this: *NodeFS, args: Arguments.Symlink, comptime flavor: Flavor) Maybe(Return.Symlink) { + if (comptime Environment.isWindows) { + return Maybe(Return.Symlink).todo; + } + var to_buf: [bun.MAX_PATH_BYTES]u8 = undefined; switch (comptime flavor) { @@ -4763,6 +4887,10 @@ pub const NodeFS = struct { return Maybe(Return.Symlink).todo; } fn _truncate(this: *NodeFS, path: PathLike, len: JSC.WebCore.Blob.SizeType, comptime flavor: Flavor) Maybe(Return.Truncate) { + 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 @@ -4787,6 +4915,10 @@ pub const NodeFS = struct { }; } pub fn unlink(this: *NodeFS, args: Arguments.Unlink, comptime flavor: Flavor) Maybe(Return.Unlink) { + 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 @@ -4801,6 +4933,10 @@ pub const NodeFS = struct { return Maybe(Return.UnwatchFile).todo; } pub fn utimes(this: *NodeFS, args: Arguments.Utimes, comptime flavor: Flavor) Maybe(Return.Utimes) { + if (comptime Environment.isWindows) { + return Maybe(Return.Utimes).todo; + } + var times = [2]std.c.timeval{ .{ .tv_sec = args.mtime, @@ -4829,6 +4965,10 @@ pub const NodeFS = struct { } pub fn lutimes(this: *NodeFS, args: Arguments.Lutimes, comptime flavor: Flavor) Maybe(Return.Lutimes) { + if (comptime Environment.isWindows) { + return Maybe(Return.Lutimes).todo; + } + var times = [2]std.c.timeval{ .{ .tv_sec = args.mtime, @@ -4856,6 +4996,11 @@ pub const NodeFS = struct { return Maybe(Return.Lutimes).todo; } pub fn watch(_: *NodeFS, args: Arguments.Watch, comptime _: Flavor) Maybe(Return.Watch) { + if (comptime Environment.isWindows) { + args.global_this.throwTODO("watch is not supported on Windows yet"); + return Maybe(Return.Watch){ .result = JSC.JSValue.undefined }; + } + const watcher = args.createFSWatcher() catch |err| { var buf = std.fmt.allocPrint(bun.default_allocator, "{s} watching {}", .{ @errorName(err), strings.QuotedFormatter{ .text = args.path.slice() } }) catch unreachable; defer bun.default_allocator.free(buf); diff --git a/src/bun.js/node/node_os.zig b/src/bun.js/node/node_os.zig index a4efe4454..07dec1c7d 100644 --- a/src/bun.js/node/node_os.zig +++ b/src/bun.js/node/node_os.zig @@ -359,7 +359,12 @@ pub const Os = struct { pub fn hostname(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { JSC.markBinding(@src()); - var name_buffer: [std.os.HOST_NAME_MAX]u8 = undefined; + if (comptime Environment.isWindows) { + globalThis.throwTODO("hostname() is not implemented on Windows"); + return .zero; + } + + var name_buffer: [bun.HOST_NAME_MAX]u8 = undefined; return JSC.ZigString.init(std.os.gethostname(&name_buffer) catch "unknown").withEncoding().toValueGC(globalThis); } @@ -369,14 +374,18 @@ pub const Os = struct { const result = C.getSystemLoadavg(); return JSC.JSArray.from(globalThis, &.{ - JSC.JSValue.jsDoubleNumber(result[0]), - JSC.JSValue.jsDoubleNumber(result[1]), - JSC.JSValue.jsDoubleNumber(result[2]), + JSC.JSValue.jsNumber(result[0]), + JSC.JSValue.jsNumber(result[1]), + JSC.JSValue.jsNumber(result[2]), }); } pub fn networkInterfaces(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { JSC.markBinding(@src()); + if (comptime Environment.isWindows) { + globalThis.throwTODO("networkInterfaces() is not implemented on Windows"); + return .zero; + } // getifaddrs sets a pointer to a linked list var interface_start: ?*C.ifaddrs = null; @@ -558,7 +567,7 @@ pub const Os = struct { pub fn release(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { JSC.markBinding(@src()); - var name_buffer: [std.os.HOST_NAME_MAX]u8 = undefined; + var name_buffer: [bun.HOST_NAME_MAX]u8 = undefined; return JSC.ZigString.init(C.getRelease(&name_buffer)).withEncoding().toValueGC(globalThis); } @@ -680,7 +689,7 @@ pub const Os = struct { pub fn version(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { JSC.markBinding(@src()); - var name_buffer: [std.os.HOST_NAME_MAX]u8 = undefined; + var name_buffer: [bun.HOST_NAME_MAX]u8 = undefined; return JSC.ZigString.init(C.getVersion(&name_buffer)).withEncoding().toValueGC(globalThis); } diff --git a/src/bun.js/node/path_watcher.zig b/src/bun.js/node/path_watcher.zig index 1f1b9d1a1..4f44a68ff 100644 --- a/src/bun.js/node/path_watcher.zig +++ b/src/bun.js/node/path_watcher.zig @@ -437,6 +437,10 @@ pub const PathWatcherManager = struct { watcher: *PathWatcher, buf: *[bun.MAX_PATH_BYTES + 1]u8, ) !void { + if (comptime Environment.isWindows) { + bun.todo(@src(), "implement directory watching on windows"); + return; + } const manager = this.manager; const path = this.path; const fd = path.fd; @@ -480,6 +484,10 @@ pub const PathWatcherManager = struct { } fn run(this: *DirectoryRegisterTask) void { + if (comptime Environment.isWindows) { + return bun.todo(@src(), {}); + } + var buf: [bun.MAX_PATH_BYTES + 1]u8 = undefined; while (this.getNext()) |watcher| { @@ -656,7 +664,7 @@ pub const PathWatcherManager = struct { var it = this.file_paths.iterator(); while (it.next()) |*entry| { const path = entry.value_ptr.*; - std.os.close(path.fd); + _ = bun.sys.close(path.fd); bun.default_allocator.free(path.path); } diff --git a/src/bun.js/node/syscall.zig b/src/bun.js/node/syscall.zig index 13158481f..95c343f1f 100644 --- a/src/bun.js/node/syscall.zig +++ b/src/bun.js/node/syscall.zig @@ -15,15 +15,16 @@ const fd_t = bun.FileDescriptor; const C = @import("root").bun.C; const linux = os.linux; const Maybe = JSC.Maybe; +const kernel32 = bun.windows; const log = bun.Output.scoped(.SYS, false); pub const syslog = log; // On Linux AARCh64, zig is missing stat & lstat syscalls const use_libc = !(Environment.isLinux and Environment.isX64); -pub const system = if (Environment.isLinux) linux else @import("root").bun.AsyncIO.darwin; +pub const system = if (Environment.isLinux) linux else @import("root").bun.AsyncIO.system; pub const S = struct { - pub usingnamespace if (Environment.isLinux) linux.S else std.os.S; + pub usingnamespace if (Environment.isLinux) linux.S else if (Environment.isPosix) std.os.S else struct {}; }; const sys = std.os.system; @@ -48,9 +49,11 @@ else if (Environment.isLinux) else @compileError("STAT"); +const windows = bun.windows; + pub const Tag = enum(u8) { TODO, - + dup, access, chmod, chown, @@ -110,6 +113,8 @@ pub const Tag = enum(u8) { pwritev, readv, preadv, + NtQueryDirectoryFile, + pub var strings = std.EnumMap(Tag, JSC.C.JSStringRef).initFull(null); }; const PathString = @import("root").bun.PathString; @@ -130,43 +135,92 @@ pub fn getcwd(buf: *[bun.MAX_PATH_BYTES]u8) Maybe([]const u8) { Result.errnoSys(0, .getcwd).?; } -pub fn fchmod(fd: bun.FileDescriptor, mode: JSC.Node.Mode) Maybe(void) { +pub fn fchmod(fd_: bun.FileDescriptor, mode: JSC.Node.Mode) Maybe(void) { + const fd = bun.fdcast(fd_); return Maybe(void).errnoSys(C.fchmod(fd, mode), .fchmod) orelse Maybe(void).success; } -pub fn chdir(destination: [:0]const u8) Maybe(void) { - const rc = sys.chdir(destination); - return Maybe(void).errnoSys(rc, .chdir) orelse Maybe(void).success; +pub fn chdirOSPath(destination: bun.OSPathSlice) Maybe(void) { + if (comptime Environment.isPosix) { + const rc = sys.chdir(destination); + return Maybe(void).errnoSys(rc, .chdir) orelse Maybe(void).success; + } + + if (comptime Environment.isWindows) { + if (kernel32.SetCurrentDirectory(destination) != 0) { + return Maybe(void).errnoSys(0, .chdir) orelse Maybe(void).success; + } + + return Maybe(void).success; + } + + @compileError("Not implemented yet"); +} + +pub fn chdir(destination: anytype) Maybe(void) { + const Type = @TypeOf(destination); + + if (comptime Environment.isPosix) { + if (comptime Type == []u8 or Type == []const u8) { + return chdirOSPath( + &(std.os.toPosixPath(destination) catch return .{ .err = .{ + .errno = @intFromEnum(bun.C.SystemErrno.EINVAL), + .syscall = .chdir, + } }), + ); + } + + return chdirOSPath(destination); + } + + if (comptime Environment.isWindows) { + if (comptime Type == bun.OSPathSlice or Type == [:0]u16) { + return chdirOSPath(@as(bun.OSPathSlice, destination)); + } + + if (comptime Type == *[*:0]u16) { + if (kernel32.SetCurrentDirectory(destination) != 0) { + return Maybe(void).errnoSys(0, .chdir) orelse Maybe(void).success; + } + + return Maybe(void).success; + } + + var wbuf: bun.MAX_WPATH = undefined; + return chdirOSPath(bun.strings.toWPath(&wbuf, destination)); + } + + return Maybe(void).todo; } -pub fn stat(path: [:0]const u8) Maybe(os.Stat) { - var stat_ = mem.zeroes(os.Stat); +pub fn stat(path: [:0]const u8) Maybe(bun.Stat) { + var stat_ = mem.zeroes(bun.Stat); const rc = statSym(path, &stat_); if (comptime Environment.allow_assert) log("stat({s}) = {d}", .{ bun.asByteSlice(path), rc }); - if (Maybe(os.Stat).errnoSys(rc, .stat)) |err| return err; - return Maybe(os.Stat){ .result = stat_ }; + if (Maybe(bun.Stat).errnoSys(rc, .stat)) |err| return err; + return Maybe(bun.Stat){ .result = stat_ }; } -pub fn lstat(path: [:0]const u8) Maybe(os.Stat) { - var stat_ = mem.zeroes(os.Stat); - if (Maybe(os.Stat).errnoSys(lstat64(path, &stat_), .lstat)) |err| return err; - return Maybe(os.Stat){ .result = stat_ }; +pub fn lstat(path: [:0]const u8) Maybe(bun.Stat) { + var stat_ = mem.zeroes(bun.Stat); + if (Maybe(bun.Stat).errnoSys(lstat64(path, &stat_), .lstat)) |err| return err; + return Maybe(bun.Stat){ .result = stat_ }; } -pub fn fstat(fd: bun.FileDescriptor) Maybe(os.Stat) { - var stat_ = mem.zeroes(os.Stat); +pub fn fstat(fd: bun.FileDescriptor) Maybe(bun.Stat) { + var stat_ = mem.zeroes(bun.Stat); const rc = fstatSym(fd, &stat_); if (comptime Environment.allow_assert) log("fstat({d}) = {d}", .{ fd, rc }); - if (Maybe(os.Stat).errnoSys(rc, .fstat)) |err| return err; - return Maybe(os.Stat){ .result = stat_ }; + if (Maybe(bun.Stat).errnoSys(rc, .fstat)) |err| return err; + return Maybe(bun.Stat){ .result = stat_ }; } pub fn mkdir(file_path: [:0]const u8, flags: JSC.Node.Mode) Maybe(void) { @@ -177,16 +231,29 @@ pub fn mkdir(file_path: [:0]const u8, flags: JSC.Node.Mode) Maybe(void) { if (comptime Environment.isLinux) { return Maybe(void).errnoSysP(linux.mkdir(file_path, flags), .mkdir, file_path) orelse Maybe(void).success; } + var wbuf: bun.MAX_WPATH = undefined; + _ = kernel32.CreateDirectoryW(bun.strings.toWPath(&wbuf, file_path).ptr, null); + + return Maybe(void).errnoSysP(0, .mkdir, file_path) orelse Maybe(void).success; } -pub fn fcntl(fd: bun.FileDescriptor, cmd: i32, arg: usize) Maybe(usize) { +pub fn fcntl(fd_: bun.FileDescriptor, cmd: i32, arg: usize) Maybe(usize) { + const fd = bun.fdcast(fd_); const result = fcntl_symbol(fd, cmd, arg); if (Maybe(usize).errnoSys(result, .fcntl)) |err| return err; return .{ .result = @as(usize, @intCast(result)) }; } -pub fn getErrno(rc: anytype) std.os.E { - if (comptime Environment.isMac) return std.os.errno(rc); +pub fn getErrno(rc: anytype) bun.C.E { + if (comptime Environment.isWindows) { + if (bun.windows.Win32Error.get().toSystemErrno()) |e| { + return e.toE(); + } + + return bun.C.E.UNKNOWN; + } + + if (comptime use_libc) return std.os.errno(rc); const Type = @TypeOf(rc); return switch (Type) { @@ -196,7 +263,139 @@ pub fn getErrno(rc: anytype) std.os.E { }; } -pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(bun.FileDescriptor) { +// pub fn openOptionsFromFlagsWindows(flags: u32) windows.OpenFileOptions { +// const w = windows; +// const O = std.os.O; + +// var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE; +// if (flags & O.RDWR != 0) { +// access_mask |= w.GENERIC_READ | w.GENERIC_WRITE; +// } else if (flags & O.WRONLY != 0) { +// access_mask |= w.GENERIC_WRITE; +// } else { +// access_mask |= w.GENERIC_READ | w.GENERIC_WRITE; +// } + +// const filter: windows.OpenFileOptions.Filter = if (flags & O.DIRECTORY != 0) .dir_only else .file_only; +// const follow_symlinks: bool = flags & O.NOFOLLOW == 0; + +// const creation: w.ULONG = blk: { +// if (flags & O.CREAT != 0) { +// if (flags & O.EXCL != 0) { +// break :blk w.FILE_CREATE; +// } +// } +// break :blk w.FILE_OPEN; +// }; + +// return .{ +// .access_mask = access_mask, +// .io_mode = .blocking, +// .creation = creation, +// .filter = filter, +// .follow_symlinks = follow_symlinks, +// }; +// } +const O = std.os.O; +const w = std.os.windows; + +pub fn openatWindows(dirfD: bun.FileDescriptor, path: []const u16, flags: JSC.Node.Mode) Maybe(bun.FileDescriptor) { + const nonblock = flags & O.NONBLOCK != 0; + + var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE; + + if (flags & O.RDWR != 0) { + access_mask |= w.GENERIC_READ | w.GENERIC_WRITE; + } else if (flags & O.WRONLY != 0) { + access_mask |= w.GENERIC_WRITE; + } else if (flags & O.APPEND != 0) { + access_mask |= w.FILE_APPEND_DATA; + } else { + access_mask |= w.GENERIC_READ; + } + + var result: windows.HANDLE = undefined; + + const path_len_bytes = std.math.cast(u16, path.len * 2) orelse return .{ + .err = .{ + .errno = @intFromEnum(bun.C.E.NOMEM), + .syscall = .open, + }, + }; + var nt_name = windows.UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(path.ptr), + }; + var attr = windows.OBJECT_ATTRIBUTES{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = if (dirfD == bun.invalid_fd or std.fs.path.isAbsoluteWindowsWTF16(path)) null else bun.fdcast(dirfD), + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var io: windows.IO_STATUS_BLOCK = undefined; + const blocking_flag: windows.ULONG = if (!nonblock) windows.FILE_SYNCHRONOUS_IO_NONALERT else 0; + const file_or_dir_flag: windows.ULONG = switch (flags & O.DIRECTORY != 0) { + // .file_only => windows.FILE_NON_DIRECTORY_FILE, + true => windows.FILE_DIRECTORY_FILE, + false => 0, + }; + const follow_symlinks = flags & O.NOFOLLOW == 0; + const creation: w.ULONG = blk: { + if (flags & O.CREAT != 0) { + if (flags & O.EXCL != 0) { + break :blk w.FILE_CREATE; + } + } + break :blk w.FILE_OPEN; + }; + + const wflags: windows.ULONG = if (follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | windows.FILE_OPEN_REPARSE_POINT; + + while (true) { + const rc = windows.ntdll.NtCreateFile( + &result, + access_mask, + &attr, + &io, + null, + w.FILE_ATTRIBUTE_NORMAL, + w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE, + creation, + wflags, + null, + 0, + ); + switch (windows.Win32Error.fromNTStatus(rc)) { + .SUCCESS => { + return JSC.Maybe(bun.FileDescriptor){ + .result = bun.toFD(result), + }; + }, + else => |code| { + if (code.toSystemErrno()) |sys_err| { + return .{ + .err = .{ + .errno = @truncate(@intFromEnum(sys_err)), + .syscall = .open, + }, + }; + } + + return .{ + .err = .{ + .errno = @intFromEnum(bun.C.E.UNKNOWN), + .syscall = .open, + }, + }; + }, + } + } +} + +pub fn openatOSPath(dirfd: bun.FileDescriptor, file_path: bun.OSPathSlice, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(bun.FileDescriptor) { if (comptime Environment.isMac) { // https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/wrappers/open-base.c const rc = bun.AsyncIO.darwin.@"openat$NOCANCEL"(dirfd, file_path.ptr, @as(c_uint, @intCast(flags)), @as(c_int, @intCast(perm))); @@ -213,6 +412,10 @@ pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Nod }; } + if (comptime Environment.isWindows) { + return openatWindows(dirfd, file_path, flags); + } + while (true) { const rc = Syscall.system.openat(@as(Syscall.system.fd_t, @intCast(dirfd)), file_path, flags, perm); log("openat({d}, {s}) = {d}", .{ dirfd, file_path, rc }); @@ -233,14 +436,23 @@ pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Nod unreachable; } +pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(bun.FileDescriptor) { + if (comptime Environment.isWindows) { + var wbuf: bun.MAX_WPATH = undefined; + return openatWindows(dirfd, bun.strings.toWPath(&wbuf, file_path), flags); + } + + return openatOSPath(dirfd, file_path, flags, perm); +} + pub fn open(file_path: [:0]const u8, flags: JSC.Node.Mode, perm: JSC.Node.Mode) Maybe(bun.FileDescriptor) { // this is what open() does anyway. - return openat(@as(bun.FileDescriptor, @intCast(std.fs.cwd().fd)), file_path, flags, perm); + return openat(bun.toFD((std.fs.cwd().fd)), file_path, flags, perm); } /// This function will prevent stdout and stderr from being closed. -pub fn close(fd: std.os.fd_t) ?Syscall.Error { - if (fd == std.os.STDOUT_FILENO or fd == std.os.STDERR_FILENO) { +pub fn close(fd: bun.FileDescriptor) ?Syscall.Error { + if (fd == bun.STDOUT_FD or fd == bun.STDERR_FD) { log("close({d}) SKIPPED", .{fd}); return null; } @@ -248,7 +460,7 @@ pub fn close(fd: std.os.fd_t) ?Syscall.Error { return closeAllowingStdoutAndStderr(fd); } -pub fn closeAllowingStdoutAndStderr(fd: std.os.fd_t) ?Syscall.Error { +pub fn closeAllowingStdoutAndStderr(fd: bun.FileDescriptor) ?Syscall.Error { log("close({d})", .{fd}); std.debug.assert(fd != bun.invalid_fd); if (comptime std.meta.trait.isSignedInt(@TypeOf(fd))) @@ -269,6 +481,14 @@ pub fn closeAllowingStdoutAndStderr(fd: std.os.fd_t) ?Syscall.Error { }; } + if (comptime Environment.isWindows) { + if (kernel32.CloseHandle(bun.fdcast(fd)) == 0) { + return Syscall.Error{ .errno = @intFromEnum(os.E.BADF), .syscall = .close }; + } + + return null; + } + @compileError("Not implemented yet"); } @@ -278,7 +498,8 @@ const max_count = switch (builtin.os.tag) { else => std.math.maxInt(isize), }; -pub fn write(fd: os.fd_t, bytes: []const u8) Maybe(usize) { +pub fn write(fd_: bun.FileDescriptor, bytes: []const u8) Maybe(usize) { + const fd = bun.fdcast(fd_); const adjusted_len = @min(max_count, bytes.len); if (comptime Environment.isMac) { @@ -314,7 +535,8 @@ fn veclen(buffers: anytype) usize { return len; } -pub fn writev(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) { +pub fn writev(fd_: bun.FileDescriptor, buffers: []std.os.iovec) Maybe(usize) { + const fd = bun.fdcast(fd_); if (comptime Environment.isMac) { const rc = writev_sym(fd, @as([*]std.os.iovec_const, @ptrCast(buffers.ptr)), @as(i32, @intCast(buffers.len))); if (comptime Environment.allow_assert) @@ -342,7 +564,8 @@ pub fn writev(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) { } } -pub fn pwritev(fd: os.fd_t, buffers: []std.os.iovec, position: isize) Maybe(usize) { +pub fn pwritev(fd_: bun.FileDescriptor, buffers: []std.os.iovec, position: isize) Maybe(usize) { + const fd = bun.fdcast(fd_); if (comptime Environment.isMac) { const rc = pwritev_sym(fd, @as([*]std.os.iovec_const, @ptrCast(buffers.ptr)), @as(i32, @intCast(buffers.len)), position); if (comptime Environment.allow_assert) @@ -370,7 +593,8 @@ pub fn pwritev(fd: os.fd_t, buffers: []std.os.iovec, position: isize) Maybe(usiz } } -pub fn readv(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) { +pub fn readv(fd_: bun.FileDescriptor, buffers: []std.os.iovec) Maybe(usize) { + const fd = bun.fdcast(fd_); if (comptime Environment.isMac) { const rc = readv_sym(fd, buffers.ptr, @as(i32, @intCast(buffers.len))); if (comptime Environment.allow_assert) @@ -398,7 +622,8 @@ pub fn readv(fd: os.fd_t, buffers: []std.os.iovec) Maybe(usize) { } } -pub fn preadv(fd: os.fd_t, buffers: []std.os.iovec, position: isize) Maybe(usize) { +pub fn preadv(fd_: bun.FileDescriptor, buffers: []std.os.iovec, position: isize) Maybe(usize) { + const fd = bun.fdcast(fd_); if (comptime Environment.isMac) { const rc = preadv_sym(fd, buffers.ptr, @as(i32, @intCast(buffers.len)), position); if (comptime Environment.allow_assert) @@ -463,8 +688,10 @@ else const fcntl_symbol = system.fcntl; -pub fn pread(fd: os.fd_t, buf: []u8, offset: i64) Maybe(usize) { +pub fn pread(fd_: bun.FileDescriptor, buf: []u8, offset: i64) Maybe(usize) { + const fd = bun.fdcast(fd_); const adjusted_len = @min(buf.len, max_count); + const ioffset = @as(i64, @bitCast(offset)); // the OS treats this as unsigned while (true) { const rc = pread_sym(fd, buf.ptr, adjusted_len, ioffset); @@ -482,7 +709,8 @@ const pwrite_sym = if (builtin.os.tag == .linux and builtin.link_libc) else sys.pwrite; -pub fn pwrite(fd: os.fd_t, bytes: []const u8, offset: i64) Maybe(usize) { +pub fn pwrite(fd_: bun.FileDescriptor, bytes: []const u8, offset: i64) Maybe(usize) { + const fd = bun.fdcast(fd_); const adjusted_len = @min(bytes.len, max_count); const ioffset = @as(i64, @bitCast(offset)); // the OS treats this as unsigned @@ -499,7 +727,8 @@ pub fn pwrite(fd: os.fd_t, bytes: []const u8, offset: i64) Maybe(usize) { unreachable; } -pub fn read(fd: os.fd_t, buf: []u8) Maybe(usize) { +pub fn read(fd_: bun.FileDescriptor, buf: []u8) Maybe(usize) { + const fd = bun.fdcast(fd_); const debug_timer = bun.Output.DebugTimer.start(); const adjusted_len = @min(buf.len, max_count); if (comptime Environment.isMac) { @@ -526,7 +755,8 @@ pub fn read(fd: os.fd_t, buf: []u8) Maybe(usize) { unreachable; } -pub fn recv(fd: os.fd_t, buf: []u8, flag: u32) Maybe(usize) { +pub fn recv(fd_: bun.FileDescriptor, buf: []u8, flag: u32) Maybe(usize) { + const fd = bun.fdcast(fd_); const adjusted_len = @min(buf.len, max_count); if (comptime Environment.isMac) { @@ -553,7 +783,8 @@ pub fn recv(fd: os.fd_t, buf: []u8, flag: u32) Maybe(usize) { unreachable; } -pub fn send(fd: os.fd_t, buf: []const u8, flag: u32) Maybe(usize) { +pub fn send(fd_: bun.FileDescriptor, buf: []const u8, flag: u32) Maybe(usize) { + const fd = bun.fdcast(fd_); if (comptime Environment.isMac) { const rc = system.@"sendto$NOCANCEL"(fd, buf.ptr, buf.len, flag, null, 0); if (Maybe(usize).errnoSys(rc, .send)) |err| { @@ -589,6 +820,13 @@ pub fn readlink(in: [:0]const u8, buf: []u8) Maybe(usize) { } pub fn ftruncate(fd: fd_t, size: isize) Maybe(void) { + if (comptime Environment.isWindows) { + if (kernel32.SetFileValidData(bun.fdcast(fd), size) == 0) { + return Maybe(void).errnoSys(0, .ftruncate) orelse Maybe(void).success; + } + + return Maybe(void).success; + } while (true) { if (Maybe(void).errnoSys(sys.ftruncate(fd, size), .ftruncate)) |err| { if (err.getErrno() == .INTR) continue; @@ -682,18 +920,17 @@ pub fn unlink(from: [:0]const u8) Maybe(void) { unreachable; } -pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) Maybe([]u8) { +pub fn getFdPath(fd_: bun.FileDescriptor, out_buffer: *[MAX_PATH_BYTES]u8) Maybe([]u8) { + const fd = bun.fdcast(fd_); switch (comptime builtin.os.tag) { .windows => { - const windows = std.os.windows; var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]) catch { - return Maybe([]u8){ .err = .{ .errno = .EBADF } }; + const wide_slice = std.os.windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]) catch { + return Maybe([]u8){ .err = .{ .errno = @intFromEnum(bun.C.SystemErrno.EBADF) } }; }; // Trust that Windows gives us valid UTF-16LE. - const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; - return .{ .result = out_buffer[0..end_index] }; + return .{ .result = @constCast(bun.strings.fromWPath(out_buffer, wide_slice)) }; }, .macos, .ios, .watchos, .tvos => { // On macOS, we can use F.GETPATH fcntl command to query the OS for @@ -738,9 +975,10 @@ fn mmap( length: usize, prot: u32, flags: u32, - fd: os.fd_t, + fd_: bun.FileDescriptor, offset: u64, ) Maybe([]align(mem.page_size) u8) { + const fd = bun.fdcast(fd_); const ioffset = @as(i64, @bitCast(offset)); // the OS treats this as unsigned const rc = std.c.mmap(ptr, length, prot, flags, fd, ioffset); const fail = std.c.MAP.FAILED; @@ -793,9 +1031,10 @@ pub fn munmap(memory: []align(mem.page_size) const u8) Maybe(void) { } pub const Error = struct { + const E = bun.C.E; const max_errno_value = brk: { - const errno_values = std.enums.values(os.E); - var err = @intFromEnum(os.E.SUCCESS); + const errno_values = std.enums.values(E); + var err = @intFromEnum(E.SUCCESS); for (errno_values) |errn| { err = @max(err, @intFromEnum(errn)); } @@ -806,13 +1045,13 @@ pub const Error = struct { errno: Int, syscall: Syscall.Tag = @as(Syscall.Tag, @enumFromInt(0)), path: []const u8 = "", - fd: i32 = -1, + fd: bun.FileDescriptor = bun.invalid_fd, pub inline fn isRetry(this: *const Error) bool { return this.getErrno() == .AGAIN; } - pub fn fromCode(errno: os.E, syscall: Syscall.Tag) Error { + pub fn fromCode(errno: E, syscall: Syscall.Tag) Error { return .{ .errno = @as(Int, @truncate(@intFromEnum(errno))), .syscall = syscall }; } @@ -820,20 +1059,20 @@ pub const Error = struct { try self.toSystemError().format(fmt, opts, writer); } - pub const oom = fromCode(os.E.NOMEM, .read); + pub const oom = fromCode(E.NOMEM, .read); pub const retry = Error{ .errno = if (Environment.isLinux) - @as(Int, @intCast(@intFromEnum(os.E.AGAIN))) + @as(Int, @intCast(@intFromEnum(E.AGAIN))) else if (Environment.isMac) - @as(Int, @intCast(@intFromEnum(os.E.WOULDBLOCK))) + @as(Int, @intCast(@intFromEnum(E.WOULDBLOCK))) else - @as(Int, @intCast(@intFromEnum(os.E.INTR))), + @as(Int, @intCast(@intFromEnum(E.INTR))), .syscall = .retry, }; - pub inline fn getErrno(this: Error) os.E { - return @as(os.E, @enumFromInt(this.errno)); + pub inline fn getErrno(this: Error) E { + return @as(E, @enumFromInt(this.errno)); } pub inline fn withPath(this: Error, path: anytype) Error { @@ -848,7 +1087,7 @@ pub const Error = struct { return Error{ .errno = this.errno, .syscall = this.syscall, - .fd = @as(i32, @intCast(fd)), + .fd = @intCast(fd), }; } @@ -889,8 +1128,10 @@ pub const Error = struct { err.path = bun.String.create(this.path); } - if (this.fd != -1) { - err.fd = this.fd; + if (this.fd != bun.invalid_fd) { + if (this.fd <= std.math.maxInt(i32)) { + err.fd = @intCast(this.fd); + } } return err; @@ -905,7 +1146,8 @@ pub const Error = struct { } }; -pub fn setPipeCapacityOnLinux(fd: bun.FileDescriptor, capacity: usize) Maybe(usize) { +pub fn setPipeCapacityOnLinux(fd_: bun.FileDescriptor, capacity: usize) Maybe(usize) { + const fd = bun.fdcast(fd_); if (comptime !Environment.isLinux) @compileError("Linux-only"); std.debug.assert(capacity > 0); @@ -941,16 +1183,16 @@ pub fn getMaxPipeSizeOnLinux() usize { fn once() c_int { const strings = bun.strings; const default_out_size = 512 * 1024; - const pipe_max_size_fd = switch (JSC.Node.Syscall.open("/proc/sys/fs/pipe-max-size", std.os.O.RDONLY, 0)) { + const pipe_max_size_fd = switch (bun.sys.open("/proc/sys/fs/pipe-max-size", std.os.O.RDONLY, 0)) { .result => |fd2| fd2, .err => |err| { log("Failed to open /proc/sys/fs/pipe-max-size: {d}\n", .{err.errno}); return default_out_size; }, }; - defer _ = JSC.Node.Syscall.close(pipe_max_size_fd); + defer _ = bun.sys.close(pipe_max_size_fd); var max_pipe_size_buf: [128]u8 = undefined; - const max_pipe_size = switch (JSC.Node.Syscall.read(pipe_max_size_fd, max_pipe_size_buf[0..])) { + const max_pipe_size = switch (bun.sys.read(pipe_max_size_fd, max_pipe_size_buf[0..])) { .result => |bytes_read| std.fmt.parseInt(i64, strings.trim(max_pipe_size_buf[0..bytes_read], "\n"), 10) catch |err| { log("Failed to parse /proc/sys/fs/pipe-max-size: {any}\n", .{@errorName(err)}); return default_out_size; @@ -968,3 +1210,126 @@ pub fn getMaxPipeSizeOnLinux() usize { }.once, c_int)), ); } + +pub fn existsOSPath(path: bun.OSPathSlice) bool { + if (comptime Environment.isPosix) { + return system.access(path, 0) == 0; + } + + if (comptime Environment.isWindows) { + const rc = kernel32.GetFileAttributesW(path) != windows.INVALID_FILE_ATTRIBUTES; + if (rc == windows.FALSE) { + return false; + } + return true; + } + + @compileError("TODO: existsOSPath"); +} + +pub fn isExecutableFileOSPath(path: bun.OSPathSlice) bool { + if (comptime Environment.isPosix) { + return bun.is_executable_fileZ(path); + } + + if (comptime Environment.isWindows) { + var out: windows.DWORD = 8; + const rc = kernel32.GetBinaryTypeW(path, &out); + log("GetBinaryTypeW({}) = {d}", .{ bun.String.init(path), out }); + + if (rc == windows.FALSE) { + return false; + } + + return switch (out) { + kernel32.SCS_32BIT_BINARY, + kernel32.SCS_64BIT_BINARY, + kernel32.SCS_DOS_BINARY, + kernel32.SCS_OS216_BINARY, + kernel32.SCS_PIF_BINARY, + kernel32.SCS_POSIX_BINARY, + => true, + else => false, + }; + } + + @compileError("TODO: isExecutablePath"); +} + +pub fn isExecutableFilePath(path: anytype) bool { + const Type = @TypeOf(path); + if (comptime Environment.isPosix) { + switch (Type) { + *[*:0]const u8, *[*:0]u8, [*:0]const u8, [*:0]u8 => return bun.is_executable_fileZ(path), + [:0]const u8, [:0]u8 => return bun.is_executable_fileZ(path.ptr), + []const u8, []u8 => return bun.is_executable_fileZ(&(std.os.toPosixPath(path) catch return false)), + else => @compileError("TODO: isExecutableFilePath"), + } + } + + if (comptime Environment.isWindows) { + var buf: [(bun.MAX_PATH_BYTES / 2) + 1]u16 = undefined; + return isExecutableFileOSPath(bun.strings.toWPath(&buf, path)); + } + + @compileError("TODO: isExecutablePath"); +} + +pub fn setFileOffset(fd: bun.FileDescriptor, offset: usize) Maybe(void) { + if (comptime Environment.isLinux) { + return Maybe(void).errnoSysFd( + linux.lseek(@intCast(fd), @intCast(offset), os.SEEK.SET), + .lseek, + @as(bun.FileDescriptor, @intCast(fd)), + ) orelse Maybe(void).success; + } + + if (comptime Environment.isMac) { + return Maybe(void).errnoSysFd( + std.c.lseek(fd, @as(std.c.off_t, @intCast(offset)), os.SEEK.SET), + .lseek, + @as(bun.FileDescriptor, @intCast(fd)), + ) orelse Maybe(void).success; + } + + if (comptime Environment.isWindows) { + const offset_high: u64 = @as(u32, @intCast(offset >> 32)); + const offset_low: u64 = @as(u32, @intCast(offset & 0xFFFFFFFF)); + var plarge_integer: i64 = @bitCast(offset_high); + const rc = kernel32.SetFilePointerEx( + bun.fdcast(fd), + @as(windows.LARGE_INTEGER, @bitCast(offset_low)), + &plarge_integer, + windows.FILE_BEGIN, + ); + if (rc == windows.FALSE) { + return Maybe(void).errnoSys(0, .lseek) orelse Maybe(void).success; + } + return Maybe(void).success; + } +} + +pub fn dup(fd: bun.FileDescriptor) Maybe(bun.FileDescriptor) { + if (comptime Environment.isWindows) { + var target: *windows.HANDLE = undefined; + const process = kernel32.GetCurrentProcess(); + const out = kernel32.DuplicateHandle( + process, + bun.fdcast(fd), + process, + target, + 0, + w.TRUE, + w.DUPLICATE_SAME_ACCESS, + ); + if (out == 0) { + if (Maybe(bun.FileDescriptor).errnoSysFd(0, .dup, fd)) |err| { + return err; + } + } + return Maybe(bun.FileDescriptor){ .result = bun.toFD(out) }; + } + + const out = std.c.dup(fd); + return Maybe(bun.FileDescriptor).errnoSysFd(out, .dup, fd) orelse Maybe(bun.FileDescriptor){ .result = bun.toFD(out) }; +} diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 6c4ee9f51..51094d629 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -20,7 +20,7 @@ const is_bindgen: bool = std.meta.globalOption("bindgen", bool) orelse false; const meta = bun.meta; /// Time in seconds. Not nanos! pub const TimeLike = c_int; -pub const Mode = if (Environment.isLinux) u32 else std.os.mode_t; +pub const Mode = bun.C.Mode; const heap_allocator = bun.default_allocator; pub fn DeclEnum(comptime T: type) type { const fieldInfos = std.meta.declarations(T); @@ -83,6 +83,12 @@ pub fn Maybe(comptime ResultType: type) type { pub const todo: @This() = @This(){ .err = Syscall.Error.todo }; + pub fn throw(this: @This()) !void { + if (this == .err) { + return bun.AsyncIO.asError(this.err.errno); + } + } + pub fn toJS(this: @This(), globalThis: *JSC.JSGlobalObject) JSC.JSValue { switch (this) { .err => |e| { @@ -171,7 +177,7 @@ pub fn Maybe(comptime ResultType: type) type { .err = .{ .errno = @as(Syscall.Error.Int, @truncate(@intFromEnum(err))), .syscall = syscall, - .fd = @as(i32, @intCast(fd)), + .fd = @intCast(bun.toFD(fd)), }, }, }; @@ -687,6 +693,18 @@ pub const PathLike = union(Tag) { return sliceZWithForceCopy(this, buf, false); } + pub inline fn sliceW(this: PathLike, buf: *[bun.MAX_PATH_BYTES]u8) [:0]const u16 { + return bun.strings.toWPath(@alignCast(std.mem.bytesAsSlice(u16, buf)), this.slice()); + } + + pub inline fn osPath(this: PathLike, buf: *[bun.MAX_PATH_BYTES]u8) bun.OSPathSlice { + if (comptime Environment.isWindows) { + return sliceW(this, buf); + } + + return sliceZWithForceCopy(this, buf, false); + } + pub inline fn sliceZAssume( this: PathLike, ) [:0]const u8 { @@ -772,7 +790,7 @@ pub const PathLike = union(Tag) { }; pub const Valid = struct { - pub fn fileDescriptor(fd: bun.FileDescriptor, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) bool { + pub fn fileDescriptor(fd: i64, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) bool { if (fd < 0) { JSC.throwInvalidArguments("Invalid file descriptor, must not be negative number", .{}, ctx, exception); return false; @@ -985,12 +1003,12 @@ pub const ArgumentsSlice = struct { pub fn fileDescriptorFromJS(ctx: JSC.C.JSContextRef, value: JSC.JSValue, exception: JSC.C.ExceptionRef) ?bun.FileDescriptor { if (!value.isNumber() or value.isBigInt()) return null; - const fd = value.toInt32(); + const fd = value.toInt64(); if (!Valid.fileDescriptor(fd, ctx, exception)) { return null; } - return @as(bun.FileDescriptor, @truncate(fd)); + return @as(bun.FileDescriptor, @intCast(fd)); } // Node.js docs: @@ -1427,26 +1445,46 @@ pub fn StatType(comptime Big: bool) type { } pub fn isBlockDevice(this: *This) JSC.JSValue { + if (comptime Environment.isWindows) { + JSC.VirtualMachine.get().global.throwTODO(comptime @src().fn_name ++ " is not implemented yet"); + return .zero; + } return JSC.JSValue.jsBoolean(os.S.ISBLK(@as(Mode, @intCast(this.modeInternal())))); } pub fn isCharacterDevice(this: *This) JSC.JSValue { + if (comptime Environment.isWindows) { + JSC.VirtualMachine.get().global.throwTODO(comptime @src().fn_name ++ " is not implemented yet"); + return .zero; + } return JSC.JSValue.jsBoolean(os.S.ISCHR(@as(Mode, @intCast(this.modeInternal())))); } pub fn isDirectory(this: *This) JSC.JSValue { + if (comptime Environment.isWindows) { + JSC.VirtualMachine.get().global.throwTODO(comptime @src().fn_name ++ " is not implemented yet"); + return .zero; + } return JSC.JSValue.jsBoolean(os.S.ISDIR(@as(Mode, @intCast(this.modeInternal())))); } pub fn isFIFO(this: *This) JSC.JSValue { + if (comptime Environment.isWindows) { + JSC.VirtualMachine.get().global.throwTODO(comptime @src().fn_name ++ " is not implemented yet"); + return .zero; + } return JSC.JSValue.jsBoolean(os.S.ISFIFO(@as(Mode, @intCast(this.modeInternal())))); } pub fn isFile(this: *This) JSC.JSValue { - return JSC.JSValue.jsBoolean(os.S.ISREG(@as(Mode, @intCast(this.modeInternal())))); + return JSC.JSValue.jsBoolean(bun.isRegularFile(@as(Mode, @intCast(this.modeInternal())))); } pub fn isSocket(this: *This) JSC.JSValue { + if (comptime Environment.isWindows) { + JSC.VirtualMachine.get().global.throwTODO(comptime @src().fn_name ++ " is not implemented yet"); + return .zero; + } return JSC.JSValue.jsBoolean(os.S.ISSOCK(@as(Mode, @intCast(this.modeInternal())))); } @@ -1456,6 +1494,10 @@ pub fn StatType(comptime Big: bool) type { /// /// See https://nodejs.org/api/fs.html#statsissymboliclink pub fn isSymbolicLink(this: *This) JSC.JSValue { + if (comptime Environment.isWindows) { + JSC.VirtualMachine.get().global.throwTODO(comptime @src().fn_name ++ " is not implemented yet"); + return .zero; + } return JSC.JSValue.jsBoolean(os.S.ISLNK(@as(Mode, @intCast(this.modeInternal())))); } @@ -1465,7 +1507,7 @@ pub fn StatType(comptime Big: bool) type { bun.default_allocator.destroy(this); } - pub fn init(stat_: os.Stat) This { + pub fn init(stat_: bun.Stat) This { const aTime = stat_.atime(); const mTime = stat_.mtime(); const cTime = stat_.ctime(); @@ -1495,7 +1537,7 @@ pub fn StatType(comptime Big: bool) type { }; } - pub fn initWithAllocator(allocator: std.mem.Allocator, stat: std.os.Stat) *This { + pub fn initWithAllocator(allocator: std.mem.Allocator, stat: bun.Stat) *This { var this = allocator.create(This) catch unreachable; this.* = init(stat); return this; @@ -1558,7 +1600,7 @@ pub const Stats = union(enum) { big: StatsBig, small: StatsSmall, - pub inline fn init(stat_: os.Stat, big: bool) Stats { + pub inline fn init(stat_: bun.Stat, big: bool) Stats { if (big) { return .{ .big = StatsBig.init(stat_) }; } else { @@ -2285,7 +2327,7 @@ pub const Path = struct { pub const Process = struct { pub fn getArgv0(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { - return JSC.ZigString.fromUTF8(bun.span(std.os.argv[0])).toValueGC(globalObject); + return JSC.ZigString.fromUTF8(bun.span(bun.argv()[0])).toValueGC(globalObject); } pub fn getExecPath(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { @@ -2305,13 +2347,13 @@ pub const Process = struct { JSC.ZigString, // argv omits "bun" because it could be "bun run" or "bun" and it's kind of ambiguous // argv also omits the script name - std.os.argv.len -| 1, + bun.argv().len -| 1, ) catch unreachable; defer allocator.free(args); var used: usize = 0; const offset: usize = 1; - for (std.os.argv[@min(std.os.argv.len, offset)..]) |arg_| { + for (bun.argv()[@min(bun.argv().len, offset)..]) |arg_| { const arg = bun.span(arg_); if (arg.len == 0) continue; |