diff --git a/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs b/src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs index 5611e9c696987..3e3486157e841 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 2ba15d31b4144..d82346f6f4f10 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs @@ -127,5 +127,24 @@ protected void ReadOnly_FileSystemHelper(Action testAction, string subDi 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 628bbb4324aea..58669657e51b1 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 0000000000000..c908601862eea --- /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 a271109ce2fc6..94cec86ce3a9a 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 ecc990b07c6e1..5a7f0c8d1bbe5 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 @@ public static ReadOnlySpan GetPathRoot(ReadOnlySpan path) 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 5e641806926b8..5f4b8e11a1920 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 @@ public static ReadOnlySpan GetPathRoot(ReadOnlySpan path) 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 7601e45e074d1..104704de7b715 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 @@ private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int by /// 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 @@ private static string GetRelativePath(string relativeTo, string path, StringComp 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 6c733ef6ca150..2a5a3c1da07fb 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 @@ private static void OnAssemblyLoad(RuntimeAssembly assembly) 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())