From c0ed319ba3164bfbd9c2c4d66f68f29f8cd78b04 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Wed, 23 Jun 2021 09:44:41 -0400 Subject: [PATCH] Change PathInternal.IsCaseSensitive to a constant (#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 https://github.com/dotnet/runtime/issues/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 Co-authored-by: Jan Kotas Co-authored-by: Adam Sitnik Co-authored-by: Stephen Toub --- .../src/System/IO/PathInternal.CaseSensitivity.cs | 36 +++++----------------- .../System.IO.FileSystem/tests/FileSystemTest.cs | 19 ++++++++++++ .../System.IO.FileSystem.Net5Compat.Tests.csproj | 2 ++ .../tests/PathInternalTests.cs | 18 +++++++++++ .../tests/System.IO.FileSystem.Tests.csproj | 3 ++ .../src/System/IO/Path.Unix.cs | 12 -------- .../src/System/IO/Path.Windows.cs | 3 -- .../System.Private.CoreLib/src/System/IO/Path.cs | 8 +---- .../System/Runtime/Loader/AssemblyLoadContext.cs | 2 +- 9 files changed, 52 insertions(+), 51 deletions(-) create mode 100644 src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs diff --git a/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs b/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs index 5611e9c..3e34861 100644 --- a/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs +++ b/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs @@ -12,47 +12,27 @@ namespace System.IO /// Contains internal path helpers that are shared between many projects. internal static partial class PathInternal { - private static readonly bool s_isCaseSensitive = GetIsCaseSensitive(); - /// Returns a comparison that can be used to compare file and directory names for equality. internal static StringComparison StringComparison { get { - return s_isCaseSensitive ? + return IsCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; } } /// Gets whether the system is case-sensitive. - internal static bool IsCaseSensitive { get { return s_isCaseSensitive; } } - - /// - /// Determines whether the file system is case sensitive. - /// - /// - /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable, - /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters - /// and then tests for its existence with lower-case letters. This could return invalid results in corner - /// cases where, for example, different file systems are mounted with differing sensitivities. - /// - private static bool GetIsCaseSensitive() + internal static bool IsCaseSensitive { - try - { - string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N")); - using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) - { - string lowerCased = pathWithUpperCase.ToLowerInvariant(); - return !File.Exists(lowerCased); - } - } - catch + get { - // In case something goes wrong (e.g. temp pointing to a privilieged directory), we don't - // want to fail just because of a casing test, so we assume case-insensitive-but-preserving. - return false; +#if MS_IO_REDIST + return false; // Windows is always case-insensitive +#else + return !(OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS()); +#endif } } } diff --git a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs index 2ba15d3..d82346f 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs @@ -127,5 +127,24 @@ namespace System.IO.Tests Assert.Equal(0, AdminHelpers.RunAsSudo($"umount {readOnlyDirectory}")); } } + + /// + /// Determines whether the file system is case sensitive by creating a file in the specified folder and observing the result. + /// + /// + /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable, + /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters + /// and then tests for its existence with lower-case letters. This could return invalid results in corner + /// cases where, for example, different file systems are mounted with differing sensitivities. + /// + protected static bool GetIsCaseSensitiveByProbing(string probingDirectory) + { + string pathWithUpperCase = Path.Combine(probingDirectory, "CASESENSITIVETEST" + Guid.NewGuid().ToString("N")); + using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) + { + string lowerCased = pathWithUpperCase.ToLowerInvariant(); + return !File.Exists(lowerCased); + } + } } } diff --git a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj index 628bbb4..5866965 100644 --- a/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj @@ -37,6 +37,8 @@ + diff --git a/src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs b/src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs new file mode 100644 index 0000000..c908601 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.IO.Tests +{ + public class PathInternalTests : FileSystemTest + { + [Fact] + [OuterLoop] + public void PathInternalIsCaseSensitiveMatchesProbing() + { + string probingDirectory = TestDirectory; + Assert.Equal(GetIsCaseSensitiveByProbing(probingDirectory), PathInternal.IsCaseSensitive); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index a271109..94cec86 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -51,6 +51,7 @@ + @@ -191,6 +192,8 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs index ecc990b..5a7f0c8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs @@ -129,17 +129,5 @@ namespace System.IO return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan.Empty; } - /// Gets whether the system is case-sensitive. - internal static bool IsCaseSensitive - { - get - { - #if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS - return false; - #else - return true; - #endif - } - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs index 5e64180..5f4b8e1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs @@ -230,9 +230,6 @@ namespace System.IO return pathRoot <= 0 ? ReadOnlySpan.Empty : path.Slice(0, pathRoot); } - /// Gets whether the system is case-sensitive. - internal static bool IsCaseSensitive => false; - /// /// Returns the volume name for dos, UNC and device paths. /// 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 7601e45..104704d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs @@ -860,7 +860,7 @@ namespace System.IO /// Thrown if or is null or an empty string. public static string GetRelativePath(string relativeTo, string path) { - return GetRelativePath(relativeTo, path, StringComparison); + return GetRelativePath(relativeTo, path, PathInternal.StringComparison); } private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) @@ -957,12 +957,6 @@ namespace System.IO return sb.ToString(); } - /// Returns a comparison that can be used to compare file and directory names for equality. - internal static StringComparison StringComparison => - IsCaseSensitive ? - StringComparison.Ordinal : - StringComparison.OrdinalIgnoreCase; - /// /// Trims one trailing directory separator beyond the root of the path. /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs index 6c733ef..2a5a3c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs @@ -781,7 +781,7 @@ namespace System.Runtime.Loader string assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!, $"{assemblyName.Name}.dll"); bool exists = System.IO.FileSystem.FileExists(assemblyPath); - if (!exists && Path.IsCaseSensitive) + if (!exists && PathInternal.IsCaseSensitive) { #if CORECLR if (AssemblyLoadContext.IsTracingEnabled()) -- 2.7.4