Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change PathInternal.IsCaseSensitive to a constant #54340

Merged
merged 13 commits into from
Jun 23, 2021
Merged
26 changes: 5 additions & 21 deletions src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,13 @@ internal static StringComparison StringComparison
/// <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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be internal static bool IsCaseSensitive property and s_isCaseSensitive should be deleted. It is not profitable to cache it in a static, and it prevents the linker constant propagation from kicking in.

{
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
{
// 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
}
}
}
28 changes: 28 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,33 @@ 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)
{
try
{
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);
}
}
catch
{
// In case something goes wrong (e.g. temp pointing to a privilieged directory), we don't
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This catch made sense when it was part of the shipping code. Now that it is test, I think it would be better to delete it. We want to know when this fails as a test.

// want to fail just because of a casing test, so we assume case-insensitive-but-preserving.
return false;
}
}
}
}
17 changes: 17 additions & 0 deletions src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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]
public void PathInternalIsCaseSensitiveMatchesProbing()
{
var 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: 4 additions & 4 deletions src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,11 +957,11 @@ private static string GetRelativePath(string relativeTo, string path, StringComp
return sb.ToString();
}

/// <summary>Gets whether the system is case-sensitive.</summary>
internal static bool IsCaseSensitive => PathInternal.IsCaseSensitive;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change the few places that use this to call PathInternal.IsCaseSensitive directly instead of introducing this wrapper.


/// <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;
internal static StringComparison StringComparison => PathInternal.StringComparison;

/// <summary>
/// Trims one trailing directory separator beyond the root of the path.
Expand Down