diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-01-11 17:48:33 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-01-11 17:48:33 -0700 |
| commit | 56c03881ebd8617e6506bfad01bf9cfdd4d3df7e (patch) | |
| tree | 25da58e774494f28c44463921ba2f2387d8e3ae3 /lib/std/os | |
| parent | fc10c9c4ce40570d0abeda764f1bd76b3e79af61 (diff) | |
| parent | c96272f6186a5e843c0586513e87ffd771c5b00c (diff) | |
| download | zig-56c03881ebd8617e6506bfad01bf9cfdd4d3df7e.tar.gz zig-56c03881ebd8617e6506bfad01bf9cfdd4d3df7e.zip | |
Merge branch 'rohlem-fix-GetFinalPathNameByHandle-before-win10_rs4'
Merges #7379
Diffstat (limited to 'lib/std/os')
| -rw-r--r-- | lib/std/os/windows.zig | 132 | ||||
| -rw-r--r-- | lib/std/os/windows/bits.zig | 15 | ||||
| -rw-r--r-- | lib/std/os/windows/ntdll.zig | 8 |
3 files changed, 124 insertions, 31 deletions
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 57755ffa49..1ed40e1f4f 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -953,7 +953,56 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 { return @bitCast(u64, result); } +pub fn QueryObjectName( + handle: HANDLE, + out_buffer: []u16, +) ![]u16 { + const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; + + const info = @ptrCast(*OBJECT_NAME_INFORMATION, out_buffer_aligned); + //buffer size is specified in bytes + const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) catch |e| switch (e) { + error.Overflow => std.math.maxInt(ULONG), + }; + //last argument would return the length required for full_buffer, not exposed here + const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null); + switch (rc) { + .SUCCESS => { + // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) + // if the object was "unnamed", not sure if this can happen for file handles + if (info.Name.MaximumLength == 0) return error.Unexpected; + // resulting string length is specified in bytes + const path_length_unterminated = @divExact(info.Name.Length, 2); + return info.Name.Buffer[0..path_length_unterminated]; + }, + .ACCESS_DENIED => return error.AccessDenied, + .INVALID_HANDLE => return error.InvalidHandle, + // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), + // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) + .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, + else => |e| return unexpectedStatus(e), + } +} +test "QueryObjectName" { + if (comptime builtin.os.tag != .windows) + return; + + //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.fd; + var out_buffer: [PATH_MAX_WIDE]u16 = undefined; + + var result_path = try QueryObjectName(handle, &out_buffer); + const required_len_in_u16 = result_path.len + @divExact(@ptrToInt(result_path.ptr) - @ptrToInt(&out_buffer), 2) + 1; + //insufficient size + std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); + //exactly-sufficient size + _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); +} + pub const GetFinalPathNameByHandleError = error{ + AccessDenied, BadPathName, FileNotFound, NameTooLong, @@ -981,32 +1030,31 @@ pub fn GetFinalPathNameByHandle( 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]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - 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, .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]; - - const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); + const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) { + // we assume InvalidHandle is close enough to FileNotFound in semantics + // to not further complicate the error set + error.InvalidHandle => return error.FileNotFound, + else => |e| return e, + }; 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]; + // the returned path is already in .Nt format + return final_path; + }, + .Dos => { + // parse the string to separate volume path from file path + const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); - if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong; + // TODO find out if a path can start with something besides `\Device\<volume name>`, + // and if we need to handle it differently + // (i.e. how to determine the start and end of the volume name in that case) + if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected; - std.mem.copy(u16, out_buffer[0..], volume_name_u16); - std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16); + const file_path_begin_index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; + const volume_name_u16 = final_path[0..file_path_begin_index]; + const file_name_u16 = final_path[file_path_begin_index..]; - 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: @@ -1039,10 +1087,10 @@ pub fn GetFinalPathNameByHandle( 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); + input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2); + @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2); - DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) { + DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) { error.AccessDenied => unreachable, else => |e| return e, }; @@ -1062,22 +1110,20 @@ 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_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]; + var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); + const prefix = prefix_buf[0..prefix_buf.len]; - if (std.mem.startsWith(u16, symlink, prefix)) { + if (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; - std.mem.copy(u16, out_buffer[0..], drive_letter); - std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); + mem.copy(u16, out_buffer, drive_letter); + mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16); const total_len = 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)) |_| { + if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { return error.BadPathName; } @@ -1092,6 +1138,30 @@ pub fn GetFinalPathNameByHandle( } } +test "GetFinalPathNameByHandle" { + if (comptime builtin.os.tag != .windows) + return; + + //any file will do + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.fd; + var buffer: [PATH_MAX_WIDE]u16 = undefined; + + //check with sufficient size + const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer); + + const required_len_in_u16 = nt_path.len + @divExact(@ptrToInt(nt_path.ptr) - @ptrToInt(&buffer), 2) + 1; + //check with insufficient size + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); + std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1])); + + //check with exactly-sufficient size + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); +} + pub const QueryInformationFileError = error{Unexpected}; pub fn QueryInformationFile( diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 33005ef366..d8a2fb4a4d 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -1620,3 +1620,18 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008; pub const SD_RECEIVE = 0; pub const SD_SEND = 1; pub const SD_BOTH = 2; + +pub const OBJECT_INFORMATION_CLASS = extern enum { + ObjectBasicInformation = 0, + ObjectNameInformation = 1, + ObjectTypeInformation = 2, + ObjectTypesInformation = 3, + ObjectHandleFlagInformation = 4, + ObjectSessionInformation = 5, + MaxObjectInfoClass, +}; + +pub const OBJECT_NAME_INFORMATION = extern struct { + Name: UNICODE_STRING, +}; +pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 1587b4d5dd..3f76036762 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -113,3 +113,11 @@ pub extern "NtDll" fn NtWaitForKeyedEvent( ) callconv(WINAPI) NTSTATUS; pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS; + +pub extern "NtDll" fn NtQueryObject( + Handle: HANDLE, + ObjectInformationClass: OBJECT_INFORMATION_CLASS, + ObjectInformation: PVOID, + ObjectInformationLength: ULONG, + ReturnLength: ?*ULONG, +) callconv(WINAPI) NTSTATUS; |
