From 747d46f22c2b5bef2d111564b5e4d362228004a2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 5 Aug 2020 17:23:35 +0200 Subject: [PATCH 1/8] Initial draft of GetFinalPathNameByHandle This commit proposes an initial draft of `GetPathNameByHandle` function which wraps NT syscalls and strives to emulate (currently only partially) the `kernel32.GetFinalPathNameByHandleW` function. --- lib/std/os.zig | 10 +--- lib/std/os/windows.zig | 88 +++++++++++++++++++++++++++++------- lib/std/os/windows/bits.zig | 26 +++++++++++ lib/std/os/windows/ntdll.zig | 34 ++++++++++++++ 4 files changed, 133 insertions(+), 25 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 06b61e8c38..88ce77dc56 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4060,7 +4060,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP } /// Same as `realpath` except `pathname` is UTF16LE-encoded. -/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { const w = windows; @@ -4095,15 +4094,10 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat defer w.CloseHandle(h_file); var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS); - - // Windows returns \\?\ prepended to the path. - // We strip it to make this function consistent across platforms. - const prefix = [_]u16{ '\\', '\\', '?', '\\' }; - const start_index = if (mem.startsWith(u16, wide_slice, &prefix)) prefix.len else 0; + const wide_slice = try w.GetFinalPathNameByHandle(h_file, wide_buf[0..]); // Trust that Windows gives us valid UTF-16LE. - const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice[start_index..]) catch unreachable; + const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; return out_buffer[0..end_index]; } diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 2b3dc29b04..d803c8ae83 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -903,24 +903,78 @@ pub const GetFinalPathNameByHandleError = error{ Unexpected, }; -pub fn GetFinalPathNameByHandleW( - hFile: HANDLE, - buf_ptr: [*]u16, - buf_len: DWORD, - flags: DWORD, -) GetFinalPathNameByHandleError![:0]u16 { - const rc = kernel32.GetFinalPathNameByHandleW(hFile, buf_ptr, buf_len, flags); - if (rc == 0) { - switch (kernel32.GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .NOT_ENOUGH_MEMORY => return error.SystemResources, - .FILENAME_EXCED_RANGE => return error.NameTooLong, - .INVALID_PARAMETER => unreachable, - else => |err| return unexpectedError(err), - } +/// Returns canonical (normalized) path of handle. The output path assumes +/// Win32 namespace, however, '\\?\' prefix is *not* prepended to the result. +/// TODO support other namespaces/volume names. +pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNameByHandleError![]u16 { + // The implementation is based on implementation found in Wine sources: + // [LINK] + var buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + MAX_PATH * 2]u8 = undefined; + var dummy: ULONG = undefined; + var rc = ntdll.NtQueryObject(hFile, OBJECT_INFORMATION_CLASS.ObjectNameInformation, &buffer, buffer.len, &dummy); + switch (rc) { + .SUCCESS => {}, + else => return unexpectedStatus(rc), } - return buf_ptr[0..rc :0]; + + const object_name = @ptrCast(*const OBJECT_NAME_INFORMATION, @alignCast(@alignOf(OBJECT_NAME_INFORMATION), &buffer)); + const object_path = @as([*]const u16, object_name.Name.Buffer)[0..object_name.Name.Length / 2]; + + // Since `NtQueryObject` returns a fully-qualified NT path, we need to translate + // the result into a Win32/DOS path (e.g., \Device\HarddiskVolume4\foo would become + // C:\foo). + const dos_drive_letters = &[_]u16{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; + var query_path = [_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\', 'C', ':' }; + for (dos_drive_letters) |drive_letter| { + const drive = &[_]u16{ drive_letter, ':' }; + std.mem.copy(u16, query_path[12..], drive[0..]); + + var sym_handle: HANDLE = undefined; + const len_bytes = @intCast(u16, query_path.len) * 2; + var nt_name = UNICODE_STRING{ + .Length = len_bytes, + .MaximumLength = len_bytes, + .Buffer = @intToPtr([*]u16, @ptrToInt(&query_path)), + }; + var attr = OBJECT_ATTRIBUTES{ + .Length = @sizeOf(OBJECT_ATTRIBUTES), + .RootDirectory = null, + .Attributes = 0, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + rc = ntdll.NtOpenSymbolicLinkObject(&sym_handle, SYMBOLIC_LINK_QUERY, attr); + switch (rc) { + .SUCCESS => {}, + .OBJECT_NAME_NOT_FOUND => continue, + else => return unexpectedStatus(rc), + } + + var link_buffer: [MAX_PATH]u8 = undefined; + var link = UNICODE_STRING{ + .Length = 0, + .MaximumLength = MAX_PATH, + .Buffer = @intToPtr([*]u16, @ptrToInt(&link_buffer[0])), + }; + rc = ntdll.NtQuerySymbolicLinkObject(sym_handle, &link, null); + CloseHandle(sym_handle); + switch (rc) { + .SUCCESS => {}, + else => return unexpectedStatus(rc), + } + + const link_path = @as([*]const u16, link.Buffer)[0..link.Length / 2]; + const idx = std.mem.indexOf(u16, object_path, link_path) orelse continue; + + std.mem.copy(u16, out_buffer[0..], drive[0..]); + std.mem.copy(u16, out_buffer[2..], object_path[link_path.len..]); + + return out_buffer[0..object_path.len - link_path.len + 2]; + } + + // If we're here, that means there was no match so error out! + unreachable; } pub const GetFileSizeError = error{Unexpected}; diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 9f50570e3e..74fe7040ab 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1573,3 +1573,29 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1; pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2; + +pub const OBJECT_INFORMATION_CLASS = extern enum { + ObjectBasicInformation, + ObjectNameInformation, + ObjectTypeInformation, + ObjectAllInformation, + ObjectDataInformation, +}; +pub const OBJECT_NAME_INFORMATION = extern struct { + Name: UNICODE_STRING, +}; + +pub const DIRECTORY_QUERY: DWORD = 0x0001; +pub const DIRECTORY_TRAVERSE: DWORD = 0x0002; +pub const DIRECTORY_CREATE_OBJECT: DWORD = 0x0004; +pub const DIRECTORY_CREATE_SUBDIRECTORY: DWORD = 0x0008; +pub const DIRECTORY_ALL_ACCESS: DWORD = STANDARD_RIGHTS_REQUIRED | 0xF; + +pub const OBJDIR_INFORMATION = extern struct { + ObjectName: UNICODE_STRING, + ObjectTypeName: UNICODE_STRING, + Data: [1]BYTE, +}; + +pub const SYMBOLIC_LINK_QUERY: DWORD = 0x0001; +pub const SYMBOLIC_LINK_ALL_ACCESS: DWORD = STANDARD_RIGHTS_REQUIRED | 0x1; \ No newline at end of file diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 5edad85c20..c41e613abb 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -106,3 +106,37 @@ pub extern "NtDll" fn NtWaitForKeyedEvent( Alertable: BOOLEAN, Timeout: ?*LARGE_INTEGER, ) callconv(.Stdcall) NTSTATUS; + +pub extern "NtDll" fn NtQueryObject( + Handle: HANDLE, + ObjectInformationClass: OBJECT_INFORMATION_CLASS, + ObjectInformation: *c_void, + ObjectInformationLength: ULONG, + ReturnLength: *ULONG, +) callconv(.Stdcall) NTSTATUS; + +pub extern "NtDll" fn NtOpenSymbolicLinkObject( + pHandle: *HANDLE, + DesiredAccess: DWORD, + ObjectAttributes: OBJECT_ATTRIBUTES, +) callconv(.Stdcall) NTSTATUS; +pub extern "NtDll" fn NtQuerySymbolicLinkObject( + SymbolicLinkHandle: HANDLE, + pLinkName: *UNICODE_STRING, + pDataWritten: ?*ULONG, +) callconv(.Stdcall) NTSTATUS; + +pub extern "NtDll" fn NtOpenDirectoryObject( + DirectoryObjectHandle: *HANDLE, + DesiredAccess: DWORD, + ObjectAttributes: OBJECT_ATTRIBUTES, +) callconv(.Stdcall) NTSTATUS; +pub extern "NtDll" fn NtQueryDirectoryObject( + DirectoryHandle: HANDLE, + Buffer: ?*c_void, + Length: ULONG, + ReturnSingleEntry: BOOLEAN, + RestartScan: BOOLEAN, + Context: *ULONG, + ReturnLength: *ULONG, +) callconv(.Stdcall) NTSTATUS; \ No newline at end of file From 2628a8846e429ba7f51927985733f8e33f3b6a65 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 5 Aug 2020 18:34:44 +0200 Subject: [PATCH 2/8] Use NtQueryInformationFile unless unavailable Favour newer API which uses `NtQueryInformationFile` with class flags `FileNormalizedNameInformation` and `FileVolumeNameInformation` instead of lower-level `NtQueryObject`. `NtQueryObject` is still used as a fallback in case the former are unavailable. --- lib/std/os/windows.zig | 70 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index d803c8ae83..d3d3c4f210 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -907,18 +907,61 @@ pub const GetFinalPathNameByHandleError = error{ /// Win32 namespace, however, '\\?\' prefix is *not* prepended to the result. /// TODO support other namespaces/volume names. pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNameByHandleError![]u16 { - // The implementation is based on implementation found in Wine sources: - // [LINK] - var buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + MAX_PATH * 2]u8 = undefined; - var dummy: ULONG = undefined; - var rc = ntdll.NtQueryObject(hFile, OBJECT_INFORMATION_CLASS.ObjectNameInformation, &buffer, buffer.len, &dummy); - switch (rc) { - .SUCCESS => {}, - else => return unexpectedStatus(rc), - } + // The implementation is based on implementation found in Wine sources, however, + // we make the following tweaks. First of all, if `NtQueryInformationFile` supports + // `FILE_INFORMATION_CLASS.FileNormalizedNameInformation` and `FileVolumeNameInformation`, + // we use those two calls to generate a valid Win32/DOS path. Otherwise, we fallback to + // more widely supported `NtQueryObject` generic routine. + // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/file.c#l1708 + var buffer: [PATH_MAX_WIDE]u16 = undefined; + const object_path = blk: { + var path_buffer: [PATH_MAX_WIDE * 2]u8 = undefined; + var io: IO_STATUS_BLOCK = undefined; + var rc = ntdll.NtQueryInformationFile(hFile, &io, &path_buffer, path_buffer.len, FILE_INFORMATION_CLASS.FileNormalizedNameInformation); + switch (rc) { + .SUCCESS => { + var nt_volume_buffer: [MAX_PATH]u8 = undefined; + rc = ntdll.NtQueryInformationFile(hFile, &io, &nt_volume_buffer, nt_volume_buffer.len, FILE_INFORMATION_CLASS.FileVolumeNameInformation); + switch (rc) { + .SUCCESS => { + const file_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &path_buffer[0])); + const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..file_name.FileNameLength / 2]; - const object_name = @ptrCast(*const OBJECT_NAME_INFORMATION, @alignCast(@alignOf(OBJECT_NAME_INFORMATION), &buffer)); - const object_path = @as([*]const u16, object_name.Name.Buffer)[0..object_name.Name.Length / 2]; + if (file_name_u16.len > PATH_MAX_WIDE) return error.NameTooLong; + + const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &nt_volume_buffer[0])); + const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0..volume_name.FileNameLength / 2]; + + std.mem.copy(u16, buffer[0..], volume_name_u16); + std.mem.copy(u16, buffer[volume_name_u16.len..], file_name_u16); + + break :blk buffer[0..volume_name_u16.len + file_name_u16.len]; + }, + .INVALID_PARAMETER => {}, // fall through + else => return unexpectedStatus(rc), + } + }, + .INVALID_PARAMETER => {}, // fall through + else => return unexpectedStatus(rc), + } + + var dummy: ULONG = undefined; + rc = ntdll.NtQueryObject(hFile, OBJECT_INFORMATION_CLASS.ObjectNameInformation, &path_buffer, path_buffer.len, &dummy); + switch (rc) { + .SUCCESS => {}, + else => return unexpectedStatus(rc), + } + + const object_name = @ptrCast(*const OBJECT_NAME_INFORMATION, @alignCast(@alignOf(OBJECT_NAME_INFORMATION), &buffer)); + + // Apparently, `NtQueryObject` has a bug when signalling an error condition. + // To check for error, i.e., FileNotFound, we check if the result Buffer is non null + // and Length is greater than zero. + // Source: https://stackoverflow.com/questions/65170/how-to-get-name-associated-with-open-handle + if (object_name.Name.Length == 0) return error.FileNotFound; + + break :blk @as([*]const u16, object_name.Name.Buffer)[0..object_name.Name.Length / 2]; + }; // Since `NtQueryObject` returns a fully-qualified NT path, we need to translate // the result into a Win32/DOS path (e.g., \Device\HarddiskVolume4\foo would become @@ -927,7 +970,7 @@ pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNa var query_path = [_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\', 'C', ':' }; for (dos_drive_letters) |drive_letter| { const drive = &[_]u16{ drive_letter, ':' }; - std.mem.copy(u16, query_path[12..], drive[0..]); + std.mem.copy(u16, query_path[query_path.len - 2..], drive[0..]); var sym_handle: HANDLE = undefined; const len_bytes = @intCast(u16, query_path.len) * 2; @@ -944,7 +987,7 @@ pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNa .SecurityDescriptor = null, .SecurityQualityOfService = null, }; - rc = ntdll.NtOpenSymbolicLinkObject(&sym_handle, SYMBOLIC_LINK_QUERY, attr); + var rc = ntdll.NtOpenSymbolicLinkObject(&sym_handle, SYMBOLIC_LINK_QUERY, attr); switch (rc) { .SUCCESS => {}, .OBJECT_NAME_NOT_FOUND => continue, @@ -967,6 +1010,7 @@ pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNa const link_path = @as([*]const u16, link.Buffer)[0..link.Length / 2]; const idx = std.mem.indexOf(u16, object_path, link_path) orelse continue; + // TODO check the provided buffer is actually big enough. std.mem.copy(u16, out_buffer[0..], drive[0..]); std.mem.copy(u16, out_buffer[2..], object_path[link_path.len..]); From e8abfef2aa8579121fbd13b8df84e8f68b533fa4 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 5 Aug 2020 19:10:10 +0200 Subject: [PATCH 3/8] Add docs --- lib/std/os/windows.zig | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index d3d3c4f210..03dbcdbd02 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -898,7 +898,6 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { pub const GetFinalPathNameByHandleError = error{ FileNotFound, - SystemResources, NameTooLong, Unexpected, }; @@ -963,9 +962,19 @@ pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNa break :blk @as([*]const u16, object_name.Name.Buffer)[0..object_name.Name.Length / 2]; }; - // Since `NtQueryObject` returns a fully-qualified NT path, we need to translate - // the result into a Win32/DOS path (e.g., \Device\HarddiskVolume4\foo would become - // C:\foo). + // By now, we got a a fully-qualified NT path, which we need to translate + // into a Win32/DOS path, for instance: + // \Device\HarddiskVolume4\foo => C:\foo + // + // NOTE: + // I couldn't figure out a better way of doing this unfortunately... + // This snippet below is in part based around `QueryDosDeviceW` implementation + // found in Wine. The trick with `NtQueryDirectoryObject` for some reason + // only lists `\DosDevices\Global` as the only SymblinkObject available, which + // means it's not possible to query the kernel for all available DOS volume name + // symlinks. + // TODO investigate! + // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/volume.c#l1009 const dos_drive_letters = &[_]u16{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; var query_path = [_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\', 'C', ':' }; for (dos_drive_letters) |drive_letter| { @@ -1010,14 +1019,17 @@ pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNa const link_path = @as([*]const u16, link.Buffer)[0..link.Length / 2]; const idx = std.mem.indexOf(u16, object_path, link_path) orelse continue; - // TODO check the provided buffer is actually big enough. + // TODO is this the most appropriate error here? + if (out_buffer.len < drive.len + object_path.len) return error.NameTooLong; + std.mem.copy(u16, out_buffer[0..], drive[0..]); std.mem.copy(u16, out_buffer[2..], object_path[link_path.len..]); return out_buffer[0..object_path.len - link_path.len + 2]; } - // If we're here, that means there was no match so error out! + // If we're here, that means there was no match so we panic! + // TODO should we actually panic here or return an error instead? unreachable; } From bdda8fa7a818c90803fb64f9784040bf3b9f0a5e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 6 Aug 2020 18:51:17 +0200 Subject: [PATCH 4/8] Redo GetFinalPathNameByHandle using DeviceIoControl This commit reimagines `std.os.windows.GetFinalPathNameByHandle` using `DeviceIoControl` to query the OS mount manager for the DOS (symlink) paths for the given NT volume name. In particular, it uses `IOCTL_MOUNTMGR_QUERY_POINTS` ioctl opcode to query the manager for the available moount points. --- lib/std/os.zig | 2 +- lib/std/os/windows.zig | 259 ++++++++++++++++++----------------- lib/std/os/windows/bits.zig | 38 ++--- lib/std/os/windows/ntdll.zig | 34 ----- 4 files changed, 149 insertions(+), 184 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 88ce77dc56..04c2340cad 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4094,7 +4094,7 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPat defer w.CloseHandle(h_file); var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; - const wide_slice = try w.GetFinalPathNameByHandle(h_file, wide_buf[0..]); + const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, wide_buf[0..]); // Trust that Windows gives us valid UTF-16LE. const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 03dbcdbd02..c45f1c1fd8 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -902,135 +902,142 @@ pub const GetFinalPathNameByHandleError = error{ Unexpected, }; -/// Returns canonical (normalized) path of handle. The output path assumes -/// Win32 namespace, however, '\\?\' prefix is *not* prepended to the result. -/// TODO support other namespaces/volume names. -pub fn GetFinalPathNameByHandle(hFile: HANDLE, out_buffer: []u16) GetFinalPathNameByHandleError![]u16 { - // The implementation is based on implementation found in Wine sources, however, - // we make the following tweaks. First of all, if `NtQueryInformationFile` supports - // `FILE_INFORMATION_CLASS.FileNormalizedNameInformation` and `FileVolumeNameInformation`, - // we use those two calls to generate a valid Win32/DOS path. Otherwise, we fallback to - // more widely supported `NtQueryObject` generic routine. - // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/file.c#l1708 - var buffer: [PATH_MAX_WIDE]u16 = undefined; - const object_path = blk: { - var path_buffer: [PATH_MAX_WIDE * 2]u8 = undefined; - var io: IO_STATUS_BLOCK = undefined; - var rc = ntdll.NtQueryInformationFile(hFile, &io, &path_buffer, path_buffer.len, FILE_INFORMATION_CLASS.FileNormalizedNameInformation); - switch (rc) { - .SUCCESS => { - var nt_volume_buffer: [MAX_PATH]u8 = undefined; - rc = ntdll.NtQueryInformationFile(hFile, &io, &nt_volume_buffer, nt_volume_buffer.len, FILE_INFORMATION_CLASS.FileVolumeNameInformation); - switch (rc) { - .SUCCESS => { - const file_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &path_buffer[0])); - const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..file_name.FileNameLength / 2]; +/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. +/// Defaults to DOS volume names. +pub const GetFinalPathNameByHandleFormat = struct { + volume_name: enum { + /// Format as DOS volume name + Dos, + /// Format as NT volume name + Nt, + } = .Dos, +}; - if (file_name_u16.len > PATH_MAX_WIDE) return error.NameTooLong; - - const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &nt_volume_buffer[0])); - const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0..volume_name.FileNameLength / 2]; - - std.mem.copy(u16, buffer[0..], volume_name_u16); - std.mem.copy(u16, buffer[volume_name_u16.len..], file_name_u16); - - break :blk buffer[0..volume_name_u16.len + file_name_u16.len]; - }, - .INVALID_PARAMETER => {}, // fall through - else => return unexpectedStatus(rc), - } - }, - .INVALID_PARAMETER => {}, // fall through - else => return unexpectedStatus(rc), - } - - var dummy: ULONG = undefined; - rc = ntdll.NtQueryObject(hFile, OBJECT_INFORMATION_CLASS.ObjectNameInformation, &path_buffer, path_buffer.len, &dummy); - switch (rc) { - .SUCCESS => {}, - else => return unexpectedStatus(rc), - } - - const object_name = @ptrCast(*const OBJECT_NAME_INFORMATION, @alignCast(@alignOf(OBJECT_NAME_INFORMATION), &buffer)); - - // Apparently, `NtQueryObject` has a bug when signalling an error condition. - // To check for error, i.e., FileNotFound, we check if the result Buffer is non null - // and Length is greater than zero. - // Source: https://stackoverflow.com/questions/65170/how-to-get-name-associated-with-open-handle - if (object_name.Name.Length == 0) return error.FileNotFound; - - break :blk @as([*]const u16, object_name.Name.Buffer)[0..object_name.Name.Length / 2]; - }; - - // By now, we got a a fully-qualified NT path, which we need to translate - // into a Win32/DOS path, for instance: - // \Device\HarddiskVolume4\foo => C:\foo - // - // NOTE: - // I couldn't figure out a better way of doing this unfortunately... - // This snippet below is in part based around `QueryDosDeviceW` implementation - // found in Wine. The trick with `NtQueryDirectoryObject` for some reason - // only lists `\DosDevices\Global` as the only SymblinkObject available, which - // means it's not possible to query the kernel for all available DOS volume name - // symlinks. - // TODO investigate! - // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/volume.c#l1009 - const dos_drive_letters = &[_]u16{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; - var query_path = [_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\', 'C', ':' }; - for (dos_drive_letters) |drive_letter| { - const drive = &[_]u16{ drive_letter, ':' }; - std.mem.copy(u16, query_path[query_path.len - 2..], drive[0..]); - - var sym_handle: HANDLE = undefined; - const len_bytes = @intCast(u16, query_path.len) * 2; - var nt_name = UNICODE_STRING{ - .Length = len_bytes, - .MaximumLength = len_bytes, - .Buffer = @intToPtr([*]u16, @ptrToInt(&query_path)), - }; - var attr = OBJECT_ATTRIBUTES{ - .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = null, - .Attributes = 0, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - var rc = ntdll.NtOpenSymbolicLinkObject(&sym_handle, SYMBOLIC_LINK_QUERY, attr); - switch (rc) { - .SUCCESS => {}, - .OBJECT_NAME_NOT_FOUND => continue, - else => return unexpectedStatus(rc), - } - - var link_buffer: [MAX_PATH]u8 = undefined; - var link = UNICODE_STRING{ - .Length = 0, - .MaximumLength = MAX_PATH, - .Buffer = @intToPtr([*]u16, @ptrToInt(&link_buffer[0])), - }; - rc = ntdll.NtQuerySymbolicLinkObject(sym_handle, &link, null); - CloseHandle(sym_handle); - switch (rc) { - .SUCCESS => {}, - else => return unexpectedStatus(rc), - } - - const link_path = @as([*]const u16, link.Buffer)[0..link.Length / 2]; - const idx = std.mem.indexOf(u16, object_path, link_path) orelse continue; - - // TODO is this the most appropriate error here? - if (out_buffer.len < drive.len + object_path.len) return error.NameTooLong; - - std.mem.copy(u16, out_buffer[0..], drive[0..]); - std.mem.copy(u16, out_buffer[2..], object_path[link_path.len..]); - - return out_buffer[0..object_path.len - link_path.len + 2]; +/// Returns canonical (normalized) path of handle. +/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include +/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). +/// If DOS volume name format is selected, note that this function does *not* prepend +/// `\\?\` prefix to the resultant path. +pub fn GetFinalPathNameByHandle( + hFile: HANDLE, + fmt: GetFinalPathNameByHandleFormat, + out_buffer: []u16, +) GetFinalPathNameByHandleError![]u16 { + // Get normalized path; doesn't include volume name though. + var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2 + 2]u8 = undefined; + var io: IO_STATUS_BLOCK = undefined; + var rc = ntdll.NtQueryInformationFile(hFile, &io, &path_buffer, path_buffer.len, FILE_INFORMATION_CLASS.FileNormalizedNameInformation); + switch (rc) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + else => return unexpectedStatus(rc), } - // If we're here, that means there was no match so we panic! - // TODO should we actually panic here or return an error instead? - unreachable; + // Get NT volume name. + var volume_buffer: [MAX_PATH]u8 = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name + rc = ntdll.NtQueryInformationFile(hFile, &io, &volume_buffer, volume_buffer.len, FILE_INFORMATION_CLASS.FileVolumeNameInformation); + switch (rc) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + else => return unexpectedStatus(rc), + } + + const file_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &path_buffer[0])); + const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2]; + + const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &volume_buffer[0])); + + switch (fmt.volume_name) { + .Nt => { + // Nothing to do, we simply copy the bytes to the user-provided buffer. + const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2]; + + if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; + + std.mem.copy(u16, out_buffer[0..], volume_name_u16); + std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); + + return out_buffer[0 .. volume_name_u16.len + file_name_u16.len]; + }, + .Dos => { + // Get DOS volume name. DOS volume names are actually symbolic link objects to the + // actual NT volume. For example: + // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: + const MIN_SIZE = @sizeOf(MOUNTMGR_MOUNT_POINT) + MAX_PATH; + // We initialize the input buffer to all zeros for convenience since + // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this. + var input_buf = [_]u8{0} ** MIN_SIZE; + var output_buf: [MIN_SIZE * 4]u8 = undefined; + + // This surprising path is a filesystem path to the mount manager on Windows. + // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points + const mgmt_path = "\\MountPointManager"; + const mgmt_path_u16 = sliceToPrefixedFileW(mgmt_path) catch unreachable; + const mgmt_handle = OpenFile(mgmt_path_u16.span(), .{ + .access_mask = SYNCHRONIZE, + .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE, + .creation = FILE_OPEN, + .io_mode = .blocking, + }) catch |err| switch (err) { + error.IsDir => unreachable, + error.NotDir => unreachable, + error.NoDevice => unreachable, + error.AccessDenied => unreachable, + error.PipeBusy => unreachable, + error.PathAlreadyExists => unreachable, + error.WouldBlock => unreachable, + else => |e| return e, + }; + defer CloseHandle(mgmt_handle); + + var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, @alignCast(@alignOf(MOUNTMGR_MOUNT_POINT), &input_buf[0])); + input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT); + input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength); + @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength); + + try DeviceIoControl( + mgmt_handle, + IOCTL_MOUNTMGR_QUERY_POINTS, + input_buf[0 .. @sizeOf(MOUNTMGR_MOUNT_POINT) + volume_name.FileNameLength], + output_buf[0..], + ); + const mount_points_struct = @ptrCast(*const MOUNTMGR_MOUNT_POINTS, @alignCast(@alignOf(MOUNTMGR_MOUNT_POINTS), &output_buf[0])); + + const mount_points = @ptrCast( + [*]const MOUNTMGR_MOUNT_POINT, + @alignCast(@alignOf(MOUNTMGR_MOUNT_POINT), &mount_points_struct.MountPoints[0]), + )[0..mount_points_struct.NumberOfMountPoints]; + + var found: bool = false; + for (mount_points) |mount_point| { + const symlink = @ptrCast( + [*]const u16, + @alignCast(@alignOf(u16), &output_buf[mount_point.SymbolicLinkNameOffset]), + )[0 .. mount_point.SymbolicLinkNameLength / 2]; + + // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks + // with traditional DOS drive letters, so pick the first one available. + const prefix = &[_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\' }; + + if (std.mem.indexOf(u16, symlink, prefix)) |idx| { + if (idx != 0) continue; + + const drive_letter = symlink[prefix.len..]; + + if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; + + std.mem.copy(u16, out_buffer[0..], drive_letter); + std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); + + return out_buffer[0 .. drive_letter.len + file_name_u16.len]; + } + } + + // If we've ended up here, then something went wrong/is corrupted in the OS, + // so error out! + return error.FileNotFound; + }, + } } pub const GetFileSizeError = error{Unexpected}; diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 74fe7040ab..516af6d4fc 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1574,28 +1574,20 @@ pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1; pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2; -pub const OBJECT_INFORMATION_CLASS = extern enum { - ObjectBasicInformation, - ObjectNameInformation, - ObjectTypeInformation, - ObjectAllInformation, - ObjectDataInformation, +pub const MOUNTMGR_MOUNT_POINT = extern struct { + SymbolicLinkNameOffset: ULONG, + SymbolicLinkNameLength: USHORT, + Reserved1: USHORT, + UniqueIdOffset: ULONG, + UniqueIdLength: USHORT, + Reserved2: USHORT, + DeviceNameOffset: ULONG, + DeviceNameLength: USHORT, + Reserved3: USHORT, }; -pub const OBJECT_NAME_INFORMATION = extern struct { - Name: UNICODE_STRING, +pub const MOUNTMGR_MOUNT_POINTS = extern struct { + Size: ULONG, + NumberOfMountPoints: ULONG, + MountPoints: [1]MOUNTMGR_MOUNT_POINT, }; - -pub const DIRECTORY_QUERY: DWORD = 0x0001; -pub const DIRECTORY_TRAVERSE: DWORD = 0x0002; -pub const DIRECTORY_CREATE_OBJECT: DWORD = 0x0004; -pub const DIRECTORY_CREATE_SUBDIRECTORY: DWORD = 0x0008; -pub const DIRECTORY_ALL_ACCESS: DWORD = STANDARD_RIGHTS_REQUIRED | 0xF; - -pub const OBJDIR_INFORMATION = extern struct { - ObjectName: UNICODE_STRING, - ObjectTypeName: UNICODE_STRING, - Data: [1]BYTE, -}; - -pub const SYMBOLIC_LINK_QUERY: DWORD = 0x0001; -pub const SYMBOLIC_LINK_ALL_ACCESS: DWORD = STANDARD_RIGHTS_REQUIRED | 0x1; \ No newline at end of file +pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; \ No newline at end of file diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index c41e613abb..5edad85c20 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -106,37 +106,3 @@ pub extern "NtDll" fn NtWaitForKeyedEvent( Alertable: BOOLEAN, Timeout: ?*LARGE_INTEGER, ) callconv(.Stdcall) NTSTATUS; - -pub extern "NtDll" fn NtQueryObject( - Handle: HANDLE, - ObjectInformationClass: OBJECT_INFORMATION_CLASS, - ObjectInformation: *c_void, - ObjectInformationLength: ULONG, - ReturnLength: *ULONG, -) callconv(.Stdcall) NTSTATUS; - -pub extern "NtDll" fn NtOpenSymbolicLinkObject( - pHandle: *HANDLE, - DesiredAccess: DWORD, - ObjectAttributes: OBJECT_ATTRIBUTES, -) callconv(.Stdcall) NTSTATUS; -pub extern "NtDll" fn NtQuerySymbolicLinkObject( - SymbolicLinkHandle: HANDLE, - pLinkName: *UNICODE_STRING, - pDataWritten: ?*ULONG, -) callconv(.Stdcall) NTSTATUS; - -pub extern "NtDll" fn NtOpenDirectoryObject( - DirectoryObjectHandle: *HANDLE, - DesiredAccess: DWORD, - ObjectAttributes: OBJECT_ATTRIBUTES, -) callconv(.Stdcall) NTSTATUS; -pub extern "NtDll" fn NtQueryDirectoryObject( - DirectoryHandle: HANDLE, - Buffer: ?*c_void, - Length: ULONG, - ReturnSingleEntry: BOOLEAN, - RestartScan: BOOLEAN, - Context: *ULONG, - ReturnLength: *ULONG, -) callconv(.Stdcall) NTSTATUS; \ No newline at end of file From 310aa87198806d695292bace8136138c0e3875ee Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 7 Aug 2020 23:21:06 +0200 Subject: [PATCH 5/8] Fix alignment issue --- lib/std/os/windows.zig | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c45f1c1fd8..544b4d7a61 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -924,7 +924,7 @@ pub fn GetFinalPathNameByHandle( out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { // Get normalized path; doesn't include volume name though. - var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2 + 2]u8 = undefined; + var path_buffer: [PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; var io: IO_STATUS_BLOCK = undefined; var rc = ntdll.NtQueryInformationFile(hFile, &io, &path_buffer, path_buffer.len, FILE_INFORMATION_CLASS.FileNormalizedNameInformation); switch (rc) { @@ -934,7 +934,7 @@ pub fn GetFinalPathNameByHandle( } // Get NT volume name. - var volume_buffer: [MAX_PATH]u8 = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name + var volume_buffer: [MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name rc = ntdll.NtQueryInformationFile(hFile, &io, &volume_buffer, volume_buffer.len, FILE_INFORMATION_CLASS.FileVolumeNameInformation); switch (rc) { .SUCCESS => {}, @@ -942,10 +942,10 @@ pub fn GetFinalPathNameByHandle( else => return unexpectedStatus(rc), } - const file_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &path_buffer[0])); + const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2]; - const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, @alignCast(@alignOf(FILE_NAME_INFORMATION), &volume_buffer[0])); + const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); switch (fmt.volume_name) { .Nt => { @@ -966,8 +966,8 @@ pub fn GetFinalPathNameByHandle( const MIN_SIZE = @sizeOf(MOUNTMGR_MOUNT_POINT) + MAX_PATH; // We initialize the input buffer to all zeros for convenience since // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this. - var input_buf = [_]u8{0} ** MIN_SIZE; - var output_buf: [MIN_SIZE * 4]u8 = undefined; + var input_buf: [MIN_SIZE]u8 align(@alignOf(MOUNTMGR_MOUNT_POINT)) = [_]u8{0} ** MIN_SIZE; + var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(MOUNTMGR_MOUNT_POINTS)) = undefined; // This surprising path is a filesystem path to the mount manager on Windows. // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points @@ -990,22 +990,17 @@ pub fn GetFinalPathNameByHandle( }; defer CloseHandle(mgmt_handle); - var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, @alignCast(@alignOf(MOUNTMGR_MOUNT_POINT), &input_buf[0])); + var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]); input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT); input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength); @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength); - try DeviceIoControl( - mgmt_handle, - IOCTL_MOUNTMGR_QUERY_POINTS, - input_buf[0 .. @sizeOf(MOUNTMGR_MOUNT_POINT) + volume_name.FileNameLength], - output_buf[0..], - ); - const mount_points_struct = @ptrCast(*const MOUNTMGR_MOUNT_POINTS, @alignCast(@alignOf(MOUNTMGR_MOUNT_POINTS), &output_buf[0])); + try DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]); + const mount_points_struct = @ptrCast(*const MOUNTMGR_MOUNT_POINTS, &output_buf[0]); const mount_points = @ptrCast( [*]const MOUNTMGR_MOUNT_POINT, - @alignCast(@alignOf(MOUNTMGR_MOUNT_POINT), &mount_points_struct.MountPoints[0]), + &mount_points_struct.MountPoints[0], )[0..mount_points_struct.NumberOfMountPoints]; var found: bool = false; From cd8e6b66d046f73e4a9a4211557f52bdc1424de7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 9 Aug 2020 10:47:52 +0200 Subject: [PATCH 6/8] Address some review comments --- lib/std/os/windows.zig | 42 +++++++++++++++++++++---------------- lib/std/os/windows/bits.zig | 2 +- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 544b4d7a61..6451c64e06 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -924,23 +924,12 @@ pub fn GetFinalPathNameByHandle( out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { // Get normalized path; doesn't include volume name though. - var path_buffer: [PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - var io: IO_STATUS_BLOCK = undefined; - var rc = ntdll.NtQueryInformationFile(hFile, &io, &path_buffer, path_buffer.len, FILE_INFORMATION_CLASS.FileNormalizedNameInformation); - switch (rc) { - .SUCCESS => {}, - .INVALID_PARAMETER => unreachable, - else => return unexpectedStatus(rc), - } + var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; + try QueryInformationFile(hFile, FILE_INFORMATION_CLASS.FileNormalizedNameInformation, path_buffer[0..]); // Get NT volume name. - var volume_buffer: [MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name - rc = ntdll.NtQueryInformationFile(hFile, &io, &volume_buffer, volume_buffer.len, FILE_INFORMATION_CLASS.FileVolumeNameInformation); - switch (rc) { - .SUCCESS => {}, - .INVALID_PARAMETER => unreachable, - else => return unexpectedStatus(rc), - } + var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name + try QueryInformationFile(hFile, FILE_INFORMATION_CLASS.FileVolumeNameInformation, volume_buffer[0..]); const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2]; @@ -1014,9 +1003,7 @@ pub fn GetFinalPathNameByHandle( // with traditional DOS drive letters, so pick the first one available. const prefix = &[_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\' }; - if (std.mem.indexOf(u16, symlink, prefix)) |idx| { - if (idx != 0) continue; - + if (std.mem.startsWith(u16, symlink, prefix)) { const drive_letter = symlink[prefix.len..]; if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; @@ -1035,6 +1022,25 @@ pub fn GetFinalPathNameByHandle( } } +pub const QueryInformationFileError = error{Unexpected}; + +pub fn QueryInformationFile( + handle: HANDLE, + info_class: FILE_INFORMATION_CLASS, + out_buffer: []u8, +) QueryInformationFileError!void { + var io: IO_STATUS_BLOCK = undefined; + const len_bytes = std.math.cast(u32, out_buffer.len) catch |err| switch (err) { + error.Overflow => std.math.maxInt(u32), // If the provided buffer is larger than what we can handle, set size to max what we can handle + }; + const rc = ntdll.NtQueryInformationFile(handle, &io, out_buffer.ptr, len_bytes, info_class); + switch (rc) { + .SUCCESS => {}, + .INVALID_PARAMETER => unreachable, + else => return unexpectedStatus(rc), + } +} + pub const GetFileSizeError = error{Unexpected}; pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 { diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 516af6d4fc..44bc1ba437 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1590,4 +1590,4 @@ pub const MOUNTMGR_MOUNT_POINTS = extern struct { NumberOfMountPoints: ULONG, MountPoints: [1]MOUNTMGR_MOUNT_POINT, }; -pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; \ No newline at end of file +pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; From 73b9f657460b040eaed4145a88f76db997d73987 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 10 Aug 2020 12:30:24 +0200 Subject: [PATCH 7/8] Validate DOS path before returning And some other minor refactors which address more review comments. --- lib/std/os/windows.zig | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 6451c64e06..cf42266d26 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -897,6 +897,7 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { } pub const GetFinalPathNameByHandleError = error{ + BadPathName, FileNotFound, NameTooLong, Unexpected, @@ -925,11 +926,11 @@ pub fn GetFinalPathNameByHandle( ) GetFinalPathNameByHandleError![]u16 { // Get normalized path; doesn't include volume name though. var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - try QueryInformationFile(hFile, FILE_INFORMATION_CLASS.FileNormalizedNameInformation, path_buffer[0..]); + try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); // Get NT volume name. var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name - try QueryInformationFile(hFile, FILE_INFORMATION_CLASS.FileVolumeNameInformation, volume_buffer[0..]); + try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]); const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2]; @@ -1010,8 +1011,14 @@ pub fn GetFinalPathNameByHandle( std.mem.copy(u16, out_buffer[0..], drive_letter); std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); + const total_len = drive_letter.len + file_name_u16.len; - return out_buffer[0 .. drive_letter.len + file_name_u16.len]; + // Validate that DOS does not contain any spurious nul bytes. + if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { + return error.BadPathName; + } + + return out_buffer[0..total_len]; } } @@ -1030,9 +1037,7 @@ pub fn QueryInformationFile( out_buffer: []u8, ) QueryInformationFileError!void { var io: IO_STATUS_BLOCK = undefined; - const len_bytes = std.math.cast(u32, out_buffer.len) catch |err| switch (err) { - error.Overflow => std.math.maxInt(u32), // If the provided buffer is larger than what we can handle, set size to max what we can handle - }; + const len_bytes = std.math.cast(u32, out_buffer.len) catch unreachable; const rc = ntdll.NtQueryInformationFile(handle, &io, out_buffer.ptr, len_bytes, info_class); switch (rc) { .SUCCESS => {}, From 901bf0a2e2821caa1087b03ed308700ec9135c87 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 10 Aug 2020 20:44:22 +0200 Subject: [PATCH 8/8] Convert prefix from u8 to u16 programmatically --- lib/std/os/windows.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index cf42266d26..c93feb20d7 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1002,7 +1002,10 @@ pub fn GetFinalPathNameByHandle( // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks // with traditional DOS drive letters, so pick the first one available. - const prefix = &[_]u16{ '\\', 'D', 'o', 's', 'D', 'e', 'v', 'i', 'c', 'e', 's', '\\' }; + const prefix_u8 = "\\DosDevices\\"; + var prefix_buf_u16: [prefix_u8.len]u16 = undefined; + const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable; + const prefix = prefix_buf_u16[0..prefix_len_u16]; if (std.mem.startsWith(u16, symlink, prefix)) { const drive_letter = symlink[prefix.len..];