@@ -1890,9 +1890,14 @@ private void Dir(
1890
1890
}
1891
1891
1892
1892
bool hidden = false ;
1893
+ bool checkReparsePoint = true ;
1893
1894
if ( ! Force )
1894
1895
{
1895
1896
hidden = ( recursiveDirectory . Attributes & FileAttributes . Hidden ) != 0 ;
1897
+
1898
+ // We've already taken the expense of initializing the Attributes property here,
1899
+ // so we can use that to avoid needing to call IsReparsePointLikeSymlink() later.
1900
+ checkReparsePoint = ( recursiveDirectory . Attributes & FileAttributes . ReparsePoint ) != 0 ;
1896
1901
}
1897
1902
1898
1903
// if "Hidden" is explicitly specified anywhere in the attribute filter, then override
@@ -1906,7 +1911,7 @@ private void Dir(
1906
1911
// c) it is not a reparse point with a target (not OneDrive or an AppX link).
1907
1912
if ( tracker == null )
1908
1913
{
1909
- if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( recursiveDirectory ) )
1914
+ if ( checkReparsePoint && InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( recursiveDirectory ) )
1910
1915
{
1911
1916
continue ;
1912
1917
}
@@ -2058,7 +2063,7 @@ string ToModeString(FileSystemInfo fileSystemInfo)
2058
2063
public static string NameString ( PSObject instance )
2059
2064
{
2060
2065
return instance ? . BaseObject is FileSystemInfo fileInfo
2061
- ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointWithTarget ( fileInfo )
2066
+ ? InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( fileInfo )
2062
2067
? $ "{ fileInfo . Name } -> { InternalSymbolicLinkLinkCodeMethods . GetTarget ( instance ) } "
2063
2068
: fileInfo . Name
2064
2069
: string . Empty ;
@@ -3098,22 +3103,31 @@ private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool
3098
3103
continueRemoval = ShouldProcess ( directory . FullName , action ) ;
3099
3104
}
3100
3105
3101
- if ( directory . Attributes . HasFlag ( FileAttributes . ReparsePoint ) )
3106
+ if ( InternalSymbolicLinkLinkCodeMethods . IsReparsePointLikeSymlink ( directory ) )
3102
3107
{
3108
+ void WriteErrorHelper ( Exception exception )
3109
+ {
3110
+ WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3111
+ }
3112
+
3103
3113
try
3104
3114
{
3105
- // TODO:
3106
- // Different symlinks seem to vary by behavior.
3107
- // In particular, OneDrive symlinks won't remove without recurse,
3108
- // but the .NET API here does not allow us to distinguish them.
3109
- // We may need to revisit using p/Invokes here to get the right behavior
3110
- directory . Delete ( ) ;
3115
+ if ( InternalTestHooks . OneDriveTestOn )
3116
+ {
3117
+ WriteErrorHelper ( new IOException ( ) ) ;
3118
+ return ;
3119
+ }
3120
+ else
3121
+ {
3122
+ // Name surrogates should just be detached.
3123
+ directory . Delete ( ) ;
3124
+ }
3111
3125
}
3112
3126
catch ( Exception e )
3113
3127
<
10000
div class="diff-text-inner"> {
3114
3128
string error = StringUtil . Format ( FileSystemProviderStrings . CannotRemoveItem , directory . FullName , e . Message ) ;
3115
3129
var exception = new IOException ( error , e ) ;
3116
- WriteError ( new ErrorRecord ( exception , errorId : "DeleteSymbolicLinkFailed" , ErrorCategory . WriteError , directory ) ) ;
3130
+ WriteErrorHelper ( exception ) ;
3117
3131
}
3118
3132
3119
3133
return ;
@@ -8024,7 +8038,7 @@ protected override bool ReleaseHandle()
8024
8038
}
8025
8039
8026
8040
// SetLastError is false as the use of this API doesn't not require GetLastError() to be called
8027
- [ DllImport ( PinvokeDllNames . FindFirstFileDllName , EntryPoint = "FindFirstFileExW" , SetLastError = false , CharSet = CharSet . Unicode ) ]
8041
+ [ DllImport ( PinvokeDllNames . FindFirstFileDllName , EntryPoint = "FindFirstFileExW" , SetLastError = true , CharSet = CharSet . Unicode ) ]
8028
8042
private static extern SafeFindHandle FindFirstFileEx ( string lpFileName , FINDEX_INFO_LEVELS fInfoLevelId , ref WIN32_FIND_DATA lpFindFileData , FINDEX_SEARCH_OPS fSearchOp , IntPtr lpSearchFilter , int dwAdditionalFlags ) ;
8029
8043
8030
8044
internal enum FINDEX_INFO_LEVELS : uint
@@ -8215,28 +8229,50 @@ internal static bool IsReparsePoint(FileSystemInfo fileInfo)
8215
8229
return fileInfo . Attributes . HasFlag ( System . IO . FileAttributes . ReparsePoint ) ;
8216
8230
}
8217
8231
8218
- internal static bool IsReparsePointWithTarget ( FileSystemInfo fileInfo )
8232
+ internal static bool IsReparsePointLikeSymlink ( FileSystemInfo fileInfo )
8219
8233
{
8220
- if ( ! IsReparsePoint ( fileInfo ) )
8234
+ #if UNIX
8235
+ // Reparse point on Unix is a symlink.
8236
+ return IsReparsePoint ( fileInfo ) ;
8237
+ #else
8238
+ if ( InternalTestHooks . OneDriveTestOn && fileInfo . Name == InternalTestHooks . OneDriveTestSymlinkName )
8221
8239
{
8222
- return false ;
8240
+ return ! InternalTestHooks . OneDriveTestRecurseOn ;
8223
8241
}
8224
- #if ! UNIX
8225
- // It is a reparse point and we should check some reparse point tags.
8226
- var data = new WIN32_FIND_DATA ( ) ;
8227
- using ( var handle = FindFirstFileEx ( fileInfo . FullName , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
8242
+
8243
+ WIN32_FIND_DATA data = default ;
8244
+ string fullPath = Path . TrimEndingDirectorySeparator ( fileInfo . FullName ) ;
8245
+ using ( var handle = FindFirstFileEx ( fullPath , FINDEX_INFO_LEVELS . FindExInfoBasic , ref data , FINDEX_SEARCH_OPS . FindExSearchNameMatch , IntPtr . Zero , 0 ) )
8228
8246
{
8247
+ if ( handle . IsInvalid )
8248
+ {
8249
+ // Our handle could be invalidated by something else touching the filesystem,
8250
+ // so ensure we deal with that possibility here
8251
+ int lastError = Marshal . GetLastWin32Error ( ) ;
8252
+ throw new Win32Exception ( lastError ) ;
8253
+ }
8254
+
8255
+ // We already have the file attribute information from our Win32 call,
8256
+ // so no need to take the expense of the FileInfo.FileAttributes call
8257
+ const int FILE_ATTRIBUTE_REPARSE_POINT = 0x0400 ;
8258
+ if ( ( data . dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) == 0 )
8259
+ {
8260
+ // Not a reparse point.
8261
+ return false ;
8262
+ }
8263
+
8229
8264
// The name surrogate bit 0x20000000 is defined in https://docs.microsoft.com/windows/win32/fileio/reparse-point-tags
8230
8265
// Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem
8231
8266
// (like symlinks and mount points).
8232
8267
// In the case of OneDrive, they are not name surrogates and would be safe to recurse into.
8233
- if ( ! handle . IsInvalid && ( data . dwReserved0 & 0x20000000 ) == 0 && ( data . dwReserved0 != IO_REPARSE_TAG_APPEXECLINK ) )
8268
+ if ( ( data . dwReserved0 & 0x20000000 ) == 0 && ( data . dwReserved0 != IO_REPARSE_TAG_APPEXECLINK ) )
8234
8269
{
8235
8270
return false ;
8236
8271
}
8237
8272
}
8238
- #endif
8273
+
8239
8274
return true ;
8275
+ #endif
8240
8276
}
8241
8277
8242
8278
internal static bool WinIsHardLink ( FileSystemInfo fileInfo )
0 commit comments