feat: Implement low-level os.zig abstractions

This commit is contained in:
Marko Vejnovic
2025-10-20 16:00:52 -07:00
parent ebc0cfeacd
commit ddb7c132a2
21 changed files with 979 additions and 155 deletions

View File

@@ -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

View File

@@ -1,5 +1,6 @@
export const test_skipped = [
"uv_getrusage_thread",
"uv_os_homedir",
"uv_thread_detach",
"uv_thread_getname",
"uv_thread_getpriority",

View File

@@ -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)

View File

@@ -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,

View File

@@ -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;
},
}
}

View File

@@ -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");

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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
View 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
View 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
View 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
View 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;

View File

@@ -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) ++ ".");

View File

@@ -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,