diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 2726dc9d3e..98a37e3d2b 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -2436,3 +2436,46 @@ test "freeing empty string with null-terminated sentinel" { const empty_string = try dupeZ(testing.allocator, u8, ""); testing.allocator.free(empty_string); } + +/// Returns a slice with the given new alignment, +/// all other pointer attributes copied from `AttributeSource`. +fn AlignedSlice(comptime AttributeSource: type, comptime new_alignment: u29) type { + const info = @typeInfo(AttributeSource).Pointer; + return @Type(.{ + .Pointer = .{ + .size = .Slice, + .is_const = info.is_const, + .is_volatile = info.is_volatile, + .is_allowzero = info.is_allowzero, + .alignment = new_alignment, + .child = info.child, + .sentinel = null, + }, + }); +} + +/// Returns the largest slice in the given bytes that conforms to the new alignment, +/// or `null` if the given bytes contain no conforming address. +pub fn alignInBytes(bytes: []u8, comptime new_alignment: usize) ?[]align(new_alignment) u8 { + const begin_address = @ptrToInt(bytes.ptr); + const end_address = begin_address + bytes.len; + + const begin_address_aligned = mem.alignForward(begin_address, new_alignment); + const new_length = std.math.sub(usize, end_address, begin_address_aligned) catch |e| switch (e) { + error.Overflow => return null, + }; + const alignment_offset = begin_address_aligned - begin_address; + return @alignCast(new_alignment, bytes[alignment_offset .. alignment_offset + new_length]); +} + +/// Returns the largest sub-slice within the given slice that conforms to the new alignment, +/// or `null` if the given slice contains no conforming address. +pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice(@TypeOf(slice), new_alignment) { + const bytes = sliceAsBytes(slice); + const aligned_bytes = alignInBytes(bytes, new_alignment) orelse return null; + + const Element = @TypeOf(slice[0]); + const slice_length_bytes = aligned_bytes.len - (aligned_bytes.len % @sizeOf(Element)); + const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]); + return @alignCast(new_alignment, aligned_slice); +} diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c20eb8ee7e..1ed40e1f4f 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -957,29 +957,29 @@ pub fn QueryObjectName( handle: HANDLE, out_buffer: []u16, ) ![]u16 { - var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined; - var info = @ptrCast(*OBJECT_NAME_INFORMATION, &full_buffer); + 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, &full_buffer, full_buffer.len, null); + 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 + // resulting string length is specified in bytes const path_length_unterminated = @divExact(info.Name.Length, 2); - if (out_buffer.len < path_length_unterminated) { - return error.NameTooLong; - } - mem.copy(WCHAR, out_buffer[0..path_length_unterminated], info.Name.Buffer[0..path_length_unterminated]); - return out_buffer[0..path_length_unterminated]; + return info.Name.Buffer[0..path_length_unterminated]; }, .ACCESS_DENIED => return error.AccessDenied, .INVALID_HANDLE => return error.InvalidHandle, - .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong, - //name_buffer.len >= @sizeOf(OBJECT_NAME_INFORMATION) holds statically - .INFO_LENGTH_MISMATCH => unreachable, + // 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), } } @@ -994,10 +994,11 @@ test "QueryObjectName" { 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 .. result_path.len - 1])); + std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); //exactly-sufficient size - _ = try QueryObjectName(handle, out_buffer[0..result_path.len]); + _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); } pub const GetFinalPathNameByHandleError = error{ @@ -1029,8 +1030,7 @@ pub fn GetFinalPathNameByHandle( fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { - var path_buffer: [math.max(@sizeOf(FILE_NAME_INFORMATION), @sizeOf(OBJECT_NAME_INFORMATION)) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; - const final_path = QueryObjectName(hFile, mem.bytesAsSlice(u16, &path_buffer)) catch |err| switch (err) { + 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, @@ -1040,11 +1040,7 @@ pub fn GetFinalPathNameByHandle( switch (fmt.volume_name) { .Nt => { // the returned path is already in .Nt format - if (out_buffer.len < final_path.len) { - return error.NameTooLong; - } - mem.copy(u16, out_buffer, final_path); - return out_buffer[0..final_path.len]; + return final_path; }, .Dos => { // parse the string to separate volume path from file path @@ -1153,16 +1149,17 @@ test "GetFinalPathNameByHandle" { var buffer: [PATH_MAX_WIDE]u16 = undefined; //check with sufficient size - const nt_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer)).len; - const dos_length = (try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer)).len; + 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 .. nt_length - 1])); - std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. dos_length - 1])); + 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..nt_length]); - _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..dos_length]); + _ = 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};