33// See the LICENSE file in the project root for more information.
44
55using System ;
6+ using System . Diagnostics ;
67using System . Runtime . InteropServices ;
7- using System . Threading ;
8+ using System . Text ;
89using Microsoft . Win32 . SafeHandles ;
910
1011internal static partial class Interop
1112{
1213 internal static partial class Sys
1314 {
14- private static readonly int s_readBufferSize = GetReadDirRBufferSize ( ) ;
15+ internal static int ReadBufferSize { get ; } = GetReadDirRBufferSize ( ) ;
1516
1617 internal enum NodeType : int
1718 {
@@ -27,76 +28,59 @@ internal enum NodeType : int
2728 }
2829
2930 [ StructLayout ( LayoutKind . Sequential ) ]
30- private unsafe struct InternalDirectoryEntry
31+ internal unsafe struct DirectoryEntry
3132 {
32- internal IntPtr Name ;
33- internal int NameLength ;
34- internal NodeType InodeType ;
35- }
33+ internal byte * Name ;
34+ internal int NameLength ;
35+ internal NodeType InodeType ;
3636
37- internal struct DirectoryEntry
38- {
39- internal NodeType InodeType ;
40- internal string InodeName ;
37+ internal ReadOnlySpan < char > GetName ( Span < char > buffer )
38+ {
39+ Debug . Assert ( buffer . Length >= Encoding . UTF8 . GetMaxCharCount ( 255 ) , "should have enough space for the max file name" ) ;
40+ Debug . Assert ( Name != null , "should not have a null name" ) ;
41+
42+ ReadOnlySpan < byte > nameBytes = NameLength == - 1
43+ // In this case the struct was allocated via struct dirent *readdir(DIR *dirp);
44+ ? new ReadOnlySpan < byte > ( Name , new ReadOnlySpan < byte > ( Name , 255 ) . IndexOf < byte > ( 0 ) )
45+ : new ReadOnlySpan < byte > ( Name , NameLength ) ;
46+
47+ Debug . Assert ( nameBytes . Length > 0 , "we shouldn't have gotten a garbage value from the OS" ) ;
48+ if ( nameBytes . Length == 0 )
49+ return buffer . Slice ( 0 , 0 ) ;
50+
51+ int charCount = Encoding . UTF8 . GetChars ( nameBytes , buffer ) ;
52+ ReadOnlySpan < char > value = buffer . Slice ( 0 , charCount ) ;
53+ Debug . Assert ( value . IndexOf ( '\0 ' ) == - 1 , "should not have embedded nulls" ) ;
54+ return value ;
55+ }
4156 }
4257
4358 [ DllImport ( Libraries . SystemNative , EntryPoint = "SystemNative_OpenDir" , SetLastError = true ) ]
44- internal static extern Microsoft . Win32 . SafeHandles . SafeDirectoryHandle OpenDir ( string path ) ;
59+ internal static extern SafeDirectoryHandle OpenDir ( string path ) ;
4560
4661 [ DllImport ( Libraries . SystemNative , EntryPoint = "SystemNative_GetReadDirRBufferSize" , SetLastError = false ) ]
4762 internal static extern int GetReadDirRBufferSize ( ) ;
4863
4964 [ DllImport ( Libraries . SystemNative , EntryPoint = "SystemNative_ReadDirR" , SetLastError = false ) ]
50- private static extern unsafe int ReadDirR ( IntPtr dir , byte * buffer , int bufferSize , out InternalDirectoryEntry outputEntry ) ;
65+ private static extern unsafe int ReadDirR ( IntPtr dir , ref byte buffer , int bufferSize , ref DirectoryEntry outputEntry ) ;
5166
5267 [ DllImport ( Libraries . SystemNative , EntryPoint = "SystemNative_CloseDir" , SetLastError = true ) ]
5368 internal static extern int CloseDir ( IntPtr dir ) ;
5469
55- // The calling pattern for ReadDir is described in src/Native/System.Native/pal_readdir.cpp
56- internal static int ReadDir ( SafeDirectoryHandle dir , out DirectoryEntry outputEntry )
70+ /// <summary>
71+ /// Get the next directory entry for the given handle. **Note** the actual memory used may be allocated
72+ /// by the OS and will be freed when the handle is closed. As such, the handle lifespan MUST be kept tightly
73+ /// controlled. The DirectoryEntry name cannot be accessed after the handle is closed.
74+ ///
75+ /// Call <see cref="ReadBufferSize"/> to see what size buffer to allocate.
76+ /// </summary>
77+ internal static int ReadDir ( SafeDirectoryHandle dir , Span < byte > buffer , ref DirectoryEntry entry )
5778 {
58- bool addedRef = false ;
59- try
60- {
61- // We avoid a native string copy into InternalDirectoryEntry.
62- // - If the platform suppors reading into a buffer, the data is read directly into the buffer. The
63- // data can be read as long as the buffer is valid.
64- // - If the platform does not support reading into a buffer, the information returned in
65- // InternalDirectoryEntry points to native memory owned by the SafeDirectoryHandle. The data is only
66- // valid until the next call to CloseDir/ReadDir. We extend the reference until we have copied all data
67- // to ensure it does not become invalid by a CloseDir; and we copy the data so our caller does not
68- // use the native memory held by the SafeDirectoryHandle.
69- dir . DangerousAddRef ( ref addedRef ) ;
79+ // The calling pattern for ReadDir is described in src/Native/Unix/System.Native/pal_io.cpp|.h
80+ Debug . Assert ( buffer . Length >= ReadBufferSize , "should have a big enough buffer for the raw data" ) ;
7081
71- unsafe
72- {
73- // s_readBufferSize is zero when the native implementation does not support reading into a buffer.
74- byte * buffer = stackalloc byte [ s_readBufferSize ] ;
75- InternalDirectoryEntry temp ;
76- int ret = ReadDirR ( dir . DangerousGetHandle ( ) , buffer , s_readBufferSize , out temp ) ;
77- // We copy data into DirectoryEntry to ensure there are no dangling references.
78- outputEntry = ret == 0 ?
79- new DirectoryEntry ( ) { InodeName = GetDirectoryEntryName ( temp ) , InodeType = temp . InodeType } :
80- default ( DirectoryEntry ) ;
81-
82- return ret ;
83- }
84- }
85- finally
86- {
87- if ( addedRef )
88- {
89- dir . DangerousRelease ( ) ;
90- }
91- }
92- }
93-
94- private static unsafe string GetDirectoryEntryName ( InternalDirectoryEntry dirEnt )
95- {
96- if ( dirEnt . NameLength == - 1 )
97- return Marshal . PtrToStringAnsi ( dirEnt . Name ) ;
98- else
99- return Marshal . PtrToStringAnsi ( dirEnt . Name , dirEnt . NameLength ) ;
82+ // ReadBufferSize is zero when the native implementation does not support reading into a buffer.
83+ return ReadDirR ( dir . DangerousGetHandle ( ) , ref MemoryMarshal . GetReference ( buffer ) , ReadBufferSize , ref entry ) ;
10084 }
10185 }
10286}
0 commit comments