Skip to content

Commit

Permalink
allow linking bins that do not exist. (oven-sh#8605)
Browse files Browse the repository at this point in the history
  • Loading branch information
paperdave authored and ryoppippi committed Feb 1, 2024
1 parent 05e5029 commit c70e2ed
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 38 deletions.
57 changes: 29 additions & 28 deletions src/install/bin.zig
Original file line number Diff line number Diff line change
Expand Up @@ -392,40 +392,41 @@ pub const Bin = extern struct {
};
defer file.close();

const first_content_chunk = contents: {
const fd = bun.sys.openatWindows(
this.package_installed_node_modules,
if (link_global)
bun.strings.toWPathNormalized(
&filename3_buf,
target_path[this.relative_path_to_bin_for_windows_global_link_offset..],
)
else
target_wpath,
std.os.O.RDONLY,
).unwrap() catch |err| {
this.err = err;
return;
};
defer _ = bun.sys.close(fd);
const reader = fd.asFile().reader();
const read = reader.read(&read_in_buf) catch |err| {
this.err = err;
return;
const shebang = shebang: {
const first_content_chunk = contents: {
const fd = bun.sys.openatWindows(
this.package_installed_node_modules,
if (link_global)
bun.strings.toWPathNormalized(
&filename3_buf,
target_path[this.relative_path_to_bin_for_windows_global_link_offset..],
)
else
target_wpath,
std.os.O.RDONLY,
).unwrap() catch break :contents null;
defer _ = bun.sys.close(fd);
const reader = fd.asFile().reader();
const read = reader.read(&read_in_buf) catch break :contents null;
if (read == 0) {
break :contents null;
}
break :contents read_in_buf[0..read];
};
if (read == 0) {
this.err = error.FileNotFound;
return;

if (first_content_chunk) |chunk| {
break :shebang WinBinLinkingShim.Shebang.parse(chunk, target_wpath) catch {
this.err = error.InvalidBinContent;
return;
};
} else {
break :shebang WinBinLinkingShim.Shebang.parseFromBinPath(target_wpath);
}
break :contents read_in_buf[0..read];
};

const shim = WinBinLinkingShim{
.bin_path = target_wpath,
.shebang = WinBinLinkingShim.Shebang.parse(first_content_chunk, target_wpath) catch {
this.err = error.InvalidBinContent;
return;
},
.shebang = shebang,
};

const len = shim.encodedLength();
Expand Down
5 changes: 4 additions & 1 deletion src/install/windows-shim/BinLinkingShim.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ shebang: ?Shebang,
/// These arbitrary numbers will probably not show up in the other fields.
/// This will reveal off-by-one mistakes.
pub const VersionFlag = enum(u13) {
pub const current = .v2;
pub const current = .v3;

v1 = 5474,
// Fix bug where paths were not joined correctly
v2 = 5475,
// Added an error message for when the process is not found
v3 = 5476,
_,
};

Expand Down
Binary file modified src/install/windows-shim/bun_shim_impl.exe
Binary file not shown.
37 changes: 28 additions & 9 deletions src/install/windows-shim/bun_shim_impl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
//! Prior Art:
//! - https://github.com/ScoopInstaller/Shim/blob/master/src/shim.cs
//!
//! The compiled binary is 10240 bytes and is `@embedFile`d into Bun itself.
//! The compiled binary is 10752 bytes and is `@embedFile`d into Bun itself.
//! When this file is updated, the new binary should be compiled and BinLinkingShim.VersionFlag.current should be updated.
const std = @import("std");
const builtin = @import("builtin");
Expand Down Expand Up @@ -148,6 +148,9 @@ const FailReason = enum {
InvalidShimValidation,
InvalidShimBounds,
CouldNotDirectLaunch,
BinNotFound,
InterpreterNotFound,
ElevationRequired,

pub fn render(reason: FailReason) []const u8 {
return switch (reason) {
Expand All @@ -159,6 +162,12 @@ const FailReason = enum {
.InvalidShimDataSize => "bin metadata is corrupt (size)",
.InvalidShimValidation => "bin metadata is corrupt (validate)",
.InvalidShimBounds => "bin metadata is corrupt (bounds)",
// The difference between these two is that one is with a shebang (#!/usr/bin/env node) and
// the other is without. This is a helpful distinction because it can detect if something
// like node or bun is not in %path%, vs the actual executable was not installed in node_modules.
.InterpreterNotFound => "interpreter executable could not be found",
.BinNotFound => "bin executable does not exist on disk",
.ElevationRequired => "process requires elevation",
.CreateProcessFailed => "could not create process",

.CouldNotDirectLaunch => if (!is_standalone)
Expand Down Expand Up @@ -678,17 +687,27 @@ fn launcher(bun_ctx: anytype) noreturn {
&process,
);
if (did_process_spawn == 0) {
const spawn_err = k32.GetLastError();
if (dbg) {
const spawn_err = k32.GetLastError();
printError("CreateProcessW failed: {s}\n", .{@tagName(spawn_err)});
}
// TODO: ERROR_ELEVATION_REQUIRED must take a fallback path, this path is potentially slower:
// This likely will not be an issue anyone runs into for a while, because it implies
// the shebang depends on something that requires UAC, which .... why?
//
// https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/how-it-works#user
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
fail(.CreateProcessFailed);
switch (spawn_err) {
.FILE_NOT_FOUND => if (flags.has_shebang)
fail(.InterpreterNotFound)
else
fail(.BinNotFound),

// TODO: ERROR_ELEVATION_REQUIRED must take a fallback path, this path is potentially slower:
// This likely will not be an issue anyone runs into for a while, because it implies
// the shebang depends on something that requires UAC, which .... why?
//
// https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/how-it-works#user
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew
.ELEVATION_REQUIRED => fail(.ElevationRequired),

else => fail(.CreateProcessFailed),
}
comptime unreachable;
}

_ = k32.WaitForSingleObject(process.hProcess, w.INFINITE);
Expand Down

0 comments on commit c70e2ed

Please sign in to comment.