@@ -902,135 +902,142 @@ pub const GetFinalPathNameByHandleError = error{
902
902
Unexpected ,
903
903
};
904
904
905
- /// Returns canonical (normalized) path of handle. The output path assumes
906
- /// Win32 namespace, however, '\\?\' prefix is *not* prepended to the result.
907
- /// TODO support other namespaces/volume names.
908
- pub fn GetFinalPathNameByHandle (hFile : HANDLE , out_buffer : []u16 ) GetFinalPathNameByHandleError ! []u16 {
909
- // The implementation is based on implementation found in Wine sources, however,
910
- // we make the following tweaks. First of all, if `NtQueryInformationFile` supports
911
- // `FILE_INFORMATION_CLASS.FileNormalizedNameInformation` and `FileVolumeNameInformation`,
912
- // we use those two calls to generate a valid Win32/DOS path. Otherwise, we fallback to
913
- // more widely supported `NtQueryObject` generic routine.
914
- // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/file.c#l1708
915
- var buffer : [PATH_MAX_WIDE ]u16 = undefined ;
916
- const object_path = blk : {
917
- var path_buffer : [PATH_MAX_WIDE * 2 ]u8 = undefined ;
918
- var io : IO_STATUS_BLOCK = undefined ;
919
- var rc = ntdll .NtQueryInformationFile (hFile , & io , & path_buffer , path_buffer .len , FILE_INFORMATION_CLASS .FileNormalizedNameInformation );
920
- switch (rc ) {
921
- .SUCCESS = > {
922
- var nt_volume_buffer : [MAX_PATH ]u8 = undefined ;
923
- rc = ntdll .NtQueryInformationFile (hFile , & io , & nt_volume_buffer , nt_volume_buffer .len , FILE_INFORMATION_CLASS .FileVolumeNameInformation );
924
- switch (rc ) {
925
- .SUCCESS = > {
926
- const file_name = @ptrCast (* const FILE_NAME_INFORMATION , @alignCast (@alignOf (FILE_NAME_INFORMATION ), & path_buffer [0 ]));
927
- const file_name_u16 = @ptrCast ([* ]const u16 , & file_name .FileName [0 ])[0.. file_name .FileNameLength / 2 ];
928
-
929
- if (file_name_u16 .len > PATH_MAX_WIDE ) return error .NameTooLong ;
930
-
931
- const volume_name = @ptrCast (* const FILE_NAME_INFORMATION , @alignCast (@alignOf (FILE_NAME_INFORMATION ), & nt_volume_buffer [0 ]));
932
- const volume_name_u16 = @ptrCast ([* ]const u16 , & volume_name .FileName [0 ])[0.. volume_name .FileNameLength / 2 ];
933
-
934
- std .mem .copy (u16 , buffer [0.. ], volume_name_u16 );
935
- std .mem .copy (u16 , buffer [volume_name_u16 .len .. ], file_name_u16 );
936
-
937
- break :blk buffer [0.. volume_name_u16 .len + file_name_u16 .len ];
938
- },
939
- .INVALID_PARAMETER = > {}, // fall through
940
- else = > return unexpectedStatus (rc ),
941
- }
942
- },
943
- .INVALID_PARAMETER = > {}, // fall through
944
- else = > return unexpectedStatus (rc ),
945
- }
905
+ /// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`.
906
+ /// Defaults to DOS volume names.
907
+ pub const GetFinalPathNameByHandleFormat = struct {
908
+ volume_name : enum {
909
+ /// Format as DOS volume name
910
+ Dos ,
911
+ /// Format as NT volume name
912
+ Nt ,
913
+ } = .Dos ,
914
+ };
946
915
947
- var dummy : ULONG = undefined ;
948
- rc = ntdll .NtQueryObject (hFile , OBJECT_INFORMATION_CLASS .ObjectNameInformation , & path_buffer , path_buffer .len , & dummy );
949
- switch (rc ) {
950
- .SUCCESS = > {},
951
- else = > return unexpectedStatus (rc ),
952
- }
916
+ /// Returns canonical (normalized) path of handle.
917
+ /// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include
918
+ /// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`).
919
+ /// If DOS volume name format is selected, note that this function does *not* prepend
920
+ /// `\\?\` prefix to the resultant path.
921
+ pub fn GetFinalPathNameByHandle (
922
+ hFile : HANDLE ,
923
+ fmt : GetFinalPathNameByHandleFormat ,
924
+ out_buffer : []u16 ,
925
+ ) GetFinalPathNameByHandleError ! []u16 {
926
+ // Get normalized path; doesn't include volume name though.
927
+ var path_buffer : [@sizeOf (FILE_NAME_INFORMATION ) + PATH_MAX_WIDE * 2 + 2 ]u8 = undefined ;
928
+ var io : IO_STATUS_BLOCK = undefined ;
929
+ var rc = ntdll .NtQueryInformationFile (hFile , & io , & path_buffer , path_buffer .len , FILE_INFORMATION_CLASS .FileNormalizedNameInformation );
930
+ switch (rc ) {
931
+ .SUCCESS = > {},
932
+ .INVALID_PARAMETER = > unreachable ,
933
+ else = > return unexpectedStatus (rc ),
934
+ }
953
935
954
- const object_name = @ptrCast (* const OBJECT_NAME_INFORMATION , @alignCast (@alignOf (OBJECT_NAME_INFORMATION ), & buffer ));
936
+ // Get NT volume name.
937
+ var volume_buffer : [MAX_PATH ]u8 = undefined ; // MAX_PATH bytes should be enough since it's Windows-defined name
938
+ rc = ntdll .NtQueryInformationFile (hFile , & io , & volume_buffer , volume_buffer .len , FILE_INFORMATION_CLASS .FileVolumeNameInformation );
939
+ switch (rc ) {
940
+ .SUCCESS = > {},
941
+ .INVALID_PARAMETER = > unreachable ,
942
+ else = > return unexpectedStatus (rc ),
943
+ }
955
944
956
- // Apparently, `NtQueryObject` has a bug when signalling an error condition.
957
- // To check for error, i.e., FileNotFound, we check if the result Buffer is non null
958
- // and Length is greater than zero.
959
- // Source: https://stackoverflow.com/questions/65170/how-to-get-name-associated-with-open-handle
960
- if (object_name .Name .Length == 0 ) return error .FileNotFound ;
945
+ const file_name = @ptrCast (* const FILE_NAME_INFORMATION , @alignCast (@alignOf (FILE_NAME_INFORMATION ), & path_buffer [0 ]));
946
+ const file_name_u16 = @ptrCast ([* ]const u16 , & file_name .FileName [0 ])[0 .. file_name .FileNameLength / 2 ];
961
947
962
- break :blk @as ([* ]const u16 , object_name .Name .Buffer )[0.. object_name .Name .Length / 2 ];
963
- };
948
+ const volume_name = @ptrCast (* const FILE_NAME_INFORMATION , @alignCast (@alignOf (FILE_NAME_INFORMATION ), & volume_buffer [0 ]));
964
949
965
- // By now, we got a a fully-qualified NT path, which we need to translate
966
- // into a Win32/DOS path, for instance:
967
- // \Device\HarddiskVolume4\foo => C:\foo
968
- //
969
- // NOTE:
970
- // I couldn't figure out a better way of doing this unfortunately...
971
- // This snippet below is in part based around `QueryDosDeviceW` implementation
972
- // found in Wine. The trick with `NtQueryDirectoryObject` for some reason
973
- // only lists `\DosDevices\Global` as the only SymblinkObject available, which
974
- // means it's not possible to query the kernel for all available DOS volume name
975
- // symlinks.
976
- // TODO investigate!
977
- // Wine source: https://source.winehq.com/git/wine.git/blob/HEAD:/dlls/kernelbase/volume.c#l1009
978
- const dos_drive_letters = &[_ ]u16 { 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' };
979
- var query_path = [_ ]u16 { '\\ ' , 'D' , 'o' , 's' , 'D' , 'e' , 'v' , 'i' , 'c' , 'e' , 's' , '\\ ' , 'C' , ':' };
980
- for (dos_drive_letters ) | drive_letter | {
981
- const drive = &[_ ]u16 { drive_letter , ':' };
982
- std .mem .copy (u16 , query_path [query_path .len - 2.. ], drive [0.. ]);
983
-
984
- var sym_handle : HANDLE = undefined ;
985
- const len_bytes = @intCast (u16 , query_path .len ) * 2 ;
986
- var nt_name = UNICODE_STRING {
987
- .Length = len_bytes ,
988
- .MaximumLength = len_bytes ,
989
- .Buffer = @intToPtr ([* ]u16 , @ptrToInt (& query_path )),
990
- };
991
- var attr = OBJECT_ATTRIBUTES {
992
- .Length = @sizeOf (OBJECT_ATTRIBUTES ),
993
- .RootDirectory = null ,
994
- .Attributes = 0 ,
995
- .ObjectName = & nt_name ,
996
- .SecurityDescriptor = null ,
997
- .SecurityQualityOfService = null ,
998
- };
999
- var rc = ntdll .NtOpenSymbolicLinkObject (& sym_handle , SYMBOLIC_LINK_QUERY , attr );
1000
- switch (rc ) {
1001
- .SUCCESS = > {},
1002
- .OBJECT_NAME_NOT_FOUND = > continue ,
1003
- else = > return unexpectedStatus (rc ),
1004
- }
950
+ switch (fmt .volume_name ) {
951
+ .Nt = > {
952
+ // Nothing to do, we simply copy the bytes to the user-provided buffer.
953
+ const volume_name_u16 = @ptrCast ([* ]const u16 , & volume_name .FileName [0 ])[0 .. volume_name .FileNameLength / 2 ];
1005
954
1006
- var link_buffer : [MAX_PATH ]u8 = undefined ;
1007
- var link = UNICODE_STRING {
1008
- .Length = 0 ,
1009
- .MaximumLength = MAX_PATH ,
1010
- .Buffer = @intToPtr ([* ]u16 , @ptrToInt (& link_buffer [0 ])),
1011
- };
1012
- rc = ntdll .NtQuerySymbolicLinkObject (sym_handle , & link , null );
1013
- CloseHandle (sym_handle );
1014
- switch (rc ) {
1015
- .SUCCESS = > {},
1016
- else = > return unexpectedStatus (rc ),
1017
- }
955
+ if (out_buffer .len < volume_name_u16 .len + file_name_u16 .len ) return error .NameTooLong ;
956
+
957
+ std .mem .copy (u16 , out_buffer [0.. ], volume_name_u16 );
958
+ std .mem .copy (u16 , out_buffer [volume_name_u16 .len .. ], file_name_u16 );
959
+
960
+ return out_buffer [0 .. volume_name_u16 .len + file_name_u16 .len ];
961
+ },
962
+ .Dos = > {
963
+ // Get DOS volume name. DOS volume names are actually symbolic link objects to the
964
+ // actual NT volume. For example:
965
+ // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
966
+ const MIN_SIZE = @sizeOf (MOUNTMGR_MOUNT_POINT ) + MAX_PATH ;
967
+ // We initialize the input buffer to all zeros for convenience since
968
+ // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this.
969
+ var input_buf = [_ ]u8 {0 } ** MIN_SIZE ;
970
+ var output_buf : [MIN_SIZE * 4 ]u8 = undefined ;
971
+
972
+ // This surprising path is a filesystem path to the mount manager on Windows.
973
+ // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
974
+ const mgmt_path = "\\ MountPointManager" ;
975
+ const mgmt_path_u16 = sliceToPrefixedFileW (mgmt_path ) catch unreachable ;
976
+ const mgmt_handle = OpenFile (mgmt_path_u16 .span (), .{
977
+ .access_mask = SYNCHRONIZE ,
978
+ .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE ,
979
+ .creation = FILE_OPEN ,
980
+ .io_mode = .blocking ,
981
+ }) catch | err | switch (err ) {
982
+ error .IsDir = > unreachable ,
983
+ error .NotDir = > unreachable ,
984
+ error .NoDevice = > unreachable ,
985
+ error .AccessDenied = > unreachable ,
986
+ error .PipeBusy = > unreachable ,
987
+ error .PathAlreadyExists = > unreachable ,
988
+ error .WouldBlock = > unreachable ,
989
+ else = > | e | return e ,
990
+ };
991
+ defer CloseHandle (mgmt_handle );
992
+
993
+ var input_struct = @ptrCast (* MOUNTMGR_MOUNT_POINT , @alignCast (@alignOf (MOUNTMGR_MOUNT_POINT ), & input_buf [0 ]));
994
+ input_struct .DeviceNameOffset = @sizeOf (MOUNTMGR_MOUNT_POINT );
995
+ input_struct .DeviceNameLength = @intCast (USHORT , volume_name .FileNameLength );
996
+ @memcpy (input_buf [@sizeOf (MOUNTMGR_MOUNT_POINT ).. ], @ptrCast ([* ]const u8 , & volume_name .FileName [0 ]), volume_name .FileNameLength );
997
+
998
+ try DeviceIoControl (
999
+ mgmt_handle ,
1000
+ IOCTL_MOUNTMGR_QUERY_POINTS ,
1001
+ input_buf [0 .. @sizeOf (MOUNTMGR_MOUNT_POINT ) + volume_name .FileNameLength ],
1002
+ output_buf [0.. ],
1003
+ );
1004
+ const mount_points_struct = @ptrCast (* const MOUNTMGR_MOUNT_POINTS , @alignCast (@alignOf (MOUNTMGR_MOUNT_POINTS ), & output_buf [0 ]));
1018
1005
1019
- const link_path = @as ([* ]const u16 , link .Buffer )[0.. link .Length / 2 ];
1020
- const idx = std .mem .indexOf (u16 , object_path , link_path ) orelse continue ;
1006
+ const mount_points = @ptrCast (
1007
+ [* ]const MOUNTMGR_MOUNT_POINT ,
1008
+ @alignCast (@alignOf (MOUNTMGR_MOUNT_POINT ), & mount_points_struct .MountPoints [0 ]),
1009
+ )[0.. mount_points_struct .NumberOfMountPoints ];
1021
1010
1022
- // TODO is this the most appropriate error here?
1023
- if (out_buffer .len < drive .len + object_path .len ) return error .NameTooLong ;
1011
+ var found : bool = false ;
1012
+ for (mount_points ) | mount_point | {
1013
+ const symlink = @ptrCast (
1014
+ [* ]const u16 ,
1015
+ @alignCast (@alignOf (u16 ), & output_buf [mount_point .SymbolicLinkNameOffset ]),
1016
+ )[0 .. mount_point .SymbolicLinkNameLength / 2 ];
1024
1017
1025
- std .mem .copy (u16 , out_buffer [0.. ], drive [0.. ]);
1026
- std .mem .copy (u16 , out_buffer [2.. ], object_path [link_path .len .. ]);
1018
+ // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
1019
+ // with traditional DOS drive letters, so pick the first one available.
1020
+ const prefix = &[_ ]u16 { '\\ ' , 'D' , 'o' , 's' , 'D' , 'e' , 'v' , 'i' , 'c' , 'e' , 's' , '\\ ' };
1027
1021
1028
- return out_buffer [0.. object_path .len - link_path .len + 2 ];
1029
- }
1022
+ if (std .mem .indexOf (u16 , symlink , prefix )) | idx | {
1023
+ if (idx != 0 ) continue ;
1024
+
1025
+ const drive_letter = std .mem .trimLeft (u16 , symlink , prefix );
1026
+
1027
+ if (out_buffer .len < drive_letter .len + file_name_u16 .len ) return error .NameTooLong ;
1028
+
1029
+ std .mem .copy (u16 , out_buffer [0.. ], drive_letter );
1030
+ std .mem .copy (u16 , out_buffer [drive_letter .len .. ], file_name_u16 );
1030
1031
1031
- // If we're here, that means there was no match so we panic!
1032
- // TODO should we actually panic here or return an error instead?
1033
- unreachable ;
1032
+ return out_buffer [0 .. drive_letter .len + file_name_u16 .len ];
1033
+ }
1034
+ }
1035
+
1036
+ // If we've ended up here, then something went wrong/is corrupted in the OS,
1037
+ // so error out!
1038
+ return error .FileNotFound ;
1039
+ },
1040
+ }
1034
1041
}
1035
1042
1036
1043
pub const GetFileSizeError = error {Unexpected };
0 commit comments