From cf1c849e4a2dbf0280247f9fc66c727a5f1b9530 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Mon, 29 Jan 2024 20:03:02 -0800 Subject: [PATCH] fix(windows): more reliable extracting (#8567) * finally * a * Update src/install/extract_tarball.zig Co-authored-by: Jarred Sumner * fix compilation --------- Co-authored-by: Jarred Sumner --- docs/project/building-windows.md | 1 + scripts/make-old-js.ps1 | 64 ++++++------- src/cli/create_command.zig | 30 ++++-- src/feature_flags.zig | 16 ---- src/install/extract_tarball.zig | 158 +++++++++++++++++++++++++------ src/install/install.zig | 18 ++-- src/libarchive/libarchive.zig | 79 ++++++++++------ src/windows.zig | 2 +- src/windows_c.zig | 7 +- 9 files changed, 248 insertions(+), 127 deletions(-) diff --git a/docs/project/building-windows.md b/docs/project/building-windows.md index 9de2eae8eb..9fe9208bb0 100644 --- a/docs/project/building-windows.md +++ b/docs/project/building-windows.md @@ -114,6 +114,7 @@ bun install # or npm install .\scripts\env.ps1 .\scripts\update-submodules.ps1 # this syncs git submodule state +.\scripts\make-old-js.ps1 # runs some old code generators .\scripts\all-dependencies.ps1 # this builds all dependencies cd build # this was created by the codegen.ps1 script earlier diff --git a/scripts/make-old-js.ps1 b/scripts/make-old-js.ps1 index f51122aa41..42cdf20c85 100644 --- a/scripts/make-old-js.ps1 +++ b/scripts/make-old-js.ps1 @@ -1,32 +1,32 @@ -$npm_client = "npm" - -# & ${npm_client} i - -$root = Join-Path (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) "..\" -$esbuild = Join-Path $root "node_modules\.bin\esbuild.cmd" - -$env:NODE_ENV = "production" - -# runtime.js -echo $esbuild -& ${esbuild} ` - "--target=esnext" "--bundle" ` - "src/runtime.bun.js" ` - "--format=esm" "--platform=node" "--minify" "--external:/bun:*" ` - "--outfile=src/runtime.out.js" -if ($LASTEXITCODE -ne 0) { throw "esbuild failed with exit code $LASTEXITCODE" } - -# fallback_decoder -& ${esbuild} --target=esnext --bundle src/fallback.ts --format=iife --platform=browser --minify > src/fallback.out.js - -# bun-error -Push-Location packages\bun-error -& ${npm_client} install -& ${npm_client} run build -Pop-Location - -# node-fallbacks -Push-Location src\node-fallbacks -& ${npm_client} install -& ${esbuild} --bundle @(Get-Item .\*.js) --outdir=out --format=esm --minify --platform=browser -Pop-Location +$npm_client = "npm" + +# & ${npm_client} i + +$root = Join-Path (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) "..\" +$esbuild = Join-Path $root "node_modules\.bin\esbuild.cmd" + +$env:NODE_ENV = "production" + +# runtime.js +echo $esbuild +& ${esbuild} ` + "--target=esnext" "--bundle" ` + "src/runtime.bun.js" ` + "--format=esm" "--platform=node" "--minify" "--external:/bun:*" ` + "--outfile=src/runtime.out.js" +if ($LASTEXITCODE -ne 0) { throw "esbuild failed with exit code $LASTEXITCODE" } + +# fallback_decoder +& ${esbuild} --target=esnext --bundle src/fallback.ts --format=iife --platform=browser --minify > src/fallback.out.js + +# bun-error +Push-Location packages\bun-error +& ${npm_client} install +& ${npm_client} run build +Pop-Location + +# node-fallbacks +Push-Location src\node-fallbacks +& ${npm_client} install +& ${esbuild} --bundle @(Get-Item .\*.js) --outdir=out --format=esm --minify --platform=browser +Pop-Location diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 99cfcbba52..f51ea93f73 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -2255,18 +2255,30 @@ const GitHandler = struct { ) !bool { const git_start = std.time.nanoTimestamp(); - // This feature flag is disabled. - // using libgit2 is slower than the CLI. - // [481.00ms] git - // [89.00ms] git - // if (comptime FeatureFlags.use_libgit2) { - // } + // Not sure why... + // But using libgit for this operation is slower than the CLI! + // Used to have a feature flag to try it but was removed: + // https://github.com/oven-sh/bun/commit/deafd3d0d42fb8d7ddf2b06cde2d7c7ee8bc7144 + // + // ~/Build/throw + // ❯ hyperfine "bun create react3 app --force --no-install" --prepare="rm -rf app" + // Benchmark #1: bun create react3 app --force --no-install + // Time (mean ± σ): 974.6 ms ± 6.8 ms [User: 170.5 ms, System: 798.3 ms] + // Range (min … max): 960.8 ms … 984.6 ms 10 runs + // + // ❯ mv /usr/local/opt/libgit2/lib/libgit2.dylib /usr/local/opt/libgit2/lib/libgit2.dylib.1 + // + // ~/Build/throw + // ❯ hyperfine "bun create react3 app --force --no-install" --prepare="rm -rf app" + // Benchmark #1: bun create react3 app --force --no-install + // Time (mean ± σ): 306.7 ms ± 6.1 ms [User: 31.7 ms, System: 269.8 ms] + // Range (min … max): 299.5 ms … 318.8 ms 10 runs if (which(&bun_path_buf, PATH, destination, "git")) |git| { const git_commands = .{ - &[_]string{ bun.asByteSlice(git), "init", "--quiet" }, - &[_]string{ bun.asByteSlice(git), "add", destination, "--ignore-errors" }, - &[_]string{ bun.asByteSlice(git), "commit", "-am", "Initial commit (via bun create)", "--quiet" }, + &[_]string{ git, "init", "--quiet" }, + &[_]string{ git, "add", destination, "--ignore-errors" }, + &[_]string{ git, "commit", "-am", "Initial commit (via bun create)", "--quiet" }, }; if (comptime verbose) { diff --git a/src/feature_flags.zig b/src/feature_flags.zig index d42ae89f68..c458909b52 100644 --- a/src/feature_flags.zig +++ b/src/feature_flags.zig @@ -69,22 +69,6 @@ pub const verbose_analytics = false; pub const disable_compression_in_http_client = false; pub const enable_keepalive = true; -// Not sure why... -// But this is slower! -// ~/Build/throw -// ❯ hyperfine "bun create react3 app --force --no-install" --prepare="rm -rf app" -// Benchmark #1: bun create react3 app --force --no-install -// Time (mean ± σ): 974.6 ms ± 6.8 ms [User: 170.5 ms, System: 798.3 ms] -// Range (min … max): 960.8 ms … 984.6 ms 10 runs - -// ❯ mv /usr/local/opt/libgit2/lib/libgit2.dylib /usr/local/opt/libgit2/lib/libgit2.dylib.1 - -// ~/Build/throw -// ❯ hyperfine "bun create react3 app --force --no-install" --prepare="rm -rf app" -// Benchmark #1: bun create react3 app --force --no-install -// Time (mean ± σ): 306.7 ms ± 6.1 ms [User: 31.7 ms, System: 269.8 ms] -// Range (min … max): 299.5 ms … 318.8 ms 10 runs -pub const use_libgit2 = true; pub const atomic_file_watcher = env.isLinux; diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig index 8f1931ebab..4c1bd8969e 100644 --- a/src/install/extract_tarball.zig +++ b/src/install/extract_tarball.zig @@ -17,6 +17,7 @@ const string = @import("../string_types.zig").string; const strings = @import("../string_immutable.zig"); const Path = @import("../resolver/resolve_path.zig"); const Environment = bun.Environment; +const w = std.os.windows; const ExtractTarball = @This(); @@ -157,7 +158,7 @@ threadlocal var folder_name_buf: bun.PathBuffer = undefined; threadlocal var json_path_buf: bun.PathBuffer = undefined; fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractData { - var tmpdir = this.temp_dir; + const tmpdir = this.temp_dir; var tmpname_buf: [bun.MAX_PATH_BYTES]u8 = undefined; const name = this.name.slice(); const basename = brk: { @@ -183,8 +184,21 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD var resolved: string = ""; const tmpname = try FileSystem.instance.tmpname(basename[0..@min(basename.len, 32)], &tmpname_buf, tgz_bytes.len); - { - var extract_destination = tmpdir.makeOpenPath(std.mem.span(tmpname), .{}) catch |err| { + const extract_fd_on_windows = brk: { + var extract_destination = switch (Environment.os) { + .windows => makeOpenPathAccessMaskW( + tmpdir, + std.mem.span(tmpname), + w.STANDARD_RIGHTS_READ | + w.FILE_READ_ATTRIBUTES | + w.FILE_READ_EA | + w.SYNCHRONIZE | + w.FILE_TRAVERSE | + w.DELETE, + false, + ), + else => tmpdir.makeOpenPath(std.mem.span(tmpname), .{}), + } catch |err| { this.package_manager.log.addErrorFmt( null, logger.Loc.Empty, @@ -195,7 +209,8 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD return error.InstallFailed; }; - defer extract_destination.close(); + errdefer if (Environment.isWindows) extract_destination.close(); + defer if (!Environment.isWindows) extract_destination.close(); if (PackageManager.verbose_install) { Output.prettyErrorln("[{s}] Start extracting {s}", .{ name, tmpname }); @@ -276,7 +291,11 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD Output.prettyErrorln("[{s}] Extracted", .{name}); Output.flush(); } - } + + if (Environment.isWindows) { + break :brk bun.toFD(extract_destination.fd); + } + }; const folder_name = switch (this.resolution.tag) { .npm => this.package_manager.cachedNPMPackageFolderNamePrint(&folder_name_buf, name, this.resolution.value.npm.version), .github => PackageManager.cachedGitHubFolderNamePrint(&folder_name_buf, resolved), @@ -295,33 +314,23 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD // Now that we've extracted the archive, we rename. if (comptime Environment.isWindows) { - // TODO(dylan-conway) make this less painful - var from_buf: bun.PathBuffer = undefined; - const tmpdir_path = try bun.getFdPath(tmpdir.fd, &from_buf); - const from_path = Path.joinAbsStringZ(tmpdir_path, &.{bun.sliceTo(tmpname, 0)}, .auto); + defer _ = bun.sys.close(extract_fd_on_windows); - var to_buf: bun.PathBuffer = undefined; - const cache_dir_path = try bun.getFdPath(cache_dir.fd, &to_buf); - const to_path = Path.joinAbsStringBufZ(cache_dir_path, &to_buf, &.{folder_name}, .auto); + var folder_name_wbuf: bun.WPathBuffer = undefined; + const folder_name_w = bun.strings.toWPathNormalized(&folder_name_wbuf, folder_name); - var from_path_buf_w: bun.WPathBuffer = undefined; - const from_path_w = bun.strings.toWPath(&from_path_buf_w, from_path); - var to_path_buf_w: bun.WPathBuffer = undefined; - const to_path_w = bun.strings.toWPath(&to_path_buf_w, to_path); - - if (bun.windows.MoveFileExW( - from_path_w, - to_path_w, - bun.windows.MOVEFILE_COPY_ALLOWED | bun.windows.MOVEFILE_REPLACE_EXISTING | bun.windows.MOVEFILE_WRITE_THROUGH, - ) == bun.windows.FALSE) { - this.package_manager.log.addErrorFmt( - null, - logger.Loc.Empty, - this.package_manager.allocator, - "moving \"{s}\" to cache dir failed: From: {s}\n To: {s}", - .{ name, tmpname, folder_name }, - ) catch unreachable; - return error.InstallFailed; + switch (bun.C.moveOpenedFileAtLoose(extract_fd_on_windows, bun.toFD(cache_dir.fd), folder_name_w, false)) { + .err => |err| { + this.package_manager.log.addErrorFmt( + null, + logger.Loc.Empty, + this.package_manager.allocator, + "moving \"{s}\" to cache dir failed: {}\n From: {s}\n To: {}", + .{ name, err, tmpname, std.unicode.fmtUtf16le(folder_name_w) }, + ) catch unreachable; + return error.InstallFailed; + }, + .result => {}, } } else { switch (bun.sys.renameat(bun.toFD(tmpdir.fd), bun.sliceTo(tmpname, 0), bun.toFD(cache_dir.fd), folder_name)) { @@ -432,3 +441,92 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !Install.ExtractD .json_len = json_len, }; } + +// TODO(@paperdave): upstream making this public into zig std +// there is zero reason this must be copied +// +/// Calls makeOpenDirAccessMaskW iteratively to make an entire path +/// (i.e. creating any parent directories that do not exist). +/// Opens the dir if the path already exists and is a directory. +/// This function is not atomic, and if it returns an error, the file system may +/// have been modified regardless. +fn makeOpenPathAccessMaskW(self: std.fs.Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) std.os.OpenError!std.fs.Dir { + var it = try std.fs.path.componentIterator(sub_path); + // If there are no components in the path, then create a dummy component with the full path. + var component = it.last() orelse std.fs.path.NativeUtf8ComponentIterator.Component{ + .name = "", + .path = sub_path, + }; + + while (true) { + const sub_path_w = try w.sliceToPrefixedFileW(self.fd, component.path); + const is_last = it.peekNext() == null; + var result = makeOpenDirAccessMaskW(self, sub_path_w.span().ptr, access_mask, .{ + .no_follow = no_follow, + .create_disposition = if (is_last) w.FILE_OPEN_IF else w.FILE_CREATE, + }) catch |err| switch (err) { + error.FileNotFound => |e| { + component = it.previous() orelse return e; + continue; + }, + else => |e| return e, + }; + + component = it.next() orelse return result; + // Don't leak the intermediate file handles + result.close(); + } +} +const MakeOpenDirAccessMaskWOptions = struct { + no_follow: bool, + create_disposition: u32, +}; + +fn makeOpenDirAccessMaskW(self: std.fs.Dir, sub_path_w: [*:0]const u16, access_mask: u32, flags: MakeOpenDirAccessMaskWOptions) std.os.OpenError!std.fs.Dir { + var result = std.fs.Dir{ + .fd = undefined, + }; + + const path_len_bytes = @as(u16, @intCast(std.mem.sliceTo(sub_path_w, 0).len * 2)); + var nt_name = w.UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w), + }; + var attr = w.OBJECT_ATTRIBUTES{ + .Length = @sizeOf(w.OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else self.fd, + .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 (flags.no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; + var io: w.IO_STATUS_BLOCK = undefined; + const rc = w.ntdll.NtCreateFile( + &result.fd, + access_mask, + &attr, + &io, + null, + w.FILE_ATTRIBUTE_NORMAL, + w.FILE_SHARE_READ | w.FILE_SHARE_WRITE, + flags.create_disposition, + w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point, + null, + 0, + ); + + switch (rc) { + .SUCCESS => return result, + .OBJECT_NAME_INVALID => return error.BadPathName, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NOT_A_DIRECTORY => return error.NotDir, + // This can happen if the directory has 'List folder contents' permission set to 'Deny' + // and the directory is trying to be opened for iteration. + .ACCESS_DENIED => return error.AccessDenied, + .INVALID_PARAMETER => return error.BadPathName, + else => return w.unexpectedStatus(rc), + } +} diff --git a/src/install/install.zig b/src/install/install.zig index 0aba136aee..e9d0292797 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -692,7 +692,9 @@ const Task = struct { ) catch |err| { if (comptime Environment.isDebug) { if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); + _ = trace; // autofix + + // std.debug.dumpStackTrace(trace.*); } } @@ -5086,7 +5088,7 @@ pub const PackageManager = struct { }, ); } else if (comptime log_level != .silent) { - const fmt = "error: {s} extracting tarball for {s}"; + const fmt = "error: {s} extracting tarball for {s}\n"; const args = .{ @errorName(err), alias, @@ -9362,14 +9364,11 @@ pub const PackageManager = struct { } } - switch (Output.enable_ansi_colors) { - inline else => |enable_ansi_colors| { - try manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), enable_ansi_colors); - }, - } - + try manager.log.printForLogLevel(Output.errorWriter()); if (manager.log.hasErrors()) Global.crash(); + manager.log.reset(); + // This operation doesn't perform any I/O, so it should be relatively cheap. manager.lockfile = try manager.lockfile.cleanWithLogger( manager.package_json_updates, @@ -9506,6 +9505,9 @@ pub const PackageManager = struct { ); } + try manager.log.printForLogLevel(Output.errorWriter()); + if (manager.log.hasErrors()) Global.crash(); + if (needs_new_lockfile) { manager.summary.add = @as(u32, @truncate(manager.lockfile.packages.len)); } diff --git a/src/libarchive/libarchive.zig b/src/libarchive/libarchive.zig index 9226c817e5..7f3bfeccee 100644 --- a/src/libarchive/libarchive.zig +++ b/src/libarchive/libarchive.zig @@ -471,7 +471,7 @@ pub const Archive = struct { pub fn extractToDir( file_buffer: []const u8, - dir_: std.fs.Dir, + dir: std.fs.Dir, ctx: ?*Archive.Context, comptime ContextType: type, appender: ContextType, @@ -487,10 +487,8 @@ pub const Archive = struct { _ = stream.openRead(); const archive = stream.archive; var count: u32 = 0; - const dir = dir_; const dir_fd = dir.fd; - const loop = if (Environment.isWindows) bun.Async.Loop.get() else {}; var w_path: if (Environment.isWindows) bun.WPathBuffer else void = undefined; loop: while (true) { @@ -564,7 +562,7 @@ pub const Archive = struct { Kind.sym_link => { const link_target = lib.archive_entry_symlink(entry).?; if (comptime Environment.isWindows) { - @panic("TODO on Windows"); + @panic("TODO on Windows: Extracting archives containing symbolic links."); } std.os.symlinkatZ(link_target, dir_fd, pathname) catch |err| brk: { switch (err) { @@ -580,36 +578,57 @@ pub const Archive = struct { }, Kind.file => { const mode: bun.Mode = if (comptime Environment.isWindows) 0 else @intCast(lib.archive_entry_perm(entry)); - const os_path = if (Environment.isWindows) bun.strings.toWPathNormalized(&w_path, pathname) else pathname; - const createFileOS = if (Environment.isWindows) std.fs.Dir.createFileW else std.fs.Dir.createFileZ; - const file = createFileOS(dir, os_path, .{ .truncate = true, .mode = mode }) catch |err| brk: { - switch (err) { - error.AccessDenied, error.FileNotFound => { - dir.makePath(std.fs.path.dirname(slice) orelse return err) catch {}; - break :brk try createFileOS(dir, os_path, .{ - .truncate = true, - .mode = mode, - }); - }, - else => { - return err; - }, + + const file_handle_native = brk: { + if (Environment.isWindows) { + const flags = std.os.O.WRONLY | std.os.O.CREAT | std.os.O.TRUNC; + const os_path = bun.strings.toWPathNormalized(&w_path, slice); + switch (bun.sys.openatWindows(bun.toFD(dir_fd), os_path, flags)) { + .result => |fd| break :brk fd, + .err => |e| switch (e.errno) { + @intFromEnum(bun.C.E.PERM), @intFromEnum(bun.C.E.NOENT) => { + dir.makePath(std.fs.path.dirname(slice) orelse return bun.errnoToZigErr(e.errno)) catch {}; + break :brk try bun.sys.openatWindows(bun.toFD(dir_fd), os_path, flags).unwrap(); + }, + else => { + return bun.errnoToZigErr(e.errno); + }, + }, + } + } else { + break :brk (dir.createFileZ(pathname, .{ .truncate = true, .mode = mode }) catch |err| { + switch (err) { + error.AccessDenied, error.FileNotFound => { + dir.makePath(std.fs.path.dirname(slice) orelse return err) catch {}; + break :brk (try dir.createFileZ(pathname, .{ + .truncate = true, + .mode = mode, + })).handle; + }, + else => { + return err; + }, + } + }).handle; } }; - const file_handle = bun.toLibUVOwnedFD(file.handle); + const file_handle = bun.toLibUVOwnedFD(file_handle_native); defer if (comptime close_handles) { - if (Environment.isWindows) { - // Using Async.Closer defers closing the file to a different thread. - // On windows, AV hangs these closes really badly. - // - // 'bun i @mui/icons-material' takes like 20 seconds to extract - // - // The install still takes a long time but this makes it a little bit better. - bun.Async.Closer.close(bun.uvfdcast(file_handle), loop); - } else { - _ = bun.sys.close(file_handle); - } + // On windows, AV hangs these closes really badly. + // 'bun i @mui/icons-material' takes like 20 seconds to extract + // mostly spend on waiting for things to close closing + // + // Using Async.Closer defers closing the file to a different thread, + // which can make the NtSetInformationFile call fail. + // + // Using async closing doesnt actually improve end user performance + // probably because our process is still waiting on AV to do it's thing. + // + // But this approach does not actually solve the problem, it just + // defers the close to a different thread. And since we are already + // on a worker thread, that doesn't help us. + _ = bun.sys.close(file_handle); }; const entry_size = @max(lib.archive_entry_size(entry), 0); diff --git a/src/windows.zig b/src/windows.zig index c74afad7c0..28fa600c4b 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -2938,7 +2938,7 @@ pub const Win32Error = enum(u16) { } pub fn toSystemErrno(this: Win32Error) ?SystemErrno { - return SystemErrno.init(this); + return SystemErrno.init(@intFromEnum(translateWinErrorToErrno(@enumFromInt(@intFromEnum(this))))); } pub fn fromNTStatus(status: win32.NTSTATUS) Win32Error { diff --git a/src/windows_c.zig b/src/windows_c.zig index 964451f719..dbac3b63bc 100644 --- a/src/windows_c.zig +++ b/src/windows_c.zig @@ -704,7 +704,7 @@ pub const SystemErrno = enum(u16) { if (comptime @TypeOf(code) == Win32Error) { return switch (code) { Win32Error.NOACCESS => SystemErrno.EACCES, - @as(Win32Error, @enumFromInt(10013)) => SystemErrno.EACCES, + Win32Error.WSAEACCES => SystemErrno.EACCES, Win32Error.ELEVATION_REQUIRED => SystemErrno.EACCES, Win32Error.CANT_ACCESS_FILE => SystemErrno.EACCES, Win32Error.ADDRESS_ALREADY_ASSOCIATED => SystemErrno.EADDRINUSE, @@ -1315,6 +1315,11 @@ pub fn moveOpenedFileAt( // and therefore having different behavior when the Windows version is >= rs1 but < rs5. comptime std.debug.assert(builtin.target.os.version_range.windows.min.isAtLeast(.win10_rs5)); + if (bun.Environment.allow_assert) { + std.debug.assert(std.mem.indexOfScalar(u16, new_file_name, '\\') == null); // Call moveOpenedFileAtLoose + std.debug.assert(std.mem.indexOfScalar(u16, new_file_name, '/') == null); // Call moveOpenedFileAtLoose + } + const struct_buf_len = @sizeOf(w.FILE_RENAME_INFORMATION_EX) + (bun.MAX_PATH_BYTES - 1); var rename_info_buf: [struct_buf_len]u8 align(@alignOf(w.FILE_RENAME_INFORMATION_EX)) = undefined;