diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs | 101 |
1 files changed, 72 insertions, 29 deletions
diff --git a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs index b921db9e6..ddfd7966b 100644 --- a/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/IO/Path.Windows.cs @@ -5,7 +5,14 @@ using System.Diagnostics; using System.Text; +#if MS_IO_REDIST +using System; +using System.IO; + +namespace Microsoft.IO +#else namespace System.IO +#endif { public static partial class Path { @@ -34,16 +41,16 @@ namespace System.IO throw new ArgumentNullException(nameof(path)); // If the path would normalize to string empty, we'll consider it empty - if (PathInternal.IsEffectivelyEmpty(path)) + if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); // Embedded null characters are the only invalid character case we trully care about. // This is because the nulls will signal the end of the string to Win32 and therefore have // unpredictable results. - if (path.IndexOf('\0') != -1) + if (path.Contains('\0')) throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); - if (PathInternal.IsExtended(path)) + if (PathInternal.IsExtended(path.AsSpan())) { // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\ // paths and neither should we. Even if we wanted to GetFullPathName does not work @@ -72,6 +79,9 @@ namespace System.IO if (IsPathFullyQualified(path)) return GetFullPath(path); + if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) + return basePath; + int length = path.Length; string combinedPath = null; @@ -80,30 +90,30 @@ namespace System.IO // Path is current drive rooted i.e. starts with \: // "\Foo" and "C:\Bar" => "C:\Foo" // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo" - combinedPath = Join(GetPathRoot(basePath.AsSpan()), path); + combinedPath = Join(GetPathRoot(basePath.AsSpan()), path.AsSpan(1)); // Cut the separator to ensure we don't end up with two separators when joining with the root. } else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar) { // Drive relative paths Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2])); - if (StringSpanHelpers.Equals(GetVolumeName(path), GetVolumeName(basePath))) + if (GetVolumeName(path.AsSpan()).EqualsOrdinal(GetVolumeName(basePath.AsSpan()))) { // Matching root // "C:Foo" and "C:\Bar" => "C:\Bar\Foo" // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = Join(basePath, path.AsSpan().Slice(2)); + combinedPath = Join(basePath.AsSpan(), path.AsSpan(2)); } else { // No matching root, root to specified drive // "D:Foo" and "C:\Bar" => "D:Foo" // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo" - combinedPath = !PathInternal.IsDevice(basePath) + combinedPath = !PathInternal.IsDevice(basePath.AsSpan()) ? path.Insert(2, @"\") : length == 2 - ? JoinInternal(basePath.AsSpan().Slice(0, 4), path, @"\") - : JoinInternal(basePath.AsSpan().Slice(0, 4), path.AsSpan().Slice(0, 2), @"\", path.AsSpan().Slice(2)); + ? JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(), @"\".AsSpan()) + : JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(0, 2), @"\".AsSpan(), path.AsSpan(2)); } } else @@ -111,37 +121,70 @@ namespace System.IO // "Simple" relative path // "Foo" and "C:\Bar" => "C:\Bar\Foo" // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = JoinInternal(basePath, path); + combinedPath = JoinInternal(basePath.AsSpan(), path.AsSpan()); } - // Device paths are normalized by definition, so passing something of this format - // to GetFullPath() won't do anything by design. Additionally, GetFullPathName() in - // Windows doesn't root them properly. As such we need to manually remove segments. - return PathInternal.IsDevice(combinedPath) - ? RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath)) + // Device paths are normalized by definition, so passing something of this format (i.e. \\?\C:\.\tmp, \\.\C:\foo) + // to Windows APIs won't do anything by design. Additionally, GetFullPathName() in Windows doesn't root + // them properly. As such we need to manually remove segments and not use GetFullPath(). + + return PathInternal.IsDevice(combinedPath.AsSpan()) + ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath.AsSpan())) : GetFullPath(combinedPath); } public static string GetTempPath() { - StringBuilder sb = StringBuilderCache.Acquire(Interop.Kernel32.MAX_PATH); - uint r = Interop.Kernel32.GetTempPathW(Interop.Kernel32.MAX_PATH, sb); - if (r == 0) + Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath]; + var builder = new ValueStringBuilder(initialBuffer); + + GetTempPath(ref builder); + + string path = PathHelper.Normalize(ref builder); + builder.Dispose(); + return path; + } + + private static void GetTempPath(ref ValueStringBuilder builder) + { + uint result = 0; + while ((result = Interop.Kernel32.GetTempPathW(builder.Capacity, ref builder.GetPinnableReference())) > builder.Capacity) + { + // Reported size is greater than the buffer size. Increase the capacity. + builder.EnsureCapacity(checked((int)result)); + } + + if (result == 0) throw Win32Marshal.GetExceptionForLastWin32Error(); - return GetFullPath(StringBuilderCache.GetStringAndRelease(sb)); + + builder.Length = (int)result; } // Returns a unique temporary file name, and creates a 0-byte file by that // name on disk. public static string GetTempFileName() { - string path = GetTempPath(); + Span<char> initialTempPathBuffer = stackalloc char[PathInternal.MaxShortPath]; + ValueStringBuilder tempPathBuilder = new ValueStringBuilder(initialTempPathBuffer); + + GetTempPath(ref tempPathBuilder); + + Span<char> initialBuffer = stackalloc char[PathInternal.MaxShortPath]; + var builder = new ValueStringBuilder(initialBuffer); - StringBuilder sb = StringBuilderCache.Acquire(Interop.Kernel32.MAX_PATH); - uint r = Interop.Kernel32.GetTempFileNameW(path, "tmp", 0, sb); - if (r == 0) + uint result = Interop.Kernel32.GetTempFileNameW( + ref tempPathBuilder.GetPinnableReference(), "tmp", 0, ref builder.GetPinnableReference()); + + tempPathBuilder.Dispose(); + + if (result == 0) throw Win32Marshal.GetExceptionForLastWin32Error(); - return StringBuilderCache.GetStringAndRelease(sb); + + builder.Length = builder.RawChars.IndexOf('\0'); + + string path = PathHelper.Normalize(ref builder); + builder.Dispose(); + return path; } // Tests if the given path contains a root. A path is considered rooted @@ -169,14 +212,14 @@ namespace System.IO // only contains whitespace characters an ArgumentException gets thrown. public static string GetPathRoot(string path) { - if (PathInternal.IsEffectivelyEmpty(path)) + if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) return null; ReadOnlySpan<char> result = GetPathRoot(path.AsSpan()); if (path.Length == result.Length) return PathInternal.NormalizeDirectorySeparators(path); - return PathInternal.NormalizeDirectorySeparators(new string(result)); + return PathInternal.NormalizeDirectorySeparators(result.ToString()); } /// <remarks> @@ -237,11 +280,11 @@ namespace System.IO { bool isDevice = PathInternal.IsDevice(path); - if (!isDevice && StringSpanHelpers.Equals(path.Slice(0, 2), @"\\") ) + if (!isDevice && path.Slice(0, 2).EqualsOrdinal(@"\\".AsSpan()) ) return 2; else if (isDevice && path.Length >= 8 - && (StringSpanHelpers.Equals(path.Slice(0, 8), PathInternal.UncExtendedPathPrefix) - || StringSpanHelpers.Equals(path.Slice(5, 4), @"UNC\"))) + && (path.Slice(0, 8).EqualsOrdinal(PathInternal.UncExtendedPathPrefix.AsSpan()) + || path.Slice(5, 4).EqualsOrdinal(@"UNC\".AsSpan()))) return 8; return -1; |