diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2020-08-05 17:23:35 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2020-08-06 23:56:37 +0200 |
| commit | 747d46f22c2b5bef2d111564b5e4d362228004a2 (patch) | |
| tree | d9a45754752c08ce8cd3ebc4e03d2a06347a3055 /lib/std | |
| parent | a2bb246db4c2bb88f402215d5db79a535dbff4b6 (diff) | |
| download | zig-747d46f22c2b5bef2d111564b5e4d362228004a2.tar.gz zig-747d46f22c2b5bef2d111564b5e4d362228004a2.zip | |
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.
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/os.zig | 10 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 86 | ||||
| -rw-r--r-- | lib/std/os/windows/bits.zig | 26 | ||||
| -rw-r--r-- | lib/std/os/windows/ntdll.zig | 34 |
4 files changed, 132 insertions, 24 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), + } + + 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]; } - return buf_ptr[0..rc :0]; + + // 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 |
