diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index f400f2c514..2e0e192cb2 100644 --- 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 index 01bcacbc29..c74850ca0a 100644 --- 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 index 6f4e80f599..47c5a4d856 100644 --- 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 index 1ef3d0e55e..ad232046ef 100644 --- 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 index fde26ee5f4..b955697d38 100644 --- 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 index e26b581f59..add20921b5 100644 --- 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;