diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs
index c679fbbb023dbd..b0a75243048b7d 100644
--- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateDirectory.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Runtime.InteropServices;
@@ -20,8 +19,9 @@ private static partial bool CreateDirectoryPrivate(
internal static bool CreateDirectory(string path, ref SECURITY_ATTRIBUTES lpSecurityAttributes)
{
- // We always want to add for CreateDirectory to get around the legacy 248 character limitation
- path = PathInternal.EnsureExtendedPrefix(path);
+ // If length is greater than `MaxShortDirectoryPath` we add a extended prefix to get around the legacy character limitation
+ path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
+
return CreateDirectoryPrivate(path, ref lpSecurityAttributes);
}
}
diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs
index 59f6d4992e89f0..914bef9040a54e 100644
--- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFileAttributesEx.cs
@@ -13,7 +13,7 @@ internal static partial class Kernel32
///
[LibraryImport(Libraries.Kernel32, EntryPoint = "GetFileAttributesExW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
- private static partial bool GetFileAttributesExPrivate(
+ internal static partial bool GetFileAttributesExPrivate(
string? name,
GET_FILEEX_INFO_LEVELS fileInfoLevel,
ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
diff --git a/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs b/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs
index dab4cf7a8f6c5c..ae1cf616840a19 100644
--- a/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs
+++ b/src/libraries/Common/src/System/IO/FileSystem.Attributes.Windows.cs
@@ -29,6 +29,17 @@ private static bool DirectoryExists(string? path, out int lastError)
((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0);
}
+ private static bool DirectoryExistsSlim(string? path, out int lastError)
+ {
+ Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
+ lastError = FillAttributeInfoSlim(path, ref data, returnErrorOnNotFound: true);
+
+ return
+ (lastError == 0) &&
+ (data.dwFileAttributes != -1) &&
+ ((data.dwFileAttributes & Interop.Kernel32.FileAttributes.FILE_ATTRIBUTE_DIRECTORY) != 0);
+ }
+
public static bool FileExists(string fullPath)
{
Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data = default;
@@ -49,53 +60,64 @@ public static bool FileExists(string fullPath)
/// Return the error code for not found errors?
internal static int FillAttributeInfo(string? path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound)
{
- int errorCode = Interop.Errors.ERROR_SUCCESS;
-
// Neither GetFileAttributes or FindFirstFile like trailing separators
path = PathInternal.TrimEndingDirectorySeparator(path);
using (DisableMediaInsertionPrompt.Create())
{
- if (!Interop.Kernel32.GetFileAttributesEx(path, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data))
+ return FillAttributeInfoSlim(path, ref data, returnErrorOnNotFound);
+ }
+ }
+
+ ///
+ /// Same as without separator-trimming and media-insertion blocking
+ ///
+ /// The file path from which the file attribute information will be filled.
+ /// A struct that will contain the attribute information.
+ /// Return the error code for not found errors?
+ internal static int FillAttributeInfoSlim(string? path, ref Interop.Kernel32.WIN32_FILE_ATTRIBUTE_DATA data, bool returnErrorOnNotFound)
+ {
+ int errorCode = Interop.Errors.ERROR_SUCCESS;
+ string? prefixedString = PathInternal.EnsureExtendedPrefixIfNeeded(path);
+
+ if (!Interop.Kernel32.GetFileAttributesExPrivate(prefixedString, Interop.Kernel32.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, ref data))
+ {
+ errorCode = Marshal.GetLastWin32Error();
+
+ Interop.Kernel32.WIN32_FIND_DATA findData = default;
+ if (!IsPathUnreachableError(errorCode))
{
- errorCode = Marshal.GetLastWin32Error();
+ // Assert so we can track down other cases (if any) to add to our test suite
+ Debug.Assert(errorCode == Interop.Errors.ERROR_ACCESS_DENIED || errorCode == Interop.Errors.ERROR_SHARING_VIOLATION || errorCode == Interop.Errors.ERROR_SEM_TIMEOUT,
+ $"Unexpected error code getting attributes {errorCode} from path {path}");
+
+ // Files that are marked for deletion will not let you GetFileAttributes,
+ // ERROR_ACCESS_DENIED is given back without filling out the data struct.
+ // FindFirstFile, however, will. Historically we always gave back attributes
+ // for marked-for-deletion files.
+ //
+ // Another case where enumeration works is with special system files such as
+ // pagefile.sys that give back ERROR_SHARING_VIOLATION on GetAttributes.
+ //
+ // Ideally we'd only try again for known cases due to the potential performance
+ // hit. The last attempt to do so baked for nearly a year before we found the
+ // pagefile.sys case. As such we're probably stuck filtering out specific
+ // cases that we know we don't want to retry on.
- if (!IsPathUnreachableError(errorCode))
+ using SafeFindHandle handle = Interop.Kernel32.FindFirstFile(prefixedString!, ref findData);
+ if (handle.IsInvalid)
{
- // Assert so we can track down other cases (if any) to add to our test suite
- Debug.Assert(errorCode == Interop.Errors.ERROR_ACCESS_DENIED || errorCode == Interop.Errors.ERROR_SHARING_VIOLATION || errorCode == Interop.Errors.ERROR_SEM_TIMEOUT,
- $"Unexpected error code getting attributes {errorCode} from path {path}");
-
- // Files that are marked for deletion will not let you GetFileAttributes,
- // ERROR_ACCESS_DENIED is given back without filling out the data struct.
- // FindFirstFile, however, will. Historically we always gave back attributes
- // for marked-for-deletion files.
- //
- // Another case where enumeration works is with special system files such as
- // pagefile.sys that give back ERROR_SHARING_VIOLATION on GetAttributes.
- //
- // Ideally we'd only try again for known cases due to the potential performance
- // hit. The last attempt to do so baked for nearly a year before we found the
- // pagefile.sys case. As such we're probably stuck filtering out specific
- // cases that we know we don't want to retry on.
-
- Interop.Kernel32.WIN32_FIND_DATA findData = default;
- using (SafeFindHandle handle = Interop.Kernel32.FindFirstFile(path!, ref findData))
- {
- if (handle.IsInvalid)
- {
- errorCode = Marshal.GetLastWin32Error();
- }
- else
- {
- errorCode = Interop.Errors.ERROR_SUCCESS;
- data.PopulateFrom(ref findData);
- }
- }
+ errorCode = Marshal.GetLastWin32Error();
+ }
+ else
+ {
+ errorCode = Interop.Errors.ERROR_SUCCESS;
+ data.PopulateFrom(ref findData);
}
}
}
+
if (errorCode != Interop.Errors.ERROR_SUCCESS && !returnErrorOnNotFound)
{
switch (errorCode)
diff --git a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs
index dba07a8fb41567..9c688358330008 100644
--- a/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs
+++ b/src/libraries/Common/src/System/IO/FileSystem.DirectoryCreation.Windows.cs
@@ -37,36 +37,44 @@ public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescr
int length = fullPath.Length;
// We need to trim the trailing slash or the code will try to create 2 directories of the same name.
- if (length >= 2 && PathInternal.EndsInDirectorySeparator(fullPath.AsSpan()))
+ if (length >= 2 && PathInternal.IsDirectorySeparator(fullPath.AsSpan()[^1]))
{
length--;
}
int lengthRoot = PathInternal.GetRootLength(fullPath.AsSpan());
- if (length > lengthRoot)
+ using (DisableMediaInsertionPrompt.Create())
{
- // Special case root (fullpath = X:\\)
- int i = length - 1;
- while (i >= lengthRoot && !somepathexists)
+ if (length > lengthRoot)
{
- string dir = fullPath.Substring(0, i + 1);
-
- if (!DirectoryExists(dir)) // Create only the ones missing
- {
- stackDir.Add(dir);
- }
- else
+ // Special case root (fullpath = X:\\)
+ int i = length - 1;
+ while (i >= lengthRoot && !somepathexists)
{
- somepathexists = true;
- }
+ ReadOnlySpan dir = fullPath.AsSpan()[..(i + 1)];
+
+ // Neither GetFileAttributes or FindFirstFile like trailing separators
+ dir = PathInternal.TrimEndingDirectorySeparator(dir);
+
+ string dirString = new(dir);
+
+ if (!DirectoryExistsSlim(dirString, out _)) // Create only the ones missing
+ {
+ stackDir.Add(dirString);
+ }
+ else
+ {
+ somepathexists = true;
+ }
+
+ while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i]))
+ {
+ i--;
+ }
- while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i]))
- {
i--;
}
-
- i--;
}
}
@@ -83,10 +91,9 @@ public static unsafe void CreateDirectory(string fullPath, byte[]? securityDescr
lpSecurityDescriptor = (IntPtr)pSecurityDescriptor
};
- while (stackDir.Count > 0)
+ for (int i = count - 1; i >= 0; i--)
{
- string name = stackDir[stackDir.Count - 1];
- stackDir.RemoveAt(stackDir.Count - 1);
+ string name = stackDir[i];
r = Interop.Kernel32.CreateDirectory(name, ref secAttrs);
if (!r && (firstError == 0))
diff --git a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
index 9015df805583e5..9ee11d0ab85607 100644
--- a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
+++ b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs
@@ -72,11 +72,10 @@ internal static bool IsValidDriveChar(char value)
return (uint)((value | 0x20) - 'a') <= (uint)('z' - 'a');
}
- internal static bool EndsWithPeriodOrSpace(string? path)
+ internal static bool EndsWithPeriodOrSpace(string path)
{
if (string.IsNullOrEmpty(path))
return false;
-
char c = path[path.Length - 1];
return c == ' ' || c == '.';
}
diff --git a/src/libraries/Common/src/System/IO/PathInternal.cs b/src/libraries/Common/src/System/IO/PathInternal.cs
index 52aaa59072dd7b..a389dde7117340 100644
--- a/src/libraries/Common/src/System/IO/PathInternal.cs
+++ b/src/libraries/Common/src/System/IO/PathInternal.cs
@@ -16,7 +16,7 @@ internal static partial class PathInternal
internal static bool StartsWithDirectorySeparator(ReadOnlySpan path) => path.Length > 0 && IsDirectorySeparator(path[0]);
internal static string EnsureTrailingSeparator(string path)
- => EndsInDirectorySeparator(path.AsSpan()) ? path : path + DirectorySeparatorCharAsString;
+ => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]) ? path : path + DirectorySeparatorCharAsString;
internal static bool IsRoot(ReadOnlySpan path)
=> path.Length == GetRootLength(path);
@@ -231,16 +231,10 @@ internal static bool EndsInDirectorySeparator(string? path) =>
/// Trims one trailing directory separator beyond the root of the path.
///
internal static ReadOnlySpan TrimEndingDirectorySeparator(ReadOnlySpan path) =>
- EndsInDirectorySeparator(path) && !IsRoot(path) ?
+ path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]) && !IsRoot(path) ?
path.Slice(0, path.Length - 1) :
path;
- ///
- /// Returns true if the path ends in a directory separator.
- ///
- internal static bool EndsInDirectorySeparator(ReadOnlySpan path) =>
- path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
-
internal static string GetLinkTargetFullPath(string path, string pathToTarget)
=> IsPartiallyQualified(pathToTarget.AsSpan()) ?
Path.Join(Path.GetDirectoryName(path.AsSpan()), pathToTarget.AsSpan()) : pathToTarget;
diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
index b6edb311eac4cf..566971994b0f3f 100644
--- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
@@ -986,7 +986,7 @@ private static string GetRelativePath(string relativeTo, string path, StringComp
///
/// Returns true if the path ends in a directory separator.
///
- public static bool EndsInDirectorySeparator(ReadOnlySpan path) => PathInternal.EndsInDirectorySeparator(path);
+ public static bool EndsInDirectorySeparator(ReadOnlySpan path) => path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
///
/// Returns true if the path ends in a directory separator.