@@ -897,30 +897,156 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
897
897
}
898
898
899
899
pub const GetFinalPathNameByHandleError = error {
900
+ BadPathName ,
900
901
FileNotFound ,
901
- SystemResources ,
902
902
NameTooLong ,
903
903
Unexpected ,
904
904
};
905
905
906
- pub fn GetFinalPathNameByHandleW (
906
+ /// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`.
907
+ /// Defaults to DOS volume names.
908
+ pub const GetFinalPathNameByHandleFormat = struct {
909
+ volume_name : enum {
910
+ /// Format as DOS volume name
911
+ Dos ,
912
+ /// Format as NT volume name
913
+ Nt ,
914
+ } = .Dos ,
915
+ };
916
+
917
+ /// Returns canonical (normalized) path of handle.
918
+ /// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include
919
+ /// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
920
+ /// If DOS volume name format is selected, note that this function does *not* prepend
921
+ /// `\\?\` prefix to the resultant path.
922
+ pub fn GetFinalPathNameByHandle (
907
923
hFile : HANDLE ,
908
- buf_ptr : [* ]u16 ,
909
- buf_len : DWORD ,
910
- flags : DWORD ,
911
- ) GetFinalPathNameByHandleError ! [:0 ]u16 {
912
- const rc = kernel32 .GetFinalPathNameByHandleW (hFile , buf_ptr , buf_len , flags );
913
- if (rc == 0 ) {
914
- switch (kernel32 .GetLastError ()) {
915
- .FILE_NOT_FOUND = > return error .FileNotFound ,
916
- .PATH_NOT_FOUND = > return error .FileNotFound ,
917
- .NOT_ENOUGH_MEMORY = > return error .SystemResources ,
918
- .FILENAME_EXCED_RANGE = > return error .NameTooLong ,
919
- .INVALID_PARAMETER = > unreachable ,
920
- else = > | err | return unexpectedError (err ),
921
- }
924
+ fmt : GetFinalPathNameByHandleFormat ,
925
+ out_buffer : []u16 ,
926
+ ) GetFinalPathNameByHandleError ! []u16 {
927
+ // Get normalized path; doesn't include volume name though.
928
+ var path_buffer : [@sizeOf (FILE_NAME_INFORMATION ) + PATH_MAX_WIDE * 2 ]u8 align (@alignOf (FILE_NAME_INFORMATION )) = undefined ;
929
+ try QueryInformationFile (hFile , .FileNormalizedNameInformation , path_buffer [0.. ]);
930
+
931
+ // Get NT volume name.
932
+ 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
933
+ try QueryInformationFile (hFile , .FileVolumeNameInformation , volume_buffer [0.. ]);
934
+
935
+ const file_name = @ptrCast (* const FILE_NAME_INFORMATION , & path_buffer [0 ]);
936
+ const file_name_u16 = @ptrCast ([* ]const u16 , & file_name .FileName [0 ])[0 .. file_name .FileNameLength / 2 ];
937
+
938
+ const volume_name = @ptrCast (* const FILE_NAME_INFORMATION , & volume_buffer [0 ]);
939
+
940
+ switch (fmt .volume_name ) {
941
+ .Nt = > {
942
+ // Nothing to do, we simply copy the bytes to the user-provided buffer.
943
+ const volume_name_u16 = @ptrCast ([* ]const u16 , & volume_name .FileName [0 ])[0 .. volume_name .FileNameLength / 2 ];
944
+
945
+ if (out_buffer .len < volume_name_u16 .len + file_name_u16 .len ) return error .NameTooLong ;
946
+
947
+ std .mem .copy (u16 , out_buffer [0.. ], volume_name_u16 );
948
+ std .mem .copy (u16 , out_buffer [volume_name_u16 .len .. ], file_name_u16 );
949
+
950
+ return out_buffer [0 .. volume_name_u16 .len + file_name_u16 .len ];
951
+ },
952
+ .Dos = > {
953
+ // Get DOS volume name. DOS volume names are actually symbolic link objects to the
954
+ // actual NT volume. For example:
955
+ // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
956
+ const MIN_SIZE = @sizeOf (MOUNTMGR_MOUNT_POINT ) + MAX_PATH ;
957
+ // We initialize the input buffer to all zeros for convenience since
958
+ // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this.
959
+ var input_buf : [MIN_SIZE ]u8 align (@alignOf (MOUNTMGR_MOUNT_POINT )) = [_ ]u8 {0 } ** MIN_SIZE ;
960
+ var output_buf : [MIN_SIZE * 4 ]u8 align (@alignOf (MOUNTMGR_MOUNT_POINTS )) = undefined ;
961
+
962
+ // This surprising path is a filesystem path to the mount manager on Windows.
963
+ // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
964
+ const mgmt_path = "\\ MountPointManager" ;
965
+ const mgmt_path_u16 = sliceToPrefixedFileW (mgmt_path ) catch unreachable ;
966
+ const mgmt_handle = OpenFile (mgmt_path_u16 .span (), .{
967
+ .access_mask = SYNCHRONIZE ,
968
+ .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE ,
969
+ .creation = FILE_OPEN ,
970
+ .io_mode = .blocking ,
971
+ }) catch | err | switch (err ) {
972
+ error .IsDir = > unreachable ,
973
+ error .NotDir = > unreachable ,
974
+ error .NoDevice = > unreachable ,
975
+ error .AccessDenied = > unreachable ,
976
+ error .PipeBusy = > unreachable ,
977
+ error .PathAlreadyExists = > unreachable ,
978
+ error .WouldBlock = > unreachable ,
979
+ else = > | e | return e ,
980
+ };
981
+ defer CloseHandle (mgmt_handle );
982
+
983
+ var input_struct = @ptrCast (* MOUNTMGR_MOUNT_POINT , & input_buf [0 ]);
984
+ input_struct .DeviceNameOffset = @sizeOf (MOUNTMGR_MOUNT_POINT );
985
+ input_struct .DeviceNameLength = @intCast (USHORT , volume_name .FileNameLength );
986
+ @memcpy (input_buf [@sizeOf (MOUNTMGR_MOUNT_POINT ).. ], @ptrCast ([* ]const u8 , & volume_name .FileName [0 ]), volume_name .FileNameLength );
987
+
988
+ try DeviceIoControl (mgmt_handle , IOCTL_MOUNTMGR_QUERY_POINTS , input_buf [0.. ], output_buf [0.. ]);
989
+ const mount_points_struct = @ptrCast (* const MOUNTMGR_MOUNT_POINTS , & output_buf [0 ]);
990
+
991
+ const mount_points = @ptrCast (
992
+ [* ]const MOUNTMGR_MOUNT_POINT ,
993
+ & mount_points_struct .MountPoints [0 ],
994
+ )[0.. mount_points_struct .NumberOfMountPoints ];
995
+
996
+ var found : bool = false ;
997
+ for (mount_points ) | mount_point | {
998
+ const symlink = @ptrCast (
999
+ [* ]const u16 ,
1000
+ @alignCast (@alignOf (u16 ), & output_buf [mount_point .SymbolicLinkNameOffset ]),
1001
+ )[0 .. mount_point .SymbolicLinkNameLength / 2 ];
1002
+
1003
+ // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
1004
+ // with traditional DOS drive letters, so pick the first one available.
1005
+ const prefix_u8 = "\\ DosDevices\\ " ;
1006
+ var prefix_buf_u16 : [prefix_u8 .len ]u16 = undefined ;
1007
+ const prefix_len_u16 = std .unicode .utf8ToUtf16Le (prefix_buf_u16 [0.. ], prefix_u8 [0.. ]) catch unreachable ;
1008
+ const prefix = prefix_buf_u16 [0.. prefix_len_u16 ];
1009
+
1010
+ if (std .mem .startsWith (u16 , symlink , prefix )) {
1011
+ const drive_letter = symlink [prefix .len .. ];
1012
+
1013
+ if (out_buffer .len < drive_letter .len + file_name_u16 .len ) return error .NameTooLong ;
1014
+
1015
+ std .mem .copy (u16 , out_buffer [0.. ], drive_letter );
1016
+ std .mem .copy (u16 , out_buffer [drive_letter .len .. ], file_name_u16 );
1017
+ const total_len = drive_letter .len + file_name_u16 .len ;
1018
+
1019
+ // Validate that DOS does not contain any spurious nul bytes.
1020
+ if (std .mem .indexOfScalar (u16 , out_buffer [0.. total_len ], 0 )) | _ | {
1021
+ return error .BadPathName ;
1022
+ }
1023
+
1024
+ return out_buffer [0.. total_len ];
1025
+ }
1026
+ }
1027
+
1028
+ // If we've ended up here, then something went wrong/is corrupted in the OS,
1029
+ // so error out!
1030
+ return error .FileNotFound ;
1031
+ },
1032
+ }
1033
+ }
1034
+
1035
+ pub const QueryInformationFileError = error {Unexpected };
1036
+
1037
+ pub fn QueryInformationFile (
1038
+ handle : HANDLE ,
1039
+ info_class : FILE_INFORMATION_CLASS ,
1040
+ out_buffer : []u8 ,
1041
+ ) QueryInformationFileError ! void {
1042
+ var io : IO_STATUS_BLOCK = undefined ;
1043
+ const len_bytes = std .math .cast (u32 , out_buffer .len ) catch unreachable ;
1044
+ const rc = ntdll .NtQueryInformationFile (handle , & io , out_buffer .ptr , len_bytes , info_class );
1045
+ switch (rc ) {
1046
+ .SUCCESS = > {},
1047
+ .INVALID_PARAMETER = > unreachable ,
1048
+ else = > return unexpectedStatus (rc ),
922
1049
}
923
- return buf_ptr [0.. rc :0 ];
924
1050
}
925
1051
926
1052
pub const GetFileSizeError = error {Unexpected };
0 commit comments