mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
feat: Implement low-level os.zig abstractions
This commit is contained in:
@@ -1260,6 +1260,7 @@ endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(${bun} PRIVATE
|
||||
advapi32
|
||||
winmm
|
||||
bcrypt
|
||||
ntdll
|
||||
@@ -1308,32 +1309,32 @@ if(NOT BUN_CPP_ONLY)
|
||||
OUTPUTS
|
||||
${BUILD_PATH}/${bunStripExe}
|
||||
)
|
||||
|
||||
|
||||
# Then sign both executables on Windows
|
||||
if(WIN32 AND ENABLE_WINDOWS_CODESIGNING)
|
||||
set(SIGN_SCRIPT "${CMAKE_SOURCE_DIR}/.buildkite/scripts/sign-windows.ps1")
|
||||
|
||||
|
||||
# Verify signing script exists
|
||||
if(NOT EXISTS "${SIGN_SCRIPT}")
|
||||
message(FATAL_ERROR "Windows signing script not found: ${SIGN_SCRIPT}")
|
||||
endif()
|
||||
|
||||
|
||||
# Use PowerShell for Windows code signing (native Windows, no path issues)
|
||||
find_program(POWERSHELL_EXECUTABLE
|
||||
find_program(POWERSHELL_EXECUTABLE
|
||||
NAMES pwsh.exe powershell.exe
|
||||
PATHS
|
||||
PATHS
|
||||
"C:/Program Files/PowerShell/7"
|
||||
"C:/Program Files (x86)/PowerShell/7"
|
||||
"C:/Windows/System32/WindowsPowerShell/v1.0"
|
||||
DOC "Path to PowerShell executable"
|
||||
)
|
||||
|
||||
|
||||
if(NOT POWERSHELL_EXECUTABLE)
|
||||
set(POWERSHELL_EXECUTABLE "powershell.exe")
|
||||
endif()
|
||||
|
||||
|
||||
message(STATUS "Using PowerShell executable: ${POWERSHELL_EXECUTABLE}")
|
||||
|
||||
|
||||
# Sign both bun-profile.exe and bun.exe after stripping
|
||||
register_command(
|
||||
TARGET
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const test_skipped = [
|
||||
"uv_getrusage_thread",
|
||||
"uv_os_homedir",
|
||||
"uv_thread_detach",
|
||||
"uv_thread_getname",
|
||||
"uv_thread_getpriority",
|
||||
|
||||
@@ -1129,10 +1129,10 @@ UV_EXTERN int uv_os_getpriority(uv_pid_t pid, int* priority)
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
extern int bunuv__os_homedir(char* buffer, size_t* size);
|
||||
UV_EXTERN int uv_os_homedir(char* buffer, size_t* size)
|
||||
{
|
||||
__bun_throw_not_implemented("uv_os_homedir");
|
||||
__builtin_unreachable();
|
||||
return bunuv__os_homedir(buffer, size);
|
||||
}
|
||||
|
||||
UV_EXTERN int uv_os_setenv(const char* name, const char* value)
|
||||
|
||||
@@ -152,6 +152,27 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Grab a const pointer to the value if it exists.
|
||||
pub inline fn asValueConstPtr(this: *const @This()) ?*const ReturnType {
|
||||
if (comptime ReturnType == void)
|
||||
@compileError("Maybe.asValueConstPtr is invalid for ReturnType == void");
|
||||
return switch (this.*) {
|
||||
.result => |*r| r,
|
||||
.err => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Grab a mutable pointer to the value if it exists.
|
||||
pub inline fn asValuePtr(this: *@This()) ?*ReturnType {
|
||||
if (comptime ReturnType == void)
|
||||
@compileError("Maybe.asValuePtr is invalid for ReturnType == void");
|
||||
|
||||
return switch (this.*) {
|
||||
.result => |*r| r,
|
||||
.err => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn isOk(this: *const @This()) bool {
|
||||
return switch (this.*) {
|
||||
.result => true,
|
||||
|
||||
@@ -302,80 +302,15 @@ pub fn getPriority(global: *jsc.JSGlobalObject, pid: i32) bun.JSError!i32 {
|
||||
}
|
||||
|
||||
pub fn homedir(global: *jsc.JSGlobalObject) !bun.String {
|
||||
// In Node.js, this is a wrapper around uv_os_homedir.
|
||||
if (Environment.isWindows) {
|
||||
var out: bun.PathBuffer = undefined;
|
||||
var size: usize = out.len;
|
||||
if (libuv.uv_os_homedir(&out, &size).toError(.uv_os_homedir)) |err| {
|
||||
var home_res = bun.os.HomeDir.query(bun.default_allocator);
|
||||
switch (home_res) {
|
||||
.result => |*r| {
|
||||
defer r.deinit();
|
||||
return bun.String.cloneUTF8(r.slice());
|
||||
},
|
||||
.err => |err| {
|
||||
return global.throwValue(err.toJS(global));
|
||||
}
|
||||
return bun.String.cloneUTF8(out[0..size]);
|
||||
} else {
|
||||
|
||||
// The posix implementation of uv_os_homedir first checks the HOME
|
||||
// environment variable, then falls back to reading the passwd entry.
|
||||
if (bun.getenvZ("HOME")) |home| {
|
||||
if (home.len > 0)
|
||||
return bun.String.init(home);
|
||||
}
|
||||
|
||||
// From libuv:
|
||||
// > Calling sysconf(_SC_GETPW_R_SIZE_MAX) would get the suggested size, but it
|
||||
// > is frequently 1024 or 4096, so we can just use that directly. The pwent
|
||||
// > will not usually be large.
|
||||
// Instead of always using an allocation, first try a stack allocation
|
||||
// of 4096, then fallback to heap.
|
||||
var stack_string_bytes: [4096]u8 = undefined;
|
||||
var string_bytes: []u8 = &stack_string_bytes;
|
||||
defer if (string_bytes.ptr != &stack_string_bytes)
|
||||
bun.default_allocator.free(string_bytes);
|
||||
|
||||
var pw: bun.c.passwd = undefined;
|
||||
var result: ?*bun.c.passwd = null;
|
||||
|
||||
const ret = while (true) {
|
||||
const ret = bun.c.getpwuid_r(
|
||||
bun.c.geteuid(),
|
||||
&pw,
|
||||
string_bytes.ptr,
|
||||
string_bytes.len,
|
||||
&result,
|
||||
);
|
||||
|
||||
if (ret == @intFromEnum(bun.sys.E.INTR))
|
||||
continue;
|
||||
|
||||
// If the system call wants more memory, double it.
|
||||
if (ret == @intFromEnum(bun.sys.E.RANGE)) {
|
||||
const len = string_bytes.len;
|
||||
bun.default_allocator.free(string_bytes);
|
||||
string_bytes = "";
|
||||
string_bytes = try bun.default_allocator.alloc(u8, len * 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
break ret;
|
||||
};
|
||||
|
||||
if (ret != 0) {
|
||||
return global.throwValue(bun.sys.Error.fromCode(
|
||||
@enumFromInt(ret),
|
||||
.uv_os_homedir,
|
||||
).toJS(global));
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
// in uv__getpwuid_r, null result throws UV_ENOENT.
|
||||
return global.throwValue(bun.sys.Error.fromCode(
|
||||
.NOENT,
|
||||
.uv_os_homedir,
|
||||
).toJS(global));
|
||||
}
|
||||
|
||||
return if (pw.pw_dir) |dir|
|
||||
bun.String.cloneUTF8(bun.span(dir))
|
||||
else
|
||||
bun.String.empty;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3798,6 +3798,8 @@ pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFra
|
||||
return jsc.JSValue.jsBoolean(Arguments.Bun__Node__UseSystemCA);
|
||||
}
|
||||
|
||||
pub const os = @import("./os.zig");
|
||||
|
||||
const CopyFile = @import("./copy_file.zig");
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
#include "../packages/bun-native-bundler-plugin-api/bundler_plugin.h"
|
||||
|
||||
#if POSIX
|
||||
#include <errno.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <limits.h>
|
||||
#include <netdb.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -25,8 +25,10 @@ pub const InstallCompletionsCommand = struct {
|
||||
|
||||
// if that fails, try $HOME/.bun/bin
|
||||
outer: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
target = std.fmt.bufPrint(&target_buf, "{s}/.bun/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
if (maybe_home_dir.asValuePtr()) |home_dir| {
|
||||
defer home_dir.deinit();
|
||||
target = std.fmt.bufPrint(&target_buf, "{s}/.bun/bin/" ++ bunx_name, .{home_dir.slice()}) catch unreachable;
|
||||
std.posix.symlink(exe, target) catch break :outer;
|
||||
return;
|
||||
}
|
||||
@@ -34,8 +36,10 @@ pub const InstallCompletionsCommand = struct {
|
||||
|
||||
// if that fails, try $HOME/.local/bin
|
||||
outer: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
target = std.fmt.bufPrint(&target_buf, "{s}/.local/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
if (maybe_home_dir.asValuePtr()) |home_dir| {
|
||||
defer home_dir.deinit();
|
||||
target = std.fmt.bufPrint(&target_buf, "{s}/.local/bin/" ++ bunx_name, .{home_dir.slice()}) catch unreachable;
|
||||
std.posix.symlink(exe, target) catch break :outer;
|
||||
return;
|
||||
}
|
||||
@@ -229,9 +233,11 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
if (maybe_home_dir.asValuePtr()) |home_dir| {
|
||||
defer home_dir.deinit();
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.config/fish/completions" };
|
||||
var paths = [_]string{ home_dir.slice(), "./.config/fish/completions" };
|
||||
completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
|
||||
break :found std.fs.openDirAbsolute(completions_dir, .{}) catch
|
||||
break :outer;
|
||||
@@ -287,10 +293,12 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
if (maybe_home_dir.asValuePtr()) |home_dir| {
|
||||
defer home_dir.deinit();
|
||||
{
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.oh-my-zsh/completions" };
|
||||
var paths = [_]string{ home_dir.slice(), "./.oh-my-zsh/completions" };
|
||||
completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
|
||||
break :found std.fs.openDirAbsolute(completions_dir, .{}) catch
|
||||
break :outer;
|
||||
@@ -299,7 +307,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
|
||||
{
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.bun" };
|
||||
var paths = [_]string{ home_dir.slice(), "./.bun" };
|
||||
completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
|
||||
break :found std.fs.openDirAbsolute(completions_dir, .{}) catch
|
||||
break :outer;
|
||||
@@ -339,10 +347,12 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
if (maybe_home_dir.asValuePtr()) |home_dir| {
|
||||
defer home_dir.deinit();
|
||||
{
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.oh-my-bash/custom/completions" };
|
||||
var paths = [_]string{ home_dir.slice(), "./.oh-my-bash/custom/completions" };
|
||||
completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
|
||||
|
||||
break :found std.fs.openDirAbsolute(completions_dir, .{}) catch
|
||||
@@ -351,7 +361,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
{
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.bash_completion.d" };
|
||||
var paths = [_]string{ home_dir.slice(), "./.bash_completion.d" };
|
||||
completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
|
||||
|
||||
break :found std.fs.openDirAbsolute(completions_dir, .{}) catch
|
||||
@@ -449,21 +459,25 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
|
||||
second: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |zdot_dir| {
|
||||
bun.copy(u8, &zshrc_filepath, zdot_dir);
|
||||
bun.copy(u8, zshrc_filepath[zdot_dir.len..], "/.zshrc");
|
||||
zshrc_filepath[zdot_dir.len + "/.zshrc".len] = 0;
|
||||
const filepath = zshrc_filepath[0 .. zdot_dir.len + "/.zshrc".len :0];
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
if (maybe_home_dir.asValuePtr()) |zdot_dir| {
|
||||
defer zdot_dir.deinit();
|
||||
bun.copy(u8, &zshrc_filepath, zdot_dir.slice());
|
||||
bun.copy(u8, zshrc_filepath[zdot_dir.slice().len..], "/.zshrc");
|
||||
zshrc_filepath[zdot_dir.slice().len + "/.zshrc".len] = 0;
|
||||
const filepath = zshrc_filepath[0 .. zdot_dir.slice().len + "/.zshrc".len :0];
|
||||
break :zshrc std.fs.openFileAbsoluteZ(filepath, .{ .mode = .read_write }) catch break :second;
|
||||
}
|
||||
}
|
||||
|
||||
third: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |zdot_dir| {
|
||||
bun.copy(u8, &zshrc_filepath, zdot_dir);
|
||||
bun.copy(u8, zshrc_filepath[zdot_dir.len..], "/.zshenv");
|
||||
zshrc_filepath[zdot_dir.len + "/.zshenv".len] = 0;
|
||||
const filepath = zshrc_filepath[0 .. zdot_dir.len + "/.zshenv".len :0];
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
if (maybe_home_dir.asValuePtr()) |zdot_dir| {
|
||||
defer zdot_dir.deinit();
|
||||
bun.copy(u8, &zshrc_filepath, zdot_dir.slice());
|
||||
bun.copy(u8, zshrc_filepath[zdot_dir.slice().len..], "/.zshenv");
|
||||
zshrc_filepath[zdot_dir.slice().len + "/.zshenv".len] = 0;
|
||||
const filepath = zshrc_filepath[0 .. zdot_dir.slice().len + "/.zshenv".len :0];
|
||||
break :zshrc std.fs.openFileAbsoluteZ(filepath, .{ .mode = .read_write }) catch break :third;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ pub const UpgradeCommand = struct {
|
||||
save_dir.deleteFileZ(tmpname) catch {};
|
||||
Global.exit(1);
|
||||
}
|
||||
} else if (Environment.isWindows) {
|
||||
} else if (comptime Environment.isWindows) {
|
||||
// Run a powershell script to unzip the file
|
||||
const unzip_script = try std.fmt.allocPrint(
|
||||
ctx.allocator,
|
||||
@@ -572,7 +572,15 @@ pub const UpgradeCommand = struct {
|
||||
const powershell_path =
|
||||
bun.which(&buf, bun.getenvZ("PATH") orelse "", "", "powershell") orelse
|
||||
hardcoded_system_powershell: {
|
||||
const system_root = bun.getenvZ("SystemRoot") orelse "C:\\Windows";
|
||||
var windir = switch (bun.os.win32.WinDir.query(ctx.allocator)) {
|
||||
.err => {
|
||||
Output.prettyErrorln("<r><red>error:<r> Failed to unzip {s} due to PowerShell not being installed.", .{tmpname});
|
||||
Global.exit(1);
|
||||
},
|
||||
.result => |v| v,
|
||||
};
|
||||
defer windir.deinit();
|
||||
const system_root = windir.slice();
|
||||
const hardcoded_system_powershell = bun.path.joinAbsStringBuf(system_root, &buf, &.{ system_root, "System32\\WindowsPowerShell\\v1.0\\powershell.exe" }, .windows);
|
||||
if (bun.sys.exists(hardcoded_system_powershell)) {
|
||||
break :hardcoded_system_powershell hardcoded_system_powershell;
|
||||
|
||||
47
src/env.zig
47
src/env.zig
@@ -172,6 +172,53 @@ else if (isAarch64)
|
||||
else
|
||||
@compileError("Please add your architecture to the Architecture enum");
|
||||
|
||||
pub const OsTypeSet = struct {
|
||||
posix: ?type = null,
|
||||
win: ?type = null,
|
||||
macos: ?type = null,
|
||||
linux: ?type = null,
|
||||
};
|
||||
|
||||
/// Given a type set, which contains possible types for different platforms, selects the correct
|
||||
/// type for the current platform.
|
||||
pub fn OsTypeSelect(comptime opts: OsTypeSet) type {
|
||||
const err_fmt = "You must specify a type for {} when compiling for {}";
|
||||
|
||||
switch (comptime bun.Environment.os) {
|
||||
.windows => {
|
||||
if (comptime opts.win) |w| {
|
||||
return w;
|
||||
}
|
||||
@compileError(std.fmt.comptimePrint(err_fmt, .{ "win", "Windows" }));
|
||||
},
|
||||
.mac => {
|
||||
if (comptime opts.macos) |m| {
|
||||
return m;
|
||||
}
|
||||
|
||||
if (comptime opts.posix) |p| {
|
||||
return p;
|
||||
}
|
||||
|
||||
@compileError(std.fmt.comptimePrint(err_fmt, .{ "macos", "macOS" }));
|
||||
},
|
||||
.linux => {
|
||||
if (comptime opts.linux) |l| {
|
||||
return l;
|
||||
}
|
||||
|
||||
if (comptime opts.posix) |p| {
|
||||
return p;
|
||||
}
|
||||
|
||||
@compileError(std.fmt.comptimePrint(err_fmt, .{ "linux", "Linux" }));
|
||||
},
|
||||
.wasm => {
|
||||
@compileError("OsTypeSelect is not supported for wasm targets. Please add support.");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
68
src/fs.zig
68
src/fs.zig
@@ -530,43 +530,47 @@ pub const FileSystem = struct {
|
||||
file_limit: usize = 32,
|
||||
file_quota: usize = 32,
|
||||
|
||||
pub var win_tempdir_cache: ?[]const u8 = undefined;
|
||||
pub var sys_tempdir_cache: ?bun.os.SysTmpDir = null;
|
||||
var sys_tempdir_mutex: Mutex = .{};
|
||||
|
||||
fn cleanupSysTempdirCache() callconv(.C) void {
|
||||
if (sys_tempdir_cache) |*c| {
|
||||
c.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn platformTempDir() []const u8 {
|
||||
return switch (Environment.os) {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw#remarks
|
||||
.windows => win_tempdir_cache orelse {
|
||||
const value = bun.getenvZ("TEMP") orelse bun.getenvZ("TMP") orelse brk: {
|
||||
if (bun.getenvZ("SystemRoot") orelse bun.getenvZ("windir")) |windir| {
|
||||
break :brk std.fmt.allocPrint(
|
||||
bun.default_allocator,
|
||||
"{s}\\Temp",
|
||||
.{strings.withoutTrailingSlash(windir)},
|
||||
) catch |err| bun.handleOom(err);
|
||||
}
|
||||
if (sys_tempdir_cache) |w| {
|
||||
return w.slice();
|
||||
}
|
||||
|
||||
if (bun.getenvZ("USERPROFILE")) |profile| {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var parts = [_]string{"AppData\\Local\\Temp"};
|
||||
const out = bun.path.joinAbsStringBuf(profile, &buf, &parts, .loose);
|
||||
break :brk bun.handleOom(bun.default_allocator.dupe(u8, out));
|
||||
}
|
||||
var temp_dir = bun.os.SysTmpDir.query(bun.default_allocator);
|
||||
switch (temp_dir) {
|
||||
.result => |*s| {
|
||||
sys_tempdir_mutex.lock();
|
||||
defer sys_tempdir_mutex.unlock();
|
||||
// After acquiring the mutex, we need to check sys_tempdir_cache again. There
|
||||
// is a race condition -- another thread may have acquired the lock just prior
|
||||
// to this thread but this thread will enter the current scope since it there
|
||||
// is a race condition on sys_temp_dir_cache. Consequently, this thread has to
|
||||
// check sys_tempdir_cache again.
|
||||
if (sys_tempdir_cache) |w| {
|
||||
return w.slice();
|
||||
}
|
||||
|
||||
var tmp_buf: bun.PathBuffer = undefined;
|
||||
const cwd = std.posix.getcwd(&tmp_buf) catch @panic("Failed to get cwd for platformTempDir");
|
||||
const root = bun.path.windowsFilesystemRoot(cwd);
|
||||
break :brk std.fmt.allocPrint(
|
||||
bun.default_allocator,
|
||||
"{s}\\Windows\\Temp",
|
||||
.{strings.withoutTrailingSlash(root)},
|
||||
) catch |err| bun.handleOom(err);
|
||||
};
|
||||
win_tempdir_cache = value;
|
||||
return value;
|
||||
defer s.deinit();
|
||||
|
||||
sys_tempdir_cache = s.*;
|
||||
// TODO(markovejnovic): Using atexit for cleanup is pretty bad practice.
|
||||
// Ideally we would have a bun mechanism for cleanup that could register within
|
||||
// __fini, but my PR is not responsible to that right now.
|
||||
_ = bun.c.atexit(cleanupSysTempdirCache);
|
||||
return sys_tempdir_cache.?.slice();
|
||||
},
|
||||
.mac => "/private/tmp",
|
||||
else => "/tmp",
|
||||
};
|
||||
.err => |e| {
|
||||
bun.Output.panic("Could not retrieve the system temporary directory: {}", .{e});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const Tmpfile = switch (Environment.os) {
|
||||
|
||||
@@ -791,7 +791,11 @@ pub fn init(
|
||||
try env.load(entries_option.entries, &[_][]u8{}, .production, false);
|
||||
|
||||
initializeStore();
|
||||
if (bun.getenvZ("XDG_CONFIG_HOME") orelse bun.getenvZ(bun.DotEnv.home_env)) |data_dir| {
|
||||
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
defer if (maybe_home_dir.asValuePtr()) |h| h.deinit();
|
||||
const home_dir = if (maybe_home_dir.asValuePtr()) |h| h.slice() else null;
|
||||
if (bun.getenvZ("XDG_CONFIG_HOME") orelse home_dir) |data_dir| {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var parts = [_]string{
|
||||
"./.npmrc",
|
||||
|
||||
@@ -224,7 +224,10 @@ pub fn openGlobalBinDir(opts_: ?*const Api.BunInstall) !std.fs.Dir {
|
||||
return try std.fs.cwd().makeOpenPath(path, .{});
|
||||
}
|
||||
|
||||
if (bun.getenvZ("XDG_CACHE_HOME") orelse bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
var maybe_home_dir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
defer if (maybe_home_dir.asValuePtr()) |h| h.deinit();
|
||||
const hdir = if (maybe_home_dir.asValuePtr()) |h| h.slice() else null;
|
||||
if (bun.getenvZ("XDG_CACHE_HOME") orelse hdir) |home_dir| {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var parts = [_]string{
|
||||
".bun",
|
||||
|
||||
@@ -16,21 +16,11 @@ const SloppyGlobalGitConfig = struct {
|
||||
}
|
||||
|
||||
pub fn loadAndParse() void {
|
||||
const home_dir_path = brk: {
|
||||
if (comptime Environment.isWindows) {
|
||||
if (bun.getenvZ("USERPROFILE")) |env|
|
||||
break :brk env;
|
||||
} else {
|
||||
if (bun.getenvZ("HOME")) |env|
|
||||
break :brk env;
|
||||
}
|
||||
|
||||
// won't find anything
|
||||
return;
|
||||
};
|
||||
var home_dir = (bun.os.HomeDir.query(bun.default_allocator)).unwrap() catch return;
|
||||
defer home_dir.deinit();
|
||||
|
||||
var config_file_path_buf: bun.PathBuffer = undefined;
|
||||
const config_file_path = bun.path.joinAbsStringBufZ(home_dir_path, &config_file_path_buf, &.{".gitconfig"}, .auto);
|
||||
const config_file_path = bun.path.joinAbsStringBufZ(home_dir.slice(), &config_file_path_buf, &.{".gitconfig"}, .auto);
|
||||
var stack_fallback = std.heap.stackFallback(4096, bun.default_allocator);
|
||||
const allocator = stack_fallback.get();
|
||||
const source = File.toSource(config_file_path, allocator, .{ .convert_bom = true }).unwrap() catch {
|
||||
|
||||
@@ -321,7 +321,7 @@ BUN_1.2 {
|
||||
uv_write2;
|
||||
uv_wtf8_length_as_utf16;
|
||||
uv_wtf8_to_utf16;
|
||||
|
||||
|
||||
|
||||
extern "C++" {
|
||||
v8::*;
|
||||
|
||||
66
src/os.zig
Normal file
66
src/os.zig
Normal file
@@ -0,0 +1,66 @@
|
||||
//! OS-level functionality.
|
||||
//! Designed to be slightly higher level than sys.zig
|
||||
|
||||
/// Platform-agnostic utility for fetching and interacting with the current system home directory.
|
||||
///
|
||||
/// Implements the following concept:
|
||||
/// ```
|
||||
/// fn HomeDirConcept(T type) concept {
|
||||
/// fn deinit(self: *T) void;
|
||||
/// /// Deduces the current user's home directory.
|
||||
/// ///
|
||||
/// /// Has multiple strategies depending on the platform and environment. Mostly compliant with
|
||||
/// /// the POSIX standard for POSIX systems.
|
||||
/// ///
|
||||
/// /// Tries to be clever on Windows, but no compliance guarantees are made.
|
||||
/// ///
|
||||
/// /// There is actually a reasonable desire to be able to query other users' home directories.
|
||||
/// /// However, we don't have a need to do that at this moment. See
|
||||
/// /// doi:10.1109/IEEESTD.2018.8277153 2.6.1 for further details.
|
||||
/// ///
|
||||
/// /// Very close to how uv_os_homedir works in libuv.
|
||||
/// fn query(allocator: std.mem.Allocator) bun.sys.Maybe(T);
|
||||
/// fn slice(self: *const T) []const u8;
|
||||
/// }
|
||||
/// ```
|
||||
pub const HomeDir = bun.Environment.OsTypeSelect(.{
|
||||
.posix = posix.HomeDir,
|
||||
.win = win32.UserProfile,
|
||||
});
|
||||
|
||||
/// Interact with the current system temporary directory.
|
||||
///
|
||||
/// The system temporary directory is a directory equivalent to `/tmp` on POSIX systems. Note that
|
||||
/// this is not ALWAYS /tmp, as `$TMPDIR` and other mechanisms MAY allow overriding this. Treat
|
||||
/// this as a black box for maximum compatibility.
|
||||
///
|
||||
/// Note: Bun also allows users to explicitly specify the temporary directory bun should use, so
|
||||
/// this function alone does not tell you which temporary directory you should commonly use.
|
||||
///
|
||||
/// Implements the following concept:
|
||||
/// ```
|
||||
/// fn SysTempDirConcept(T type) concept {
|
||||
/// fn deinit(self: *T) void;
|
||||
/// fn query(allocator: std.mem.Allocator) bun.sys.Maybe(T);
|
||||
/// fn slice(self: *const T) []const u8;
|
||||
/// }
|
||||
/// ```
|
||||
pub const SysTmpDir = bun.Environment.OsTypeSelect(.{
|
||||
.posix = posix.SysTmpDir,
|
||||
.win = win32.TempDir,
|
||||
});
|
||||
|
||||
pub const win32 = @import("./os/win32.zig");
|
||||
pub const posix = @import("./os/posix.zig");
|
||||
|
||||
comptime {
|
||||
// TODO(markovejnovic): This probably shouldn't exist in src/os, but rather in another spot.
|
||||
if (bun.Environment.isPosix) {
|
||||
// uvinterop only supports POSIX at the time of writing. No need to pollute the Windows
|
||||
// binary with it.
|
||||
_ = @import("./os/uvinterop.zig"); // Necessary to link into the binary.
|
||||
}
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
249
src/os/posix.zig
Normal file
249
src/os/posix.zig
Normal file
@@ -0,0 +1,249 @@
|
||||
/// Opaque type representing the user ID.
|
||||
pub const Uid = if (bun.Environment.isPosix) struct {
|
||||
const Self = @This();
|
||||
|
||||
_underlying: std.c.uid_t,
|
||||
|
||||
pub fn queryEffective() Self {
|
||||
// TODO(markovejnovic): Unfortunately, geteuid is not available in Zig's stdlib at the
|
||||
// moment, so we need to use @cImport above.
|
||||
return .{ ._underlying = bun.c.geteuid() };
|
||||
}
|
||||
|
||||
pub fn queryReal() Self {
|
||||
// TODO(markovejnovic): Unfortunately, getuid is not available in Zig's stdlib at the
|
||||
// moment, so we need to use @cImport above.
|
||||
return .{ ._underlying = bun.c.getuid() };
|
||||
}
|
||||
|
||||
/// Falls back to queryEffective.
|
||||
pub fn queryCurrent() Self {
|
||||
return queryEffective();
|
||||
}
|
||||
|
||||
fn init(underlying: std.c.uid_t) Self {
|
||||
return .{ ._underlying = underlying };
|
||||
}
|
||||
} else struct {};
|
||||
|
||||
/// Fully managed representation of a passwd entry.
|
||||
pub const PasswdEntry = if (bun.Environment.isPosix) struct {
|
||||
const Self = @This();
|
||||
|
||||
var _pwbuf_size: std.atomic.Value(usize) = .init(0);
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
buffer: []u8,
|
||||
entry: bun.c.struct_passwd,
|
||||
entry_ptr: *bun.c.struct_passwd,
|
||||
|
||||
#memo: struct {
|
||||
pw_dir_len: ?usize = null,
|
||||
} = .{},
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.allocator.free(self.buffer);
|
||||
}
|
||||
|
||||
pub fn pwNameZ(self: *Self) ?[*:0]u8 {
|
||||
return self.entry_ptr.pw_name;
|
||||
}
|
||||
|
||||
pub fn pwUidC(self: *const Self) std.c.uid_t {
|
||||
return self.entry_ptr.pw_uid;
|
||||
}
|
||||
|
||||
pub fn pwUid(self: *const Self) Uid {
|
||||
return .init(self.pwUidC());
|
||||
}
|
||||
|
||||
pub fn pwGidC(self: *Self) std.c.gid_t {
|
||||
return self.entry_ptr.pw_gid;
|
||||
}
|
||||
|
||||
pub fn pwDir(self: *Self) ?[]u8 {
|
||||
self.#memo.pw_dir_len = std.mem.len(self.entry_ptr.pw_dir);
|
||||
return self.entry_ptr.pw_dir[0..self.#memo.pw_dir_len.?];
|
||||
}
|
||||
|
||||
pub fn pwDirZ(self: *Self) ?[*:0]u8 {
|
||||
return self.entry_ptr.pw_dir;
|
||||
}
|
||||
|
||||
pub fn pwShellZ(self: *Self) ?[*:0]u8 {
|
||||
return self.entry_ptr.pw_shell;
|
||||
}
|
||||
|
||||
/// Attempt to retrieve the result of getpwuid_r.
|
||||
///
|
||||
/// May spinlock for an extremely brief period of time as it allocates memory.
|
||||
pub fn init(user: Uid, alloc: std.mem.Allocator) bun.sys.Maybe(Self) {
|
||||
// Every iteration we will increase the buffer size by this factor.
|
||||
const buf_size_gain = 2;
|
||||
|
||||
// Claude said that _SC_GETPW_R_SIZE_MAX is by default 1024 on Linux. I don't trust Claude
|
||||
// with anything so we're going 4X that.
|
||||
const default_buf_size: usize = 4096;
|
||||
|
||||
// The maximum total number of attempts we will have at reading getpwuid_r before giving
|
||||
// up. There are a few cases which benefit from re-attempting a read.
|
||||
const max_buf_size: comptime_int = 1 * 1024 * 1024; // If we need to load a 1MB buffer,
|
||||
// something is very wrong.
|
||||
|
||||
var buffer_size: usize = Self._pwbuf_size.load(.monotonic);
|
||||
if (buffer_size == 0) {
|
||||
// We know that we haven't initialized it yet. Let's try to do so now.
|
||||
const initial_buf_size: usize =
|
||||
@intCast(bun.sys.sysconf(bun.c._SC_GETPW_R_SIZE_MAX, .{
|
||||
.default = default_buf_size,
|
||||
}));
|
||||
|
||||
buffer_size = initial_buf_size;
|
||||
}
|
||||
|
||||
var result: ?*bun.c.struct_passwd = undefined;
|
||||
var self: Self = .{
|
||||
.allocator = alloc,
|
||||
.buffer = alloc.alloc(u8, buffer_size) catch {
|
||||
return .initErr(.fromCode(.NOMEM, .getpwuid_r));
|
||||
},
|
||||
.entry = undefined,
|
||||
.entry_ptr = undefined,
|
||||
};
|
||||
errdefer self.allocator.free(self.buffer);
|
||||
|
||||
while (buffer_size <= max_buf_size) {
|
||||
const rc = bun.c.getpwuid_r(
|
||||
user._underlying,
|
||||
&self.entry,
|
||||
self.buffer.ptr,
|
||||
self.buffer.len,
|
||||
@ptrCast(&result),
|
||||
);
|
||||
if (rc == 0) {
|
||||
// Since we found the correct buffer size, let's store it for future use.
|
||||
Self._pwbuf_size.store(buffer_size, .monotonic);
|
||||
|
||||
if (result) |r| {
|
||||
self.entry_ptr = r;
|
||||
return .initResult(self);
|
||||
}
|
||||
|
||||
return .initErr(.fromCode(.NOENT, .getpwuid_r));
|
||||
}
|
||||
|
||||
const err = std.posix.errno(rc);
|
||||
switch (err) {
|
||||
.NOMEM, .RANGE => {
|
||||
// ENOMEM -- Insufficient memory to allocate passwd structure.
|
||||
// ERANGE -- Insufficient buffer space supplied.
|
||||
buffer_size *= buf_size_gain;
|
||||
self.buffer = alloc.realloc(self.buffer, buffer_size) catch {
|
||||
return .initErr(.fromCode(.NOMEM, .getpwuid_r));
|
||||
};
|
||||
continue;
|
||||
},
|
||||
.INTR => {
|
||||
// We got hit by a signal, let's just try again.
|
||||
continue;
|
||||
},
|
||||
.IO, .MFILE, .NFILE => {
|
||||
return .initErr(.fromCode(err, .getpwuid_r));
|
||||
},
|
||||
else => {
|
||||
// The man page says we have covered the total set of error codes.
|
||||
@branchHint(.cold);
|
||||
return .initErr(.fromCode(err, .getpwuid_r));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return .initErr(.fromCode(.SRCH, .getpwuid_r));
|
||||
}
|
||||
} else struct {};
|
||||
|
||||
/// Utilities for fetching and interacting with the current user's home directory.
|
||||
pub const HomeDir = if (bun.Environment.isPosix) struct {
|
||||
const Self = @This();
|
||||
|
||||
#path: union(enum) {
|
||||
managed: PasswdEntry,
|
||||
unmanaged: []const u8,
|
||||
},
|
||||
|
||||
pub fn slice(self: *Self) []const u8 {
|
||||
return switch (self.#path) {
|
||||
// Safe to unwrap since all processes which add a .managed field validate that the
|
||||
// field is non-null.
|
||||
.managed => self.#path.managed.pwDir().?,
|
||||
.unmanaged => self.#path.unmanaged,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
switch (self.#path) {
|
||||
.managed => self.#path.managed.deinit(),
|
||||
.unmanaged => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Deduces the current user's home directory on POSIX systems.
|
||||
///
|
||||
/// Whichever of the following returns a value is returned.
|
||||
/// - Per doi:10.1109/IEEESTD.2018.8277153, the `$HOME` variable.
|
||||
/// - The `getpwuid_r` function.
|
||||
pub fn query(allocator: std.mem.Allocator) bun.sys.Maybe(Self) {
|
||||
if (bun.getenvZ("HOME") orelse bun.getenvZ("USERPROFILE")) |home| {
|
||||
// The user may override $HOME with a non-absolute path. We know that's wrong.
|
||||
if (std.fs.path.isAbsolute(home)) {
|
||||
return .initResult(.{ .#path = .{ .unmanaged = home } });
|
||||
}
|
||||
}
|
||||
|
||||
var passwd_entry = PasswdEntry.init(Uid.queryCurrent(), allocator);
|
||||
switch (passwd_entry) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |*r| {
|
||||
return if (r.pwDir() != null)
|
||||
.initResult(.{ .#path = .{ .managed = r.* } })
|
||||
else
|
||||
.initErr(.fromCode(.NOENT, .getpwuid_r));
|
||||
},
|
||||
}
|
||||
}
|
||||
} else struct {};
|
||||
|
||||
/// Work with the system temporary directory.
|
||||
pub const SysTmpDir = if (bun.Environment.isPosix) struct {
|
||||
const Self = @This();
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
_ = self; // self is unused, it's there for concept compatibility.
|
||||
}
|
||||
|
||||
pub fn slice(self: *const Self) []const u8 {
|
||||
_ = self; // self is unused, it's there for concept compatibility.
|
||||
|
||||
// Implementations are encouraged to provide suitable directory names in the environment
|
||||
// variable TMPDIR and applications are encouraged to use the contents of TMPDIR for
|
||||
// creating temporary files.
|
||||
// - IEEE Std 1003_1-2017 (POSIX 2017)
|
||||
if (bun.getenvZ("TMPDIR")) |tmpdir| {
|
||||
return tmpdir;
|
||||
}
|
||||
|
||||
// The /tmp directory is retained in POSIX.1-2017 to accommodate historical applications
|
||||
// that assume its availability.
|
||||
return "/tmp";
|
||||
}
|
||||
|
||||
pub fn query(allocator: std.mem.Allocator) bun.sys.Maybe(Self) {
|
||||
_ = allocator; // Never allocates, used for conformance to
|
||||
return .initResult(.{});
|
||||
}
|
||||
} else struct {};
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
50
src/os/uvinterop.zig
Normal file
50
src/os/uvinterop.zig
Normal file
@@ -0,0 +1,50 @@
|
||||
//! Utilities for interoping with libuv.
|
||||
//!
|
||||
//! TODO(markovejnovic): This file probably doesn't belong in src/os. Consider moving it to
|
||||
//! src/bunuv or similar.
|
||||
extern fn uv_translate_sys_error(sys_errno: c_int) callconv(.C) c_int;
|
||||
fn translateSysErrorToUv(sys_errno: c_int) c_int {
|
||||
// TODO(markovejnovic): This is a hack because uv_translate_sys_error is stubbed out on POSIX.
|
||||
if (bun.Environment.isPosix) {
|
||||
// Exactly matches libuv's behavior.
|
||||
return if (sys_errno <= 0) sys_errno else -sys_errno;
|
||||
}
|
||||
|
||||
if (bun.Environment.isWindows) {
|
||||
return uv_translate_sys_error(sys_errno);
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn bunuv__os_homedir(buffer: ?[*]u8, size: ?*usize) callconv(.C) c_int {
|
||||
if (buffer == null or size == null) {
|
||||
return translateSysErrorToUv(@intFromEnum(bun.sys.E.INVAL));
|
||||
}
|
||||
|
||||
// TODO(markovejnovic): This implementation could be slightly better. I don't know how to
|
||||
// return the total size needed (from os.HomeDir.query) if the buffer is
|
||||
// too small.
|
||||
var homedir = bun.os.HomeDir.query(bun.default_allocator);
|
||||
switch (homedir) {
|
||||
.result => |*r| {
|
||||
defer r.deinit();
|
||||
|
||||
const out = r.slice();
|
||||
// +1 for null terminator
|
||||
if (out.len + 1 > size.?.*) {
|
||||
size.?.* = out.len + 1;
|
||||
return libuv.UV_ENOBUFS;
|
||||
}
|
||||
|
||||
@memcpy(buffer.?[0..out.len], out);
|
||||
buffer.?[out.len] = 0; // null terminator
|
||||
size.?.* = out.len + 1;
|
||||
return 0;
|
||||
},
|
||||
.err => |err| {
|
||||
return translateSysErrorToUv(err.errno);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const libuv = @import("../deps/libuv.zig");
|
||||
338
src/os/win32.zig
Normal file
338
src/os/win32.zig
Normal file
@@ -0,0 +1,338 @@
|
||||
//! Module for higher-level Windows utilities. Unlike sys.zig, this module has a slightly higher
|
||||
//! level of abstraction and is not a direct mapping to Win32 APIs, but is still very
|
||||
//! Windows-specific.
|
||||
|
||||
/// Utilities for retrieving C:\Windows, D:\Windows or whatnot. Does not support POSIX.
|
||||
pub const WinDir = if (bun.Environment.isWindows) struct {
|
||||
const Self = @This();
|
||||
|
||||
#path: union(enum) {
|
||||
managed: paths.AutoAbsPath,
|
||||
unmanaged: []const u8,
|
||||
},
|
||||
|
||||
pub fn slice(self: *const Self) []const u8 {
|
||||
return switch (self.#path) {
|
||||
.managed => self.#path.managed.slice(),
|
||||
.unmanaged => self.#path.unmanaged,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
switch (self.#path) {
|
||||
.managed => return self.#path.managed.deinit(),
|
||||
.unmanaged => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Query the system installation directory, typically C:\Windows.
|
||||
///
|
||||
/// May or may not allocate. Make no assumptions -- call .deinit(), as that will handle both
|
||||
/// cases for you.
|
||||
pub fn query(allocator: std.mem.Allocator) bun.sys.Maybe(Self) {
|
||||
const env_path = bun.getenvZ("WINDIR") orelse bun.getenvZ("SYSTEMROOT");
|
||||
if (env_path) |p| {
|
||||
return .initResult(.{ .#path = .{ .unmanaged = p } });
|
||||
}
|
||||
|
||||
// This is the slow and expensive path -- we have to actually query the system. Oof.
|
||||
var stack = std.heap.stackFallback(bun.windows.MAX_PATH * @sizeOf(u16), allocator);
|
||||
var alloc = stack.get();
|
||||
|
||||
var wchar_buf = alloc.alloc(u16, bun.windows.MAX_PATH) catch {
|
||||
return .initErr(.fromCode(.NOMEM, .GetSystemWindowsDirectoryW));
|
||||
};
|
||||
defer alloc.free(wchar_buf);
|
||||
|
||||
const rc = queryIntoSlice(wchar_buf);
|
||||
switch (rc) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |r| {
|
||||
if (r <= wchar_buf.len) {
|
||||
var path = paths.AutoAbsPath.init();
|
||||
// GetSystemWindowsDirectoryW returns the length without the null terminator.
|
||||
path.append(wchar_buf[0..@as(usize, @intCast(r))]);
|
||||
return .initResult(.{ .#path = .{ .managed = path } });
|
||||
}
|
||||
|
||||
// We need a bigger buffer.
|
||||
wchar_buf = alloc.realloc(wchar_buf, @as(usize, @intCast(r))) catch {
|
||||
return .initErr(.fromCode(.NOMEM, .GetSystemWindowsDirectoryW));
|
||||
};
|
||||
const rc2 = queryIntoSlice(wchar_buf);
|
||||
|
||||
switch (rc2) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |r2| {
|
||||
if (r2 <= wchar_buf.len) {
|
||||
var path = paths.AutoAbsPath.init();
|
||||
// GetSystemWindowsDirectoryW returns the length without the null
|
||||
// terminator.
|
||||
path.append(wchar_buf[0..@as(usize, @intCast(r2))]);
|
||||
return .initResult(.{ .#path = .{ .managed = path } });
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// If the buffer is STILL too small, then the system is pulling our leg.
|
||||
bun.Output.panic("GetSystemWindowsDirectoryW keeps returning a size larger than " ++
|
||||
"the buffer we provide. This is a bug in Bun. Please report it.", .{});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn queryIntoSlice(wchar_buf: []u16) bun.sys.Maybe(usize) {
|
||||
const rc = bun.windows.GetSystemWindowsDirectoryW(
|
||||
@ptrCast(wchar_buf.ptr),
|
||||
@intCast(wchar_buf.len),
|
||||
);
|
||||
|
||||
// If the function fails, the return value is zero. To get extended error information, call
|
||||
// GetLastError.
|
||||
if (rc == 0) {
|
||||
const err = bun.windows.GetLastError();
|
||||
// TODO(markovejnovic): This whole conversion between Win32Error -> SystemErrno -> E
|
||||
// feels very wrong.
|
||||
return .initErr(.fromCode(@enumFromInt(@intFromEnum(err)), .GetSystemWindowsDirectoryW));
|
||||
}
|
||||
|
||||
// If the function succeeds, the return value is the length of the string copied to the
|
||||
// buffer, in TCHARs, not including the terminating null character.
|
||||
// If the length is greater than the size of the buffer, the return value is the size of
|
||||
// the buffer required to hold the path.
|
||||
//
|
||||
// In both cases, we return the actual length.
|
||||
return .initResult(rc);
|
||||
}
|
||||
} else struct {};
|
||||
|
||||
/// Utilities for retrieving C:\Users\<User> or whatnot.
|
||||
pub const UserProfile = if (bun.Environment.isWindows) struct {
|
||||
const Self = @This();
|
||||
|
||||
#path: union(enum) {
|
||||
managed: paths.AutoAbsPath,
|
||||
unmanaged: []const u8,
|
||||
},
|
||||
|
||||
pub fn slice(self: *const Self) []const u8 {
|
||||
return switch (self.#path) {
|
||||
.managed => self.#path.managed.slice(),
|
||||
.unmanaged => self.#path.unmanaged,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
switch (self.#path) {
|
||||
.managed => return self.#path.managed.deinit(),
|
||||
.unmanaged => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(allocator: std.mem.Allocator) bun.sys.Maybe(Self) {
|
||||
const env_path = bun.getenvZ("HOME");
|
||||
if (env_path) |p| {
|
||||
// @markovejnovic:
|
||||
// This feels very counter-intuitive to me but it matches what libuv does -- we run
|
||||
// some heuristics to test the quality of the environment variable. I think this is
|
||||
// kind of misguided, but decided not to deviate from libuv here.
|
||||
if (p.len < "C:\\".len) {
|
||||
return .initErr(.fromCode(.NOENT, .GetUserProfileDirectoryW));
|
||||
}
|
||||
|
||||
return .initResult(.{ .#path = .{ .unmanaged = p } });
|
||||
}
|
||||
|
||||
var stack = std.heap.stackFallback(bun.windows.MAX_PATH * @sizeOf(u16), allocator);
|
||||
var alloc = stack.get();
|
||||
|
||||
var wchar_buf = alloc.alloc(u16, bun.windows.MAX_PATH) catch {
|
||||
return .initErr(.fromCode(.NOMEM, .GetUserProfileDirectoryW));
|
||||
};
|
||||
defer alloc.free(wchar_buf);
|
||||
|
||||
const rc = queryIntoSlice(wchar_buf);
|
||||
switch (rc) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |r| {
|
||||
if (r <= wchar_buf.len) {
|
||||
var path = paths.AutoAbsPath.init();
|
||||
// -1 because we don't want the null terminator.
|
||||
path.append(wchar_buf[0..@as(usize, if (r == 0) 0 else @intCast(r - 1))]);
|
||||
return .initResult(.{ .#path = .{ .managed = path } });
|
||||
}
|
||||
|
||||
// We need a bigger buffer.
|
||||
wchar_buf = alloc.realloc(wchar_buf, @as(usize, @intCast(r))) catch {
|
||||
return .initErr(.fromCode(.NOMEM, .GetUserProfileDirectoryW));
|
||||
};
|
||||
const rc2 = queryIntoSlice(wchar_buf);
|
||||
|
||||
switch (rc2) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |r2| {
|
||||
if (r2 <= wchar_buf.len) {
|
||||
var path = paths.AutoAbsPath.init();
|
||||
// -1 because we don't want the null terminator.
|
||||
path.append(
|
||||
wchar_buf[0..@as(usize, if (r2 == 0) 0 else @intCast(r2 - 1))],
|
||||
);
|
||||
return .initResult(.{ .#path = .{ .managed = path } });
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// If the buffer is STILL too small, then the system is pulling our leg.
|
||||
bun.Output.panic("GetUserProfileDirectoryW keeps returning a size larger than " ++
|
||||
"the buffer we provide. This is a bug in Bun. Please report it.", .{});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn queryIntoSlice(wchar_buf: []u16) bun.sys.Maybe(usize) {
|
||||
var proc_token: bun.windows.HANDLE = undefined;
|
||||
const proc_hndl = bun.windows.GetCurrentProcess();
|
||||
var rc = bun.windows.OpenProcessToken(proc_hndl, bun.windows.TOKEN_QUERY, &proc_token);
|
||||
if (rc == bun.windows.FALSE) {
|
||||
const err = bun.windows.GetLastError();
|
||||
// TODO(markovejnovic): This whole conversion between Win32Error -> SystemErrno -> E
|
||||
// feels very wrong.
|
||||
return .initErr(.fromCode(@enumFromInt(@intFromEnum(err)), .OpenProcessToken));
|
||||
}
|
||||
defer _ = bun.windows.CloseHandle(proc_token);
|
||||
|
||||
var path_size: bun.windows.DWORD = @as(bun.windows.DWORD, @intCast(wchar_buf.len));
|
||||
rc = bun.windows.GetUserProfileDirectoryW(
|
||||
proc_token,
|
||||
@ptrCast(wchar_buf.ptr),
|
||||
&path_size,
|
||||
);
|
||||
|
||||
if (rc == bun.windows.FALSE and
|
||||
path_size <= @as(bun.windows.DWORD, @intCast(wchar_buf.len)))
|
||||
{
|
||||
// Otherwise we found ourselves some really weird error.
|
||||
const err = bun.windows.GetLastError();
|
||||
// TODO(markovejnovic): This whole conversion between Win32Error -> SystemErrno -> E
|
||||
// feels very wrong.
|
||||
return .initErr(.fromCode(@enumFromInt(@intFromEnum(err)), .GetUserProfileDirectoryW));
|
||||
}
|
||||
|
||||
// Either the buffer was too small (in which case path_size contains the required size),
|
||||
// or we succeeded (in which case path_size contains the actual size).
|
||||
return .initResult(@intCast(path_size));
|
||||
}
|
||||
} else struct {};
|
||||
|
||||
// Windows-specific TMP/TEMP directory retrieval.
|
||||
pub const TempDir = if (bun.Environment.isWindows) struct {
|
||||
const Self = @This();
|
||||
|
||||
#path: union(enum) {
|
||||
unmanaged: []const u8,
|
||||
managed: struct {
|
||||
allocator: std.mem.Allocator,
|
||||
buf: []u8,
|
||||
},
|
||||
},
|
||||
|
||||
pub fn slice(self: *const Self) []const u8 {
|
||||
return switch (self.#path) {
|
||||
.unmanaged => self.#path.unmanaged,
|
||||
.managed => |*m| m.buf,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
switch (self.#path) {
|
||||
.unmanaged => {},
|
||||
.managed => |*m| {
|
||||
m.allocator.free(m.buf);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(allocator: std.mem.Allocator) bun.sys.Maybe(Self) {
|
||||
// Kind of mimics what GetTempPath2 does.
|
||||
//
|
||||
// Microsoft says:
|
||||
// For non-system processes, the GetTempPath2 function checks for the existence of
|
||||
// environment variables in the following order and uses the first path found:
|
||||
// 1. The path specified by the TMP environment variable.
|
||||
// 2. The path specified by the TEMP environment variable.
|
||||
// 3. The path specified by the USERPROFILE environment variable.
|
||||
// 4. The Windows directory.
|
||||
// The maximum possible return value is MAX_PATH+1 (261).
|
||||
//
|
||||
// They also say:
|
||||
//
|
||||
// When calling this function from a process running as SYSTEM it will
|
||||
// return the path C:\Windows\SystemTemp, which is inaccessible to non-SYSTEM processes.
|
||||
// For non-SYSTEM processes, GetTempPath2 will behave the same as GetTemppaths.
|
||||
//
|
||||
// For system processes, the GetTempPath2 function checks for the existence of the
|
||||
// environment variable SystemTemp. If this environment variable is set, it will use the
|
||||
// value of the environment variable as the path instead of the default system provided
|
||||
// path on the C: drive.
|
||||
//
|
||||
// We do not implement this SYSTEM-specific behavior. There is no reason to call Bun as a
|
||||
// SYSTEM process.
|
||||
//
|
||||
// Furthermore, we deviate from the documented behavior of GetTempPath2 since it's fucking
|
||||
// stupid -- why on earth would we ever want to polute the user directory, or worse even
|
||||
// the Windows directory? That makes zero sense. Therefore, if we do fall that far down the
|
||||
// list, we will append temp to the path.
|
||||
//
|
||||
// This matches Node and is close to what CPython does.
|
||||
if (bun.getenvZ("TMP")) |t| {
|
||||
return .initResult(.{ .#path = .{ .unmanaged = t } });
|
||||
}
|
||||
|
||||
if (bun.getenvZ("TEMP")) |t| {
|
||||
return .initResult(.{ .#path = .{ .unmanaged = t } });
|
||||
}
|
||||
|
||||
var user_profile = UserProfile.query(allocator);
|
||||
if (user_profile.asValuePtr()) |up| {
|
||||
defer up.deinit();
|
||||
return .initResult(.{ .#path = .{ .managed = .{
|
||||
.allocator = allocator,
|
||||
.buf = std.mem.concat(allocator, u8, &.{ up.slice(), "\\Temp" }) catch |err| {
|
||||
switch (err) {
|
||||
error.OutOfMemory => return .initErr(.fromCode(.NOMEM, .GetTempPath2)),
|
||||
}
|
||||
},
|
||||
} } });
|
||||
}
|
||||
|
||||
var win_dir = WinDir.query(allocator);
|
||||
if (win_dir.asValuePtr()) |wd| {
|
||||
defer wd.deinit();
|
||||
return .initResult(.{ .#path = .{ .managed = .{
|
||||
.allocator = allocator,
|
||||
.buf = std.mem.concat(allocator, u8, &.{ wd.slice(), "\\Temp" }) catch |err| {
|
||||
switch (err) {
|
||||
error.OutOfMemory => return .initErr(.fromCode(.NOMEM, .GetTempPath2)),
|
||||
}
|
||||
},
|
||||
} } });
|
||||
}
|
||||
|
||||
return .initErr(user_profile.asErr() orelse
|
||||
win_dir.asErr() orelse
|
||||
.fromCode(.UNKNOWN, .GetTempPath2));
|
||||
}
|
||||
} else struct {};
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
const paths = bun.paths;
|
||||
64
src/sys.zig
64
src/sys.zig
@@ -275,6 +275,7 @@ pub const Tag = enum(u8) {
|
||||
setsockopt,
|
||||
statx,
|
||||
rm,
|
||||
getpwuid_r,
|
||||
|
||||
uv_spawn,
|
||||
uv_pipe,
|
||||
@@ -291,6 +292,10 @@ pub const Tag = enum(u8) {
|
||||
CloseHandle,
|
||||
SetFilePointerEx,
|
||||
SetEndOfFile,
|
||||
OpenProcessToken,
|
||||
GetUserProfileDirectoryW,
|
||||
GetSystemWindowsDirectoryW,
|
||||
GetTempPath2,
|
||||
|
||||
pub fn isWindows(this: Tag) bool {
|
||||
return @intFromEnum(this) > @intFromEnum(Tag.WriteFile);
|
||||
@@ -4260,6 +4265,65 @@ pub fn dlsymImpl(handle: ?*anyopaque, name: [:0]const u8) ?*anyopaque {
|
||||
@compileError("dlsym unimplemented for this target");
|
||||
}
|
||||
|
||||
const SysconfOpts = struct {
|
||||
default: ?i64 = null,
|
||||
|
||||
const SysconfErrorSet = error{
|
||||
IndeterminateLimit,
|
||||
InvalidName,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
/// Deduce the return type of sysconf based on whether a default was provided.
|
||||
fn sysconfReturnType(self: SysconfOpts) type {
|
||||
return if (self.default != null) i64 else SysconfErrorSet!i64;
|
||||
}
|
||||
};
|
||||
|
||||
/// Matches the POSIX sysconf function, except returns rich error information.
|
||||
///
|
||||
/// Providing a default will cause that value to be returned instead of an error in the case of
|
||||
/// error. You can use this to provide fallback values for systems that don't support certain
|
||||
/// sysconf names.
|
||||
///
|
||||
/// On platforms which do not support sysconf, this will return the default if provided, or a
|
||||
/// compile-time error if not.
|
||||
pub fn sysconf(name: c_int, comptime opts: SysconfOpts) opts.sysconfReturnType() {
|
||||
// This function follows the documentation of sysconf(3) under POSIX.1-2008.
|
||||
if (comptime !bun.Environment.isPosix) {
|
||||
if (comptime opts.default) |d| {
|
||||
return d;
|
||||
}
|
||||
|
||||
@compileError("sysconf is not supported on this platform. Either provide a default " ++
|
||||
"value or avoid calling sysconf on this platform.");
|
||||
}
|
||||
|
||||
// Man page says we need to set errno to 0 before calling sysconf.
|
||||
std.c._errno().* = 0;
|
||||
const ret = std.c.sysconf(name);
|
||||
if (ret != -1) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// As per the docblock, don't return errors if a default was provided.
|
||||
if (comptime opts.default) |d| {
|
||||
return d;
|
||||
}
|
||||
|
||||
// Lots of different error cases that may happen in sysconf.
|
||||
const err_code: std.posix.E = @enumFromInt(std.c._errno().*);
|
||||
return switch (err_code) {
|
||||
// If name corresponds to a maximum or minimum limit, and that limit is indeterminate, -1
|
||||
// is returned and errno is not changed. (To distinguish an indeterminate limit from
|
||||
// an error, set errno to zero before the call, and then check whether errno is nonzero
|
||||
// when -1 is returned.)
|
||||
.SUCCESS => error.IndeterminateLimit,
|
||||
.INVAL => error.InvalidName,
|
||||
else => std.posix.unexpectedErrno(err_code),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dlsymWithHandle(comptime Type: type, comptime name: [:0]const u8, comptime handle_getter: fn () ?*anyopaque) ?Type {
|
||||
if (comptime @typeInfo(Type) != .pointer) {
|
||||
@compileError("dlsym must be a pointer type (e.g. ?const *fn()). Received " ++ @typeName(Type) ++ ".");
|
||||
|
||||
@@ -86,6 +86,31 @@ pub const WPathBuffer = if (Environment.isWindows) bun.WPathBuffer else void;
|
||||
pub const HANDLE = win32.HANDLE;
|
||||
pub const HMODULE = win32.HMODULE;
|
||||
|
||||
/// learn.microsoft.com/en-us/windows/win32/secauthz/access-rights-for-access-token-objects
|
||||
pub const TOKEN_QUERY: DWORD = 0x0008;
|
||||
|
||||
/// learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken
|
||||
pub extern "advapi32" fn OpenProcessToken(
|
||||
ProcessHandle: HANDLE,
|
||||
DesiredAccess: DWORD,
|
||||
TokenHandle: *HANDLE,
|
||||
) callconv(windows.WINAPI) BOOL;
|
||||
|
||||
/// learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw
|
||||
pub extern "userenv" fn GetUserProfileDirectoryW(
|
||||
hToken: HANDLE,
|
||||
lpProfileDir: LPWSTR,
|
||||
lpcchSize: *DWORD,
|
||||
) callconv(windows.WINAPI) BOOL;
|
||||
|
||||
// learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemwindowsdirectoryw
|
||||
pub extern "kernel32" fn GetSystemWindowsDirectoryW(
|
||||
lpBuffer: LPWSTR,
|
||||
uSize: UINT,
|
||||
) callconv(windows.WINAPI) UINT;
|
||||
|
||||
pub const GetCurrentProcess = windows.GetCurrentProcess;
|
||||
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
|
||||
pub extern "kernel32" fn GetFileInformationByHandle(
|
||||
hFile: HANDLE,
|
||||
|
||||
Reference in New Issue
Block a user