Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Remove readdir_r entirely and handle filenames > 255 bytes - #116639

Merged
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r
Jun 16, 2025
Merged

Remove readdir_r entirely and handle filenames > 255 bytes#116639
jozkee merged 5 commits into
dotnet:mainfrom
jozkee:readdir_r

Conversation

@jozkee

Copy link
Copy Markdown
Member

Alternative approach described in #116619 suitable for main but requires validation for multiple platforms, especially the ones where historical fixes are being removed.

@NattyNarwhal, as per the git history, you've been submitting multiple fixes for AIX, could you please help validating this one?
Tagging IllumOS folks and kindly asking the same: @AustinWise@gwr@am11.

@jozkeejozkee added this to the 10.0.0 milestone Jun 13, 2025
@jozkee
jozkee requested review from a team, GrabYourPitchforks and jkotasJune 13, 2025 16:54
@jozkeejozkee self-assigned this Jun 13, 2025
CopilotAI review requested due to automatic review settings June 13, 2025 16:54
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the legacy readdir_r support across the native libraries and switches to using readdir exclusively, simplifying the buffer management and unifying the directory enumeration path. It also adds a manual test for NTFS filename length handling and extends the existing parallel file enumeration tests.

  • Remove HAVE_READDIR_R checks and associated APIs in both CMake config and headers.
  • Replace SystemNative_ReadDirR/buffer-size API with SystemNative_ReadDir in the PAL and interop layers.
  • Update FileSystemEnumerator.Unix and related code to drop manual buffer management.
  • Add manual NTFS-on-Linux mount setup and a new threaded enumeration test.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
FileDescription
src/native/libs/configure.cmakeDropped check for readdir_r availability
src/native/libs/System.Native/pal_io.hRemoved R-buffer APIs; added SystemNative_ReadDir
src/native/libs/System.Native/pal_io.cDeleted readdir_r path; streamlined SystemNative_ReadDir
src/native/libs/System.Native/entrypoints.cUpdated entrypoints to use SystemNative_ReadDir
src/native/libs/Common/pal_config.h.inRemoved HAVE_READDIR_R define
src/libraries/System.Runtime/tests/.../ManualTests.csprojIncluded new NTFS manual test source files
src/libraries/System.Runtime/tests/.../NtfsOnLinuxTests.csAdded manual NTFS filename-length test
src/libraries/System.Runtime/tests/.../NtfsOnLinuxSetup.csAdded loopback NTFS mount/unmount fixture for manual tests
src/libraries/System.Runtime/tests/.../EnumerableTests.csAdded a Parallel.ForEach enumeration test
src/libraries/System.Private.CoreLib/.../NonAndroid.csRemoved unused GetDirectoryEntryFullPath helper
src/libraries/System.Private.CoreLib/.../FileSystemEnumerator.Unix.csDeleted entry-buffer logic; call into new ReadDir
src/libraries/System.Private.CoreLib/.../FileSystemEntry.Unix.csUpdated fixed buffer size constant and span usage
src/libraries/Common/.../Interop.ReadDir.csUpdated interop to only export ReadDir; improved GetName

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Span<char>buffer=MemoryMarshal.CreateSpan(ref_fileNameBuffer._buffer[0],DecodedNameBufferLength);
_fileName=_directoryEntry.GetName(buffer);
_fileName=_directoryEntry.GetName(_buffer);

If you accept the suggestion above, this should be able to use default conversion from InlineArray to Span

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

This failed for me locally:

error CS9084: Struct member returns 'this' or other instance members by reference

I pushed anyways to share the CI error, it does compile fine in sharplab.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotas

Copy link
Copy Markdown
Member

LGTM otherwise.

@AustinWise

AustinWise commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

In my survey of open source libc's, readdir_r ensures thread safety by taking a lock on the DIR*. Since FileSystemEnumerator takes a lock when calling readdir, so I think this PR ensures an equivalent level of thread safety.

References to libc implementations

@jozkee

Copy link
Copy Markdown
MemberAuthor

