Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 8 additions & 28 deletions src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,27 @@ namespace System.IO
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
internal static partial class PathInternal
{
private static readonly bool s_isCaseSensitive = GetIsCaseSensitive();

/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
internal static StringComparison StringComparison
{
get
{
return s_isCaseSensitive ?
return IsCaseSensitive ?
StringComparison.Ordinal :
StringComparison.OrdinalIgnoreCase;
}
}

/// <summary>Gets whether the system is case-sensitive.</summary>
internal static bool IsCaseSensitive { get { return s_isCaseSensitive; } }

/// <summary>
/// Determines whether the file system is case sensitive.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,24 @@ protected void ReadOnly_FileSystemHelper(Action<string> testAction, string subDi
Assert.Equal(0, AdminHelpers.RunAsSudo($"umount {readOnlyDirectory}"));
}
}

/// <summary>
/// Determines whether the file system is case sensitive by creating a file in the specified folder and observing the result.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />
<Content Include="..\DirectoryInfo\test-dir\dummy.txt" Link="test-dir\dummy.txt" />
<Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
Expand Down
18 changes: 18 additions & 0 deletions src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<Compile Include="Enumeration\ExampleTests.cs" />
<Compile Include="Enumeration\RemovedDirectoryTests.cs" />
<Compile Include="Enumeration\SymbolicLinksTests.cs" />
<Compile Include="PathInternalTests.cs" />
<Compile Include="RandomAccess\Base.cs" />
<Compile Include="RandomAccess\GetLength.cs" />
<Compile Include="RandomAccess\Read.cs" />
Expand Down Expand Up @@ -191,6 +192,8 @@
<Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
<Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />
<Content Include="DirectoryInfo\test-dir\dummy.txt" Link="test-dir\dummy.txt" />
<Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
Expand Down
12 changes: 0 additions & 12 deletions src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,5 @@ public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan<char>.Empty;
}

/// <summary>Gets whether the system is case-sensitive.</summary>
internal static bool IsCaseSensitive
{
get
{
#if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
return false;
#else
return true;
#endif
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,6 @@ public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
return pathRoot <= 0 ? ReadOnlySpan<char>.Empty : path.Slice(0, pathRoot);
}

/// <summary>Gets whether the system is case-sensitive.</summary>
internal static bool IsCaseSensitive => false;

/// <summary>
/// Returns the volume name for dos, UNC and device paths.
/// </summary>
Expand Down
8 changes: 1 addition & 7 deletions src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int by
/// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
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)
Expand Down Expand Up @@ -957,12 +957,6 @@ private static string GetRelativePath(string relativeTo, string path, StringComp
return sb.ToString();
}

/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
internal static StringComparison StringComparison =>
IsCaseSensitive ?
StringComparison.Ordinal :
StringComparison.OrdinalIgnoreCase;

/// <summary>
/// Trims one trailing directory separator beyond the root of the path.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down