Skip to content

Tar: Only treat reparse points marked as junctions or symlinks as actual tar symlinks#124753

Merged
rzikm merged 3 commits intodotnet:mainfrom
rzikm:fix/tar-reparse-point-82949
Feb 26, 2026
Merged

Tar: Only treat reparse points marked as junctions or symlinks as actual tar symlinks#124753
rzikm merged 3 commits intodotnet:mainfrom
rzikm:fix/tar-reparse-point-82949

Conversation

@rzikm
Copy link
Member

@rzikm rzikm commented Feb 23, 2026

Fixes #82949

Before, we would throw on any reparse point, which is not a symlink/junction. (because for these, LinkTarget is null) and the exception would be something cryptic like

System.ArgumentException: The value cannot be an empty string. (Parameter 'value')
   at System.ArgumentException.ThrowNullOrEmptyException(String argument, String paramName)
   at System.Formats.Tar.TarEntry.set_LinkName(String value)

With this change, we will attempt to include non-symlink reparse points as regular files,. For some types, where opening the file fails (such as AppExecLink reparse point), we throw IOException with message that given file is not supported.

An alternative would be to throw for all non-symlink reparse points (including file deduplication reparse point mentioned in the original issue), and only "dereference" these if we implement some sort of "DereferenceSymlinks" tar creation options.

Copy link
Contributor

Copilot AI left a comment

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 fixes a critical bug where TarWriter on Windows incorrectly classified all reparse points (including data deduplication, OneDrive placeholders, and AppExecLinks) as symbolic links. The fix leverages the FileSystemInfo.LinkTarget property which only returns a non-null value for true symbolic links (IO_REPARSE_TAG_SYMLINK) and junctions (IO_REPARSE_TAG_MOUNT_POINT), allowing other reparse point types to be correctly classified as regular files or directories.

Changes:

  • Modified TarWriter.Windows.cs to check LinkTarget property instead of just the ReparsePoint attribute
  • Extracted GetRegularFileEntryTypeForFormat helper to TarHelpers.cs to eliminate duplication
  • Added comprehensive tests for junctions and non-symlink reparse points (using AppExecLinks)
  • Moved GetAppExecLinkPath utility to shared ReparsePointUtilities.cs for reuse across test projects

Reviewed changes

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

Show a summary per file
File Description
TarWriter.Windows.cs Core fix: checks LinkTarget is not null to distinguish symlinks/junctions from other reparse points; caches LinkTarget value to avoid redundant I/O
TarHelpers.cs Adds GetRegularFileEntryTypeForFormat helper method to centralize V7 vs regular file type selection logic
TarWriter.Unix.cs Refactored to use new helper method; removed unused using directives
TarWriter.WriteEntry.File.Tests.Windows.cs New test file with junction test and non-symlink reparse point test (sync version)
TarWriter.WriteEntryAsync.File.Tests.Windows.cs New test file with junction test and non-symlink reparse point test (async version)
System.Formats.Tar.Tests.csproj Registers new Windows-specific test files
TarTestsBase.cs Adds GetRegularFileEntryTypeForFormat helper for test code reuse
Various test files Replace inline ternary expressions with GetRegularFileEntryTypeForFormat helper for consistency
ReparsePointUtilities.cs Adds GetAppExecLinkPath method migrated from FileSystem tests for cross-project reuse
File/FileInfo SymbolicLinks.cs Updated to use MountHelper.GetAppExecLinkPath() instead of local method
BaseSymbolicLinks.FileSystem.cs Removed GetAppExecLinkPath (migrated to shared utilities); removed unused using directive

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

…n Windows

Only treat reparse points as symbolic links when FileSystemInfo.LinkTarget
returns a non-null value, indicating the reparse point is a true symlink or
junction. Other reparse points (e.g., deduplication, OneDrive) now fall
through to be handled as regular files or directories.

Added GetRegularFileEntryTypeForFormat helper to reduce duplication of the
V7/regular file entry type check pattern across source and test files.

Moved GetAppExecLinkPath to shared ReparsePointUtilities so it can be used
by both FileSystem and Tar tests.

Added tests verifying junctions are correctly written as symbolic link
entries and that non-symlink reparse points are not misidentified.

Fixes dotnet#82949

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rzikm rzikm force-pushed the fix/tar-reparse-point-82949 branch from 926b2fd to 1e1492f Compare February 24, 2026 15:30
Copilot AI review requested due to automatic review settings February 24, 2026 15:43
@rzikm rzikm changed the title Fix TarWriter treating non-symlink reparse points as symbolic links on Windows Tar: Only treat reparse points marked as junctions or symlinks as actual tar symlinks Feb 24, 2026
@rzikm rzikm requested review from a team and removed request for a team February 24, 2026 15:48
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.

Remove FindFirstFile/GetFindData interop and use FileSystemInfo.LinkTarget
to detect symlinks and junctions. Non-symlink reparse points with file
attributes (e.g., dedup, OneDrive) fall through to be classified as regular
files or directories based on their attributes. Opaque reparse points (e.g.,
AppExecLinks) that cannot be read are caught at FileStream open and rethrown
with TarUnsupportedFile message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rzikm
Copy link
Member Author

rzikm commented Feb 25, 2026

@copilot run the code review skill

Copy link
Member

@ericstj ericstj left a comment

Choose a reason for hiding this comment

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

This change looks good.

I think this will make us take existing archives which might have excluded some files and automatically include them. While most people might want this behavior it could be seen as breaking behavior to some and we should be sure to document.

If instead we'd always throw before then it's less breaking to make that work and I wouldn't recommend documenting as breaking change.

@rzikm
Copy link
Member Author

rzikm commented Feb 26, 2026

I think this will make us take existing archives which might have excluded some files and automatically include them.

This change affects only creating new tar archives

Before, we would throw on any reparse point, which is not a symlink/junction. (because for these, LinkTarget is null) and the exception would be something cryptic like

System.ArgumentException: The value cannot be an empty string. (Parameter 'value')
   at System.ArgumentException.ThrowNullOrEmptyException(String argument, String paramName)
   at System.Formats.Tar.TarEntry.set_LinkName(String value)

With this change, we will attempt to include non-symlink reparse points as regular files, for some types, where opening the file fails (such as AppExecLink reparse point), we throw IOException with message that given file is not supported.

An alternative would be to throw for all non-symlink reparse points (including file deduplication reparse point mentioned in the original issue), and only "dereference" these if we implement some sort of "DereferenceSymlinks" tar creation options.

I will update the PR description to make this more clear

@ericstj
Copy link
Member

ericstj commented Feb 26, 2026

Sorry, yes I understand it will only effect new archives. I meant to say "existing code paths creating archives". Trying to evaluate compat behavior.

Making an exception path successful is something we do consider non-breaking. So long as all the cases this unblocks are that way I'm good with us not marking as breaking.

@rzikm rzikm merged commit 63bdcc4 into dotnet:main Feb 26, 2026
90 of 92 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TarWriter on Windows Server treats File Deduplication reparse point flag as symbolic links

4 participants