diff options
| -rw-r--r-- | lib/std/os/windows.zig | 119 |
1 files changed, 51 insertions, 68 deletions
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 725880a4b9..d32a448ed5 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1001,19 +1001,13 @@ test "QueryObjectName" { _ = try QueryObjectName(file.handle, out_buffer[0..result_path.len]); } -pub const GetFinalPathNameByHandleError = error { - BadPathName, - FileNotFound, - NameTooLong, - Unexpected, - } - || if((comptime builtin.os.tag != .windows) or (targetVersionIsAtLeast(WindowsVersion.win10_rs4) == true)) - error {} - else - error { - AccessDenied, - SystemResources, - }; +pub const GetFinalPathNameByHandleError = error{ + AccessDenied, + BadPathName, + FileNotFound, + NameTooLong, + Unexpected, +}; /// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. /// Defaults to DOS volume names. @@ -1036,75 +1030,64 @@ pub fn GetFinalPathNameByHandle( fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { + var path_buffer: [std.math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; + 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 - var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - + var file_name_u16: []const u16 = undefined; + var volume_name_u16: []const u16 = undefined; if ((comptime (targetVersionIsAtLeast(WindowsVersion.win10_rs4) != true)) //need explicit comptime, because error returns affect return type - and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4)) { - // TODO: directly replace/emulate QueryInformationFile of .FileNormalizedNameInformation - // with ntdll instead of calling into kernel32 - // (probably using some less-powerful query and looping over path segments) - const flags: DWORD = FILE_NAME_NORMALIZED | switch(fmt.volume_name) { - .Dos => @as(DWORD, VOLUME_NAME_DOS), - .Nt => @as(DWORD, VOLUME_NAME_NT), + and !runtimeVersionIsAtLeast(WindowsVersion.win10_rs4)) + { + const final_path = QueryObjectName(hFile, std.mem.bytesAsSlice(u16, path_buffer[0..])) catch |err| return switch (err) { + error.InvalidHandle => error.FileNotFound, //close enough? + else => |e| e, }; - const wide_path_buffer = std.mem.bytesAsSlice(u16, path_buffer[0..]); - const rc = kernel32.GetFinalPathNameByHandleW(hFile, wide_path_buffer.ptr, @intCast(u32, wide_path_buffer.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, - .ACCESS_DENIED => return error.AccessDenied, //can happen in SMB sub-queries for parent path segments - .INVALID_PARAMETER => unreachable, - else => |err| return unexpectedError(err), + + if (fmt.volume_name == .Nt) { + if (out_buffer.len < final_path.len) { + return error.NameTooLong; } + std.mem.copy(u16, out_buffer[0..], final_path[0..]); + return final_path; //we can directly return the slice we received } - //in case of failure, rc == length of string INCLUDING null terminator, - if (rc > wide_path_buffer.len) return error.NameTooLong; - //in case of success, rc == length of string EXCLUDING null terminator - const result_slice = switch(fmt.volume_name) { - .Dos => blk: { - const expected_prefix = [_]u16{'\\', '\\', '?', '\\'}; - if (!std.mem.eql(u16, expected_prefix[0..], wide_path_buffer[0..expected_prefix.len])) { - return error.BadPathName; - } - break :blk wide_path_buffer[expected_prefix.len..rc:0]; - }, - //no prefix here - .Nt => wide_path_buffer[0..rc:0], - }; - if(result_slice.len > out_buffer.len) return error.NameTooLong; - std.mem.copy(u16, out_buffer[0..], result_slice); - return out_buffer[0..result_slice.len]; - } - - // Get normalized path; doesn't include volume name though. - 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..]); + //otherwise we need to parse the string for volume path for the .Dos logic below to work + const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); + if (!std.mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) { + //TODO find out if this can occur, and if we need to handle it differently + //(i.e. how to determine the end of a volume name) + return error.BadPathName; + } + const index = std.mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable; + volume_name_u16 = final_path[0..index]; + file_name_u16 = final_path[index..]; - 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]; + //fallthrough for fmt.volume_name != .Nt + } else { + // Get normalized path; doesn't include volume name though. + try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]); + const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]); + file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0..@divExact(file_name.FileNameLength, 2)]; - const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); + // Get NT volume name. + try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]); + const volume_name_info = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); + volume_name_u16 = @ptrCast([*]const u16, &volume_name_info.FileName[0])[0..@divExact(volume_name_info.FileNameLength, 2)]; - switch (fmt.volume_name) { - .Nt => { + if (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]; - }, + } + //fallthrough for fmt.volume_name != .Nt + } + + switch (fmt.volume_name) { + .Nt => unreachable, //handled above .Dos => { // Get DOS volume name. DOS volume names are actually symbolic link objects to the // actual NT volume. For example: @@ -1138,8 +1121,8 @@ 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) { error.AccessDenied => unreachable, |
