mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Compare commits
3 Commits
bun-v1.3.4
...
user/marko
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e79a73ecb | ||
|
|
667404c883 | ||
|
|
ddb7c132a2 |
@@ -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)
|
||||
|
||||
@@ -302,80 +302,13 @@ 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| {
|
||||
switch (bun.os.queryHomeDir()) {
|
||||
.result => |r| {
|
||||
return bun.String.cloneUTF8(r);
|
||||
},
|
||||
.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,7 +25,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
|
||||
// if that fails, try $HOME/.bun/bin
|
||||
outer: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
if (bun.os.queryHomeDir().asValue()) |home_dir| {
|
||||
target = std.fmt.bufPrint(&target_buf, "{s}/.bun/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
|
||||
std.posix.symlink(exe, target) catch break :outer;
|
||||
return;
|
||||
@@ -34,7 +34,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
|
||||
// if that fails, try $HOME/.local/bin
|
||||
outer: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
if (bun.os.queryHomeDir().asValue()) |home_dir| {
|
||||
target = std.fmt.bufPrint(&target_buf, "{s}/.local/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
|
||||
std.posix.symlink(exe, target) catch break :outer;
|
||||
return;
|
||||
@@ -229,7 +229,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
if (bun.os.queryHomeDir().asValue()) |home_dir| {
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.config/fish/completions" };
|
||||
completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
|
||||
@@ -287,7 +287,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
if (bun.os.queryHomeDir().asValue()) |home_dir| {
|
||||
{
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.oh-my-zsh/completions" };
|
||||
@@ -339,7 +339,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
|
||||
if (bun.os.queryHomeDir().asValue()) |home_dir| {
|
||||
{
|
||||
outer: {
|
||||
var paths = [_]string{ home_dir, "./.oh-my-bash/custom/completions" };
|
||||
@@ -449,7 +449,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
|
||||
second: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |zdot_dir| {
|
||||
if (bun.os.queryHomeDir().asValue()) |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;
|
||||
@@ -459,7 +459,7 @@ pub const InstallCompletionsCommand = struct {
|
||||
}
|
||||
|
||||
third: {
|
||||
if (bun.getenvZ(bun.DotEnv.home_env)) |zdot_dir| {
|
||||
if (bun.os.queryHomeDir().asValue()) |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;
|
||||
@@ -531,7 +531,6 @@ pub const InstallCompletionsCommand = struct {
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
const DotEnv = @import("../env_loader.zig");
|
||||
const ShellCompletions = @import("./shell_completions.zig");
|
||||
const fs = @import("../fs.zig");
|
||||
const resolve_path = @import("../resolver/resolve_path.zig");
|
||||
|
||||
@@ -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,13 @@ 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";
|
||||
const system_root = switch (bun.os.win32.queryWinDir()) {
|
||||
.err => {
|
||||
Output.prettyErrorln("<r><red>error:<r> Failed to unzip {s} due to PowerShell not being installed.", .{tmpname});
|
||||
Global.exit(1);
|
||||
},
|
||||
.result => |v| v,
|
||||
};
|
||||
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;
|
||||
|
||||
42
src/fs.zig
42
src/fs.zig
@@ -530,43 +530,15 @@ pub const FileSystem = struct {
|
||||
file_limit: usize = 32,
|
||||
file_quota: usize = 32,
|
||||
|
||||
pub var win_tempdir_cache: ?[]const u8 = undefined;
|
||||
|
||||
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 (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 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;
|
||||
switch (bun.os.querySysTmpDir()) {
|
||||
.result => |s| {
|
||||
return s;
|
||||
},
|
||||
.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,8 @@ 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| {
|
||||
|
||||
if (bun.getenvZ("XDG_CONFIG_HOME") orelse bun.os.queryHomeDir().asValue()) |data_dir| {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var parts = [_]string{
|
||||
"./.npmrc",
|
||||
|
||||
@@ -224,7 +224,7 @@ 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| {
|
||||
if (bun.getenvZ("XDG_CACHE_HOME") orelse bun.os.queryHomeDir().asValue()) |home_dir| {
|
||||
var buf: bun.PathBuffer = undefined;
|
||||
var parts = [_]string{
|
||||
".bun",
|
||||
|
||||
@@ -16,21 +16,9 @@ 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;
|
||||
};
|
||||
|
||||
const home_dir = (bun.os.queryHomeDir()).unwrap() catch return;
|
||||
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, &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::*;
|
||||
|
||||
@@ -202,6 +202,25 @@ pub fn dropSentinel(ptr: anytype, allocator: std.mem.Allocator) blk: {
|
||||
return allocator.dupe(Child, slice);
|
||||
}
|
||||
|
||||
extern "C" fn __lsan_ignore_object(object: *const anyopaque) callconv(.C) void;
|
||||
|
||||
/// Mark the given object as intentionally leaked, so that leak sanitizers do not report it.
|
||||
pub fn INTENTIONALLY_LEAK(
|
||||
comptime alloc: anytype,
|
||||
object: *anyopaque,
|
||||
comptime why: []const u8,
|
||||
) void {
|
||||
_ = why;
|
||||
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
if (comptime bun.allocators.isDefault(alloc)) {
|
||||
__lsan_ignore_object(object);
|
||||
} else {
|
||||
@compileError("INTENTIONALLY_LEAK is not yet implemented for non-default allocators");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
|
||||
41
src/os.zig
Normal file
41
src/os.zig
Normal file
@@ -0,0 +1,41 @@
|
||||
//! 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.
|
||||
pub fn queryHomeDir() bun.sys.Maybe([]const u8) {
|
||||
if (comptime bun.Environment.isWindows) {
|
||||
return win32.queryUserProfile();
|
||||
}
|
||||
|
||||
return posix.queryHomeDir();
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn querySysTmpDir() bun.sys.Maybe([]const u8) {
|
||||
if (comptime bun.Environment.isWindows) {
|
||||
return win32.querySysTmpDir();
|
||||
}
|
||||
|
||||
return .initResult(posix.getSysTmpDir());
|
||||
}
|
||||
|
||||
pub const win32 = if (bun.Environment.isWindows) @import("./os/win32.zig") else struct {};
|
||||
pub const posix = if (bun.Environment.isPosix) @import("./os/posix.zig") else struct {};
|
||||
|
||||
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");
|
||||
270
src/os/posix.zig
Normal file
270
src/os/posix.zig
Normal file
@@ -0,0 +1,270 @@
|
||||
/// Fully managed representation of a passwd entry.
|
||||
pub const PasswdEntry = struct {
|
||||
const Self = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
buffer: []u8,
|
||||
entry: bun.c.struct_passwd,
|
||||
entry_ptr: *bun.c.struct_passwd,
|
||||
|
||||
_memo: struct {
|
||||
// Holds the length of the pw_dir string slice to avoid recomputing it from the C string.
|
||||
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 pwGidC(self: *Self) std.c.gid_t {
|
||||
return self.entry_ptr.pw_gid;
|
||||
}
|
||||
|
||||
pub fn pwDir(self: *Self) ?[]u8 {
|
||||
if (self.entry_ptr.pw_dir == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// Query an entry in the system passwd database by user ID.
|
||||
/// May spinlock for an extremely brief period of time as it allocates memory.
|
||||
pub fn query(user: std.c.uid_t, alloc: std.mem.Allocator) bun.sys.Maybe(PasswdEntry) {
|
||||
const Memo = struct {
|
||||
var pwbuf_size: std.atomic.Value(usize) = .init(0);
|
||||
};
|
||||
|
||||
// Every iteration we will increase the buffer size by this factor.
|
||||
// mem_allocated(n) = buf_size_gain^n
|
||||
const buf_size_gain = 2;
|
||||
|
||||
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; // 1MB
|
||||
|
||||
// I try to be slightly clever here and memoize the last successful buffer size.
|
||||
var buffer_size: usize = Memo.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) catch default_buf_size,
|
||||
);
|
||||
|
||||
buffer_size = initial_buf_size;
|
||||
}
|
||||
|
||||
var result: ?*bun.c.struct_passwd = undefined;
|
||||
var self: PasswdEntry = .{
|
||||
.allocator = alloc,
|
||||
.buffer = alloc.alloc(u8, buffer_size) catch {
|
||||
return .initErr(.fromCode(.NOMEM, .getpwuid_r));
|
||||
},
|
||||
.entry = undefined,
|
||||
.entry_ptr = undefined,
|
||||
};
|
||||
var deallocate_buffer: bool = true;
|
||||
defer {
|
||||
if (deallocate_buffer) {
|
||||
self.allocator.free(self.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
while (buffer_size <= max_buf_size) {
|
||||
const rc = bun.c.getpwuid_r(
|
||||
user,
|
||||
&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.
|
||||
Memo.pwbuf_size.store(buffer_size, .monotonic);
|
||||
|
||||
if (result) |r| {
|
||||
self.entry_ptr = r;
|
||||
deallocate_buffer = false;
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
/// Deduces the effective user's home directory on POSIX systems.
|
||||
///
|
||||
/// Memoizes the result for future calls. There is no way to "unset" the home directory once it has
|
||||
/// been set. The user must restart the process to reset it.
|
||||
///
|
||||
/// Whichever of the following returns a value is returned.
|
||||
/// - Per doi:10.1109/IEEESTD.2018.8277153, the `$HOME` variable -- does not allocate.
|
||||
/// - The `getpwuid_r` function -- allocates.
|
||||
///
|
||||
/// Ensure you call `.deinit` on the result type.
|
||||
pub fn queryHomeDir() bun.sys.Maybe([]const u8) {
|
||||
const allocator = bun.default_allocator;
|
||||
|
||||
const Store = struct {
|
||||
const DataPtr = bun.TaggedPointerUnion(.{
|
||||
PasswdEntry,
|
||||
[]const u8,
|
||||
});
|
||||
|
||||
var atomic_data = std.atomic.Value(DataPtr).init(DataPtr.Null);
|
||||
|
||||
fn getSlice() ?[]const u8 {
|
||||
const data = atomic_data.load(.acquire);
|
||||
return if (data != DataPtr.Null) switch (data.tag()) {
|
||||
DataPtr.case([]const u8) => data.as([]const u8).*,
|
||||
DataPtr.case(PasswdEntry) => data.as(PasswdEntry).pwDir().?,
|
||||
else => unreachable,
|
||||
} else null;
|
||||
}
|
||||
};
|
||||
|
||||
if (Store.getSlice()) |s| {
|
||||
return .initResult(s);
|
||||
}
|
||||
|
||||
if (bun.getenvZ("HOME") orelse bun.getenvZ("USERPROFILE")) |home| use_envvar: {
|
||||
// The user may override $HOME with a non-absolute path. We know that's wrong.
|
||||
if (!std.fs.path.isAbsolute(home)) {
|
||||
break :use_envvar;
|
||||
}
|
||||
|
||||
const hpath = bun.handleOom(allocator.create([]const u8));
|
||||
hpath.* = home;
|
||||
|
||||
// Failure order requires that we observe the changes to DataPtr, so we need acquire.
|
||||
// Success order requires that we publish our changes to DataPtr, so we need release.
|
||||
if (Store.atomic_data.cmpxchgStrong(
|
||||
Store.DataPtr.Null,
|
||||
.init(hpath),
|
||||
.release,
|
||||
.acquire,
|
||||
)) |d| {
|
||||
// Another thread beat us to it.
|
||||
allocator.destroy(hpath);
|
||||
return .initResult(switch (d.tag()) {
|
||||
Store.DataPtr.case([]const u8) => d.as([]const u8).*,
|
||||
Store.DataPtr.case(PasswdEntry) => d.as(PasswdEntry).pwDir().?,
|
||||
else => unreachable,
|
||||
});
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(hpath),
|
||||
"home directory considered singleton",
|
||||
);
|
||||
return .initResult(hpath.*);
|
||||
}
|
||||
|
||||
var passwd_entry = PasswdEntry.query(bun.c.geteuid(), allocator);
|
||||
switch (passwd_entry) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |*r| {
|
||||
if (r.pwDir() == null) {
|
||||
r.deinit();
|
||||
return .initErr(.fromCode(.NOENT, .getpwuid_r));
|
||||
}
|
||||
|
||||
const pwent = bun.handleOom(allocator.create(PasswdEntry));
|
||||
pwent.* = r.*;
|
||||
|
||||
// Failure order requires that we observe the changes to DataPtr, so we need acquire.
|
||||
// Success order requires that we publish our changes to DataPtr, so we need release.
|
||||
if (Store.atomic_data.cmpxchgStrong(
|
||||
Store.DataPtr.Null,
|
||||
.init(pwent),
|
||||
.release,
|
||||
.acquire,
|
||||
)) |d| {
|
||||
// Another thread beat us to it.
|
||||
pwent.deinit();
|
||||
allocator.destroy(pwent);
|
||||
return .initResult(switch (d.tag()) {
|
||||
Store.DataPtr.case([]const u8) => d.as([]const u8).*,
|
||||
Store.DataPtr.case(PasswdEntry) => d.as(PasswdEntry).pwDir().?,
|
||||
else => unreachable,
|
||||
});
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(pwent),
|
||||
"home directory considered singleton",
|
||||
);
|
||||
return .initResult(pwent.pwDir().?);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Query the system temporary directory.
|
||||
/// Follows the suggestions of IEEE Std 1003.1-2017 (POSIX 2017).
|
||||
pub fn getSysTmpDir() []const u8 {
|
||||
// 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)
|
||||
return if (bun.getenvZ("TMPDIR")) |tmpdir|
|
||||
tmpdir
|
||||
// The /tmp directory is retained in POSIX.1-2017 to accommodate historical applications
|
||||
// that assume its availability.
|
||||
else
|
||||
"/tmp";
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
46
src/os/uvinterop.zig
Normal file
46
src/os/uvinterop.zig
Normal file
@@ -0,0 +1,46 @@
|
||||
//! 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.
|
||||
switch (bun.os.queryHomeDir()) {
|
||||
.result => |out| {
|
||||
// +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;
|
||||
return 0;
|
||||
},
|
||||
.err => |err| {
|
||||
return translateSysErrorToUv(err.errno);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const libuv = @import("../deps/libuv.zig");
|
||||
398
src/os/win32.zig
Normal file
398
src/os/win32.zig
Normal file
@@ -0,0 +1,398 @@
|
||||
//! 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.
|
||||
|
||||
const AtomicMaybeManagedPath = struct {
|
||||
const DataPtr = bun.ptr.TaggedPointerUnion(.{ []const u8, []u8 });
|
||||
|
||||
fn forceAsSlice(data_ptr: DataPtr) []const u8 {
|
||||
return switch (data_ptr.tag()) {
|
||||
DataPtr.case([]const u8) => data_ptr.as([]const u8).*,
|
||||
DataPtr.case([]u8) => data_ptr.as([]u8).*,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
atomic_data: std.atomic.Value(DataPtr) = .init(DataPtr.Null),
|
||||
|
||||
fn getSlice(self: AtomicMaybeManagedPath) ?[]const u8 {
|
||||
const data = self.atomic_data.load(.acquire);
|
||||
return if (data != DataPtr.Null) forceAsSlice(data) else null;
|
||||
}
|
||||
|
||||
/// Attempt to store the given value into the atomic data pointer, if it is not already set.
|
||||
/// Returns the existing value if it was already set.
|
||||
fn tryStore(self: *AtomicMaybeManagedPath, value: DataPtr) ?DataPtr {
|
||||
// Failure order requires that we observe the changes to DataPtr, so we need acquire.
|
||||
// Success order requires that we publish our changes to DataPtr, so we need release.
|
||||
return self.atomic_data.cmpxchgStrong(DataPtr.Null, value, .release, .acquire);
|
||||
}
|
||||
};
|
||||
|
||||
/// Many Win32 calls require you provide them with a buffer. If the buffer ends up being too small,
|
||||
/// the call will return the required size, and you are expected to reallocate the buffer and try
|
||||
/// again.
|
||||
///
|
||||
/// This function does all of that for you -- you just tell it what syscall to call.
|
||||
fn runWin32CallWithResizingBuffer(
|
||||
allocator: std.mem.Allocator,
|
||||
comptime syscall_tag: bun.sys.Tag,
|
||||
comptime queryFn: fn ([]u16) bun.sys.Maybe(usize),
|
||||
) bun.sys.Maybe(struct {
|
||||
buf: []u16,
|
||||
slice: []const u16,
|
||||
}) {
|
||||
var wchar_buf = allocator.alloc(u16, bun.windows.MAX_PATH) catch {
|
||||
return .initErr(.fromCode(.NOMEM, syscall_tag));
|
||||
};
|
||||
|
||||
const rc = queryFn(wchar_buf);
|
||||
switch (rc) {
|
||||
.err => |e| {
|
||||
allocator.free(wchar_buf);
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |r| {
|
||||
if (r <= wchar_buf.len) {
|
||||
return .initResult(.{
|
||||
.buf = wchar_buf,
|
||||
.slice = wchar_buf[0..@as(usize, @intCast(r))],
|
||||
});
|
||||
}
|
||||
|
||||
wchar_buf = allocator.realloc(wchar_buf, @as(usize, @intCast(r))) catch {
|
||||
allocator.free(wchar_buf);
|
||||
return .initErr(.fromCode(.NOMEM, syscall_tag));
|
||||
};
|
||||
|
||||
const rc2 = queryFn(wchar_buf);
|
||||
|
||||
switch (rc2) {
|
||||
.err => |e| {
|
||||
allocator.free(wchar_buf);
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |r2| {
|
||||
if (r2 <= wchar_buf.len) {
|
||||
return .initResult(.{
|
||||
.buf = wchar_buf,
|
||||
.slice = wchar_buf[0..@as(usize, @intCast(r2))],
|
||||
});
|
||||
}
|
||||
|
||||
bun.Output.panic(
|
||||
@tagName(syscall_tag) ++ "keeps returning a size larger than the " ++
|
||||
"buffer we provide. This is a bug in Bun. Please report it on Github.",
|
||||
.{},
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to the Windows directory, eg. C:\Windows.
|
||||
pub fn queryWinDir() bun.sys.Maybe([]const u8) {
|
||||
const allocator = bun.default_allocator;
|
||||
|
||||
const Static = struct {
|
||||
var path: AtomicMaybeManagedPath = .{};
|
||||
};
|
||||
|
||||
if (Static.path.getSlice()) |s| {
|
||||
return .initResult(s);
|
||||
}
|
||||
|
||||
if (bun.getenvZ("WINDIR") orelse bun.getenvZ("SYSTEMROOT")) |wd| use_envvar: {
|
||||
if (!std.fs.path.isAbsolute(wd)) {
|
||||
break :use_envvar;
|
||||
}
|
||||
|
||||
const hpath = bun.handleOom(allocator.create([]const u8));
|
||||
hpath.* = wd;
|
||||
|
||||
if (Static.path.tryStore(.init(hpath))) |v| {
|
||||
allocator.destroy(hpath);
|
||||
return .initResult(AtomicMaybeManagedPath.forceAsSlice(v));
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(hpath),
|
||||
"windows directory considered singleton",
|
||||
);
|
||||
return .initResult(wd);
|
||||
}
|
||||
|
||||
switch (runWin32CallWithResizingBuffer(
|
||||
allocator,
|
||||
.GetSystemWindowsDirectoryW,
|
||||
(struct {
|
||||
fn doSyscall(wchar_buf: []u16) bun.sys.Maybe(usize) {
|
||||
const rc = bun.windows.GetSystemWindowsDirectoryW(
|
||||
@ptrCast(wchar_buf.ptr),
|
||||
@intCast(wchar_buf.len),
|
||||
);
|
||||
|
||||
if (rc == 0) {
|
||||
const err = bun.windows.GetLastError();
|
||||
// TODO(markovejnovic): This conversion between Win32Error -> SystemErrno -> E
|
||||
// feels very wrong.
|
||||
return .initErr(
|
||||
.fromCode(@enumFromInt(@intFromEnum(err)), .GetSystemWindowsDirectoryW),
|
||||
);
|
||||
}
|
||||
|
||||
return .initResult(rc);
|
||||
}
|
||||
}).doSyscall,
|
||||
)) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |res| {
|
||||
defer allocator.free(res.buf);
|
||||
const data_ptr = bun.handleOom(allocator.create([]u8));
|
||||
data_ptr.* = bun.handleOom(bun.strings.toUTF8Alloc(
|
||||
allocator,
|
||||
res.slice,
|
||||
));
|
||||
|
||||
if (Static.path.tryStore(.init(data_ptr))) |v| {
|
||||
allocator.free(data_ptr.*);
|
||||
allocator.destroy(data_ptr);
|
||||
return .initResult(AtomicMaybeManagedPath.forceAsSlice(v));
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(data_ptr),
|
||||
"windows directory considered singleton",
|
||||
);
|
||||
return .initResult(data_ptr.*);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to the current user directory, eg. C:\Users\<User>.
|
||||
pub fn queryUserProfile() bun.sys.Maybe([]const u8) {
|
||||
const allocator = bun.default_allocator;
|
||||
|
||||
const Static = struct {
|
||||
var path: AtomicMaybeManagedPath = .{};
|
||||
};
|
||||
|
||||
if (Static.path.getSlice()) |s| {
|
||||
return .initResult(s);
|
||||
}
|
||||
|
||||
if (bun.getenvZ("USERPROFILE")) |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));
|
||||
}
|
||||
|
||||
const hpath = bun.handleOom(allocator.create([]const u8));
|
||||
hpath.* = p;
|
||||
|
||||
if (Static.path.tryStore(.init(hpath))) |v| {
|
||||
allocator.destroy(hpath);
|
||||
return .initResult(AtomicMaybeManagedPath.forceAsSlice(v));
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(hpath),
|
||||
"user profile directory considered singleton",
|
||||
);
|
||||
return .initResult(p);
|
||||
}
|
||||
|
||||
switch (runWin32CallWithResizingBuffer(
|
||||
allocator,
|
||||
.GetUserProfileDirectoryW,
|
||||
(struct {
|
||||
fn doSyscall(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_READ,
|
||||
&proc_token,
|
||||
);
|
||||
|
||||
if (rc == bun.windows.FALSE) {
|
||||
const err = bun.windows.GetLastError();
|
||||
// TODO(markovejnovic): This 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 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));
|
||||
}
|
||||
}).doSyscall,
|
||||
)) {
|
||||
.err => |e| {
|
||||
return .initErr(e);
|
||||
},
|
||||
.result => |res| {
|
||||
defer allocator.free(res.buf);
|
||||
const data_ptr = bun.handleOom(allocator.create([]u8));
|
||||
data_ptr.* = bun.handleOom(bun.strings.toUTF8Alloc(
|
||||
allocator,
|
||||
res.slice,
|
||||
));
|
||||
|
||||
if (Static.path.tryStore(.init(data_ptr))) |v| {
|
||||
allocator.free(data_ptr.*);
|
||||
allocator.destroy(data_ptr);
|
||||
return .initResult(AtomicMaybeManagedPath.forceAsSlice(v));
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(data_ptr),
|
||||
"windows directory considered singleton",
|
||||
);
|
||||
return .initResult(data_ptr.*);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Windows-specific TMP/TEMP directory retrieval.
|
||||
pub fn querySysTmpDir() bun.sys.Maybe([]const u8) {
|
||||
// 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.
|
||||
const allocator = bun.default_allocator;
|
||||
|
||||
const Static = struct {
|
||||
var path: AtomicMaybeManagedPath = .{};
|
||||
};
|
||||
|
||||
if (Static.path.getSlice()) |s| {
|
||||
return .initResult(s);
|
||||
}
|
||||
|
||||
if (bun.getenvZ("TMP") orelse bun.getenvZ("TEMP")) |t| use_envvar: {
|
||||
if (!std.fs.path.isAbsolute(t)) {
|
||||
break :use_envvar;
|
||||
}
|
||||
|
||||
const hpath = bun.handleOom(allocator.create([]const u8));
|
||||
hpath.* = t;
|
||||
|
||||
if (Static.path.tryStore(.init(hpath))) |v| {
|
||||
allocator.destroy(hpath);
|
||||
return .initResult(AtomicMaybeManagedPath.forceAsSlice(v));
|
||||
}
|
||||
return .initResult(t);
|
||||
}
|
||||
|
||||
const user_profile = queryUserProfile();
|
||||
if (user_profile.asValue()) |up| {
|
||||
const result = std.mem.concat(allocator, u8, &.{ up, "\\Temp" }) catch |err| {
|
||||
switch (err) {
|
||||
error.OutOfMemory => return .initErr(.fromCode(.NOMEM, .GetTempPath2)),
|
||||
}
|
||||
};
|
||||
|
||||
const hpath = bun.handleOom(allocator.create([]u8));
|
||||
hpath.* = result;
|
||||
|
||||
if (Static.path.tryStore(.init(hpath))) |v| {
|
||||
allocator.free(result);
|
||||
allocator.destroy(hpath);
|
||||
return .initResult(AtomicMaybeManagedPath.forceAsSlice(v));
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(hpath),
|
||||
"temp directory considered singleton",
|
||||
);
|
||||
return .initResult(result);
|
||||
}
|
||||
|
||||
const win_dir = queryWinDir();
|
||||
if (win_dir.asValue()) |wd| {
|
||||
const result = std.mem.concat(allocator, u8, &.{ wd, "\\Temp" }) catch |err| {
|
||||
switch (err) {
|
||||
error.OutOfMemory => return .initErr(.fromCode(.NOMEM, .GetTempPath2)),
|
||||
}
|
||||
};
|
||||
|
||||
const hpath = bun.handleOom(allocator.create([]u8));
|
||||
hpath.* = result;
|
||||
|
||||
if (Static.path.tryStore(.init(hpath))) |v| {
|
||||
allocator.destroy(hpath);
|
||||
return .initResult(AtomicMaybeManagedPath.forceAsSlice(v));
|
||||
}
|
||||
|
||||
bun.memory.INTENTIONALLY_LEAK(
|
||||
allocator,
|
||||
@ptrCast(hpath),
|
||||
"root directory considered singleton",
|
||||
);
|
||||
return .initResult(result);
|
||||
}
|
||||
|
||||
return .initErr(
|
||||
user_profile.asErr() orelse win_dir.asErr() orelse .fromCode(.UNKNOWN, .GetTempPath2),
|
||||
);
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
@@ -88,7 +88,7 @@ pub fn TaggedPointerUnion(comptime Types: anytype) type {
|
||||
|
||||
const TagType: type = result.tag_type;
|
||||
|
||||
return struct {
|
||||
return packed struct {
|
||||
pub const Tag = TagType;
|
||||
pub const TagInt = TaggedPointer.Tag;
|
||||
pub const type_map: TypeMap(Types) = result.ty_map;
|
||||
@@ -136,7 +136,7 @@ pub fn TaggedPointerUnion(comptime Types: anytype) type {
|
||||
}
|
||||
|
||||
pub fn case(comptime Type: type) Tag {
|
||||
return @field(Tag, bun.meta.typeBaseName(@typeName(Type)));
|
||||
return @field(Tag, @typeName(Type));
|
||||
}
|
||||
|
||||
/// unsafely cast a tagged pointer to a specific type, without checking that it's really that type
|
||||
@@ -234,5 +234,4 @@ pub fn TaggedPointerUnion(comptime Types: anytype) type {
|
||||
};
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
|
||||
33
src/sys.zig
33
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,34 @@ pub fn dlsymImpl(handle: ?*anyopaque, name: [:0]const u8) ?*anyopaque {
|
||||
@compileError("dlsym unimplemented for this target");
|
||||
}
|
||||
|
||||
/// Matches the POSIX sysconf function, except returns rich error information.
|
||||
pub fn sysconf(name: c_int) !u32 {
|
||||
// This function follows the documentation of sysconf(3) under POSIX.1-2008.
|
||||
if (comptime !bun.Environment.isPosix) {
|
||||
@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 >= 0) {
|
||||
return @intCast(ret);
|
||||
}
|
||||
|
||||
// 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_READ: DWORD = 0x20008;
|
||||
|
||||
/// 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