@@ -889,61 +889,28 @@ pub const ReadLinkError = error{
889889 AccessDenied ,
890890 Unexpected ,
891891 NameTooLong ,
892+ BadPathName ,
893+ AntivirusInterference ,
892894 UnsupportedReparsePointType ,
893895};
894896
895- pub fn ReadLink (dir : ? HANDLE , sub_path_w : []const u16 , out_buffer : []u8 ) ReadLinkError ! []u8 {
896- // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.
897- // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that
898- // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible
899- // to open the symlink there and then.
900- const path_len_bytes = math .cast (u16 , sub_path_w .len * 2 ) orelse return error .NameTooLong ;
901- var nt_name = UNICODE_STRING {
902- .Length = path_len_bytes ,
903- .MaximumLength = path_len_bytes ,
904- .Buffer = @constCast (sub_path_w .ptr ),
905- };
906- var attr = OBJECT_ATTRIBUTES {
907- .Length = @sizeOf (OBJECT_ATTRIBUTES ),
908- .RootDirectory = if (std .fs .path .isAbsoluteWindowsWtf16 (sub_path_w )) null else dir ,
909- .Attributes = 0 , // Note we do not use OBJ_CASE_INSENSITIVE here.
910- .ObjectName = & nt_name ,
911- .SecurityDescriptor = null ,
912- .SecurityQualityOfService = null ,
897+ /// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it
898+ /// is safe to reuse a single buffer for both.
899+ pub fn ReadLink (dir : ? HANDLE , sub_path_w : []const u16 , out_buffer : []u16 ) ReadLinkError ! []u16 {
900+ const result_handle = OpenFile (sub_path_w , .{
901+ .access_mask = FILE_READ_ATTRIBUTES | SYNCHRONIZE ,
902+ .dir = dir ,
903+ .creation = FILE_OPEN ,
904+ .follow_symlinks = false ,
905+ .filter = .any ,
906+ }) catch | err | switch (err ) {
907+ error .IsDir , error .NotDir = > return error .Unexpected , // filter = .any
908+ error .PathAlreadyExists = > return error .Unexpected , // FILE_OPEN
909+ error .WouldBlock = > return error .Unexpected ,
910+ error .NoDevice = > return error .FileNotFound ,
911+ error .PipeBusy = > return error .AccessDenied ,
912+ else = > | e | return e ,
913913 };
914- var result_handle : HANDLE = undefined ;
915- var io : IO_STATUS_BLOCK = undefined ;
916-
917- const rc = ntdll .NtCreateFile (
918- & result_handle ,
919- FILE_READ_ATTRIBUTES | SYNCHRONIZE ,
920- & attr ,
921- & io ,
922- null ,
923- FILE_ATTRIBUTE_NORMAL ,
924- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ,
925- FILE_OPEN ,
926- FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT ,
927- null ,
928- 0 ,
929- );
930- switch (rc ) {
931- .SUCCESS = > {},
932- .OBJECT_NAME_INVALID = > unreachable ,
933- .OBJECT_NAME_NOT_FOUND = > return error .FileNotFound ,
934- .OBJECT_PATH_NOT_FOUND = > return error .FileNotFound ,
935- .NO_MEDIA_IN_DEVICE = > return error .FileNotFound ,
936- .BAD_NETWORK_PATH = > return error .NetworkNotFound , // \\server was not found
937- .BAD_NETWORK_NAME = > return error .NetworkNotFound , // \\server was found but \\server\share wasn't
938- .INVALID_PARAMETER = > unreachable ,
939- .SHARING_VIOLATION = > return error .AccessDenied ,
940- .ACCESS_DENIED = > return error .AccessDenied ,
941- .PIPE_BUSY = > return error .AccessDenied ,
942- .OBJECT_PATH_SYNTAX_BAD = > unreachable ,
943- .OBJECT_NAME_COLLISION = > unreachable ,
944- .FILE_IS_A_DIRECTORY = > unreachable ,
945- else = > return unexpectedStatus (rc ),
946- }
947914 defer CloseHandle (result_handle );
948915
949916 var reparse_buf : [MAXIMUM_REPARSE_DATA_BUFFER_SIZE ]u8 align (@alignOf (REPARSE_DATA_BUFFER )) = undefined ;
@@ -961,35 +928,33 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLin
961928 const len = buf .SubstituteNameLength >> 1 ;
962929 const path_buf = @as ([* ]const u16 , & buf .PathBuffer );
963930 const is_relative = buf .Flags & SYMLINK_FLAG_RELATIVE != 0 ;
964- return parseReadlinkPath (path_buf [offset .. ][0.. len ], is_relative , out_buffer );
931+ return parseReadLinkPath (path_buf [offset .. ][0.. len ], is_relative , out_buffer );
965932 },
966933 IO_REPARSE_TAG_MOUNT_POINT = > {
967934 const buf : * const MOUNT_POINT_REPARSE_BUFFER = @ptrCast (@alignCast (& reparse_struct .DataBuffer [0 ]));
968935 const offset = buf .SubstituteNameOffset >> 1 ;
969936 const len = buf .SubstituteNameLength >> 1 ;
970937 const path_buf = @as ([* ]const u16 , & buf .PathBuffer );
971- return parseReadlinkPath (path_buf [offset .. ][0.. len ], false , out_buffer );
938+ return parseReadLinkPath (path_buf [offset .. ][0.. len ], false , out_buffer );
972939 },
973- else = > | value | {
974- std .debug .print ("unsupported symlink type: {}" , .{value });
940+ else = > {
975941 return error .UnsupportedReparsePointType ;
976942 },
977943 }
978944}
979945
980- /// Asserts that there is enough space is `out_buffer`.
981- /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
982- fn parseReadlinkPath (path : []const u16 , is_relative : bool , out_buffer : []u8 ) []u8 {
983- const win32_namespace_path = path : {
984- if (is_relative ) break :path path ;
985- const win32_path = ntToWin32Namespace (path ) catch | err | switch (err ) {
986- error .NameTooLong = > unreachable ,
987- error .NotNtPath = > break :path path ,
946+ fn parseReadLinkPath (path : []const u16 , is_relative : bool , out_buffer : []u16 ) error {NameTooLong }! []u16 {
947+ path : {
948+ if (is_relative ) break :path ;
949+ return ntToWin32Namespace (path , out_buffer ) catch | err | switch (err ) {
950+ error .NameTooLong = > | e | return e ,
951+ error .NotNtPath = > break :path ,
988952 };
989- break :path win32_path .span ();
990- };
991- const out_len = std .unicode .wtf16LeToWtf8 (out_buffer , win32_namespace_path );
992- return out_buffer [0.. out_len ];
953+ }
954+ if (out_buffer .len < path .len ) return error .NameTooLong ;
955+ const dest = out_buffer [0.. path .len ];
956+ @memcpy (dest , path );
957+ return dest ;
993958}
994959
995960pub const DeleteFileError = error {
@@ -2620,34 +2585,31 @@ test getUnprefixedPathType {
26202585/// https://github.com/reactos/reactos/blob/master/modules/rostests/apitests/ntdll/RtlNtPathNameToDosPathName.c
26212586///
26222587/// `path` should be encoded as WTF-16LE.
2623- pub fn ntToWin32Namespace (path : []const u16 ) ! PathSpace {
2588+ ///
2589+ /// Supports in-place modification (`path` and `out` may refer to the same slice).
2590+ pub fn ntToWin32Namespace (path : []const u16 , out : []u16 ) error { NameTooLong , NotNtPath }! []u16 {
26242591 if (path .len > PATH_MAX_WIDE ) return error .NameTooLong ;
26252592
2626- var path_space : PathSpace = undefined ;
26272593 const namespace_prefix = getNamespacePrefix (u16 , path );
26282594 switch (namespace_prefix ) {
26292595 .nt = > {
26302596 var dest_index : usize = 0 ;
26312597 var after_prefix = path [4.. ]; // after the `\??\`
26322598 // The prefix \??\UNC\ means this is a UNC path, in which case the
26332599 // `\??\UNC\` should be replaced by `\\` (two backslashes)
2634- // TODO: the "UNC" should technically be matched case-insensitively, but
2635- // it's unlikely to matter since most/all paths passed into this
2636- // function will have come from the OS meaning it should have
2637- // the 'canonical' uppercase UNC.
26382600 const is_unc = after_prefix .len >= 4 and
2639- std . mem . eql ( u16 , after_prefix [0.. 3], std .unicode .utf8ToUtf16LeStringLiteral ("UNC" )) and
2601+ eqlIgnoreCaseWTF16 ( after_prefix [0.. 3], std .unicode .utf8ToUtf16LeStringLiteral ("UNC" )) and
26402602 std .fs .path .PathType .windows .isSep (u16 , std .mem .littleToNative (u16 , after_prefix [3 ]));
2603+ const win32_len = path .len - @as (usize , if (is_unc ) 6 else 4 );
2604+ if (out .len < win32_len ) return error .NameTooLong ;
26412605 if (is_unc ) {
2642- path_space . data [0 ] = comptime std .mem .nativeToLittle (u16 , '\\ ' );
2606+ out [0 ] = comptime std .mem .nativeToLittle (u16 , '\\ ' );
26432607 dest_index += 1 ;
26442608 // We want to include the last `\` of `\??\UNC\`
26452609 after_prefix = path [7.. ];
26462610 }
2647- @memcpy (path_space .data [dest_index .. ][0.. after_prefix .len ], after_prefix );
2648- path_space .len = dest_index + after_prefix .len ;
2649- path_space .data [path_space .len ] = 0 ;
2650- return path_space ;
2611+ @memmove (out [dest_index .. ][0.. after_prefix .len ], after_prefix );
2612+ return out [0.. win32_len ];
26512613 },
26522614 else = > return error .NotNtPath ,
26532615 }
@@ -2656,25 +2618,14 @@ pub fn ntToWin32Namespace(path: []const u16) !PathSpace {
26562618test ntToWin32Namespace {
26572619 const L = std .unicode .utf8ToUtf16LeStringLiteral ;
26582620
2659- try testNtToWin32Namespace (L ("UNC" ), L ("\\ ??\\ UNC" ));
2660- try testNtToWin32Namespace (L ("\\\\ " ), L ("\\ ??\\ UNC\\ " ));
2661- try testNtToWin32Namespace (L ("\\\\ path1" ), L ("\\ ??\\ UNC\\ path1" ));
2662- try testNtToWin32Namespace (L ("\\\\ path1\\ path2" ), L ("\\ ??\\ UNC\\ path1\\ path2" ));
2663-
2664- try testNtToWin32Namespace (L ("" ), L ("\\ ??\\ " ));
2665- try testNtToWin32Namespace (L ("C:" ), L ("\\ ??\\ C:" ));
2666- try testNtToWin32Namespace (L ("C:\\ " ), L ("\\ ??\\ C:\\ " ));
2667- try testNtToWin32Namespace (L ("C:\\ test" ), L ("\\ ??\\ C:\\ test" ));
2668- try testNtToWin32Namespace (L ("C:\\ test\\ " ), L ("\\ ??\\ C:\\ test\\ " ));
2621+ var mutable_unc_path_buf = L ("\\ ??\\ UNC\\ path1\\ path2" ).* ;
2622+ try std .testing .expectEqualSlices (u16 , L ("\\\\ path1\\ path2" ), try ntToWin32Namespace (& mutable_unc_path_buf , & mutable_unc_path_buf ));
26692623
2670- try std .testing .expectError (error .NotNtPath , ntToWin32Namespace (L ("foo" )));
2671- try std .testing .expectError (error .NotNtPath , ntToWin32Namespace (L ("C:\\ test" )));
2672- try std .testing .expectError (error .NotNtPath , ntToWin32Namespace (L ("\\\\ .\\ test" )));
2673- }
2624+ var mutable_path_buf = L ("\\ ??\\ C:\\ test\\ " ).* ;
2625+ try std .testing .expectEqualSlices (u16 , L ("C:\\ test\\ " ), try ntToWin32Namespace (& mutable_path_buf , & mutable_path_buf ));
26742626
2675- fn testNtToWin32Namespace (expected : []const u16 , path : []const u16 ) ! void {
2676- const converted = try ntToWin32Namespace (path );
2677- try std .testing .expectEqualSlices (u16 , expected , converted .span ());
2627+ var too_small_buf : [6 ]u16 = undefined ;
2628+ try std .testing .expectError (error .NameTooLong , ntToWin32Namespace (L ("\\ ??\\ C:\\ test" ), & too_small_buf ));
26782629}
26792630
26802631fn getFullPathNameW (path : [* :0 ]const u16 , out : []u16 ) ! usize {
0 commit comments