aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-08-10 20:09:45 -0400
committerGitHub <noreply@github.com>2020-08-10 20:09:45 -0400
commit6325d6a48628bff89b2e243e0a1b6eedf39b0ec6 (patch)
tree54f6965f18db1a1f55e318238d15f6095bff7c23 /lib
parentbc176fb7390b4d03b786bbc567448836140a67fc (diff)
parent901bf0a2e2821caa1087b03ed308700ec9135c87 (diff)
downloadzig-6325d6a48628bff89b2e243e0a1b6eedf39b0ec6.tar.gz
zig-6325d6a48628bff89b2e243e0a1b6eedf39b0ec6.zip
Merge pull request #5993 from kubkon/getpathnamebyhandle
Implement std.os.windows.GetPathNameByHandle using NT routines only
Diffstat (limited to 'lib')
-rw-r--r--lib/std/os.zig10
-rw-r--r--lib/std/os/windows.zig160
-rw-r--r--lib/std/os/windows/bits.zig18
3 files changed, 163 insertions, 25 deletions
diff --git a/lib/std/os.zig b/lib/std/os.zig
index 06b61e8c38..04c2340cad 100644
--- a/lib/std/os.zig
+++ b/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];
}
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index 2b3dc29b04..c93feb20d7 100644
--- a/lib/std/os/windows.zig
+++ b/lib/std/os/windows.zig
@@ -897,30 +897,156 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
}
pub const GetFinalPathNameByHandleError = error{
+ BadPathName,
FileNotFound,
- SystemResources,
NameTooLong,
Unexpected,
};
-pub fn GetFinalPathNameByHandleW(
+/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`.
+/// Defaults to DOS volume names.
+pub const GetFinalPathNameByHandleFormat = struct {
+ volume_name: enum {
+ /// Format as DOS volume name
+ Dos,
+ /// Format as NT volume name
+ Nt,
+ } = .Dos,
+};
+
+/// Returns canonical (normalized) path of handle.
+/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include
+/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
+/// If DOS volume name format is selected, note that this function does *not* prepend
+/// `\\?\` prefix to the resultant path.
+pub fn GetFinalPathNameByHandle(
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),
- }
+ 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]);
+
+ 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];
+
+ 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];
+ },
+ .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:
+ const MIN_SIZE = @sizeOf(MOUNTMGR_MOUNT_POINT) + MAX_PATH;
+ // We initialize the input buffer to all zeros for convenience since
+ // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this.
+ var input_buf: [MIN_SIZE]u8 align(@alignOf(MOUNTMGR_MOUNT_POINT)) = [_]u8{0} ** MIN_SIZE;
+ var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(MOUNTMGR_MOUNT_POINTS)) = undefined;
+
+ // This surprising path is a filesystem path to the mount manager on Windows.
+ // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
+ const mgmt_path = "\\MountPointManager";
+ const mgmt_path_u16 = sliceToPrefixedFileW(mgmt_path) catch unreachable;
+ const mgmt_handle = OpenFile(mgmt_path_u16.span(), .{
+ .access_mask = SYNCHRONIZE,
+ .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
+ .creation = FILE_OPEN,
+ .io_mode = .blocking,
+ }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ error.NotDir => unreachable,
+ error.NoDevice => unreachable,
+ error.AccessDenied => unreachable,
+ error.PipeBusy => unreachable,
+ error.PathAlreadyExists => unreachable,
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
+ defer CloseHandle(mgmt_handle);
+
+ 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);
+
+ try DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]);
+ const mount_points_struct = @ptrCast(*const MOUNTMGR_MOUNT_POINTS, &output_buf[0]);
+
+ const mount_points = @ptrCast(
+ [*]const MOUNTMGR_MOUNT_POINT,
+ &mount_points_struct.MountPoints[0],
+ )[0..mount_points_struct.NumberOfMountPoints];
+
+ var found: bool = false;
+ for (mount_points) |mount_point| {
+ const symlink = @ptrCast(
+ [*]const u16,
+ @alignCast(@alignOf(u16), &output_buf[mount_point.SymbolicLinkNameOffset]),
+ )[0 .. mount_point.SymbolicLinkNameLength / 2];
+
+ // 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];
+
+ if (std.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);
+ 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)) |_| {
+ return error.BadPathName;
+ }
+
+ return out_buffer[0..total_len];
+ }
+ }
+
+ // If we've ended up here, then something went wrong/is corrupted in the OS,
+ // so error out!
+ return error.FileNotFound;
+ },
+ }
+}
+
+pub const QueryInformationFileError = error{Unexpected};
+
+pub fn QueryInformationFile(
+ handle: HANDLE,
+ info_class: FILE_INFORMATION_CLASS,
+ out_buffer: []u8,
+) QueryInformationFileError!void {
+ var io: IO_STATUS_BLOCK = undefined;
+ const len_bytes = std.math.cast(u32, out_buffer.len) catch unreachable;
+ const rc = ntdll.NtQueryInformationFile(handle, &io, out_buffer.ptr, len_bytes, info_class);
+ switch (rc) {
+ .SUCCESS => {},
+ .INVALID_PARAMETER => unreachable,
+ else => return unexpectedStatus(rc),
}
- return buf_ptr[0..rc :0];
}
pub const GetFileSizeError = error{Unexpected};
diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig
index 9f50570e3e..44bc1ba437 100644
--- a/lib/std/os/windows/bits.zig
+++ b/lib/std/os/windows/bits.zig
@@ -1573,3 +1573,21 @@ 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 MOUNTMGR_MOUNT_POINT = extern struct {
+ SymbolicLinkNameOffset: ULONG,
+ SymbolicLinkNameLength: USHORT,
+ Reserved1: USHORT,
+ UniqueIdOffset: ULONG,
+ UniqueIdLength: USHORT,
+ Reserved2: USHORT,
+ DeviceNameOffset: ULONG,
+ DeviceNameLength: USHORT,
+ Reserved3: USHORT,
+};
+pub const MOUNTMGR_MOUNT_POINTS = extern struct {
+ Size: ULONG,
+ NumberOfMountPoints: ULONG,
+ MountPoints: [1]MOUNTMGR_MOUNT_POINT,
+};
+pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;