Skip to content

Commit c0ed319

Browse files
lambdageekstephentoubjkotasadamsitnik
authored
Change PathInternal.IsCaseSensitive to a constant (dotnet#54340)
* Return constants in PathInternal.GetIsCaseSensitive() on mobile platforms In particular on Android probing using I/O is slow and contributes to slow app startup. Fixes dotnet#54339 * Implement Path.IsCaseSensitive as PathInternal.IsCaseSensitive Also Path.StringComparison => PathInternal.StringComparison * Add test for PathInternal.IsCaseSensitive Move GetIsCaseSensitiveByProbing to FileSystemTest * Drop PathInternal.s_isCaseSensitive cache field * Delete Path.IsCaseSensitive and Path.StringComparison update callers to use PathInternal.IsCaseSensitive and PathInternal.StringComparison * Remove catch clause from GetIsCaseSensitiveByProbing * Mark new test [OuterLoop] * Apply suggestions from code review Co-authored-by: Stephen Toub <stoub@microsoft.com> Co-authored-by: Jan Kotas <jkotas@microsoft.com> Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com> Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent fa86c81 commit c0ed319

File tree

9 files changed

+52
-51
lines changed

9 files changed

+52
-51
lines changed

src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,27 @@ namespace System.IO
1212
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
1313
internal static partial class PathInternal
1414
{
15-
private static readonly bool s_isCaseSensitive = GetIsCaseSensitive();
16-
1715
/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
1816
internal static StringComparison StringComparison
1917
{
2018
get
2119
{
22-
return s_isCaseSensitive ?
20+
return IsCaseSensitive ?
2321
StringComparison.Ordinal :
2422
StringComparison.OrdinalIgnoreCase;
2523
}
2624
}
2725

2826
/// <summary>Gets whether the system is case-sensitive.</summary>
29-
internal static bool IsCaseSensitive { get { return s_isCaseSensitive; } }
30-
31-
/// <summary>
32-
/// Determines whether the file system is case sensitive.
33-
/// </summary>
34-
/// <remarks>
35-
/// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable,
36-
/// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters
37-
/// and then tests for its existence with lower-case letters. This could return invalid results in corner
38-
/// cases where, for example, different file systems are mounted with differing sensitivities.
39-
/// </remarks>
40-
private static bool GetIsCaseSensitive()
27+
internal static bool IsCaseSensitive
4128
{
42-
try
43-
{
44-
string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N"));
45-
using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose))
46-
{
47-
string lowerCased = pathWithUpperCase.ToLowerInvariant();
48-
return !File.Exists(lowerCased);
49-
}
50-
}
51-
catch
29+
get
5230
{
53-
// In case something goes wrong (e.g. temp pointing to a privilieged directory), we don't
54-
// want to fail just because of a casing test, so we assume case-insensitive-but-preserving.
55-
return false;
31+
#if MS_IO_REDIST
32+
return false; // Windows is always case-insensitive
33+
#else
34+
return !(OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS());
35+
#endif
5636
}
5737
}
5838
}

src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,24 @@ protected void ReadOnly_FileSystemHelper(Action<string> testAction, string subDi
127127
Assert.Equal(0, AdminHelpers.RunAsSudo($"umount {readOnlyDirectory}"));
128128
}
129129
}
130+
131+
/// <summary>
132+
/// Determines whether the file system is case sensitive by creating a file in the specified folder and observing the result.
133+
/// </summary>
134+
/// <remarks>
135+
/// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable,
136+
/// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters
137+
/// and then tests for its existence with lower-case letters. This could return invalid results in corner
138+
/// cases where, for example, different file systems are mounted with differing sensitivities.
139+
/// </remarks>
140+
protected static bool GetIsCaseSensitiveByProbing(string probingDirectory)
141+
{
142+
string pathWithUpperCase = Path.Combine(probingDirectory, "CASESENSITIVETEST" + Guid.NewGuid().ToString("N"));
143+
using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose))
144+
{
145+
string lowerCased = pathWithUpperCase.ToLowerInvariant();
146+
return !File.Exists(lowerCased);
147+
}
148+
}
130149
}
131150
}

