Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.IO.Directory.Delete should check for the name surrogate bit on reparse tags before deciding if it should enumerate directories with reparse points #23646

Closed
Petermarcu opened this issue Sep 26, 2017 · 7 comments
Assignees
Milestone

Comments

@Petermarcu
Copy link
Member

Copied from internal bug 402782

System.IO.Directory.Delete recursive delete will not recurse through directory placeholders as it should (directory placeholders are a feature of GvFlt and the
container filter).

Christian Allred [callred] found that the Delete code "will avoid trying to enumerate directories that are any kind of reparse point, rather than just name surrogates ... It should be fixed to check for the name surrogate bit on the reparse tag, same as rmdir /s does"

See: http://ddindex/?rightProject=mscorlib&file=system%5cio%5cdirectory.cs&line=1324)

rmdir code

//
//  We now enumerate and delete children iff:
//      directory and not reparse/namesurrogate
//
if (IsDirectory( find_data.dwFileAttributes )
    && !(IsReparse( find_data.dwFileAttributes ) && IsReparseTagNameSurrogate( find_data.dwReserved0 ))) {
    ...
    //  (Enumerate, deleting files and recursing into directories)
    ...
}

return RemoveDirectoryForce(pszDirectory);

...

Rmdir /s checks the name surrogate bit using IsReparseTagNameSurrogate, which is in ntifs.h. It is defined as:

#define IsReparseTagNameSurrogate(_tag) (
((_tag) & 0x20000000)
)

If you're doing a FindFirst/FindNext, the _tag value is the dwReserved0 field of the WIN32_FIND_DATA structure. That is how rmdir /s does its check:

//
//  We now enumerate and delete children iff:
//      directory and not reparse/namesurrogate
//
if (IsDirectory( find_data.dwFileAttributes )
    && !(IsReparse( find_data.dwFileAttributes ) && IsReparseTagNameSurrogate( find_data.dwReserved0 ))) {
    ...
    //  (Enumerate, deleting files and recursing into directories)
    ...
}
return RemoveDirectoryForce(pszDirectory);

...

Forgot to mention, the name surrogate bit is set on directory junctions, symlinks, etc. So having this check correctly identifies other types of reparse points that a recursive delete would not want to recurse in to.

@JeremyKuhne
Copy link
Member

@danmosemsft Another fungible one that needs to be done.

@danmoseley
Copy link
Member

danmoseley commented Feb 7, 2018

existing code

                            // Reparse point, don't recurse, just remove. (dwReserved0 is documented for this flag)
                            if (findData.dwReserved0 == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT)
                            {
                                // Mount point. Unmount using full path plus a trailing '\'.
                                // (Note: This doesn't remove the underlying directory)
...
                                if (!Interop.Kernel32.DeleteVolumeMountPoint(mountPoint) && exception == null)
                                {
...
                                }
                            }

This seems wrong in the first place because MSDN says we should first check for FILE_ATTRIBUTE_REPARSE_POINT and we're not.

If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT attribute, this member specifies the reparse point tag.

Otherwise, this value is undefined and should not be used.

It looks like the IsReparseTagNameSurrogate behavior is sufficiently documented for us to use.

So it seems we should add a check for FILE_ATTRIBUTE_REPARSE_POINT. If it is true then check for IsReparseTagNameSurrogate (the 29th bit, so just & 0x20000000). If that is false, treat as a regular directory. If that is true, check for IO_REPARSE_TAG_MOUNT_POINT and continue as per existing code.

Is that correct @JeremyKuhne ? If I understand correctly these directory placeholders are not name surrogates and we are expected to enumerate through them.

@danmoseley
Copy link
Member

@maryamariyan another one for you I think.

@JeremyKuhne
Copy link
Member

@danmosemsft We do check above the code you mentioned:

https://github.com/dotnet/corefx/blob/master/src/System.IO.FileSystem/src/System/IO/FileSystem.Windows.cs#L452

The answer is, yes, we need to check the flag on any reparse point and just delete (without recursing) if it is a name surrogate. Note that we do another check for reparse point status at the top level entry point for directory deletion.

@danmoseley
Copy link
Member

@maryamariyan not sure how you'd test this, perhaps a little pinvoking to make a temporary reparse point of each flavor.

@JeremyKuhne
Copy link
Member

I don't think we can create any of the non-surrogate type outside of OneDrive folders, which you'd have to set up and manually test. Hard and symbolic links are the only ones I know how to create programmatically and they are surrogates. You might want to contact Christian Allred (mentioned above) to see if they have any suggestions.

@JeremyKuhne
Copy link
Member

@maryamariyan I spoke with @danmosemsft- I'm going to take this back as we want to get the change into the system soon.

asfgit referenced this issue in apache/subversion May 22, 2019
…ative

Win32 APIs on Windows:

  svn_io_check_path()
  svn_io_check_resolved_path()
  svn_io_check_special_path()

This changeset aims for two distinct things:

1) First of all, starting from r1833621, these functions were patched to stop
   checking symlinks on Windows.  The root cause for this change has been an
   incorrect implementation of stat in APR that didn't properly distinguish
   between various types of reparse points, some of which are not symlinks.

   Providing the custom implementation allows us to remove the hack and
   properly handle such reparse points irrespectively of the APR version
   in use.

   Additional details on the core of the issue can be found here:
   - golang/go#23684
   - https://github.com/dotnet/corefx/issues/24250

2) These APIs are used in various performance-critical code paths such as
   in the core part of `svn st`.

   On Win32 the necessary answers for these particular functions can be
   obtained with a single call to GetFileAttributes(), which is much faster
   than using the generic stat implementations from APR 1.6.x and 1.7.x
   (I believe that it would be even slower in the latter case).

   A couple of quick tests show about 20%-25% improvement in the speed of
   `svn st` for a large working copy.  The improvement may be more significant
   with indexers or virus scanners, as just asking for file attributes may
   completely avoid opening a file (by retrieving the result through the
   fast I/O QueryOpen).


* subversion/libsvn_subr/io.c
  (FILE_ATTRIBUTE_TAG_INFO, FileAttributeTagInfo): Add these definitions
   for old versions of Windows SDK.
  (typedef GetFileInformationByHandleEx_t,
   get_file_information_by_handle_ex_proc): New.
  (win_init_dynamic_imports): Import `GetFileInformationByHandleEx()`.
  (win32_get_file_information_by_handle): New helper function.
  (io_win_check_path): New helper with the Win32 implementation required
   for the "check path" functions.
  (svn_io_check_path,
   svn_io_check_resolved_path,
   svn_io_check_special_path): Invoke the new helper.
  (io_check_path): Undo the workaround from r1833621 that stopped passing
   APR_FINFO_LINK when performing a stat.


git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1859732 13f79535-47bb-0310-9956-ffa450edef68
@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 2.1.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants