diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs | 66 |
1 files changed, 44 insertions, 22 deletions
diff --git a/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs index 74ceed10a..bada2f5cd 100644 --- a/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs @@ -26,30 +26,57 @@ namespace System.IO internal static string Normalize(string path) { Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath]; - ValueStringBuilder builder = new ValueStringBuilder(initialBuffer); + var builder = new ValueStringBuilder(initialBuffer); // Get the full path - GetFullPathName(path, ref builder); + GetFullPathName(path.AsSpan(), ref builder); // If we have the exact same string we were passed in, don't allocate another string. // TryExpandShortName does this input identity check. - string result = builder.AsSpan().Contains('~') + string result = builder.AsSpan().IndexOf('~') >= 0 ? TryExpandShortFileName(ref builder, originalPath: path) - : builder.AsSpan().Equals(path.AsSpan()) ? path : builder.ToString(); + : builder.AsSpan().Equals(path.AsSpan(), StringComparison.Ordinal) ? path : builder.ToString(); // Clear the buffer builder.Dispose(); return result; } - private static void GetFullPathName(string path, ref ValueStringBuilder builder) + /// <summary> + /// Normalize the given path. + /// </summary> + /// <remarks> + /// Exceptions are the same as the string overload. + /// </remarks> + internal static string Normalize(ref ValueStringBuilder path) + { + Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath]; + var builder = new ValueStringBuilder(initialBuffer); + + // Get the full path + GetFullPathName(path.AsSpan(terminate: true), ref builder); + + string result = builder.AsSpan().IndexOf('~') >= 0 + ? TryExpandShortFileName(ref builder, originalPath: null) + : builder.ToString(); + + // Clear the buffer + builder.Dispose(); + return result; + } + + /// <summary> + /// Calls GetFullPathName on the given path. + /// </summary> + /// <param name="path">The path name. MUST be null terminated after the span.</param> + private static void GetFullPathName(ReadOnlySpan<char> path, ref ValueStringBuilder builder) { // If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as // it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here. Debug.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path)); uint result = 0; - while ((result = Interop.Kernel32.GetFullPathNameW(path, (uint)builder.Capacity, ref builder.GetPinnableReference(), IntPtr.Zero)) > builder.Capacity) + while ((result = Interop.Kernel32.GetFullPathNameW(ref MemoryMarshal.GetReference(path), (uint)builder.Capacity, ref builder.GetPinnableReference(), IntPtr.Zero)) > builder.Capacity) { // Reported size is greater than the buffer size. Increase the capacity. builder.EnsureCapacity(checked((int)result)); @@ -61,13 +88,13 @@ namespace System.IO int errorCode = Marshal.GetLastWin32Error(); if (errorCode == 0) errorCode = Interop.Errors.ERROR_BAD_PATHNAME; - throw Win32Marshal.GetExceptionForWin32Error(errorCode, path); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, path.ToString()); } builder.Length = (int)result; } - private static int PrependDevicePathChars(ref ValueStringBuilder content, bool isDosUnc, ref ValueStringBuilder buffer) + internal static int PrependDevicePathChars(ref ValueStringBuilder content, bool isDosUnc, ref ValueStringBuilder buffer) { int length = content.Length; @@ -84,7 +111,7 @@ namespace System.IO buffer.Append(PathInternal.UncExtendedPathPrefix); // Copy Server\Share\... over to the buffer - buffer.Append(content.AsSpan().Slice(PathInternal.UncPrefixLength)); + buffer.Append(content.AsSpan(PathInternal.UncPrefixLength)); // Return the prefix difference return PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength; @@ -98,7 +125,7 @@ namespace System.IO } } - private static string TryExpandShortFileName(ref ValueStringBuilder outputBuilder, string originalPath) + internal static string TryExpandShortFileName(ref ValueStringBuilder outputBuilder, string originalPath) { // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls. @@ -119,7 +146,7 @@ namespace System.IO bool isDevice = PathInternal.IsDevice(outputBuilder.AsSpan()); // As this is a corner case we're not going to add a stackalloc here to keep the stack pressure down. - ValueStringBuilder inputBuilder = new ValueStringBuilder(); + var inputBuilder = new ValueStringBuilder(); bool isDosUnc = false; int rootDifference = 0; @@ -149,12 +176,10 @@ namespace System.IO bool success = false; int foundIndex = inputBuilder.Length - 1; - // Need to null terminate the input builder - inputBuilder.Append('\0'); - while (!success) { - uint result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity); + uint result = Interop.Kernel32.GetLongPathNameW( + ref inputBuilder.GetPinnableReference(terminate: true), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity); // Replace any temporary null we added if (inputBuilder[foundIndex] == '\0') inputBuilder[foundIndex] = '\\'; @@ -198,14 +223,11 @@ namespace System.IO if (foundIndex < inputLength - 1) { // It was a partial find, put the non-existent part of the path back - outputBuilder.Append(inputBuilder.AsSpan().Slice(foundIndex, inputBuilder.Length - foundIndex)); + outputBuilder.Append(inputBuilder.AsSpan(foundIndex, inputBuilder.Length - foundIndex)); } } } - // Need to trim out the trailing separator in the input builder - inputBuilder.Length = inputBuilder.Length - 1; - // If we were able to expand the path, use it, otherwise use the original full path result ref ValueStringBuilder builderToUse = ref (success ? ref outputBuilder : ref inputBuilder); @@ -218,10 +240,10 @@ namespace System.IO builderToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\'; // Strip out any added characters at the front of the string - ReadOnlySpan<char> output = builderToUse.AsSpan().Slice(rootDifference); + ReadOnlySpan<char> output = builderToUse.AsSpan(rootDifference); - string returnValue = output.Equals(originalPath.AsSpan()) - ? originalPath : new string(output); + string returnValue = ((originalPath != null) && output.Equals(originalPath.AsSpan(), StringComparison.Ordinal)) + ? originalPath : output.ToString(); inputBuilder.Dispose(); return returnValue; |