@AustinWise the question is more about readdir using a global static buffer, see #116619 (comment).

@jozkee

Copy link
Copy Markdown
MemberAuthor

I think POSIX.2024 also alludes to the buffer being global, and not just stored in each DIR:

Historically, readdir() returned a pointer to an internal static buffer that was overwritten by each call

@AustinWise

Copy link
Copy Markdown
Contributor

I don't see the readdir implementations cited above making use of some state that the readdir_r variants don't use. So my reasoning is readdir_r is safe to use with its lock, readdir is just as safe to use with a lock in C#. It's hard to prove the absence of a problem of course.

I tried to find some example of a libc returning an "internal static buffer". I don't see it in the earliest available commits for illumos from 2005 or GLibC from 1995. BSD stopped using a static buffer in 1982.

Comment on lines 102 to 103
Span<char> buffer = MemoryMarshal.CreateSpan(ref _fileNameBuffer._buffer[0], DecodedNameBufferLength);
_fileName = _directoryEntry.GetName(buffer);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah ok, I have not noticed that the Span escapes here and so it is not possible to create it in safe way (without large refactoring that would likely hurt overall readability).

@jkotasjkotas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

@jozkee

Copy link
Copy Markdown
MemberAuthor

/ba-g #116697.

@hamarb123

Copy link
Copy Markdown
Contributor

Won't this regress #47584? macOS used to be using readdir_r with EINTR handling, but the EINTR handling hasn't been added to readdir, despite being used instead now.

@jkotas

Copy link
Copy Markdown
Member

Yes, it looks like a problem - even on Linux. @hamarb123 Would you like to submit a PR with the fix?

@hamarb123

Copy link
Copy Markdown
Contributor

@jkotas should I just fix the one in pal_io.c or should I also adjust all the other instances of readdir, opendir, etc. that I find?

@jkotas

Copy link
Copy Markdown
Member

It would be great if you can fix all instances that you can find.

@hamarb123

hamarb123 commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

@jkotas I've had a look at some other functions which have EINTR handling in some places too, such as write - we do not seem to handle its result consistently - in some places we don't loop the call, in some places, we loop the call, but don't handle partial writes, and in some places we do handle partial writes. I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea? If so, where would the best spot to place it be? It seems that src/native/libs/Common/pal_io_common.h has something like this pre-existing with its Common_Write, but it is not used consistently - should I just be including that header in places that don't have it & using that & adding new ones into there, or would it be better to extract this into a lower-level implementation in a similar fashion? I was thinking that making some file like libc_wrappers.h (or some other similar name) & just adding originalname_wrapped variants of the functions with EINTR handling would be useful, as it can centralise the logic we use to wrap them & ensure it's consistent everywhere that we want it to be.

Or would you rather I just do the change for opendir / readdir & leave something more extensive for the future?

@jkotas

Copy link
Copy Markdown
Member

I think it would be easiest to add wrappers for APIs like this & just call into those - does that sound like a good idea?

I think there should not be that many places that call the Linux syscalls directly in our shipping code. I am not sure whether we need a shared wrapper around the syscalls. Do you have a list of places that have the potential problems?

@hamarb123

Copy link
Copy Markdown
Contributor

I will have a proper look tomorrow, can you remind me which path/s I can limit my search to for just the actual shipping code?

@jkotas

Copy link
Copy Markdown
Member

If the path contains "test", it is non-shipping code. Also, the code under src\native\external is shipping code that should be fixed in the upstream copy (it can be done in parallel with fixing it here).

@gwr

gwr commented Jul 7, 2025

Copy link
Copy Markdown
Contributor

A bit late, but my testing on illumos with this included seems fine.

jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 23, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
jonathanpeppers added a commit to dotnet/android that referenced this pull request Jul 24, 2025
Fixes: #10329
Context: dotnet/runtime#116639
Some `SystemNative_` methods were removed/changed
in .NET 10 Preview 6. We should update our tables.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 7, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jozkee@jkotas@AustinWise@hamarb123@gwr