commit 9b415761dd66904aef363e387b4501f3ddd0bf76 (tree)
parent 5571c08e6603173378db7cfe1436e33e902f9c0a
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 28 Jan 2026 20:05:20 -0800
std.os.windows: delete unused APIs
Intention is to go through std.Io for these things.
Diffstat:
6 files changed, 36 insertions(+), 398 deletions(-)
diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig
@@ -48,7 +48,7 @@ pub const Error = error{
LockViolation,
} || Io.Cancelable || Io.UnexpectedError;
-pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{
+pub const SizeError = File.StatError || error{
/// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
Streaming,
};
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
@@ -13960,8 +13960,19 @@ fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child
fn childCleanupWindows(child: *process.Child) void {
const handle = child.id orelse return;
- if (child.request_resource_usage_statistics)
- child.resource_usage_statistics.rusage = windows.GetProcessMemoryInfo(handle) catch null;
+ if (child.request_resource_usage_statistics) {
+ var vmc: windows.VM_COUNTERS = undefined;
+ switch (windows.ntdll.NtQueryInformationProcess(
+ handle,
+ .VmCounters,
+ &vmc,
+ @sizeOf(windows.VM_COUNTERS),
+ null,
+ )) {
+ .SUCCESS => child.resource_usage_statistics.rusage = vmc,
+ else => child.resource_usage_statistics.rusage = null,
+ }
+ }
windows.CloseHandle(handle);
child.id = null;
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -2906,275 +2906,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
return buffer[0..end_index];
}
-pub const DeleteFileError = error{
- FileNotFound,
- AccessDenied,
- NameTooLong,
- /// Also known as sharing violation.
- FileBusy,
- Unexpected,
- NotDir,
- IsDir,
- DirNotEmpty,
- NetworkNotFound,
-};
-
-pub const DeleteFileOptions = struct {
- dir: ?HANDLE,
- remove_dir: bool = false,
-};
-
-pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
- const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2));
- var nt_name: UNICODE_STRING = .{
- .Length = path_len_bytes,
- .MaximumLength = path_len_bytes,
- // The Windows API makes this mutable, but it will not mutate here.
- .Buffer = @constCast(sub_path_w.ptr),
- };
-
- if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
- // Windows does not recognize this, but it does work with empty string.
- nt_name.Length = 0;
- }
- if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
- // Can't remove the parent directory with an open handle.
- return error.FileBusy;
- }
-
- var io: IO_STATUS_BLOCK = undefined;
- var tmp_handle: HANDLE = undefined;
- var rc = ntdll.NtCreateFile(
- &tmp_handle,
- .{ .STANDARD = .{
- .RIGHTS = .{ .DELETE = true },
- .SYNCHRONIZE = true,
- } },
- &.{
- .Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
- .Attributes = .{},
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- },
- &io,
- null,
- .{},
- .VALID_FLAGS,
- .OPEN,
- .{
- .DIRECTORY_FILE = options.remove_dir,
- .NON_DIRECTORY_FILE = !options.remove_dir,
- .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead?
- },
- null,
- 0,
- );
- switch (rc) {
- .SUCCESS => {},
- .OBJECT_NAME_INVALID => unreachable,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found
- .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't
- .INVALID_PARAMETER => unreachable,
- .FILE_IS_A_DIRECTORY => return error.IsDir,
- .NOT_A_DIRECTORY => return error.NotDir,
- .SHARING_VIOLATION => return error.FileBusy,
- .ACCESS_DENIED => return error.AccessDenied,
- .DELETE_PENDING => return,
- else => return unexpectedStatus(rc),
- }
- defer CloseHandle(tmp_handle);
-
- // FileDispositionInformationEx has varying levels of support:
- // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1
- // (INVALID_INFO_CLASS is returned if not supported)
- // - Requires the NTFS filesystem
- // (on filesystems like FAT32, INVALID_PARAMETER is returned)
- // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1
- // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
- // (NOT_SUPPORTED is returned if a flag is unsupported)
- //
- // The strategy here is just to try using FileDispositionInformationEx and fall back to
- // FileDispositionInformation if the return value lets us know that some aspect of it is not supported.
- const need_fallback = need_fallback: {
- // Deletion with posix semantics if the filesystem supports it.
- var info: FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{
- .DELETE = true,
- .POSIX_SEMANTICS = true,
- .IGNORE_READONLY_ATTRIBUTE = true,
- } };
- rc = ntdll.NtSetInformationFile(
- tmp_handle,
- &io,
- &info,
- @sizeOf(FILE.DISPOSITION.INFORMATION.EX),
- .DispositionEx,
- );
- switch (rc) {
- .SUCCESS => return,
- // The filesystem does not support FileDispositionInformationEx
- .INVALID_PARAMETER,
- // The operating system does not support FileDispositionInformationEx
- .INVALID_INFO_CLASS,
- // The operating system does not support one of the flags
- .NOT_SUPPORTED,
- => break :need_fallback true,
- // For all other statuses, fall down to the switch below to handle them.
- else => break :need_fallback false,
- }
- };
-
- if (need_fallback) {
- // Deletion with file pending semantics, which requires waiting or moving
- // files to get them removed (from here).
- var file_dispo: FILE.DISPOSITION.INFORMATION = .{
- .DeleteFile = TRUE,
- };
- rc = ntdll.NtSetInformationFile(
- tmp_handle,
- &io,
- &file_dispo,
- @sizeOf(FILE.DISPOSITION.INFORMATION),
- .Disposition,
- );
- }
- switch (rc) {
- .SUCCESS => {},
- .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
- .INVALID_PARAMETER => unreachable,
- .CANNOT_DELETE => return error.AccessDenied,
- .MEDIA_WRITE_PROTECTED => return error.AccessDenied,
- .ACCESS_DENIED => return error.AccessDenied,
- else => return unexpectedStatus(rc),
- }
-}
-
-pub const RenameError = error{
- IsDir,
- NotDir,
- FileNotFound,
- NoDevice,
- AccessDenied,
- PipeBusy,
- PathAlreadyExists,
- Unexpected,
- NameTooLong,
- NetworkNotFound,
- AntivirusInterference,
- BadPathName,
- CrossDevice,
-} || UnexpectedError;
-
-pub fn RenameFile(
- /// May only be `null` if `old_path_w` is a fully-qualified absolute path.
- old_dir_fd: ?HANDLE,
- old_path_w: []const u16,
- /// May only be `null` if `new_path_w` is a fully-qualified absolute path,
- /// or if the file is not being moved to a different directory.
- new_dir_fd: ?HANDLE,
- new_path_w: []const u16,
- replace_if_exists: bool,
-) RenameError!void {
- const src_fd = OpenFile(old_path_w, .{
- .dir = old_dir_fd,
- .access_mask = .{
- .STANDARD = .{
- .RIGHTS = .{ .DELETE = true },
- .SYNCHRONIZE = true,
- },
- .GENERIC = .{ .WRITE = true },
- },
- .creation = .OPEN,
- .filter = .any, // This function is supposed to rename both files and directories.
- .follow_symlinks = false,
- }) catch |err| switch (err) {
- error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`.
- else => |e| return e,
- };
- defer CloseHandle(src_fd);
-
- var rc: NTSTATUS = undefined;
- // FileRenameInformationEx has varying levels of support:
- // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1
- // (INVALID_INFO_CLASS is returned if not supported)
- // - Requires the NTFS filesystem
- // (on filesystems like FAT32, INVALID_PARAMETER is returned)
- // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1
- // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5
- // (NOT_SUPPORTED is returned if a flag is unsupported)
- //
- // The strategy here is just to try using FileRenameInformationEx and fall back to
- // FileRenameInformation if the return value lets us know that some aspect of it is not supported.
- const need_fallback = need_fallback: {
- var rename_info: FILE.RENAME_INFORMATION = .init(.{
- .Flags = .{
- .REPLACE_IF_EXISTS = replace_if_exists,
- .POSIX_SEMANTICS = true,
- .IGNORE_READONLY_ATTRIBUTE = true,
- },
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
- .FileName = new_path_w,
- });
- var io_status_block: IO_STATUS_BLOCK = undefined;
- const rename_info_buf = rename_info.toBuffer();
- rc = ntdll.NtSetInformationFile(
- src_fd,
- &io_status_block,
- rename_info_buf.ptr,
- @intCast(rename_info_buf.len), // already checked for error.NameTooLong
- .RenameEx,
- );
- switch (rc) {
- .SUCCESS => return,
- // The filesystem does not support FileDispositionInformationEx
- .INVALID_PARAMETER,
- // The operating system does not support FileDispositionInformationEx
- .INVALID_INFO_CLASS,
- // The operating system does not support one of the flags
- .NOT_SUPPORTED,
- => break :need_fallback true,
- // For all other statuses, fall down to the switch below to handle them.
- else => break :need_fallback false,
- }
- };
-
- if (need_fallback) {
- var rename_info: FILE.RENAME_INFORMATION = .init(.{
- .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists },
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd,
- .FileName = new_path_w,
- });
- var io_status_block: IO_STATUS_BLOCK = undefined;
- const rename_info_buf = rename_info.toBuffer();
- rc = ntdll.NtSetInformationFile(
- src_fd,
- &io_status_block,
- rename_info_buf.ptr,
- @intCast(rename_info_buf.len), // already checked for error.NameTooLong
- .Rename,
- );
- }
-
- switch (rc) {
- .SUCCESS => {},
- .INVALID_HANDLE => unreachable,
- .INVALID_PARAMETER => unreachable,
- .OBJECT_PATH_SYNTAX_BAD => unreachable,
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NOT_SAME_DEVICE => return error.CrossDevice,
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
- .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
- .FILE_IS_A_DIRECTORY => return error.IsDir,
- .NOT_A_DIRECTORY => return error.NotDir,
- else => return unexpectedStatus(rc),
- }
-}
-
pub const GetStdHandleError = error{
NoStandardHandleAttached,
Unexpected,
@@ -3508,18 +3239,6 @@ test GetFinalPathNameByHandle {
_ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]);
}
-pub const GetFileSizeError = error{Unexpected};
-
-pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 {
- var file_size: LARGE_INTEGER = undefined;
- if (kernel32.GetFileSizeEx(hFile, &file_size) == 0) {
- switch (GetLastError()) {
- else => |err| return unexpectedError(err),
- }
- }
- return @as(u64, @bitCast(file_size));
-}
-
pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 {
return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen)));
}
@@ -3714,69 +3433,6 @@ pub const CreateProcessFlags = packed struct(u32) {
create_ignore_system_default: bool = false,
};
-pub fn CreateProcessW(
- lpApplicationName: ?LPCWSTR,
- lpCommandLine: ?LPWSTR,
- lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
- lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
- bInheritHandles: BOOL,
- dwCreationFlags: CreateProcessFlags,
- lpEnvironment: ?[*:0]u16,
- lpCurrentDirectory: ?LPCWSTR,
- lpStartupInfo: *STARTUPINFOW,
- lpProcessInformation: *PROCESS_INFORMATION,
-) CreateProcessError!void {
- if (kernel32.CreateProcessW(
- lpApplicationName,
- lpCommandLine,
- lpProcessAttributes,
- lpThreadAttributes,
- bInheritHandles,
- dwCreationFlags,
- lpEnvironment,
- lpCurrentDirectory,
- lpStartupInfo,
- lpProcessInformation,
- ) == 0) {
- switch (GetLastError()) {
- .FILE_NOT_FOUND => return error.FileNotFound,
- .PATH_NOT_FOUND => return error.FileNotFound,
- .DIRECTORY => return error.FileNotFound,
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_PARAMETER => unreachable,
- .INVALID_NAME => return error.InvalidName,
- .FILENAME_EXCED_RANGE => return error.NameTooLong,
- .SHARING_VIOLATION => return error.FileBusy,
- // These are all the system errors that are mapped to ENOEXEC by
- // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error
- // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK)
- // or urt/misc/errno.cpp (newer SDK) in the Windows SDK.
- .BAD_FORMAT,
- .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp
- .INVALID_STACKSEG,
- .INVALID_MODULETYPE,
- .INVALID_EXE_SIGNATURE,
- .EXE_MARKED_INVALID,
- .BAD_EXE_FORMAT,
- .ITERATED_DATA_EXCEEDS_64k,
- .INVALID_MINALLOCSIZE,
- .DYNLINK_FROM_INVALID_RING,
- .IOPL_NOT_ENABLED,
- .INVALID_SEGDPL,
- .AUTODATASEG_EXCEEDS_64k,
- .RING2SEG_MUST_BE_MOVABLE,
- .RELOC_CHAIN_XEEDS_SEGLIM,
- .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp
- // This one is not mapped to ENOEXEC but it is possible, for example
- // when calling CreateProcessW on a plain text file with a .exe extension
- .EXE_MACHINE_TYPE_MISMATCH,
- => return error.InvalidExe,
- .COMMITMENT_LIMIT => return error.SystemResources,
- else => |err| return unexpectedError(err),
- }
- }
-}
-
pub const LoadLibraryError = error{
FileNotFound,
Unexpected,
@@ -3843,10 +3499,6 @@ pub fn QueryPerformanceCounter() u64 {
return @as(u64, @bitCast(result));
}
-pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter: ?*anyopaque, Context: ?*anyopaque) void {
- assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0);
-}
-
/// This is a workaround for the C backend until zig has the ability to put
/// C code in inline assembly.
extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque;
@@ -6072,24 +5724,6 @@ pub const PROCESS_MEMORY_COUNTERS_EX = extern struct {
PrivateUsage: SIZE_T,
};
-pub const GetProcessMemoryInfoError = error{
- AccessDenied,
- InvalidHandle,
- Unexpected,
-};
-
-pub fn GetProcessMemoryInfo(hProcess: HANDLE) GetProcessMemoryInfoError!VM_COUNTERS {
- var vmc: VM_COUNTERS = undefined;
- const rc = ntdll.NtQueryInformationProcess(hProcess, .VmCounters, &vmc, @sizeOf(VM_COUNTERS), null);
- switch (rc) {
- .SUCCESS => return vmc,
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_HANDLE => return error.InvalidHandle,
- .INVALID_PARAMETER => unreachable,
- else => return unexpectedStatus(rc),
- }
-}
-
pub const PERFORMANCE_INFORMATION = extern struct {
cb: DWORD,
CommitTotal: SIZE_T,
@@ -6623,7 +6257,11 @@ pub fn WriteProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []const u8) Wri
}
}
-pub const ProcessBaseAddressError = GetProcessMemoryInfoError || ReadMemoryError;
+pub const ProcessBaseAddressError = error{
+ AccessDenied,
+ InvalidHandle,
+ Unexpected,
+} || ReadMemoryError;
/// Returns the base address of the process loaded into memory.
pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE {
diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig
@@ -117,12 +117,6 @@ pub extern "kernel32" fn WriteFile(
in_out_lpOverlapped: ?*OVERLAPPED,
) callconv(.winapi) BOOL;
-// TODO: wrapper for NtQueryInformationFile + `FILE_STANDARD_INFORMATION`
-pub extern "kernel32" fn GetFileSizeEx(
- hFile: HANDLE,
- lpFileSize: *LARGE_INTEGER,
-) callconv(.winapi) BOOL;
-
// TODO: Wrapper around GetStdHandle + NtFlushBuffersFile.
pub extern "kernel32" fn FlushFileBuffers(
hFile: HANDLE,
@@ -283,12 +277,6 @@ pub extern "kernel32" fn GetExitCodeProcess(
lpExitCode: *DWORD,
) callconv(.winapi) BOOL;
-// TODO: Wrapper around RtlSetEnvironmentVar.
-pub extern "kernel32" fn SetEnvironmentVariableW(
- lpName: LPCWSTR,
- lpValue: ?LPCWSTR,
-) callconv(.winapi) BOOL;
-
pub extern "kernel32" fn CreateToolhelp32Snapshot(
dwFlags: DWORD,
th32ProcessID: DWORD,
@@ -311,13 +299,6 @@ pub extern "kernel32" fn CreateThread(
// Locks, critical sections, initializers
-pub extern "kernel32" fn InitOnceExecuteOnce(
- InitOnce: *INIT_ONCE,
- InitFn: INIT_ONCE_FN,
- Parameter: ?*anyopaque,
- Context: ?*anyopaque,
-) callconv(.winapi) BOOL;
-
// TODO:
// - dwMilliseconds -> LARGE_INTEGER.
// - RtlSleepConditionVariableSRW
diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig
@@ -129,7 +129,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
};
var proc_info: windows.PROCESS_INFORMATION = undefined;
- try windows.CreateProcessW(
+ if (windows.kernel32.CreateProcessW(
@constCast(verify_path.ptr),
@constCast(cmd_line.ptr),
null,
@@ -140,7 +140,10 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
null,
&startup_info,
&proc_info,
- );
+ ) == 0) {
+ std.process.fatal("kernel32 CreateProcessW failed with {t}", .{windows.kernel32.GetLastError()});
+ }
+
windows.CloseHandle(proc_info.hThread);
break :spawn proc_info.hProcess;
diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig
@@ -31,13 +31,13 @@ pub fn main(init: std.process.Init) !void {
defer gpa.free(tmp_relative_path);
// Clear PATH
- std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
+ std.debug.assert(SetEnvironmentVariableW(
utf16Literal("PATH"),
null,
) == windows.TRUE);
// Set PATHEXT to something predictable
- std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
+ std.debug.assert(SetEnvironmentVariableW(
utf16Literal("PATHEXT"),
utf16Literal(".COM;.EXE;.BAT;.CMD;.JS"),
) == windows.TRUE);
@@ -48,7 +48,7 @@ pub fn main(init: std.process.Init) !void {
// make sure we don't get error.BadPath traversing out of cwd with a relative path
try testExecError(error.FileNotFound, gpa, io, "..\\.\\.\\.\\\\..\\more_missing");
- std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
+ std.debug.assert(SetEnvironmentVariableW(
utf16Literal("PATH"),
tmp_absolute_path_w,
) == windows.TRUE);
@@ -131,7 +131,7 @@ pub fn main(init: std.process.Init) !void {
const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0);
defer gpa.free(something_subdir_abs_path);
- std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
+ std.debug.assert(SetEnvironmentVariableW(
utf16Literal("PATH"),
something_subdir_abs_path,
) == windows.TRUE);
@@ -171,7 +171,7 @@ pub fn main(init: std.process.Init) !void {
defer gpa.free(denormed_something_subdir_wtf8);
// clear the path to ensure that the match comes from the cwd
- std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
+ std.debug.assert(SetEnvironmentVariableW(
utf16Literal("PATH"),
null,
) == windows.TRUE);
@@ -179,7 +179,7 @@ pub fn main(init: std.process.Init) !void {
try testExecWithCwd(gpa, io, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n");
// normalization should also work if the non-normalized path is found in the PATH var.
- std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
+ std.debug.assert(SetEnvironmentVariableW(
utf16Literal("PATH"),
denormed_something_subdir_abs_path,
) == windows.TRUE);
@@ -193,7 +193,7 @@ pub fn main(init: std.process.Init) !void {
try std.process.setCurrentDir(io, subdir_cwd);
// clear the PATH again
- std.debug.assert(windows.kernel32.SetEnvironmentVariableW(
+ std.debug.assert(SetEnvironmentVariableW(
utf16Literal("PATH"),
null,
) == windows.TRUE);
@@ -235,3 +235,8 @@ fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []cons
else => |e| return e,
};
}
+
+pub extern "kernel32" fn SetEnvironmentVariableW(
+ lpName: windows.LPCWSTR,
+ lpValue: ?windows.LPCWSTR,
+) callconv(.winapi) windows.BOOL;