src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
3838
<Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />
3939
<Content Include="..\DirectoryInfo\test-dir\dummy.txt" Link="test-dir\dummy.txt" />
40+
<Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
41+
Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
4042
</ItemGroup>
4143
<ItemGroup>
4244
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Xunit;
5+
6+
namespace System.IO.Tests
7+
{
8+
public class PathInternalTests : FileSystemTest
9+
{
10+
[Fact]
11+
[OuterLoop]
12+
public void PathInternalIsCaseSensitiveMatchesProbing()
13+
{
14+
string probingDirectory = TestDirectory;
15+
Assert.Equal(GetIsCaseSensitiveByProbing(probingDirectory), PathInternal.IsCaseSensitive);
16+
}
17+
}
18+
}

src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<Compile Include="Enumeration\ExampleTests.cs" />
5252
<Compile Include="Enumeration\RemovedDirectoryTests.cs" />
5353
<Compile Include="Enumeration\SymbolicLinksTests.cs" />
54+
<Compile Include="PathInternalTests.cs" />
5455
<Compile Include="RandomAccess\Base.cs" />
5556
<Compile Include="RandomAccess\GetLength.cs" />
5657
<Compile Include="RandomAccess\Read.cs" />
@@ -191,6 +192,8 @@
191192
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
192193
<Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />
193194
<Content Include="DirectoryInfo\test-dir\dummy.txt" Link="test-dir\dummy.txt" />
195+
<Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
196+
Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
194197
</ItemGroup>
195198
<ItemGroup>
196199
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />

src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,5 @@ public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
129129
return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan<char>.Empty;
130130
}
131131

132-
/// <summary>Gets whether the system is case-sensitive.</summary>
133-
internal static bool IsCaseSensitive
134-
{
135-
get
136-
{
137-
#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
138-
return false;
139-
#else
140-
return true;
141-
#endif
142-
}
143-
}
144132
}
145133
}

src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,6 @@ public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
230230
return pathRoot <= 0 ? ReadOnlySpan<char>.Empty : path.Slice(0, pathRoot);
231231
}
232232

233-
/// <summary>Gets whether the system is case-sensitive.</summary>
234-
internal static bool IsCaseSensitive => false;
235-
236233
/// <summary>
237234
/// Returns the volume name for dos, UNC and device paths.
238235
/// </summary>

src/libraries/System.Private.CoreLib/src/System/IO/Path.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,7 @@ private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int by
860860
/// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
861861
public static string GetRelativePath(string relativeTo, string path)
862862
{
863-
return GetRelativePath(relativeTo, path, StringComparison);
863+
return GetRelativePath(relativeTo, path, PathInternal.StringComparison);
864864
}
865865

866866
private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
@@ -957,12 +957,6 @@ private static string GetRelativePath(string relativeTo, string path, StringComp
957957
return sb.ToString();
958958
}
959959

960-
/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
961-
internal static StringComparison StringComparison =>
962-
IsCaseSensitive ?
963-
StringComparison.Ordinal :
964-
StringComparison.OrdinalIgnoreCase;
965-
966960
/// <summary>
967961
/// Trims one trailing directory separator beyond the root of the path.
968962
/// </summary>

src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ private static void OnAssemblyLoad(RuntimeAssembly assembly)
781781
string assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!, $"{assemblyName.Name}.dll");
782782

783783
bool exists = System.IO.FileSystem.FileExists(assemblyPath);
784-
if (!exists && Path.IsCaseSensitive)
784+
if (!exists && PathInternal.IsCaseSensitive)
785785
{
786786
#if CORECLR
787787
if (AssemblyLoadContext.IsTracingEnabled())

0 commit comments

Comments
 (0)