Commit 747d46f22c

Jakub Konka <kubkon@jakubkonka.com>
2020-08-05 17:23:35
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.
1 parent a2bb246
Changed files (4)
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
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
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};
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];
 }