From f3815a94011e12cbce7ce2e41328f5faa54cad0e Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Mon, 9 Dec 2019 08:01:47 -0500 Subject: Backport FixPathLength 255 PR Fixes https://github.com/mono/mono/issues/17948 Pulled in upstream change https://github.com/dotnet/corefx/pull/34389 --- .../Interop/Unix/System.Native/Interop.ReadDir.cs | 10 +++--- .../System/IO/Enumeration/FileSystemEntry.Unix.cs | 7 ++-- .../Enumeration/ErrorHandlingTests.netcoreapp.cs | 37 ++++++++++++++++++++++ src/System.IO.FileSystem/tests/File/Create.cs | 27 ++++++++++++++++ .../tests/FileStream/LockUnlock.cs | 2 +- 5 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.ReadDir.cs b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.ReadDir.cs index 9e32f1ccc2..5888c80fbc 100644 --- a/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.ReadDir.cs +++ b/src/Common/src/CoreLib/Interop/Unix/System.Native/Interop.ReadDir.cs @@ -30,21 +30,21 @@ internal static partial class Interop internal byte* Name; internal int NameLength; internal NodeType InodeType; - internal const int NameBufferSize = 256; + internal const int NameBufferSize = 256; // sizeof(dirent->d_name) == NAME_MAX + 1 internal ReadOnlySpan GetName(Span buffer) { - Debug.Assert(buffer.Length >= Encoding.UTF8.GetMaxCharCount(NameBufferSize - 1), "should have enough space for the max file name"); + // -1 for null terminator (buffer will not include one), + // and -1 because GetMaxCharCount pessimistically assumes the buffer may start with a partial surrogate + Debug.Assert(buffer.Length >= Encoding.UTF8.GetMaxCharCount(NameBufferSize - 1 - 1)); Debug.Assert(Name != null, "should not have a null name"); ReadOnlySpan nameBytes = (NameLength == -1) // In this case the struct was allocated via struct dirent *readdir(DIR *dirp); - ? new ReadOnlySpan(Name, new ReadOnlySpan(Name, NameBufferSize - 1).IndexOf(0)) + ? new ReadOnlySpan(Name, new ReadOnlySpan(Name, NameBufferSize).IndexOf(0)) : new ReadOnlySpan(Name, NameLength); Debug.Assert(nameBytes.Length > 0, "we shouldn't have gotten a garbage value from the OS"); - if (nameBytes.Length == 0) - return buffer.Slice(0, 0); int charCount = Encoding.UTF8.GetChars(nameBytes, buffer); ReadOnlySpan value = buffer.Slice(0, charCount); diff --git a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs index 1139d2f3f5..728275bf95 100644 --- a/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs +++ b/src/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEntry.Unix.cs @@ -10,14 +10,13 @@ namespace System.IO.Enumeration /// Lower level view of FileSystemInfo used for processing and filtering find results. /// public unsafe ref partial struct FileSystemEntry - { - private const int FileNameBufferSize = 256; + { internal Interop.Sys.DirectoryEntry _directoryEntry; private FileStatus _status; private Span _pathBuffer; private ReadOnlySpan _fullPath; private ReadOnlySpan _fileName; - private fixed char _fileNameBuffer[FileNameBufferSize]; + private fixed char _fileNameBuffer[Interop.Sys.DirectoryEntry.NameBufferSize]; private FileAttributes _initialAttributes; internal static FileAttributes Initialize( @@ -112,7 +111,7 @@ namespace System.IO.Enumeration { fixed (char* c = _fileNameBuffer) { - Span buffer = new Span(c, FileNameBufferSize); + Span buffer = new Span(c, Interop.Sys.DirectoryEntry.NameBufferSize); _fileName = _directoryEntry.GetName(buffer); } } diff --git a/src/System.IO.FileSystem/tests/Enumeration/ErrorHandlingTests.netcoreapp.cs b/src/System.IO.FileSystem/tests/Enumeration/ErrorHandlingTests.netcoreapp.cs index e4c85d831e..60bc3d9224 100644 --- a/src/System.IO.FileSystem/tests/Enumeration/ErrorHandlingTests.netcoreapp.cs +++ b/src/System.IO.FileSystem/tests/Enumeration/ErrorHandlingTests.netcoreapp.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Collections.Generic; +using System.Linq; using System.IO.Enumeration; using Xunit; @@ -96,5 +99,39 @@ namespace System.IO.Tests Assert.Equal(info.FullName, ie.DirectoryFinished); } } + + [Fact] + public void VariableLengthFileNames_AllCreatableFilesAreEnumerable() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + var names = new List(); + + for (int length = 1; length < 10_000; length++) // arbitrarily large limit for the test + { + string name = new string('a', length); + try { File.Create(Path.Join(testDirectory.FullName, name)).Dispose(); } + catch { break; } + names.Add(name); + } + Assert.InRange(names.Count, 1, int.MaxValue); + Assert.Equal(names.OrderBy(n => n), Directory.GetFiles(testDirectory.FullName).Select(n => Path.GetFileName(n)).OrderBy(n => n)); + } + + [Fact] + public void VariableLengthDirectoryNames_AllCreatableDirectoriesAreEnumerable() + { + DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); + var names = new List(); + + for (int length = 1; length < 10_000; length++) // arbitrarily large limit for the test + { + string name = new string('a', length); + try { Directory.CreateDirectory(Path.Join(testDirectory.FullName, name)); } + catch { break; } + names.Add(name); + } + Assert.InRange(names.Count, 1, int.MaxValue); + Assert.Equal(names.OrderBy(n => n), Directory.GetDirectories(testDirectory.FullName).Select(n => Path.GetFileName(n)).OrderBy(n => n)); + } } } diff --git a/src/System.IO.FileSystem/tests/File/Create.cs b/src/System.IO.FileSystem/tests/File/Create.cs index c910e735b3..be16528e76 100644 --- a/src/System.IO.FileSystem/tests/File/Create.cs +++ b/src/System.IO.FileSystem/tests/File/Create.cs @@ -168,6 +168,33 @@ namespace System.IO.Tests #region PlatformSpecific + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void LongDirectoryName() + { + // 255 = NAME_MAX on Linux and macOS + DirectoryInfo path = Directory.CreateDirectory(Path.Combine(GetTestFilePath(), new string('a', 255))); + + Assert.True(Directory.Exists(path.FullName)); + Directory.Delete(path.FullName); + Assert.False(Directory.Exists(path.FullName)); + } + + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public void LongFileName() + { + // 255 = NAME_MAX on Linux and macOS + var dir = GetTestFilePath(); + Directory.CreateDirectory(dir); + var path = Path.Combine(dir, new string('b', 255)); + File.Create(path).Dispose(); + + Assert.True(File.Exists(path)); + File.Delete(path); + Assert.False(File.Exists(path)); + } + [Fact] [PlatformSpecific(CaseSensitivePlatforms)] public void CaseSensitive() diff --git a/src/System.IO.FileSystem/tests/FileStream/LockUnlock.cs b/src/System.IO.FileSystem/tests/FileStream/LockUnlock.cs index 2d7207e8d5..68e7a56336 100644 --- a/src/System.IO.FileSystem/tests/FileStream/LockUnlock.cs +++ b/src/System.IO.FileSystem/tests/FileStream/LockUnlock.cs @@ -153,7 +153,7 @@ namespace System.IO.Tests } } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))] // https://github.com/dotnet/corefx/issues/34397 [InlineData(10, 0, 10, 1, 2)] [InlineData(10, 3, 5, 3, 5)] [InlineData(10, 3, 5, 3, 4)] -- cgit v1.2.3