// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.IO;
using System.Runtime.InteropServices;
using System.Text;
internal partial class Kernel32
{
/// <summary>
- /// WARNING: This method does not implicitly handle long paths. Use GetLongPathName.
+ /// WARNING: This method does not implicitly handle long paths.
/// </summary>
[DllImport(Libraries.Kernel32, EntryPoint = "GetLongPathNameW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = false)]
- private static extern int GetLongPathNamePrivate(string path, [Out]StringBuilder longPathBuffer, int bufferLength);
-
- internal static int GetLongPathName(string path, [Out]StringBuilder longPathBuffer, int bufferLength)
- {
- path = PathInternal.EnsureExtendedPrefixIfNeeded(path);
- return GetLongPathNamePrivate(path, longPathBuffer, bufferLength);
- }
+ internal static extern int GetLongPathName(string path, [Out]StringBuilder longPathBuffer, int bufferLength);
}
}
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
internal static partial class PathInternal
{
- // There is only one invalid path character in Unix
- private const char InvalidPathChar = '\0';
- internal static char[] GetInvalidPathChars() => new char[] { InvalidPathChar };
-
- internal const string ParentDirectoryPrefix = @"../";
-
internal static int GetRootLength(ReadOnlySpan<char> path)
{
return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
return c == Path.DirectorySeparatorChar;
}
- /// <summary>
- /// Normalize separators in the given path. Compresses forward slash runs.
- /// </summary>
- internal static string NormalizeDirectorySeparators(string path)
- {
- if (string.IsNullOrEmpty(path)) return path;
-
- // Make a pass to see if we need to normalize so we can potentially skip allocating
- bool normalized = true;
-
- for (int i = 0; i < path.Length; i++)
- {
- if (IsDirectorySeparator(path[i])
- && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
- {
- normalized = false;
- break;
- }
- }
-
- if (normalized) return path;
-
- StringBuilder builder = new StringBuilder(path.Length);
-
- for (int i = 0; i < path.Length; i++)
- {
- char current = path[i];
-
- // Skip if we have another separator following
- if (IsDirectorySeparator(current)
- && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
- continue;
-
- builder.Append(current);
- }
-
- return builder.ToString();
- }
-
- /// <summary>
- /// Returns true if the character is a directory or volume separator.
- /// </summary>
- /// <param name="ch">The character to test.</param>
- internal static bool IsDirectoryOrVolumeSeparator(char ch)
- {
- // The directory separator, volume separator, and the alternate directory
- // separator should be the same on Unix, so we only need to check one.
- Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar);
- Debug.Assert(Path.DirectorySeparatorChar == Path.VolumeSeparatorChar);
- return ch == Path.DirectorySeparatorChar;
- }
internal static bool IsPartiallyQualified(string path)
{
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Diagnostics;
using System.Runtime.CompilerServices;
-using System.Text;
namespace System.IO
{
// Local and Global MS-DOS Device Names
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff554302.aspx
- internal const string ExtendedPathPrefix = @"\\?\";
+ internal const string ExtendedDevicePathPrefix = @"\\?\";
internal const string UncPathPrefix = @"\\";
- internal const string UncExtendedPrefixToInsert = @"?\UNC\";
+ internal const string UncDevicePrefixToInsert = @"?\UNC\";
internal const string UncExtendedPathPrefix = @"\\?\UNC\";
internal const string DevicePathPrefix = @"\\.\";
- internal const string ParentDirectoryPrefix = @"..\";
internal const int MaxShortPath = 260;
- internal const int MaxShortDirectoryPath = 248;
- internal const int MaxLongPath = short.MaxValue;
+
// \\?\, \\.\, \??\
internal const int DevicePrefixLength = 4;
- // \\
- internal const int UncPrefixLength = 2;
- // \\?\UNC\, \\.\UNC\
- internal const int UncExtendedPrefixLength = 8;
-
- internal static char[] GetInvalidPathChars() => new char[]
- {
- '|', '\0',
- (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
- (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
- (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
- (char)31
- };
-
- // [MS - FSA] 2.1.4.4 Algorithm for Determining if a FileName Is in an Expression
- // https://msdn.microsoft.com/en-us/library/ff469270.aspx
- private static readonly char[] s_wildcardChars =
- {
- '\"', '<', '>', '*', '?'
- };
/// <summary>
/// Returns true if the given character is a valid drive letter
// Given \\server\share in longpath becomes \\?\UNC\server\share
if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase))
- return path.Insert(2, PathInternal.UncExtendedPrefixToInsert);
+ return path.Insert(2, UncDevicePrefixToInsert);
- return PathInternal.ExtendedPathPrefix + path;
+ return ExtendedDevicePathPrefix + path;
}
/// <summary>
&& path[3] == '\\';
}
- /// <summary>
- /// Check for known wildcard characters. '*' and '?' are the most common ones.
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static unsafe bool HasWildCardCharacters(string path)
- {
- // Question mark is part of dos device syntax so we have to skip if we are
- int startIndex = PathInternal.IsDevice(path) ? ExtendedPathPrefix.Length : 0;
-
- return path.IndexOfAny(s_wildcardChars, startIndex) >= 0;
- }
-
+#if !NOSPAN
/// <summary>
/// Gets the length of the root of the path (drive, share, etc.).
/// </summary>
- internal static unsafe int GetRootLength(string path)
- {
- fixed(char* value = path)
- {
- return (int)GetRootLength(value, (uint)path.Length);
- }
- }
-
- private static unsafe uint GetRootLength(char* path, uint pathLength)
+ internal static int GetRootLength(ReadOnlySpan<char> path)
{
- uint i = 0;
- uint volumeSeparatorLength = 2; // Length to the colon "C:"
- uint uncRootLength = 2; // Length to the start of the server name "\\"
+ int i = 0;
+ int volumeSeparatorLength = 2; // Length to the colon "C:"
+ int uncRootLength = 2; // Length to the start of the server name "\\"
- bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix);
- bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix);
+ bool extendedSyntax = path.StartsWith(ExtendedDevicePathPrefix);
+ bool extendedUncSyntax = path.StartsWith(UncExtendedPathPrefix);
if (extendedSyntax)
{
// Shift the position we look for the root from to account for the extended prefix
if (extendedUncSyntax)
{
// "\\" -> "\\?\UNC\"
- uncRootLength = (uint)UncExtendedPathPrefix.Length;
+ uncRootLength = UncExtendedPathPrefix.Length;
}
else
{
// "C:" -> "\\?\C:"
- volumeSeparatorLength += (uint)ExtendedPathPrefix.Length;
+ volumeSeparatorLength += ExtendedDevicePathPrefix.Length;
}
}
- if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0]))
+ if ((!extendedSyntax || extendedUncSyntax) && path.Length > 0 && IsDirectorySeparator(path[0]))
{
// UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
i = 1; // Drive rooted (\foo) is one character
- if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1])))
+ if (extendedUncSyntax || (path.Length > 1 && IsDirectorySeparator(path[1])))
{
// UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
// (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
i = uncRootLength;
int n = 2; // Maximum separators to skip
- while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
+ while (i < path.Length && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
}
}
- else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar)
+ else if (path.Length >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar)
{
// Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
// If the colon is followed by a directory separator, move past it
i = volumeSeparatorLength;
- if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
+ if (path.Length >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
}
return i;
}
-
- private static unsafe bool StartsWithOrdinal(char* source, uint sourceLength, string value)
- {
- if (sourceLength < (uint)value.Length) return false;
- for (int i = 0; i < value.Length; i++)
- {
- if (value[i] != source[i]) return false;
- }
- return true;
- }
+ #endif
/// <summary>
/// Returns true if the path specified is relative to the current drive or working directory.
}
/// <summary>
- /// Returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator.
- /// (examples are " C:", " \")
- /// This is a legacy behavior of Path.GetFullPath().
- /// </summary>
- /// <remarks>
- /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip.
- /// </remarks>
- internal static int PathStartSkip(string path)
- {
- int startIndex = 0;
- while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
-
- if (startIndex > 0 && (startIndex < path.Length && PathInternal.IsDirectorySeparator(path[startIndex]))
- || (startIndex + 1 < path.Length && path[startIndex + 1] == ':' && PathInternal.IsValidDriveChar(path[startIndex])))
- {
- // Go ahead and skip spaces as we're either " C:" or " \"
- return startIndex;
- }
-
- return 0;
- }
-
- /// <summary>
/// True if the given character is a directory separator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
{
return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
}
-
- /// <summary>
- /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present.
- /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip).
- ///
- /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false.
- /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as
- /// such can't be used here (and is overkill for our uses).
- ///
- /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments.
- /// </summary>
- /// <remarks>
- /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do
- /// not need trimming of trailing whitespace here.
- ///
- /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization.
- ///
- /// For legacy desktop behavior with ExpandShortPaths:
- /// - It has no impact on GetPathRoot() so doesn't need consideration.
- /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share).
- ///
- /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was
- /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you
- /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by
- /// this undocumented behavior.
- ///
- /// We won't match this old behavior because:
- ///
- /// 1. It was undocumented
- /// 2. It was costly (extremely so if it actually contained '~')
- /// 3. Doesn't play nice with string logic
- /// 4. Isn't a cross-plat friendly concept/behavior
- /// </remarks>
- internal static string NormalizeDirectorySeparators(string path)
- {
- if (string.IsNullOrEmpty(path)) return path;
-
- char current;
- int start = PathStartSkip(path);
-
- if (start == 0)
- {
- // Make a pass to see if we need to normalize so we can potentially skip allocating
- bool normalized = true;
-
- for (int i = 0; i < path.Length; i++)
- {
- current = path[i];
- if (IsDirectorySeparator(current)
- && (current != Path.DirectorySeparatorChar
- // Check for sequential separators past the first position (we need to keep initial two for UNC/extended)
- || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))))
- {
- normalized = false;
- break;
- }
- }
-
- if (normalized) return path;
- }
-
- StringBuilder builder = new StringBuilder(path.Length);
-
- if (IsDirectorySeparator(path[start]))
- {
- start++;
- builder.Append(Path.DirectorySeparatorChar);
- }
-
- for (int i = start; i < path.Length; i++)
- {
- current = path[i];
-
- // If we have a separator
- if (IsDirectorySeparator(current))
- {
- // If the next is a separator, skip adding this
- if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))
- {
- continue;
- }
-
- // Ensure it is the primary separator
- current = Path.DirectorySeparatorChar;
- }
-
- builder.Append(current);
- }
-
- return builder.ToString();
- }
-
- /// <summary>
- /// Returns true if the character is a directory or volume separator.
- /// </summary>
- /// <param name="ch">The character to test.</param>
- internal static bool IsDirectoryOrVolumeSeparator(char ch)
- {
- return PathInternal.IsDirectorySeparator(ch) || Path.VolumeSeparatorChar == ch;
- }
}
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.Text;
-
-namespace System.IO
-{
- /// <summary>Contains internal path helpers that are shared between many projects.</summary>
- internal static partial class PathInternal
- {
- /// <summary>
- /// Returns true if the given StringBuilder starts with the given value.
- /// </summary>
- /// <param name="value">The string to compare against the start of the StringBuilder.</param>
- internal static bool StartsWithOrdinal(this StringBuilder builder, string value)
- {
- if (value == null || builder.Length < value.Length)
- return false;
-
- for (int i = 0; i < value.Length; i++)
- {
- if (builder[i] != value[i]) return false;
- }
- return true;
- }
-
- /// <summary>
- /// Returns true if the given string starts with the given value.
- /// </summary>
- /// <param name="value">The string to compare against the start of the source string.</param>
- internal static bool StartsWithOrdinal(this string source, string value)
- {
- if (value == null || source.Length < value.Length)
- return false;
-
- return source.StartsWith(value, StringComparison.Ordinal);
- }
-
- /// <summary>
- /// Trims the specified characters from the end of the StringBuilder.
- /// </summary>
- internal static StringBuilder TrimEnd(this StringBuilder builder, params char[] trimChars)
- {
- if (trimChars == null || trimChars.Length == 0)
- return builder;
-
- int end = builder.Length - 1;
-
- for (; end >= 0; end--)
- {
- int i = 0;
- char ch = builder[end];
- for (; i < trimChars.Length; i++)
- {
- if (trimChars[i] == ch) break;
- }
- if (i == trimChars.Length)
- {
- // Not a trim char
- break;
- }
- }
-
- builder.Length = end + 1;
- return builder;
- }
-
- /// <summary>
- /// Returns true if the path ends in a directory separator.
- /// </summary>
- internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) =>
- path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
-
- /// <summary>
- /// Get the common path length from the start of the string.
- /// </summary>
- internal static int GetCommonPathLength(string first, string second, bool ignoreCase)
- {
- int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);
-
- // If nothing matches
- if (commonChars == 0)
- return commonChars;
-
- // Or we're a full string and equal length or match to a separator
- if (commonChars == first.Length
- && (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
- return commonChars;
-
- if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
- return commonChars;
-
- // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
- while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
- commonChars--;
-
- return commonChars;
- }
-
- /// <summary>
- /// Gets the count of common characters from the left optionally ignoring case
- /// </summary>
- internal static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase)
- {
- if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;
-
- int commonChars = 0;
-
- fixed (char* f = first)
- fixed (char* s = second)
- {
- char* l = f;
- char* r = s;
- char* leftEnd = l + first.Length;
- char* rightEnd = r + second.Length;
-
- while (l != leftEnd && r != rightEnd
- && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r)))))
- {
- commonChars++;
- l++;
- r++;
- }
- }
-
- return commonChars;
- }
-
- /// <summary>
- /// Returns true if the two paths have the same root
- /// </summary>
- internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType)
- {
- int firstRootLength = GetRootLength(first);
- int secondRootLength = GetRootLength(second);
-
- return firstRootLength == secondRootLength
- && string.Compare(
- strA: first,
- indexA: 0,
- strB: second,
- indexB: 0,
- length: firstRootLength,
- comparisonType: comparisonType) == 0;
- }
- }
-}
<Compile Include="$(CommonPath)\System\Collections\Generic\LargeArrayBuilder.cs">
<Link>Common\System\Collections\Generic\LargeArrayBuilder.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
- <Link>Common\System\IO\PathInternal.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\IO\PathInternal.CaseSensitivity.cs">
<Link>Common\System\IO\PathInternal.CaseSensitivity.cs</Link>
</Compile>
<Compile Include="Tests\System\StringExtensions.Tests.cs" />
<Compile Include="Tests\System\Collections\Generic\ArrayBuilderTests.cs" />
<Compile Include="Tests\System\Collections\Generic\LargeArrayBuilderTests.cs" />
- <Compile Include="Tests\System\IO\PathInternal_Unix_Tests.cs" />
<Compile Include="Tests\System\IO\RowConfigReaderTests.cs" />
- <Compile Include="Tests\System\IO\PathInternal.Tests.cs" />
<Compile Include="Tests\System\Net\HttpKnownHeaderNamesTests.cs" />
<Compile Include="System\Net\Sockets\Fletcher32.cs">
<Link>System\Net\Sockets\Fletcher32.cs</Link>
</ItemGroup>
<ItemGroup>
<Folder Include="CommonTest\System\" />
+ <Folder Include="Common\System\Security\Cryptography\" />
<Folder Include="System\Net\Sockets\" />
<Folder Include="System\Net\VirtualNetwork\" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
+</Project>
\ No newline at end of file
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.IO;
-using System.Text;
-using Xunit;
-
-namespace Tests.System.IO
-{
- public class PathInternalTests
- {
- [Theory,
- InlineData("Foo", "Foos"),
- InlineData("Foo", null)]
- public void StartsWithOrdinal_NegativeCases(string source, string value)
- {
- Assert.False(PathInternal.StartsWithOrdinal(source, value));
- Assert.False(PathInternal.StartsWithOrdinal(new StringBuilder(source), value));
- }
-
- [Theory,
- InlineData("FOO", "foo", false),
- InlineData("FOO", "FOO", true),
- InlineData("FOO", "fo", false),
- InlineData("FOO", "FO", true),
- InlineData("FOO", "oo", false),
- InlineData("FOO", "OO", false)]
- public void StartsWithOrdinal_PositiveCases(string source, string value, bool expected)
- {
- Assert.Equal(expected, PathInternal.StartsWithOrdinal(source, value));
- Assert.Equal(expected, PathInternal.StartsWithOrdinal(new StringBuilder(source), value));
- }
-
- [Theory,
- InlineData("", "", true, 0),
- InlineData("", "", false, 0),
- InlineData("a", "", true, 0),
- InlineData("a", "", false, 0),
- InlineData("", "b", true, 0),
- InlineData("", "b", false, 0),
- InlineData("\0", "\0", true, 1),
- InlineData("\0", "\0", false, 1),
- InlineData("ABcd", "ABCD", true, 4),
- InlineData("ABCD", "ABcd", true, 4),
- InlineData("ABcd", "ABCD", false, 2),
- InlineData("ABCD", "ABcd", false, 2),
- InlineData("AB\0cd", "AB\0CD", true, 5),
- InlineData("AB\0CD", "AB\0cd", true, 5),
- InlineData("AB\0cd", "AB\0CD", false, 3),
- InlineData("AB\0CD", "AB\0cd", false, 3),
- InlineData("ABc\0", "ABC\0", true, 4),
- InlineData("ABC\0", "ABc\0", true, 4),
- InlineData("ABc\0", "ABC\0", false, 2),
- InlineData("ABC\0", "ABc\0", false, 2),
- InlineData("ABcdxyzl", "ABCDpdq", true, 4),
- InlineData("ABCDxyz", "ABcdpdql", true, 4),
- InlineData("ABcdxyz", "ABCDpdq", false, 2),
- InlineData("ABCDxyzoo", "ABcdpdq", false, 2)]
- public void EqualStartingCharacterCount(string first, string second, bool ignoreCase, int expected)
- {
- Assert.Equal(expected, PathInternal.EqualStartingCharacterCount(first, second, ignoreCase));
- }
-
-
- [Theory,
- InlineData(@"", @"", true, 0),
- InlineData(@"", @"", false, 0),
- InlineData(@"a", @"A", true, 1),
- InlineData(@"A", @"a", true, 1),
- InlineData(@"a", @"A", false, 0),
- InlineData(@"A", @"a", false, 0),
- InlineData(@"foo", @"foobar", true, 0),
- InlineData(@"foo", @"foobar", false, 0),
- InlineData(@"foo", @"foo/bar", true, 3),
- InlineData(@"foo", @"foo/bar", false, 3),
- InlineData(@"foo/", @"foo/bar", true, 4),
- InlineData(@"foo/", @"foo/bar", false, 4),
- InlineData(@"foo/bar", @"foo/bar", true, 7),
- InlineData(@"foo/bar", @"foo/bar", false, 7),
- InlineData(@"foo/bar", @"foo/BAR", true, 7),
- InlineData(@"foo/bar", @"foo/BAR", false, 4),
- InlineData(@"foo/bar", @"foo/barb", true, 4),
- InlineData(@"foo/bar", @"foo/barb", false, 4)]
- public void GetCommonPathLength(string first, string second, bool ignoreCase, int expected)
- {
- Assert.Equal(expected, PathInternal.GetCommonPathLength(first, second, ignoreCase));
- }
- }
-}
using System;
using System.IO;
-using System.Runtime.InteropServices;
using Xunit;
public class PathInternal_Windows_Tests
{
[Theory,
- InlineData(@"\\?\", false),
- InlineData(@"\\?\?", true),
- InlineData(@"//?/", false),
- InlineData(@"//?/*", true),
- InlineData(@"\\.\>", true),
- InlineData(@"C:\", false),
- InlineData(@"C:\<", true),
- InlineData("\"MyFile\"", true)
- ]
- [PlatformSpecific(TestPlatforms.Windows)]
- public void HasWildcardCharacters(string path, bool expected)
- {
- Assert.Equal(expected, PathInternal.HasWildCardCharacters(path));
- }
-
- [Theory,
- InlineData(PathInternal.ExtendedPathPrefix, PathInternal.ExtendedPathPrefix),
+ InlineData(PathInternal.ExtendedDevicePathPrefix, PathInternal.ExtendedDevicePathPrefix),
InlineData(@"Foo", @"Foo"),
InlineData(@"C:\Foo", @"\\?\C:\Foo"),
InlineData(@"\\.\Foo", @"\\.\Foo"),
{
Assert.Equal(expected, PathInternal.IsPartiallyQualified(path));
}
-
- [Theory,
- InlineData(@"", 0),
- InlineData(@" :", 0),
- InlineData(@" C:", 2),
- InlineData(@" C:\", 3),
- InlineData(@"C:\", 0),
- InlineData(@" ", 0),
- InlineData(@" \", 2),
- InlineData(@" 8:", 0),
- InlineData(@" \\", 4),
- InlineData(@"\\", 0)
- ]
- [PlatformSpecific(TestPlatforms.Windows)]
- public void PathStartSkipTest(string path, int expected)
- {
- Assert.Equal(expected, PathInternal.PathStartSkip(path));
- }
-
- [Theory,
- InlineData(@"", @""),
- InlineData(null, null),
- InlineData(@"\", @"\"),
- InlineData(@"/", @"\"),
- InlineData(@"\\", @"\\"),
- InlineData(@"\\\", @"\\"),
- InlineData(@"//", @"\\"),
- InlineData(@"///", @"\\"),
- InlineData(@"\/", @"\\"),
- InlineData(@"\/\", @"\\"),
-
- InlineData(@"a\a", @"a\a"),
- InlineData(@"a\\a", @"a\a"),
- InlineData(@"a/a", @"a\a"),
- InlineData(@"a//a", @"a\a"),
- InlineData(@"a\", @"a\"),
- InlineData(@"a\\", @"a\"),
- InlineData(@"a/", @"a\"),
- InlineData(@"a//", @"a\"),
- InlineData(@"\a", @"\a"),
- InlineData(@"\\a", @"\\a"),
- InlineData(@"/a", @"\a"),
- InlineData(@"//a", @"\\a"),
-
- // Skip tests
- InlineData(@" :", @" :"),
- InlineData(@" C:", @"C:"),
- InlineData(@" C:\", @"C:\"),
- InlineData(@" C:/", @"C:\"),
- InlineData(@" ", @" "),
- InlineData(@" \", @"\"),
- InlineData(@" /", @"\"),
- InlineData(@" 8:", @" 8:"),
- InlineData(@" \\", @"\\"),
- InlineData(@" //", @"\\")
- ]
- [PlatformSpecific(TestPlatforms.Windows)]
- public void NormalizeDirectorySeparatorTests(string path, string expected)
- {
- string result = PathInternal.NormalizeDirectorySeparators(path);
- Assert.Equal(expected, result);
- if (string.Equals(path, expected, StringComparison.Ordinal))
- Assert.Same(path, result);
- }
-
- [Theory,
- InlineData(@"", @"", StringComparison.OrdinalIgnoreCase, true),
- InlineData(@"", @"", StringComparison.Ordinal, true),
- InlineData(@"A", @"a", StringComparison.OrdinalIgnoreCase, true),
- InlineData(@"A", @"a", StringComparison.Ordinal, true),
- InlineData(@"C:\", @"c:\", StringComparison.OrdinalIgnoreCase, true),
- InlineData(@"C:\", @"c:\", StringComparison.Ordinal, false)
- ]
- [PlatformSpecific(TestPlatforms.Windows)]
- public void AreRootsEqual(string first, string second, StringComparison comparisonType, bool expected)
- {
- Assert.Equal(expected, PathInternal.AreRootsEqual(first, second, comparisonType));
- }
-
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.IO;
-using System.Text;
-using Xunit;
-
-public class PathInternal_Unix_Tests
-{
- [Theory,
- InlineData(@"", @""),
- InlineData(null, null),
- InlineData(@"/", @"/"),
- InlineData(@"//", @"/"),
- InlineData(@"///", @"/"),
- InlineData(@"\", @"\"),
- InlineData(@"\\", @"\\"),
- InlineData(@"\\\", @"\\\"),
- InlineData(@"\/", @"\/"),
- InlineData(@"\/\", @"\/\"),
-
- InlineData(@"a/a", @"a/a"),
- InlineData(@"a//a", @"a/a"),
- InlineData(@"a\\a", @"a\\a"),
- InlineData(@"/a", @"/a"),
- InlineData(@"//a", @"/a"),
- InlineData(@"\\a", @"\\a"),
- InlineData(@"a/", @"a/"),
- InlineData(@"a//", @"a/"),
- InlineData(@"a\\", @"a\\"),
- ]
- [PlatformSpecific(TestPlatforms.AnyUnix)]
- public void NormalizeDirectorySeparatorTests(string path, string expected)
- {
- string result = PathInternal.NormalizeDirectorySeparators(path);
- Assert.Equal(expected, result);
- if (string.Equals(path, expected, StringComparison.Ordinal))
- Assert.Same(path, result);
- }
-}
<Compile Include="System\IO\Compression\ZipArchiveEntry.netcoreapp.cs" />
<Compile Include="System\IO\Compression\ZipCustomStreams.netcoreapp.cs" />
<Compile Include="System\IO\Compression\PositionPreservingWriteOnlyStreamWrapper.netcoreapp.cs" />
- <Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
- <Link>Common\System\IO\PathInternal.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\IO\StreamHelpers.CopyValidation.cs">
<Link>Common\System\IO\StreamHelpers.CopyValidation.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\HResults.cs">
<Link>Common\System\HResults.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
- <Link>Common\System\IO\PathInternal.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\IO\PathInternal.CaseSensitivity.cs">
<Link>Common\System\IO\PathInternal.CaseSensitivity.cs</Link>
</Compile>
<Reference Include="System.Diagnostics.Debug" />
<Reference Include="System.Diagnostics.Tools" />
<Reference Include="System.IO.FileSystem" />
+ <Reference Include="System.Memory" />
<Reference Include="System.Resources.ResourceManager" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
<Compile Include="System\IO\RenamedEventHandler.cs" />
<Compile Include="System\IO\WatcherChangeTypes.cs" />
<Compile Include="System\IO\WaitForChangedResult.cs" />
- <Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
- <Link>Common\System\IO\PathInternal.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\IO\PathInternal.CaseSensitivity.cs">
<Link>Common\System\IO\PathInternal.CaseSensitivity.cs</Link>
</Compile>
<Reference Include="System.Diagnostics.Debug" />
<Reference Include="System.Diagnostics.Tools" />
<Reference Include="System.IO.FileSystem" />
+ <Reference Include="System.Memory" />
<Reference Include="System.Resources.ResourceManager" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Extensions" />
{3C42F714-82AF-4A43-9B9C-744DE31B5C5D}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
{3C42F714-82AF-4A43-9B9C-744DE31B5C5D}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{3C42F714-82AF-4A43-9B9C-744DE31B5C5D}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
- {1B528B61-14F9-4BFC-A79A-F0BDB3339150}.Debug|Any CPU.ActiveCfg = netcoreapp-Unix-Debug|Any CPU
- {1B528B61-14F9-4BFC-A79A-F0BDB3339150}.Debug|Any CPU.Build.0 = netcoreapp-Unix-Debug|Any CPU
+ {1B528B61-14F9-4BFC-A79A-F0BDB3339150}.Debug|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Debug|Any CPU
+ {1B528B61-14F9-4BFC-A79A-F0BDB3339150}.Debug|Any CPU.Build.0 = netcoreapp-Windows_NT-Debug|Any CPU
{1B528B61-14F9-4BFC-A79A-F0BDB3339150}.Release|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU
{1B528B61-14F9-4BFC-A79A-F0BDB3339150}.Release|Any CPU.Build.0 = netcoreapp-Windows_NT-Release|Any CPU
{4B15C12E-B6AB-4B05-8ECA-C2E2AEA67482}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
<Compile Include="$(CommonPath)\CoreLib\System\Text\ValueStringBuilder.cs">
<Link>Common\CoreLib\System\Text\ValueStringBuilder.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
- <Link>Common\System\IO\PathInternal.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\IO\PathInternal.CaseSensitivity.cs">
<Link>Common\System\IO\PathInternal.CaseSensitivity.cs</Link>
</Compile>
<Compile Include="System\IO\Enumeration\FileSystemEnumerator.Windows.cs" />
<Compile Include="System\IO\CharSpanExtensions.Windows.cs" />
<Compile Include="System\IO\DisableMediaInsertionPrompt.cs" />
- <Compile Include="System\IO\DirectoryInfo.Windows.cs" />
- <Compile Include="System\IO\FileInfo.Windows.cs" />
<Compile Include="System\IO\FileSystemInfo.Windows.cs" />
- <Compile Include="System\IO\PathHelpers.Windows.cs" />
<Compile Include="System\IO\Enumeration\FileSystemEntry.Windows.cs" />
<Compile Include="System\IO\FileSystem.Windows.cs" />
<Compile Include="Microsoft\Win32\SafeHandles\SafeFindHandle.Windows.cs" />
<Compile Include="System\IO\Enumeration\FileSystemEntry.Unix.cs" />
<Compile Include="System\IO\Enumeration\FileSystemEnumerator.Unix.cs" />
<Compile Include="System\IO\CharSpanExtensions.Unix.cs" />
- <Compile Include="System\IO\FileInfo.Unix.cs" />
- <Compile Include="System\IO\DirectoryInfo.Unix.cs" />
<Compile Include="System\IO\FileSystemInfo.Unix.cs" />
- <Compile Include="System\IO\PathHelpers.Unix.cs" />
<Compile Include="System\IO\FileSystem.Unix.cs" />
<Compile Include="$(CommonPath)\Interop\Unix\Interop.Libraries.cs">
<Link>Common\Interop\Unix\Interop.Libraries.cs</Link>
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- partial class DirectoryInfo
- {
- internal static unsafe DirectoryInfo Create(string fullPath, string fileName, ref FileStatus fileStatus)
- {
- DirectoryInfo info = new DirectoryInfo(fullPath, fileName: fileName, isNormalized: true);
- info.Init(ref fileStatus);
- return info;
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.IO.Enumeration;
-
-namespace System.IO
-{
- partial class DirectoryInfo
- {
- internal static unsafe DirectoryInfo Create(string fullPath, ref FileSystemEntry findData)
- {
- DirectoryInfo info = new DirectoryInfo(fullPath, fileName: findData.FileName.GetStringFromFixedBuffer(), isNormalized: true);
- info.Init(findData._info);
- return info;
- }
- }
-}
OriginalPath = originalPath ?? throw new ArgumentNullException("path");
fullPath = fullPath ?? originalPath;
- Debug.Assert(!isNormalized || !PathInternal.IsPartiallyQualified(fullPath), "should be fully qualified if normalized");
+ Debug.Assert(!isNormalized || !PathInternal.IsPartiallyQualified(fullPath), $"'{fullPath}' should be fully qualified if normalized");
fullPath = isNormalized ? fullPath : Path.GetFullPath(fullPath);
_name = fileName ?? (PathHelpers.IsRoot(fullPath) ?
private ReadOnlySpan<char> _fileName;
private fixed char _fileNameBuffer[FileNameBufferSize];
- internal static bool Initialize(
+ internal static FileAttributes Initialize(
ref FileSystemEntry entry,
Interop.Sys.DirectoryEntry directoryEntry,
ReadOnlySpan<char> directory,
entry._fullPath = ReadOnlySpan<char>.Empty;
entry._fileName = ReadOnlySpan<char>.Empty;
- // Get from the dir entry whether the entry is a file or directory.
- // We classify everything as a file unless we know it to be a directory.
- // (This includes regular files, FIFOs, etc.)
+ // IMPORTANT: Attribute logic must match the logic in FileStatus
bool isDirectory = false;
if (directoryEntry.InodeType == Interop.Sys.NodeType.DT_DIR)
// We know it's a directory.
isDirectory = true;
}
- else if ((directoryEntry.InodeType == Interop.Sys.NodeType.DT_LNK || directoryEntry.InodeType == Interop.Sys.NodeType.DT_UNKNOWN)
+ else if ((directoryEntry.InodeType == Interop.Sys.NodeType.DT_LNK)
&& Interop.Sys.Stat(entry.FullPath, out Interop.Sys.FileStatus targetStatus) >= 0)
{
- // It's a symlink or unknown: stat to it to see if we can resolve it to a directory.
+ // It's a symlink: stat to it to see if we can resolve it to a directory.
isDirectory = (targetStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR;
}
entry._status = default;
FileStatus.Initialize(ref entry._status, isDirectory);
- return isDirectory;
+
+ FileAttributes attributes = FileAttributes.Normal;
+ if (directoryEntry.InodeType == Interop.Sys.NodeType.DT_LNK)
+ attributes |= FileAttributes.ReparsePoint;
+ if (isDirectory)
+ attributes |= FileAttributes.Directory;
+ if (directoryEntry.Name[0] == '.')
+ attributes |= FileAttributes.Hidden;
+
+ return attributes;
}
public FileSystemInfo ToFileSystemInfo()
{
string fullPath = ToFullPath();
- if (_status.InitiallyDirectory)
- {
- return DirectoryInfo.Create(fullPath, new string(FileName), ref _status);
- }
- else
- {
- return FileInfo.Create(fullPath, new string(FileName), ref _status);
- }
+ return FileSystemInfo.Create(fullPath, new string(FileName), ref _status);
}
/// <summary>
public bool IsDirectory => (Attributes & FileAttributes.Directory) != 0;
public FileSystemInfo ToFileSystemInfo()
- {
- string fullPath = PathHelpers.CombineNoChecks(Directory, FileName);
-
- return IsDirectory
- ? DirectoryInfo.Create(fullPath, ref this)
- : (FileSystemInfo)FileInfo.Create(fullPath, ref this);
- }
+ => FileSystemInfo.Create(PathHelpers.CombineNoChecks(Directory, FileName), ref this);
/// <summary>
/// Returns the full path for find results, based on the initially provided path.
if (_lastEntryFound)
return false;
- bool isDirectory = FileSystemEntry.Initialize(ref entry, _entry, _currentPath, _rootDirectory, _originalRootDirectory, new Span<char>(_pathBuffer));
+ FileAttributes attributes = FileSystemEntry.Initialize(ref entry, _entry, _currentPath, _rootDirectory, _originalRootDirectory, new Span<char>(_pathBuffer));
+ bool isDirectory = (attributes & FileAttributes.Directory) != 0;
bool isSpecialDirectory = false;
if (isDirectory)
if (!isSpecialDirectory && _options.AttributesToSkip != 0)
{
- if ((_options.AttributesToSkip & ~(FileAttributes.Directory | FileAttributes.Hidden | FileAttributes.ReparsePoint)) == 0)
+ if ((_options.AttributesToSkip & FileAttributes.ReadOnly) != 0)
{
- // These three we don't have to hit the disk again to evaluate
- if (((_options.AttributesToSkip & FileAttributes.Directory) != 0 && isDirectory)
- || ((_options.AttributesToSkip & FileAttributes.Hidden) != 0 && _entry.Name[0] == '.')
- || ((_options.AttributesToSkip & FileAttributes.ReparsePoint) != 0 && _entry.InodeType == Interop.Sys.NodeType.DT_LNK))
- continue;
+ // ReadOnly is the only attribute that requires hitting entry.Attributes (which hits the disk)
+ attributes = entry.Attributes;
}
- else if (entry.Attributes != (FileAttributes)(-1) && (_options.AttributesToSkip & entry.Attributes) != 0)
+
+ if (attributes != (FileAttributes)(-1) && (_options.AttributesToSkip & attributes) != 0)
{
- // Hitting Attributes on the FileSystemEntry will cause a stat call
continue;
}
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- partial class FileInfo
- {
- internal static unsafe FileInfo Create(string fullPath, string fileName, ref FileStatus fileStatus)
- {
- FileInfo info = new FileInfo(fullPath, fileName: fileName, isNormalized: true);
- info.Init(ref fileStatus);
- return info;
- }
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.IO.Enumeration;
-
-namespace System.IO
-{
- partial class FileInfo
- {
- internal static unsafe FileInfo Create(string fullPath, ref FileSystemEntry findData)
- {
- FileInfo info = new FileInfo(fullPath, fileName: findData.FileName.GetStringFromFixedBuffer(), isNormalized: true);
- info.Init(findData._info);
- return info;
- }
- }
-}
public FileAttributes GetAttributes(ReadOnlySpan<char> path, ReadOnlySpan<char> fileName)
{
+ // IMPORTANT: Attribute logic must match the logic in FileSystemEntry
+
EnsureStatInitialized(path);
if (!_exists)
FileAttributes attrs = default;
- bool IsReadOnly(ref Interop.Sys.FileStatus fileStatus)
+ Interop.Sys.Permissions readBit, writeBit;
+ if (_fileStatus.Uid == Interop.Sys.GetEUid())
{
- Interop.Sys.Permissions readBit, writeBit;
- if (fileStatus.Uid == Interop.Sys.GetEUid())
- {
- // User effectively owns the file
- readBit = Interop.Sys.Permissions.S_IRUSR;
- writeBit = Interop.Sys.Permissions.S_IWUSR;
- }
- else if (fileStatus.Gid == Interop.Sys.GetEGid())
- {
- // User belongs to a group that effectively owns the file
- readBit = Interop.Sys.Permissions.S_IRGRP;
- writeBit = Interop.Sys.Permissions.S_IWGRP;
- }
- else
- {
- // Others permissions
- readBit = Interop.Sys.Permissions.S_IROTH;
- writeBit = Interop.Sys.Permissions.S_IWOTH;
- }
-
- return
- (fileStatus.Mode & (int)readBit) != 0 && // has read permission
- (fileStatus.Mode & (int)writeBit) == 0; // but not write permission
+ // User effectively owns the file
+ readBit = Interop.Sys.Permissions.S_IRUSR;
+ writeBit = Interop.Sys.Permissions.S_IWUSR;
}
-
- if (_isDirectory) // this is the one attribute where we follow symlinks
+ else if (_fileStatus.Gid == Interop.Sys.GetEGid())
{
- attrs |= FileAttributes.Directory;
+ // User belongs to a group that effectively owns the file
+ readBit = Interop.Sys.Permissions.S_IRGRP;
+ writeBit = Interop.Sys.Permissions.S_IWGRP;
}
- if (IsReadOnly(ref _fileStatus))
+ else
+ {
+ // Others permissions
+ readBit = Interop.Sys.Permissions.S_IROTH;
+ writeBit = Interop.Sys.Permissions.S_IWOTH;
+ }
+
+ if ((_fileStatus.Mode & (int)readBit) != 0 && // has read permission
+ (_fileStatus.Mode & (int)writeBit) == 0) // but not write permission
{
attrs |= FileAttributes.ReadOnly;
}
+
if ((_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK)
- {
attrs |= FileAttributes.ReparsePoint;
- }
+
+ if (_isDirectory)
+ attrs |= FileAttributes.Directory;
// If the filename starts with a period, it's hidden.
if (fileName.Length > 0 && fileName[0] == '.')
- {
attrs |= FileAttributes.Hidden;
- }
return attrs != default ? attrs : FileAttributes.Normal;
}
// storing those results separately. We only report failure if the initial
// lstat fails, as a broken symlink should still report info on exists, attributes, etc.
_isDirectory = false;
- if (path.Length > 1 && PathInternal.EndsInDirectorySeparator(path))
+ if (path.Length > 1 && PathHelpers.EndsInDirectorySeparator(path))
path = path.Slice(0, path.Length - 1);
int result = Interop.Sys.LStat(path, out _fileStatus);
if (result < 0)
}
_exists = true;
+
+ // IMPORTANT: Is directory logic must match the logic in FileSystemEntry
_isDirectory = (_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR;
// If we're a symlink, attempt to check the target to see if it is a directory
FileStatus.Initialize(ref _fileStatus, this is DirectoryInfo);
}
+ internal static unsafe FileSystemInfo Create(string fullPath, string fileName, ref FileStatus fileStatus)
+ {
+ FileSystemInfo info = fileStatus.InitiallyDirectory
+ ? (FileSystemInfo)new DirectoryInfo(fullPath, fileName: fileName, isNormalized: true)
+ : new FileInfo(fullPath, fileName: fileName, isNormalized: true);
+
+ info.Init(ref fileStatus);
+ return info;
+ }
+
internal void Invalidate() => _fileStatus.Invalidate();
internal unsafe void Init(ref FileStatus fileStatus)
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Security;
+using System.IO.Enumeration;
namespace System.IO
{
{
}
+ internal static unsafe FileSystemInfo Create(string fullPath, ref FileSystemEntry findData)
+ {
+ FileSystemInfo info = findData.IsDirectory
+ ? (FileSystemInfo) new DirectoryInfo(fullPath, fileName: new string(findData.FileName), isNormalized: true)
+ : new FileInfo(fullPath, fileName: new string(findData.FileName), isNormalized: true);
+
+ info.Init(findData._info);
+ return info;
+ }
+
internal void Invalidate()
{
_dataInitialized = -1;
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-namespace System.IO
-{
- internal static partial class PathHelpers
- {
- internal static bool ShouldReviseDirectoryPathToCurrent(string path)
- {
- // Unlike on Windows, there are no special cases on Unix where we'd want to ignore
- // user-provided path and instead automatically use the current directory.
- return false;
- }
-
- internal static string TrimEndingDirectorySeparator(string path) =>
- path.Length > 1 && PathInternal.IsDirectorySeparator(path[path.Length - 1]) ? // exclude root "/"
- path.Substring(0, path.Length - 1) :
- path;
- }
-}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Runtime.InteropServices;
-
-namespace System.IO
-{
- internal static partial class PathInternal
- {
- internal static unsafe int GetRootLength(ReadOnlySpan<char> path)
- {
- fixed (char* p = &MemoryMarshal.GetReference(path))
- {
- return (int)GetRootLength(p, (uint)path.Length);
- }
- }
- }
-
- internal static partial class PathHelpers
- {
- internal static string TrimEndingDirectorySeparator(string path) =>
- EndsInDirectorySeparator(path) ?
- path.Substring(0, path.Length - 1) :
- path;
- }
-}
throw new ArgumentException(SR.Arg_Path2IsRooted, nameof(path2));
}
- internal static bool IsRoot(string path)
- {
- return path.Length == PathInternal.GetRootLength(path);
- }
+ internal static bool IsRoot(ReadOnlySpan<char> path)
+ => path.Length == PathInternal.GetRootLength(path);
- internal static bool EndsInDirectorySeparator(string path)
- {
- return path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
- }
+ internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
+ => path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
+
+ internal static string TrimEndingDirectorySeparator(string path) =>
+ EndsInDirectorySeparator(path) && !IsRoot(path) ?
+ path.Substring(0, path.Length - 1) :
+ path;
+
+ internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
+ EndsInDirectorySeparator(path) && !IsRoot(path) ?
+ path.Slice(0, path.Length - 1) :
+ path;
/// <summary>
/// Combines two paths. Does no validation of paths, only concatenates the paths
<ProjectGuid>{187503F4-BEF9-4369-A1B2-E3DC5D564E4E}</ProjectGuid>
<IsPartialFacadeAssembly Condition="'$(TargetGroup)' == 'netfx'">true</IsPartialFacadeAssembly>
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetGroup)' == 'netstandard' AND '$(TargetsWindows)' != 'true'">SR.PlatformNotSupported_IOPorts</GeneratePlatformNotSupportedAssemblyMessage>
+ <DefineConstants>$(DefineConstants);NOSPAN</DefineConstants>
<IncludeDllSafeSearchPathAttribute>true</IncludeDllSafeSearchPathAttribute>
</PropertyGroup>
<!-- Default configurations to help VS understand the options -->
<Compile Include="$(CommonPath)\System\IO\StringBuilderCache.cs">
<Link>Common\System\IO\StringBuilderCache.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\System\IO\PathInternal.cs">
- <Link>Common\System\IO\PathInternal.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\HResults.cs">
<Link>Common\System\HResults.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\IO\DriveInfoInternal.Win32.cs">
<Link>Common\System\IO\DriveInfoInternal.Win32.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\System\IO\PathInternal.Windows.cs">
- <Link>Common\System\IO\PathInternal.Windows.cs</Link>
- </Compile>
</ItemGroup>
<!-- UNIX -->
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
<Compile Include="$(CommonPath)\System\IO\DriveInfoInternal.Unix.cs">
<Link>Common\System\IO\DriveInfoInternal.Unix.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\System\IO\PathInternal.Unix.cs">
- <Link>Common\System\IO\PathInternal.Unix.cs</Link>
- </Compile>
<Compile Include="$(CommonPath)\System\IO\PersistedFiles.Unix.cs">
<Link>Common\System\IO\PersistedFiles.Unix.cs</Link>
</Compile>
<ReferenceFromRuntime Include="System.Private.CoreLib" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
-</Project>
+</Project>
\ No newline at end of file
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Internal.Runtime.Augments;
using System.Collections;
using System.Collections.Generic;
-using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
-using System.Text;
+using Internal.Runtime.Augments;
namespace System
{