Make FileStream.Name work for FileStream instances created out of handle#126629
Make FileStream.Name work for FileStream instances created out of handle#126629
Conversation
|
Tagging subscribers to this area: @dotnet/area-system-io |
- Add SystemNative_GetFilePathFromHandle to pal_io.h and pal_io.c: - Linux: readlink /proc/self/fd/<fd> - BSD/macOS: fcntl(fd, F_GETPATH, buf) - Other: errno=ENOTSUP, return -1 - Add HAVE_F_GETPATH cmake check and pal_config.h.in define - Add DllImportEntry for new native function - Add Interop.GetFilePathFromHandle.cs Unix P/Invoke wrapper - Add public Name property to SafeFileHandle.cs - Add GetName() implementations to SafeFileHandle.Unix.cs and SafeFileHandle.Windows.cs - Update OSFileStreamStrategy.Name to use SafeFileHandle.Name - Update System.Runtime ref assembly with new Name property - Add Name tests to GetFileType.cs, GetFileType.Unix.cs, GetFileType.Windows.cs - Add NonInheritedFileHandle_IsNotAvailableInChildProcess test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/65fff4ad-9081-4ef8-b744-02fef761db67 Co-authored-by: adamsitnik <[email protected]>
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetFilePathFromHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetFilePathFromHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Outdated
Show resolved
Hide resolved
| // GetFinalPathNameByHandle always returns with extended DOS prefix (\\?\). | ||
| // Trim the prefix to keep the result consistent with the path stored in _path. |
There was a problem hiding this comment.
Is the conversion always as simple as removing first four characters?
There was a problem hiding this comment.
Not always — for UNC paths, GetFinalPathNameByHandle returns \\?\UNC\server\share, and just removing 4 chars gives UNC\server\share (wrong). Fixed in b600d32 to handle both cases:
\\?\UNC\server\share→\\server\share(strips\\?\UNC\, prepends\\)\\?\C:\foo→C:\foo(strips\\?\)
…SafeFileHandle in interop Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d6f0695b-ba5d-44a8-aa39-c07ea398bcd0 Co-authored-by: adamsitnik <[email protected]>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d6f0695b-ba5d-44a8-aa39-c07ea398bcd0 Co-authored-by: adamsitnik <[email protected]>
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetFilePathFromHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs
Outdated
Show resolved
Hide resolved
...libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Unix.cs
Outdated
Show resolved
Hide resolved
...raries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.Windows.cs
Outdated
Show resolved
Hide resolved
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/bb17e842-35f7-4bae-8d99-bf98e21541b2 Co-authored-by: adamsitnik <[email protected]>
src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.cs
Outdated
Show resolved
Hide resolved
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot fix following build failure:
src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.cs(557,84): error CS1739: (NETCORE_ENGINEERING_TELEMETRY=Build) The best overload for 'FileStream' does not have a parameter named 'leaveOpen'
make sure you build the product and run the tests before pushing an update
…Stream, remove redundant SkipOnPlatform Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/45057e03-9697-4819-a2c0-72b02fddccf6 Co-authored-by: adamsitnik <[email protected]>
Fixed in a33d3f9. |
There was a problem hiding this comment.
Pull request overview
This PR makes FileStream.Name return the real file path for FileStream instances created from raw OS handles by teaching SafeFileHandle to resolve a path from the OS when no cached path is available.
Changes:
- Added a new native PAL export to resolve a file path from a handle/FD (Linux via
/proc/self/fd, Apple/BSD/Solaris viafcntl(F_GETPATH)). - Updated
SafeFileHandle.Pathto delegate to a platform-specificGetPath()implementation that can query the OS on-demand. - Added/updated tests to validate
FileStream.Namefor both path-opened streams and raw-handle-opened streams, and added a new process handle inheritance test.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/libs/System.Native/pal_io.h | Declares SystemNative_GetFilePathFromHandle export. |
| src/native/libs/System.Native/pal_io.c | Implements path resolution from FD via F_GETPATH//proc/self/fd. |
| src/native/libs/System.Native/entrypoints.c | Registers the new native entrypoint for interop. |
| src/native/libs/configure.cmake | Detects F_GETPATH support at configure time. |
| src/native/libs/Common/pal_config.h.in | Adds HAVE_F_GETPATH to generated PAL config. |
| src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetFilePathFromHandle.cs | Adds Unix interop wrapper to call the new native export and decode the returned UTF-8 path. |
| src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems | Includes the new Unix interop file in CoreLib builds. |
| src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs | Changes SafeFileHandle.Path to resolve via GetPath() rather than only returning _path. |
| src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs | Implements GetPath() using the new Unix interop and caches the resolved path. |
| src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs | Implements GetPath() using GetFinalPathNameByHandle and normalizes extended path prefixes. |
| src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/SafeFileHandle/GetFileType.cs | Adds cross-platform tests for FileStream.Name for path-opened and raw-handle-opened cases. |
| src/libraries/System.Diagnostics.Process/tests/ProcessHandlesTests.cs | Adds a RemoteExecutor test ensuring a non-inherited file handle isn’t usable in a child process. |
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
Outdated
Show resolved
Hide resolved
🤖 Copilot Code Review — PR #126629Note This review was generated by GitHub Copilot using multiple AI models (Claude Opus 4.6 primary, Claude Sonnet 4.5 secondary). Holistic AssessmentMotivation: Resolving file paths from OS-level handles is a real gap — when a Approach: The approach is sound — using Summary: Detailed Findings
|
adamsitnik
left a comment
There was a problem hiding this comment.
@copilot address my feedback
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetFilePathFromHandle.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetFilePathFromHandle.cs
Outdated
Show resolved
Hide resolved
… guard, return SR.IO_UnknownFileName on Unix failure Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c0bb5bd4-b358-456c-a074-116c8a17d415 Co-authored-by: adamsitnik <[email protected]>
Addressed in b97f003:
|
|
@jkotas I've marked it as "NO REVIEW" as I expect it's going to take me a while to get to the point where I am happy about path normalization myself |
| buffer = ArrayPool<char>.Shared.Rent((int)result); | ||
| ArrayPool<char>.Shared.Return(toReturn); | ||
|
|
||
| result = GetFinalPathNameByHandleHelper(buffer); |
There was a problem hiding this comment.
Is it guaranteed that the path cannot change between the two calls? This pattern is often wrapped in a loop (see e.g.
)
FileStream.Namereturned[Unknown]for streams created from raw OS handles becauseSafeFileHandlehad no mechanism to query the OS for the path. This extends the internalSafeFileHandle.Pathproperty to resolve the path from the kernel when not already known, soFileStream.Namereturns the real path in those cases.Changes
Native (
System.Native)SystemNative_GetFilePathFromHandle(fd, buf, size)inpal_io.h/pal_io.c:readlink("/proc/self/fd/<fd>", buf, size)fcntl(fd, F_GETPATH, buf)(guarded byHAVE_F_GETPATH)errno = ENOTSUP; return -1assert(buffer != NULL && bufferSize > 0)(no redundant runtime check)F_GETPATHplatforms, added aMAXPATHLENguard: returnserrno = ENAMETOOLONGif buffer is too small, preventing native buffer overwriteHAVE_F_GETPATHcmake detection +pal_config.h.inentryentrypoints.cManaged (
System.Private.CoreLib)internal string? SafeFileHandle.Pathcaches lazily via_path ??= GetPath()— all caching is centralized in thePathproperty; platform-specificGetPath()implementations just fetch the valueSafeFileHandle.Windows.cs:GetPath()viaGetFinalPathNameByHandle, correctly handles both\\?\C:\fooand\\?\UNC\server\shareprefixes; uses 26-char initial buffer in non-RELEASEbuilds to exercise buffer-growing code pathSafeFileHandle.Unix.cs:GetPath()simplified toInterop.Sys.GetFilePathFromHandle(this)(no caching;Pathhandles it)GetFilePathFromHandlereturns non-nullablestring; returnsSR.IO_UnknownFileNameinstead ofnullon failure; usesSafeFileHandleparameter (notIntPtr) and a fixed 4096-byteArrayPoolbuffer; usesAsSpanfor null-terminator searchOSFileStreamStrategy.Nameunchanged — still_fileHandle.Path ?? SR.IO_UnknownFileNameTests
GetFileType.cs: added twoFileStream.Nametests — one for path-via-open, one for path-via-raw-handle (asserts exact path equality; CI will identify any unsupported platforms)GetFileType.Unix.csandGetFileType.Windows.cs(covered by shared test)ProcessHandlesTests.cs: addedNonInheritedFileHandle_IsNotAvailableInChildProcess(decorated with[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]only) — saves the raw handle value, creates aFileStreamfrom the handle and assertsAssert.Equal(path, parentFs.Name)in the parent process, then spawns a child withInheritedHandles = []and verifies the file handle is not accessibleExample