From f9d325cb56f0ddfa6b5315c69e658a2f12c2f6d2 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 5 Sep 2023 05:08:37 -0800 Subject: [PATCH] quite a lot of fixes --- .../bindings/workaround-missing-symbols.cpp | 22 +--- src/bun.js/javascript.zig | 2 +- src/bun.zig | 30 +++-- src/bundler/entry_points.zig | 29 +---- src/cli.zig | 13 +- src/fs.zig | 19 ++- src/install/install.zig | 15 ++- src/main.zig | 18 +-- src/resolver/resolve_path.zig | 73 +++++++++--- src/resolver/resolver.zig | 38 ++++-- src/string_immutable.zig | 97 +++++++++++---- src/sync.zig | 6 +- src/sys.zig | 111 ++++++++++++++++-- 13 files changed, 331 insertions(+), 142 deletions(-) diff --git a/src/bun.js/bindings/workaround-missing-symbols.cpp b/src/bun.js/bindings/workaround-missing-symbols.cpp index be896a8b61..68ea6d9f38 100644 --- a/src/bun.js/bindings/workaround-missing-symbols.cpp +++ b/src/bun.js/bindings/workaround-missing-symbols.cpp @@ -14,30 +14,10 @@ #undef _environ #undef environ -// TODO: figure out why these symbols were not already defined +// Some libraries need these symbols. Windows makes it extern "C" char** environ = nullptr; extern "C" char** _environ = nullptr; -// TODO: figure out why the stack check symbols are missing -extern "C" void __stack_chk_fail() { abort(); } -extern "C" void ___chkstk_ms() {} -extern "C" size_t __stack_chk_guard = 0; - -extern "C" int windows_main(int argc, char** argv, char** envp); - -extern "C" int __main() -{ - environ = *__p__environ(); -// TODO: figure out why the stack check symbols are missing -#ifdef BUN_DEBUG - __stack_chk_guard = 0xdeadbeefd00dfeed; -#endif - _environ = environ; - -// this is a workaround for an infinite loop that happens if we call main directly - return windows_main(__argc, __argv, environ); -} - extern "C" int strncasecmp(const char* s1, const char* s2, size_t n) { return _strnicmp(s1, s2, n); diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 071b192ebe..4559773b1f 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -1965,7 +1965,7 @@ pub const VirtualMachine = struct { try this.entry_point.generate( this.allocator, this.bun_watcher != null, - Fs.PathName.init(entry_path), + entry_path, main_file_name, ); this.eventLoop().ensureWaker(); diff --git a/src/bun.zig b/src/bun.zig index a77d76a9d9..31f59b1c2a 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -741,8 +741,25 @@ pub const DateTime = @import("./deps/zig-datetime/src/datetime.zig"); pub var start_time: i128 = 0; pub fn openDir(dir: std.fs.Dir, path_: [:0]const u8) !std.fs.IterableDir { - const fd = try std.os.openatZ(dir.fd, path_, std.os.O.DIRECTORY | std.os.O.CLOEXEC | 0, 0); - return std.fs.IterableDir{ .dir = .{ .fd = fd } }; + if (comptime Environment.isWindows) { + const res = sys.openDirAtWindowsA(toFD(dir.fd), path_, true, false); + try res.throw(); + return std.fs.IterableDir{ .dir = .{ .fd = fdcast(res.result) } }; + } else { + const fd = try std.os.openatZ(dir.fd, path_, std.os.O.DIRECTORY | std.os.O.CLOEXEC | 0, 0); + return std.fs.IterableDir{ .dir = .{ .fd = fd } }; + } +} + +pub fn openDirAbsolute(path_: []const u8) !std.fs.Dir { + if (comptime Environment.isWindows) { + const res = sys.openDirAtWindowsA(invalid_fd, path_, true, false); + try res.throw(); + return std.fs.Dir{ .fd = fdcast(res.result) }; + } else { + const fd = try std.os.openatZ(invalid_fd, path_, std.os.O.DIRECTORY | std.os.O.CLOEXEC | 0, 0); + return std.fs.Dir{ .fd = fd }; + } } pub const MimallocArena = @import("./mimalloc_arena.zig").Arena; @@ -1074,13 +1091,13 @@ pub fn getcwd(buf_: []u8) ![]u8 { return std.os.getcwd(buf_); } - var temp: [MAX_PATH_BYTES ]u8 = undefined; + var temp: [MAX_PATH_BYTES]u8 = undefined; var temp_slice = try std.os.getcwd(&temp); return path.normalizeBuf(temp_slice, buf_, .loose); } pub fn getcwdAlloc(allocator: std.mem.Allocator) ![]u8 { - var temp: [MAX_PATH_BYTES ]u8 = undefined; + var temp: [MAX_PATH_BYTES]u8 = undefined; var temp_slice = try getcwd(&temp); return allocator.dupe(u8, temp_slice); } @@ -1090,7 +1107,7 @@ pub fn getcwdAlloc(allocator: std.mem.Allocator) ![]u8 { pub fn getFdPath(fd_: anytype, buf: *[@This().MAX_PATH_BYTES]u8) ![]u8 { const fd = fdcast(toFD(fd_)); if (comptime Environment.isWindows) { - var temp: [MAX_PATH_BYTES ]u8 = undefined; + var temp: [MAX_PATH_BYTES]u8 = undefined; var temp_slice = try std.os.getFdPath(fd, &temp); return path.normalizeBuf(temp_slice, buf, .loose); } @@ -1753,7 +1770,7 @@ pub const win32 = struct { pub var STDIN_FD: FileDescriptor = undefined; pub inline fn argv() [][*:0]u8 { - return std.os.argv; + return std.os.argv; } pub fn stdio(i: anytype) FileDescriptor { @@ -1764,7 +1781,6 @@ pub const win32 = struct { else => @panic("Invalid stdio fd"), }; } - }; pub usingnamespace if (@import("builtin").target.os.tag != .windows) posix else win32; diff --git a/src/bundler/entry_points.zig b/src/bundler/entry_points.zig index 5e8310d927..52698d1a69 100644 --- a/src/bundler/entry_points.zig +++ b/src/bundler/entry_points.zig @@ -163,29 +163,16 @@ pub const ServerEntryPoint = struct { entry: *ServerEntryPoint, allocator: std.mem.Allocator, is_hot_reload_enabled: bool, - original_path: Fs.PathName, + path_to_use: string, name: string, ) !void { - - // This is *extremely* naive. - // The basic idea here is this: - // -- - // import * as EntryPoint from 'entry-point'; - // import boot from 'framework'; - // boot(EntryPoint); - // -- - // We go through the steps of printing the code -- only to then parse/transpile it because - // we want it to go through the linker and the rest of the transpilation process - - const dir_to_use: string = original_path.dirWithTrailingSlash(); - const code = brk: { if (is_hot_reload_enabled) { break :brk try std.fmt.allocPrint( allocator, \\// @bun \\var hmrSymbol = Symbol.for("BunServerHMR"); - \\import * as start from '{s}{s}'; + \\import * as start from '{s}'; \\var entryNamespace = start; \\if (typeof entryNamespace?.then === 'function') {{ \\ entryNamespace = entryNamespace.then((entryNamespace) => {{ @@ -210,16 +197,13 @@ pub const ServerEntryPoint = struct { \\}} \\ , - .{ - dir_to_use, - original_path.filename, - }, + .{path_to_use}, ); } break :brk try std.fmt.allocPrint( allocator, \\// @bun - \\import * as start from '{s}{s}'; + \\import * as start from '{s}'; \\var entryNamespace = start; \\if (typeof entryNamespace?.then === 'function') {{ \\ entryNamespace = entryNamespace.then((entryNamespace) => {{ @@ -232,10 +216,7 @@ pub const ServerEntryPoint = struct { \\}} \\ , - .{ - dir_to_use, - original_path.filename, - }, + .{path_to_use}, ); }; diff --git a/src/cli.zig b/src/cli.zig index b43e425d5a..e436d283cf 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -241,16 +241,19 @@ pub const Arguments = struct { } pub fn loadConfigPath(allocator: std.mem.Allocator, auto_loaded: bool, config_path: [:0]const u8, ctx: *Command.Context, comptime cmd: Command.Tag) !void { - var config_file = std.fs.File{ - .handle = std.os.openZ(config_path, std.os.O.RDONLY, 0) catch |err| { + + var config_file = switch (bun.sys.openA(config_path, std.os.O.RDONLY, 0)){ + .result => |fd| std.fs.File{ .handle = bun.fdcast( fd) }, + .err => |err| { if (auto_loaded) return; - Output.prettyErrorln("error: {s} opening config \"{s}\"", .{ - @errorName(err), + Output.prettyErrorln("{}\nwhile opening config \"{s}\"", .{ + err, config_path, }); Global.exit(1); - }, + } }; + defer config_file.close(); var contents = config_file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| { if (auto_loaded) return; diff --git a/src/fs.zig b/src/fs.zig index 5003177949..5cb34ed9bc 100644 --- a/src/fs.zig +++ b/src/fs.zig @@ -833,10 +833,10 @@ pub const FileSystem = struct { }; pub fn openDir(_: *RealFS, unsafe_dir_string: string) !std.fs.Dir { - const dirfd = bun.sys.openA(unsafe_dir_string, std.os.O.DIRECTORY, 0); + const dirfd = bun.sys.openDirAtWindowsA(bun.invalid_fd, unsafe_dir_string, true, true); try dirfd.throw(); return std.fs.Dir{ - .fd = bun.fdcast( dirfd.result), + .fd = bun.fdcast(dirfd.result), }; } @@ -1391,7 +1391,7 @@ pub const PathName = struct { return if (this.dir.len == 0) "./" else this.dir.ptr[0 .. this.dir.len + @as( usize, @intCast(@intFromBool( - !bun.path.isSepAny(this.dir[this.dir.len - 1]) and (@intFromPtr(this.dir.ptr) + this.dir.len + 1) == @intFromPtr(this.base.ptr), + !bun.path.isSepAny(this.dir[this.dir.len - 1]) and (@intFromPtr(this.dir.ptr) + this.dir.len + 1) == @intFromPtr(this.base.ptr), )), )]; } @@ -1402,6 +1402,13 @@ pub const PathName = struct { var ext = path; var dir = path; var is_absolute = true; + const has_disk_designator = path.len > 2 and path[1] == ':' and switch (path[0]) { + 'a'...'z', 'A'...'Z' => true, + else => false, + } and bun.path.isSepAny(path[2]); + if (has_disk_designator) { + path = path[2..]; + } var _i = bun.path.lastIndexOfSep(path); while (_i) |i| { @@ -1430,10 +1437,14 @@ pub const PathName = struct { dir = &([_]u8{}); } - if (base.len > 1 and bun.path.isSepAny( base[base.len - 1])) { + if (base.len > 1 and bun.path.isSepAny(base[base.len - 1])) { base = base[0 .. base.len - 1]; } + if (!is_absolute and has_disk_designator) { + dir = _path[0 .. dir.len + 2]; + } + return PathName{ .dir = dir, .base = base, diff --git a/src/install/install.zig b/src/install/install.zig index 6788e498ff..94906dc5fb 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -1691,8 +1691,11 @@ const TaskCallbackContext = union(Tag) { const TaskCallbackList = std.ArrayListUnmanaged(TaskCallbackContext); const TaskDependencyQueue = std.HashMapUnmanaged(u64, TaskCallbackList, IdentityContext(u64), 80); -const TaskChannel = sync.Channel(Task, .{ .Static = 4096 }); -const NetworkChannel = sync.Channel(*NetworkTask, .{ .Static = 8192 }); + +// Windows seems to stack overflow in debug builds due to the size of these allocations. +const TaskChannel = sync.Channel(Task, .{ .Static = 4096 / (if (Environment.isWindows) 16 else 1) }); +const NetworkChannel = sync.Channel(*NetworkTask, .{ .Static = 8192 / (if (Environment.isWindows) 16 else 1) }); + const ThreadPool = bun.ThreadPool; const PackageManifestMap = std.HashMapUnmanaged(PackageNameHash, Npm.PackageManifest, IdentityContext(PackageNameHash), 80); const RepositoryMap = std.HashMapUnmanaged(u64, bun.FileDescriptor, IdentityContext(u64), 80); @@ -5231,14 +5234,14 @@ pub const PackageManager = struct { var this_cwd = original_cwd; const child_json = child: { while (true) { - var dir = std.fs.openDirAbsolute(this_cwd, .{}) catch |err| { + var dir = bun.openDirAbsolute(this_cwd) catch |err| { Output.prettyErrorln("Error {s} accessing {s}", .{ @errorName(err), this_cwd }); Output.flush(); return err; }; defer dir.close(); break :child dir.openFileZ("package.json", .{ .mode = .read_write }) catch { - if (std.fs.path.dirname(this_cwd)) |parent| { + if (bun.path.nextDirname(this_cwd)) |parent| { this_cwd = parent; continue; } else { @@ -5252,8 +5255,8 @@ pub const PackageManager = struct { const child_cwd = this_cwd; // Check if this is a workspace; if so, use root package var found = false; - while (std.fs.path.dirname(this_cwd)) |parent| : (this_cwd = parent) { - var dir = std.fs.openDirAbsolute(parent, .{}) catch break; + while (bun.path.nextDirname(this_cwd)) |parent| : (this_cwd = parent) { + var dir = bun.openDirAbsolute(parent) catch break; defer dir.close(); const json_file = dir.openFileZ("package.json", .{ .mode = .read_write }) catch { continue; diff --git a/src/main.zig b/src/main.zig index aca3a86307..4b534aff41 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,6 +11,8 @@ pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, addr const CrashReporter = @import("./crash_reporter.zig"); +extern "C" var _environ: ?*anyopaque; + pub fn main() void { const bun = @import("root").bun; const Output = bun.Output; @@ -19,6 +21,10 @@ pub fn main() void { if (comptime Environment.isRelease) CrashReporter.start() catch unreachable; + if (comptime Environment.isWindows) { + std.c.environ = @ptrCast(std.os.environ.ptr); + _environ = @ptrCast( std.os.environ.ptr); + } bun.start_time = std.time.nanoTimestamp(); @@ -39,18 +45,7 @@ pub fn main() void { bun.CLI.Cli.start(bun.default_allocator, stdout, stderr, MainPanicHandler); } -pub export fn windows_main(argc: c_int, argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.C) c_int { - var env_count: usize = 0; - while (c_envp[env_count] != null) : (env_count += 1) {} - const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count]; - std.os.argv = @ptrCast(argv[0..@intCast(argc)]); - std.os.environ = envp; - - main(); - - return 0; -} test "panic" { panic("woah", null); @@ -59,5 +54,4 @@ test "panic" { pub const build_options = @import("build_options"); comptime { - _ = windows_main; } \ No newline at end of file diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 06ce075972..55351459a1 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -321,7 +321,7 @@ pub fn getIfExistsLongestCommonPath(input: []const []const u8) ?[]const u8 { } pub fn longestCommonPathWindows(input: []const []const u8) []const u8 { - return longestCommonPathGeneric(input, std.fs.path.sep_windows, isSepWin32); + return longestCommonPathGeneric(input, std.fs.path.sep_windows, isSepAny); } pub fn longestCommonPathPosix(input: []const []const u8) []const u8 { @@ -463,10 +463,11 @@ pub fn relative(from: []const u8, to: []const u8) []const u8 { } pub fn relativePlatform(from: []const u8, to: []const u8, comptime platform: Platform, comptime always_copy: bool) []const u8 { - const normalized_from = if (from.len > 0 and from[0] == platform.separator()) brk: { - var path = normalizeStringBuf(from, relative_from_buf[1..], true, platform, true); - relative_from_buf[0] = platform.separator(); - break :brk relative_from_buf[0 .. path.len + 1]; + const normalized_from = if (platform.isAbsolute(from)) brk: { + const leading_separator = platform.leadingSeparatorIndex(from) orelse break :brk from; + var path = normalizeStringBuf(from, relative_from_buf[leading_separator..], true, platform, true); + @memcpy(relative_from_buf[0..leading_separator], from[0..leading_separator]); + break :brk relative_from_buf[0 .. path.len + leading_separator]; } else joinAbsStringBuf( Fs.FileSystem.instance.top_level_dir, &relative_from_buf, @@ -614,7 +615,7 @@ pub const Platform = enum { return isSepAny; }, .windows => { - return isSepWin32; + return isSepAny; }, .posix => { return isSepPosix; @@ -644,7 +645,7 @@ pub const Platform = enum { return isSepAny(char); }, .windows => { - return isSepWin32(char); + return isSepAny(char); }, .posix => { return isSepPosix(char); @@ -655,7 +656,7 @@ pub const Platform = enum { pub fn trailingSeparator(comptime _platform: Platform) [2]u8 { return comptime switch (_platform) { .auto => _platform.resolve().trailingSeparator(), - .windows => ".\\".*, + .windows => "./".*, .posix, .loose => "./".*, }; } @@ -728,8 +729,7 @@ pub fn normalizeBuf(str: []const u8, buf: []u8, comptime _platform: Platform) [] const is_absolute = _platform.isAbsolute(str); - const trailing_separator = - buf[buf.len - 1] == _platform.separator(); + const trailing_separator = _platform.getLastSeparatorFunc()(str) == str.len - 1; if (is_absolute and trailing_separator) return normalizeStringBuf(str, buf, true, _platform, true); @@ -956,7 +956,7 @@ pub fn isSepAny(char: u8) bool { } pub fn lastIndexOfSeparatorWindows(slice: []const u8) ?usize { - return std.mem.lastIndexOfScalar(u8, slice, std.fs.path.sep_windows); + return lastIndexOfSep(slice); } pub fn lastIndexOfSeparatorPosix(slice: []const u8) ?usize { @@ -975,7 +975,7 @@ pub fn lastIndexOfNonSeparatorPosix(slice: []const u8) ?u32 { } pub fn lastIndexOfSeparatorLoose(slice: []const u8) ?usize { - return std.mem.lastIndexOfAny(u8, slice, "/\\"); + return lastIndexOfSep(slice); } pub fn normalizeStringLooseBuf( @@ -1005,8 +1005,8 @@ pub fn normalizeStringWindows( str, buf, allow_above_root, - std.fs.path.sep_windows, - isSepWin32, + std.fs.path.sep_posix, + isSepAny, lastIndexOfSeparatorWindows, preserve_trailing_slash, ); @@ -1405,13 +1405,12 @@ test "longestCommonPath" { _ = t.expect("/app/public/", longestCommonPath(more[0..2]), @src()); } - pub fn basename(path: []const u8) []const u8 { if (path.len == 0) return &[_]u8{}; var end_index: usize = path.len - 1; - while (isSepAny( path[end_index])) { + while (isSepAny(path[end_index])) { if (end_index == 0) return &[_]u8{}; end_index -= 1; @@ -1435,7 +1434,7 @@ pub fn lastIndexOfSep(path: []const u8) ?usize { return null; var i: usize = path.len - 1; - if (isSepAny( path[i])) + if (isSepAny(path[i])) return i; while (i != 0) : (i -= 1) { @@ -1445,4 +1444,42 @@ pub fn lastIndexOfSep(path: []const u8) ?usize { } return null; -} \ No newline at end of file +} + +pub fn nextDirname(path_: []const u8) ?[]const u8 { + var path = path_; + var root_prefix: []const u8 = ""; + if (path.len > 3) { + // disk designator + if (path[1] == ':' and isSepAny(path[2])) { + root_prefix = path[0..3]; + } + + // TODO: unc path + + } + + if (path.len == 0) + return if (root_prefix.len > 0) root_prefix else null; + + var end_index: usize = path.len - 1; + while (isSepAny(path[end_index])) { + if (end_index == 0) + return if (root_prefix.len > 0) root_prefix else null; + end_index -= 1; + } + + while (!isSepAny(path[end_index])) { + if (end_index == 0) + return if (root_prefix.len > 0) root_prefix else null; + end_index -= 1; + } + + if (end_index == 0 and isSepAny(path[0])) + return path[0..1]; + + if (end_index == 0) + return if (root_prefix.len > 0) root_prefix else null; + + return path[0 .. end_index + 1]; +} diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 216e6c7822..5854d7a83d 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -2476,14 +2476,12 @@ pub const Resolver = struct { path[0..1]; var rfs: *Fs.FileSystem.RealFS = &r.fs.fs; - rfs.entries_mutex.lock(); defer rfs.entries_mutex.unlock(); while (!strings.eql(top, root_path)) : (top = Dirname.dirname(top)) { var result = try r.dir_cache.getOrPut(top); - if (result.status != .unknown) { top_parent = result; break; @@ -2566,12 +2564,24 @@ pub const Resolver = struct { defer path.ptr[queue_top.unsafe_path.len] = prev_char; var sentinel = path.ptr[0..queue_top.unsafe_path.len :0]; - _open_dir = std.fs.openIterableDirAbsoluteZ( - sentinel, - .{ - .no_follow = !follow_symlinks, - }, - ); + if (comptime Environment.isPosix) { + _open_dir = std.fs.openIterableDirAbsoluteZ( + sentinel, + .{ + .no_follow = !follow_symlinks, + }, + ); + } else if (comptime Environment.isWindows) { + const dirfd_result = bun.sys.openDirAtWindowsA(bun.invalid_fd, sentinel, true, !follow_symlinks); + if (dirfd_result.throw()) { + _open_dir = std.fs.IterableDir{ .dir = .{ + .fd = bun.fdcast(dirfd_result.result), + } }; + } else |err| { + _open_dir = err; + } + } + bun.fs.debug("open({s}) = {any}", .{ sentinel, _open_dir }); // } } @@ -3933,24 +3943,26 @@ pub const Dirname = struct { var path = path_; const root = brk: { if (Environment.isWindows) { - if (path.len > 1 and path[1] == ':' and switch(path[0]) { - 'A' ... 'Z', 'a' ... 'z' => true, + if (path.len > 1 and path[1] == ':' and switch (path[0]) { + 'A'...'Z', 'a'...'z' => true, else => false, }) { - break :brk path[0 .. 2]; + break :brk path[0..2]; } + // TODO: UNC paths + // TODO: NT paths break :brk "/c/"; } break :brk "/"; }; - + if (path.len == 0) return root; var end_index: usize = path.len - 1; - while (bun.path.isSepAny( path[end_index])) { + while (bun.path.isSepAny(path[end_index])) { if (end_index == 0) return root; end_index -= 1; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 2b958d50d5..1952fd0d8d 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -711,7 +711,7 @@ pub inline fn endsWithChar(self: string, char: u8) bool { pub fn withoutTrailingSlash(this: string) []const u8 { var href = this; - while (href.len > 1 and (switch (href[href.len - 1]) { + while (href.len > 1 and (switch (href[href.len - 1]) { '/' => true, '\\' => true, else => false, @@ -1488,34 +1488,80 @@ pub fn fromWPath(buf: []u8, utf16: []const u16) [:0]const u8 { return buf[0..encode_into_result.written :0]; } -pub fn toWPathNormalized(wbuf: []u16, utf8: []const u8) [:0]const u16 { - var renormalized: [bun.MAX_PATH_BYTES]u8 = undefined; - var path_to_use = utf8; - if (bun.strings.containsChar(utf8, '/')) { - @memcpy(renormalized[0..utf8.len], utf8); - for (renormalized[utf8.len..]) |*c| { - if (c.* == '/') { - c.* = '\\'; - } - } - path_to_use = renormalized[0..utf8.len]; - } +pub fn toWObjectPath(wbuf: []u16, utf8: []const u8) [:0]const u16 { + if (!std.fs.path.isAbsoluteWindows(utf8)) { + return toWPathNormalized(wbuf, utf8); + } - return toWPath(wbuf, path_to_use); + wbuf[0..4].* = [_]u16{ '\\', '?', '?', '\\' }; + return wbuf[0..toWPathNormalized(wbuf[4..], utf8).len + 4:0]; +} + +// These are the same because they don't have rules like needing a trailing slash +pub const toWObjectDir = toWObjectPath; + +pub fn toWPathNormalized(wbuf: []u16, utf8: []const u8) [:0]const u16 { + var renormalized: [bun.MAX_PATH_BYTES]u8 = undefined; + var path_to_use = utf8; + + if (bun.strings.containsChar(utf8, '/')) { + @memcpy(renormalized[0..utf8.len], utf8); + for (renormalized[0..utf8.len]) |*c| { + if (c.* == '/') { + c.* = '\\'; + } + } + path_to_use = renormalized[0..utf8.len]; + } + + // is there a trailing slash? Let's remove it before converting to UTF-16 + if (path_to_use.len > 3 and bun.path.isSepAny(path_to_use[path_to_use.len - 1])) { + path_to_use = path_to_use[0 .. path_to_use.len - 1]; + } + + return toWPath(wbuf, path_to_use); +} + +pub fn toWDirNormalized(wbuf: []u16, utf8: []const u8) [:0]const u16 { + var renormalized: [bun.MAX_PATH_BYTES]u8 = undefined; + var path_to_use = utf8; + + if (bun.strings.containsChar(utf8, '/')) { + @memcpy(renormalized[0..utf8.len], utf8); + for (renormalized[0..utf8.len]) |*c| { + if (c.* == '/') { + c.* = '\\'; + } + } + path_to_use = renormalized[0..utf8.len]; + } + + return toWDirPath(wbuf, path_to_use); } pub fn toWPath(wbuf: []u16, utf8: []const u8) [:0]const u16 { + return toWPathMaybeDir(wbuf, utf8, false); +} + +pub fn toWDirPath(wbuf: []u16, utf8: []const u8) [:0]const u16 { + return toWPathMaybeDir(wbuf, utf8, true); +} + +pub fn toWPathMaybeDir(wbuf: []u16, utf8: []const u8, comptime add_trailing_lash: bool) [:0]const u16 { std.debug.assert(wbuf.len > 0); var result = bun.simdutf.convert.utf8.to.utf16.with_errors.le( utf8, - wbuf[0..wbuf.len -| 1], + wbuf[0..wbuf.len -| (1 + @as(usize, @intFromBool( add_trailing_lash)))], ); - // TODO: error handling - // if (result.status == .surrogate) { - // } + + if (add_trailing_lash and result.count > 0 and wbuf[result.count - 1] != '\\') { + wbuf[result.count] = '\\'; + result.count += 1; + } + wbuf[result.count] = 0; - + return wbuf[0..result.count :0]; } @@ -4055,6 +4101,18 @@ pub fn formatUTF16(slice_: []align(1) const u16, writer: anytype) !void { return formatUTF16Type([]align(1) const u16, slice_, writer); } +pub const FormatUTF16 = struct { + buf: []const u16, + pub fn format(self: @This(), comptime _: []const u8, opts: anytype, writer: anytype) !void { + _ = opts; + try formatUTF16Type([]const u16, self.buf, writer); + } +}; + +pub fn fmtUTF16(buf: []const u16) FormatUTF16 { + return FormatUTF16{ .buf = buf }; +} + pub fn formatLatin1(slice_: []const u8, writer: anytype) !void { var chunk = getSharedBuffer(); var slice = slice_; @@ -4750,4 +4808,3 @@ pub fn concatIfNeeded( } std.debug.assert(remain.len == 0); } - diff --git a/src/sync.zig b/src/sync.zig index 5e57f7c6c5..befa7b73ab 100644 --- a/src/sync.zig +++ b/src/sync.zig @@ -373,17 +373,17 @@ pub fn Channel( pub usingnamespace switch (buffer_type) { .Static => struct { - pub fn init() Self { + pub inline fn init() Self { return Self.withBuffer(Buffer.init()); } }, .Slice => struct { - pub fn init(buf: []T) Self { + pub inline fn init(buf: []T) Self { return Self.withBuffer(Buffer.init(buf)); } }, .Dynamic => struct { - pub fn init(allocator: std.mem.Allocator) Self { + pub inline fn init(allocator: std.mem.Allocator) Self { return Self.withBuffer(Buffer.init(allocator)); } }, diff --git a/src/sys.zig b/src/sys.zig index 28d33c2231..feab69b891 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -148,10 +148,13 @@ pub fn chdirOSPath(destination: bun.OSPathSlice) Maybe(void) { } if (comptime Environment.isWindows) { - if (kernel32.SetCurrentDirectory(destination) != 0) { + if (kernel32.SetCurrentDirectory(destination) == windows.FALSE) { + log("SetCurrentDirectory({}) = {d}", .{ bun.strings.fmtUTF16(destination), kernel32.GetLastError() }); return Maybe(void).errnoSys(0, .chdir) orelse Maybe(void).success; } + log("SetCurrentDirectory({}) = {d}", .{ bun.strings.fmtUTF16(destination), 0 }); + return Maybe(void).success; } @@ -188,7 +191,7 @@ pub fn chdir(destination: anytype) Maybe(void) { } var wbuf: bun.MAX_WPATH = undefined; - return chdirOSPath(bun.strings.toWPath(&wbuf, destination)); + return chdirOSPath(bun.strings.toWDirPath(&wbuf, destination)); } return Maybe(void).todo; @@ -232,7 +235,7 @@ pub fn mkdir(file_path: [:0]const u8, flags: bun.Mode) Maybe(void) { 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); + _ = kernel32.CreateDirectoryW(bun.strings.toWObjectDir(&wbuf, file_path).ptr, null); return Maybe(void).errnoSysP(0, .mkdir, file_path) orelse Maybe(void).success; } @@ -299,6 +302,86 @@ pub fn getErrno(rc: anytype) bun.C.E { const O = std.os.O; const w = std.os.windows; +pub fn openDirAtWindows( + dirFd: bun.FileDescriptor, + path: [:0]const u16, + iterable: bool, + no_follow: bool, +) Maybe(bun.FileDescriptor) { + const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | + w.SYNCHRONIZE | w.FILE_TRAVERSE; + const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags; + + const path_len_bytes: u16 = @truncate(path.len * 2); + var nt_name = w.UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(path.ptr), + }; + var attr = w.OBJECT_ATTRIBUTES{ + .Length = @sizeOf(w.OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(path)) null else bun.fdcast(dirFd), + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; + var fd: w.HANDLE = w.INVALID_HANDLE_VALUE; + var io: w.IO_STATUS_BLOCK = undefined; + const rc = w.ntdll.NtCreateFile( + &fd, + flags, + &attr, + &io, + null, + 0, + w.FILE_SHARE_READ | w.FILE_SHARE_WRITE, + w.FILE_OPEN, + w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point, + null, + 0, + ); + + if (comptime Environment.allow_assert) { + log("NtCreateFile({d}, {}) = {d} (dir)", .{ dirFd, bun.strings.fmtUTF16(path), rc }); + } + + switch (windows.Win32Error.fromNTStatus(rc)) { + .SUCCESS => { + return JSC.Maybe(bun.FileDescriptor){ + .result = bun.toFD(fd), + }; + }, + 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 noinline fn openDirAtWindowsA( + dirFd: bun.FileDescriptor, + path: []const u8, + iterable: bool, + no_follow: bool, +) Maybe(bun.FileDescriptor) { + var wbuf: bun.MAX_WPATH = undefined; + return openDirAtWindows(dirFd, bun.strings.toWObjectDir(&wbuf, path), iterable, no_follow); +} pub fn openatWindows(dirfD: bun.FileDescriptor, path: []const u16, flags: bun.Mode) Maybe(bun.FileDescriptor) { const nonblock = flags & O.NONBLOCK != 0; @@ -368,6 +451,11 @@ pub fn openatWindows(dirfD: bun.FileDescriptor, path: []const u16, flags: bun.Mo null, 0, ); + + if (comptime Environment.allow_assert) { + log("NtCreateFile({d}, {}) = {d} (file)", .{ dirfD, bun.strings.fmtUTF16(path), rc }); + } + switch (windows.Win32Error.fromNTStatus(rc)) { .SUCCESS => { return JSC.Maybe(bun.FileDescriptor){ @@ -438,8 +526,12 @@ pub fn openatOSPath(dirfd: bun.FileDescriptor, file_path: bun.OSPathSlice, flags pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: bun.Mode, perm: bun.Mode) Maybe(bun.FileDescriptor) { if (comptime Environment.isWindows) { + if (flags & O.DIRECTORY != 0) { + return openDirAtWindowsA(dirfd, file_path, false, flags & O.NOFOLLOW != 0); + } + var wbuf: bun.MAX_WPATH = undefined; - return openatWindows(dirfd, bun.strings.toWPath(&wbuf, file_path), flags); + return openatWindows(dirfd, bun.strings.toWObjectPath(&wbuf, file_path), flags); } return openatOSPath(dirfd, file_path, flags, perm); @@ -447,11 +539,15 @@ pub fn openat(dirfd: bun.FileDescriptor, file_path: [:0]const u8, flags: bun.Mod pub fn openatA(dirfd: bun.FileDescriptor, file_path: []const u8, flags: bun.Mode, perm: bun.Mode) Maybe(bun.FileDescriptor) { if (comptime Environment.isWindows) { - var wbuf:bun.MAX_WPATH = undefined; - return openatWindows(dirfd, bun.strings.toWPathNormalized(&wbuf, file_path), flags); + if (flags & O.DIRECTORY != 0) { + return openDirAtWindowsA(dirfd, file_path, false, flags & O.NOFOLLOW != 0); + } + + var wbuf: bun.MAX_WPATH = undefined; + return openatWindows(dirfd, bun.strings.toWObjectPath(&wbuf, file_path), flags); } - return openatOSPath(dirfd, std.os.toPosixPath( file_path) catch return Maybe(bun.FileDescriptor){ + return openatOSPath(dirfd, std.os.toPosixPath(file_path) catch return Maybe(bun.FileDescriptor){ .err = .{ .errno = @intFromEnum(bun.C.E.NOMEM), .syscall = .open, @@ -464,7 +560,6 @@ pub fn openA(file_path: []const u8, flags: bun.Mode, perm: bun.Mode) Maybe(bun.F return openatA(bun.toFD((std.fs.cwd().fd)), file_path, flags, perm); } - pub fn open(file_path: [:0]const u8, flags: bun.Mode, perm: bun.Mode) Maybe(bun.FileDescriptor) { // this is what open() does anyway. return openat(bun.toFD((std.fs.cwd().fd)), file_path, flags, perm);