-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Fix std.os.windows.GetFinalPathNameByHandle
for os versions before win10_rs4
#7379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix std.os.windows.GetFinalPathNameByHandle
for os versions before win10_rs4
#7379
Conversation
0217461
to
7539116
Compare
lib/std/os/windows.zig
Outdated
Unexpected, | ||
}; | ||
pub fn QueryObjectName( | ||
Handle: HANDLE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lowercase parameter name.
lib/std/os/windows.zig
Outdated
pub fn QueryObjectName( | ||
Handle: HANDLE, | ||
out_buffer: []u16, | ||
ReturnLength: ?PULONG, //returned in bytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lowercase parameter name.
lib/std/os/windows.zig
Outdated
var info = @ptrCast(*OBJECT_NAME_INFORMATION, aligned_raw_buffer); | ||
info.* = undefined; //all of the fields we read will be overwritten, and the result string is placed in the same buffer after the struct | ||
const out_buffer_length = std.math.cast(ULONG, aligned_raw_buffer.len) catch |e| std.math.maxInt(u32); //we'll just use as much of it as we can | ||
const rc = ntdll.NtQueryObject(Handle, .ObjectNameInformation, out_buffer.ptr, out_buffer_length, ReturnLength); //buffer size is specified in u16... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is extremely hairy, it takes an output buffer out_buffer
where the pathname is stored and uses it to allocate a OBJECT_NAME_INFORMATION
too.
I'd simply allocate a buffer that's hopefully large enough (path_max plus the OBJECT_NAME_INFORMATION
overhead) and copy the result over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll change that. It just seemed more minimalist / less intrusive to directly use the provided buffer.
lib/std/os/windows.zig
Outdated
info.* = undefined; //all of the fields we read will be overwritten, and the result string is placed in the same buffer after the struct | ||
const out_buffer_length = std.math.cast(ULONG, aligned_raw_buffer.len) catch |e| std.math.maxInt(u32); //we'll just use as much of it as we can | ||
const rc = ntdll.NtQueryObject(Handle, .ObjectNameInformation, out_buffer.ptr, out_buffer_length, ReturnLength); //buffer size is specified in u16... | ||
switch(rc){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The missing spaces hint you're not using zig fmt
, please run it on all the files this PR is touching.
lib/std/os/windows.zig
Outdated
if (comptime builtin.os.tag != .windows) | ||
return; | ||
|
||
const file: std.fs.File = try std.fs.openSelfExe(.{}); //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redundant types are redundant.
And please keep the comments in a line above the one they refer to, not everyone has a huge monitor.
const file: std.fs.File = try std.fs.openSelfExe(.{}); //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. | |
//any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. | |
const file = try std.fs.openSelfExe(.{}); |
lib/std/os/windows.zig
Outdated
const S = struct {fn check(file: std.fs.File, buffer: anytype, fmt: std.os.windows.GetFinalPathNameByHandleFormat) !usize { | ||
const real = try std.os.windows.GetFinalPathNameByHandle(file.handle, fmt, buffer[0..]); | ||
const offset = @divExact((@ptrToInt(real.ptr) - @ptrToInt(@ptrCast([*]u16, buffer))), 2); | ||
return real.len + offset; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this offset
for? Why do you care about what's before real.ptr
?
lib/std/os/windows.zig
Outdated
const dos_length = try S.check(file, buffer[0..], .{.volume_name = .Dos}); | ||
|
||
//check with size insufficient by more than 1 character: always errors | ||
std.testing.expect(null == S.check(file, buffer[0..nt_length-1], .{.volume_name = .Nt}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std.testing.expect(null == S.check(file, buffer[0..nt_length-1], .{.volume_name = .Nt}) | |
std.testing.expectEqual(null, S.check(file, buffer[0..nt_length-1], .{.volume_name = .Nt}) |
Ditto everywhere else, this way if the check fails it'll show you the faulty value.
lib/std/os/windows.zig
Outdated
//check with size insufficient for null terminator: errors only on NtQueryObject path | ||
const queryObjectPath = !std.os.windows.runtimeVersionIsAtLeast(std.os.windows.WindowsVersion.win10_rs4); | ||
std.testing.expect(queryObjectPath == (null == S.check(file, buffer[0..nt_length], .{.volume_name = .Nt}) | ||
catch |e| switch(e) {error.NameTooLong => null, else => unreachable})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's testing.expectError
for this.
lib/std/os/windows.zig
Outdated
|
||
switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) { | ||
.SUCCESS => {}, | ||
else => |e| { //only return value STATUS_SUCCESS is documented |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else => |e| { //only return value STATUS_SUCCESS is documented | |
else => unreachable |
This API cannot fail according to the docs.
lib/std/os/windows.zig
Outdated
|
||
/// Returns whether the runtime os version is at least as new as the argument. | ||
pub fn runtimeVersionIsAtLeast(requested_version: WindowsVersion) bool { | ||
return if(targetVersionIsAtLeast(requested_version)) |fully| fully |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return if(targetVersionIsAtLeast(requested_version)) |fully| fully | |
return if(targetVersionIsAtLeast(requested_version)) orelse | |
@enumToInt(detectRuntimeVersion()) >= @enumToInt(requested_version); |
Thanks for the quick review, I might only get to it tomorrow though. |
Just wanted to say thank you to @rohlem for showing windows some love, it desperately needs it. |
…patibility The NtQueryInformationFile with .FileNormalizedNameInformation is only available in Windows 10 1803 (rs4) and later, however there is probably still another route we can go via ntdll.
Removes the call to kernel32.GetFinalPathNameByHandleW in favor of NtQueryObject, which means we can reuse the other codepath's logic for DOS naming.
7539116
to
74963e8
Compare
Rebased onto master and integrated all review comments, sorry it took so long. Edit: forgot to un-draft this earlier. |
lib/std/os/windows.zig
Outdated
//last argument would return the length required for full_buffer, not exposed here | ||
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null); | ||
return switch (rc) { | ||
.SUCCESS => if (@ptrCast(?[*]WCHAR, info.Name.Buffer)) |buffer| blk: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The if (x) |y| else return k;
pattern can be rewritten as y = x orelse return k;
, you can avoid the whole if.
This said I wouldn't expect Buffer
to be null if NtQueryObject
returns SUCCESS
.
If you want to be extra-safe add a if (info.Name.Length == 0) return &[_]u16{};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation for ObQueryNameString , which I based this on since it seems to be used under the hood, mentions a NULL
value under "Remarks" (If the given object is unnamed [...]
). So it's not just "being extra-safe", it's documented in the API.
Returning an empty string would require checking for that in the consumer code (GetFinalPathNameByHandle
). The object's name isn't the empty string in this case, it has no name - I think either returning an optional or error still makes more sense, but please tell me if you think differently.
info.MaxLength
is also set to 0
in that case, so I'm happy to change the check to that - is that an actual improvement in that case though? That is, either
if (info.Name.MaxLength == 0) error.Unexpected
else blk: {
const buffer = info.Name.Buffer;
//...
break :blk ...;
}
or
blk: {
const buffer = if(info.Name.MaxLength > 0) info.Name.Buffer else return error.Unexpected;
//...
break :blk ...;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still on the fence with the idea of having error.Unexpected
. If something like this happens, shouldn't we simply trigger a nonrecoverable runtime trap instead? Or do we intend for user-space code to be recoverable in those circumstances? If that's the case, anyone's got any ideas for a scenario when that would be the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"nonrecoverable runtime trap" (so basically @panic
?) sounds very cut-throat to me.
If the file was supplied by a user, say interactively via stdin
, maybe you just want to notify the user "this file didn't work, try another / moving it to a different path" (or something more specific if you figure out the underlying problem).
To me this seems like a fitting scenario for an error code: We failed to provide the requested service, but if/how to recover is up to the caller. That's why I made it return error.Unnamed
before.
If we don't expect this API to be used with unnamed objects, then this scenario is "unexpected". error.Unexpected
is not inherently reserved for kernel failures, so I think it fits.
But these are just my thoughts, whatever you think is best I'll implement.
lib/std/os/windows.zig
Outdated
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); | ||
//buffer size is specified in bytes | ||
const full_buffer_length = @intCast(ULONG, @sizeOf(OBJECT_NAME_INFORMATION) + std.math.min(PATH_MAX_WIDE, (out_buffer.len + 1) * 2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd simply use PATH_MAX_WIDE
, the NameTooLong
check should catch the problem below.
Nit: std.math
-> math
, it's defined at the top of this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, you're right, though now that I look at it again, with this exact size specification we could conversely remove the size check below, which would be more optimal.
But I guess the explicit check below is better in the sense of readability?
lib/std/os/windows.zig
Outdated
//buffer size is specified in bytes | ||
const full_buffer_length = @intCast(ULONG, @sizeOf(OBJECT_NAME_INFORMATION) + std.math.min(PATH_MAX_WIDE, (out_buffer.len + 1) * 2)); | ||
//last argument would return the length required for full_buffer, not exposed here | ||
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null); | |
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, &full_buffer, full_buffer.len, null); |
lib/std/os/windows.zig
Outdated
if (out_buffer.len < path_length_unterminated) { | ||
return error.NameTooLong; | ||
} | ||
std.mem.copy(WCHAR, out_buffer[0..path_length_unterminated], buffer[0..path_length_unterminated :0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The :0
is not needed and according to the docs may be harmful (and trip the missing sentinel check).
Minor nit, std.mem.
-> mem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, good eye. Strange to document that with the data structure, but I guess that means it could apply anywhere it's used...
lib/std/os/windows.zig
Outdated
const file = try std.fs.openSelfExe(.{}); | ||
defer file.close(); | ||
//make this large enough for the test runner exe path | ||
var out_buffer align(16) = std.mem.zeroes([1 << 10]u16); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a huge alignment factor! Is that really needed?
And 1 << 10
=> PATH_MAX_WIDE
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that is a leftover from earlier.
And sure, if we have the stack space for another PATH_MAX_WIDE that's fine by me.
lib/std/os/windows.zig
Outdated
if (out_buffer.len < final_path.len) { | ||
return error.NameTooLong; | ||
} | ||
std.mem.copy(u16, out_buffer[0..], final_path[0..]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std.mem.copy(u16, out_buffer[0..], final_path[0..]); | |
std.mem.copy(u16, &out_buffer, &final_path); |
lib/std/os/windows.zig
Outdated
return error.NameTooLong; | ||
} | ||
std.mem.copy(u16, out_buffer[0..], final_path[0..]); | ||
return final_path; //we can directly return the slice we received |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final_path
comes from QueryObjectName
with path_buffer
as input and that's stack-allocated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right, meant out_buffer of course, thanks.
lib/std/os/windows.zig
Outdated
const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2]; | ||
//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])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this is a precondition we expect the kernel to respect, an assert is fine.
Please use mem
instead of std.mem
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I'm not knowledgeable enough about these NT paths to say whether the returned string always starts with \Device\
, it's just the only scenario I've encountered, but I don't have any network sharing set up f.e. .
EDIT: I misread the code below, error.BadPathName
is more likely to come from the file path than the DOS volume name we resolved.
So I'll change it back to an assert here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rethinking it yet again, the way we just blindly assume the prefix could lead to nonsensical results in unsafe build modes (i.e. returning a path to a different file).
An error return makes much more sense to me. But speak up if you feel differently.
lib/std/os/windows.zig
Outdated
volume_name_u16 = final_path[0..index]; | ||
file_name_u16 = final_path[index..]; | ||
|
||
//fallthrough for fmt.volume_name != .Nt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fallthrough? The other case is handled in the other branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the "other branch" (the else at line 1065
of the if at line 1037
) is for the newer windows version... unless I'm missing something?
lib/std/os/windows.zig
Outdated
|
||
if (fmt.volume_name == .Nt) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in the (fmt.volume_name != .Nt)
branch, it'll never be executed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this is the win10_rs4 or newer
branch, which is the previous code I kept intact.
lib/std/target.zig
Outdated
17134, //win_rs4 | ||
17763, //win_rs5 | ||
18362, //win_19h1 | ||
19041, //win_20h1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so at first too, but those don't impact the NTDDI
header constant we're trying to mirror here. (Also the values are unchanged from the previous implementation.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This page is dated 2020-06-19, 19042
was released the 2020-10-20 and on the β channel the 2020-06-16.
i.e. it's outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, it seems that you're right, this repository's own sdkddkver.h
already includes more constants than I found when I was looking for them earlier. Thank you for pointing this out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI the next one will probably be 20231 (21H1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for taking on this project, @rohlem. This is good stuff. Needs a bit of cleanup, but the important parts are in place.
If we trust this new implementation, I would be inclined to maybe delete the other codepath (that needs two
ntdll
calls instead of just one) in favor of this one. (And maybe move the logic for finding DOS volume names into its own function.)
But now that I've worked on this for a day, I thought I would ask for some feedback, to make sure I'm not overlooking anything crucial.
If it works empirically (which it sounds like it does) then I would be in favor of deleting the other codepath and deleting the version checking.
lib/std/os/windows/bits.zig
Outdated
@@ -65,6 +65,7 @@ pub const SIZE_T = usize; | |||
pub const TCHAR = if (UNICODE) WCHAR else u8; | |||
pub const UINT = c_uint; | |||
pub const ULONG_PTR = usize; | |||
pub const PULONG = *ULONG; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to use *ULONG
directly rather than PULONG
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, Zig has more "specific" ways of identifying pointers *ULONG
vs [*]ULONG
. PULONG
is more ambiguous.
lib/std/os/windows.zig
Outdated
//version detection | ||
usingnamespace std.zig.system.windows; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to avoid usingnamespace
unless it's to set up the public declarations (as above). Here we should give it a name and refer to the functions therein via field access syntax.
lib/std/os/windows.zig
Outdated
const full_buffer_length = @intCast(ULONG, @sizeOf(OBJECT_NAME_INFORMATION) + std.math.min(PATH_MAX_WIDE, (out_buffer.len + 1) * 2)); | ||
//last argument would return the length required for full_buffer, not exposed here | ||
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, full_buffer[0..], full_buffer_length, null); | ||
return switch (rc) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When returning errors, prefer to put return
on multiple lines. This will make the error return trace point nicely to the error code that triggered the crash, rather than pointing to return switch (rc) {
.
lib/std/os/windows.zig
Outdated
handle: HANDLE, | ||
out_buffer: []u16, | ||
) ![]u16 { | ||
var full_buffer: [@sizeOf(OBJECT_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(OBJECT_NAME_INFORMATION)) = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why 2 buffers with a memcpy? We could pass out_buffer directly to NtQueryObject right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's what the code did originally, which was commented on here, so I changed it.
I can change it back if you want - I don't think there was a particular reason besides readability - but I don't like stepping on anyone's toes, so I'll put that in as the last commit and then we can pick whichever turns out looking nicer.
lib/std/os/windows.zig
Outdated
return; | ||
|
||
//any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. | ||
const file = try std.fs.openSelfExe(.{}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better not to entangle this test with the functionality of openSelfExe. I suggest to use std.testing.tmpDir
. You can see example usage in std/fs/test.zig
.
lib/std/os/windows.zig
Outdated
const file = try std.fs.openSelfExe(.{}); | ||
defer file.close(); | ||
//make this large enough for the test runner exe path | ||
var out_buffer align(16) = std.mem.zeroes([1 << 10]u16); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the purpose of calling mem.zeroes here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that was a "sane default" during early testing that I forgot to remove. The code shouldn't rely on the value, I'll change it to undefined
.
lib/std/os/windows.zig
Outdated
|
||
const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]); | ||
if (fmt.volume_name == .Nt) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to switch
on enums rather than if
Side note @kubkon - why is GetFinalPathNameByHandleFormat
a struct containing an enum, and not just an enum?
lib/std/os/windows.zig
Outdated
switch (fmt.volume_name) { | ||
.Nt => unreachable, //handled above |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This construct can be replaced with assert(fmt.volume_name == .Nt);
lib/std/zig/system/windows.zig
Outdated
pub fn targetVersionIsAtLeast(requested_version: WindowsVersion) ?bool { | ||
const requested = @enumToInt(requested_version); | ||
const version_range = std.builtin.os.version_range.windows; | ||
const target_min = @enumToInt(version_range.min); | ||
const target_max = @enumToInt(version_range.max); | ||
return if (target_max < requested) false else if (target_min >= requested) true else null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like duplicated code from std.Target.Os.isAtLeast
I should probably explain what the motivation was for using |
... and mem.copy operations. Requires slightly larger input buffers than result length. Add helper functions std.mem.alignInBytes and std.mem.alignInSlice.
4925dc5
to
befaeb3
Compare
Addressed all review comments, besides the one suggested |
After 83 comments it looks good now!
The helper function makes the code much easier to read and review, thanks. |
Landed in 56c0388 |
This PR fixes
std.os.windows.GetFinalPathNameByHandle
for older Windows versions, unblocking #7242 (although that might be an ongoing effort, so shouldn't be closed yet I think).#5993 removed the codepath that called the
kernel32.dll
implementation. According to some document I found, the new.FileNormalizedNameInformation
query value was only added as recently as 2018 (not even 3 years ago), meaning we dropped support for more than just Windows 7.Originally I just wanted to query the runtime OS version and reintroduce the old codepath, but then I tried the prophesized
NtQueryObject
function, and that ended up working too. Note though that it's using an undocumented value (ObjectNameInformation = 1
appears inntifs.h
, but not the documentation) and astruct
that is named appropriately but also documented somewhere else. And yet, it still works.If we trust this new implementation, I would be inclined to maybe delete the other codepath (that needs two
ntdll
calls instead of just one) in favor of this one. (And maybe move the logic for finding DOS volume names into its own function.)But now that I've worked on this for a day, I thought I would ask for some feedback, to make sure I'm not overlooking anything crucial.
Tested (see new tests in std/os/windows.zig) on my Windows 7 and an up-to-date Windows 10 machine. Things I tried with this implementation:
Also let me know if I should squish/reorganize commits in any way, I thought for documentation/discussion (and maybe unexpected troubleshooting) the commits with the kernel32 path were worth keeping for now.