Commit 747d46f22c
Changed files (4)
lib
std
os
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];
}