Function GetFinalPathNameByHandle [src]

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.

Prototype

pub fn GetFinalPathNameByHandle( hFile: HANDLE, fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16

Parameters

hFile: HANDLEfmt: GetFinalPathNameByHandleFormatout_buffer: []u16

Possible Errors

AccessDenied
BadPathName
FileNotFound
NameTooLong
Unexpected
UnrecognizedVolume

The volume does not contain a recognized file system. File system drivers might not be loaded, or the volume may be corrupt.

Example

test GetFinalPathNameByHandle { if (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(@intFromPtr(nt_path.ptr) - @intFromPtr(&buffer), 2) + 1; //check with insufficient size try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); try 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]); }

Source

pub fn GetFinalPathNameByHandle( hFile: HANDLE, fmt: GetFinalPathNameByHandleFormat, out_buffer: []u16, ) GetFinalPathNameByHandleError![]u16 { 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 => { // 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\\"); // TODO find out if a path can start with something besides `\Device\`, // 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; 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 device_name_u16 = volume_name_u16[expected_prefix.len..]; const file_name_u16 = final_path[file_path_begin_index..]; // MUP is Multiple UNC Provider, and indicates that the path is a UNC // path. In this case, the canonical UNC path can be gotten by just // dropping the \Device\Mup\ and making sure the path begins with \\ if (mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) { out_buffer[0] = '\\'; mem.copyForwards(u16, out_buffer[1..][0..file_name_u16.len], file_name_u16); return out_buffer[0 .. 1 + file_name_u16.len]; } // 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 // This is the NT namespaced version of \\.\MountPointManager const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager"); const mgmt_handle = OpenFile(mgmt_path_u16, .{ .access_mask = SYNCHRONIZE, .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, .creation = FILE_OPEN, }) catch |err| switch (err) { error.IsDir => return error.Unexpected, error.NotDir => return error.Unexpected, error.NoDevice => return error.Unexpected, error.AccessDenied => return error.Unexpected, error.PipeBusy => return error.Unexpected, error.PathAlreadyExists => return error.Unexpected, error.WouldBlock => return error.Unexpected, error.NetworkNotFound => return error.Unexpected, error.AntivirusInterference => return error.Unexpected, else => |e| return e, }; defer CloseHandle(mgmt_handle); var input_struct: *MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]); input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT); input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2); @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr))); DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) { error.AccessDenied => return error.Unexpected, else => |e| return e, }; const mount_points_struct: *const MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]); const mount_points = @as( [*]const MOUNTMGR_MOUNT_POINT, @ptrCast(&mount_points_struct.MountPoints[0]), )[0..mount_points_struct.NumberOfMountPoints]; for (mount_points) |mount_point| { const symlink = @as( [*]const u16, @ptrCast(@alignCast(&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. var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); const prefix = prefix_buf[0..prefix_buf.len]; 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; @memcpy(out_buffer[0..drive_letter.len], drive_letter); mem.copyForwards(u16, out_buffer[drive_letter.len..][0..file_name_u16.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 (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| { return error.BadPathName; } return out_buffer[0..total_len]; } else if (mountmgrIsVolumeName(symlink)) { // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d}, // then it is a volume mounted as a path rather than a drive letter. We need to // query the mount manager again to get the DOS path for the volume. // 49 is the maximum length accepted by mountmgrIsVolumeName const vol_input_size = @sizeOf(MOUNTMGR_TARGET_NAME) + (49 * 2); var vol_input_buf: [vol_input_size]u8 align(@alignOf(MOUNTMGR_TARGET_NAME)) = [_]u8{0} ** vol_input_size; // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path, // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>). // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here. const min_output_size = @sizeOf(MOUNTMGR_VOLUME_PATHS) + (PATH_MAX_WIDE * 2); var vol_output_buf: [min_output_size]u8 align(@alignOf(MOUNTMGR_VOLUME_PATHS)) = undefined; var vol_input_struct: *MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]); vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, &vol_input_buf, &vol_output_buf) catch |err| switch (err) { error.AccessDenied => return error.Unexpected, else => |e| return e, }; const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); const volume_path = std.mem.sliceTo(@as( [*]const u16, &volume_paths_struct.MultiSz, )[0 .. volume_paths_struct.MultiSzLength / 2], 0); if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong; // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where // we want to place the filename before returning. Here are the possible overlapping cases: // // out_buffer: [filename] // dest: [___(a)___] [___(b)___] // // In the case of (a), we need to copy forwards, and in the case of (b) we need // to copy backwards. We also need to do this before copying the volume path because // it could overwrite the file_name_u16 memory. const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len]; const file_name_byte_offset = @intFromPtr(file_name_u16.ptr) - @intFromPtr(out_buffer.ptr); const file_name_index = file_name_byte_offset / @sizeOf(u16); if (volume_path.len > file_name_index) mem.copyBackwards(u16, file_name_dest, file_name_u16) else mem.copyForwards(u16, file_name_dest, file_name_u16); @memcpy(out_buffer[0..volume_path.len], volume_path); const total_len = volume_path.len + file_name_u16.len; // Validate that DOS does not contain any spurious nul bytes. if (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; }, } }