diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs
index 0095c09cdac3a4..4d93bc968d30bb 100644
--- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs
+++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FindFirstFileEx.cs
@@ -24,6 +24,21 @@ internal static SafeFindHandle FindFirstFile(string fileName, ref WIN32_FIND_DAT
return FindFirstFileExPrivate(fileName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0);
}
+ internal static void GetFindData(string fullPath, bool isDirectory, bool ignoreAccessDenied, ref WIN32_FIND_DATA findData)
+ {
+ using SafeFindHandle handle = FindFirstFile(Path.TrimEndingDirectorySeparator(fullPath), ref findData);
+ if (handle.IsInvalid)
+ {
+ int errorCode = Marshal.GetLastPInvokeError();
+ // File not found doesn't make much sense coming from a directory.
+ if (isDirectory && errorCode == Errors.ERROR_FILE_NOT_FOUND)
+ errorCode = Errors.ERROR_PATH_NOT_FOUND;
+ if (ignoreAccessDenied && errorCode == Errors.ERROR_ACCESS_DENIED)
+ return;
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
+ }
+ }
+
internal enum FINDEX_INFO_LEVELS : uint
{
FindExInfoStandard = 0x0u,
diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs
index 205e045617d910..39b09a407081d5 100644
--- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs
+++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs
@@ -13,6 +13,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.IO.Enumeration;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
@@ -115,6 +116,38 @@ public static bool CreateJunction(string junctionPath, string targetPath)
return RunProcess(CreateProcessStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath));
}
+ ///
+ /// Retrieves the first appexeclink in this machine, if any.
+ ///
+ /// A string that represents a path to an appexeclink, if found, or null if not.
+ public static string? GetAppExecLinkPath()
+ {
+ string localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA");
+ if (localAppDataPath is null)
+ {
+ return null;
+ }
+
+ string windowsAppsDir = Path.Join(localAppDataPath, "Microsoft", "WindowsApps");
+
+ if (!Directory.Exists(windowsAppsDir))
+ {
+ return null;
+ }
+
+ var opts = new EnumerationOptions { RecurseSubdirectories = true };
+
+ return new FileSystemEnumerable(
+ windowsAppsDir,
+ (ref FileSystemEntry entry) => entry.ToFullPath(),
+ opts)
+ {
+ ShouldIncludePredicate = (ref FileSystemEntry entry) =>
+ FileSystemName.MatchesWin32Expression("*.exe", entry.FileName) &&
+ (entry.Attributes & FileAttributes.ReparsePoint) != 0
+ }.FirstOrDefault();
+ }
+
public static void Mount(string volumeName, string mountPoint)
{
if (volumeName[volumeName.Length - 1] != Path.DirectorySeparatorChar)
diff --git a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
index f27374cd8e3aa6..b7a7f820c1f8d6 100644
--- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
+++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
@@ -45,7 +45,14 @@
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs
index 2d8f56a62eeb52..dc4f64ada63269 100644
--- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs
+++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs
@@ -215,6 +215,9 @@ internal static TarEntryType GetCorrectTypeFlagForFormat(TarEntryFormat format,
return entryType;
}
+ // Chooses the compatible regular file entry type for the specified format.
+ internal static TarEntryType GetRegularFileEntryTypeForFormat(TarEntryFormat format) => format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+
/// Parses a byte span that represents an ASCII string containing a number in octal base.
internal static T ParseOctal(ReadOnlySpan buffer) where T : struct, INumber
{
diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs
index de3aeb31cb8920..258f4ec8b5d518 100644
--- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs
+++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs
@@ -4,8 +4,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
namespace System.Formats.Tar
{
@@ -33,7 +31,7 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil
Interop.Sys.FileTypes.S_IFCHR => TarEntryType.CharacterDevice,
Interop.Sys.FileTypes.S_IFIFO => TarEntryType.Fifo,
Interop.Sys.FileTypes.S_IFLNK => TarEntryType.SymbolicLink,
- Interop.Sys.FileTypes.S_IFREG => Format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile,
+ Interop.Sys.FileTypes.S_IFREG => TarHelpers.GetRegularFileEntryTypeForFormat(Format),
Interop.Sys.FileTypes.S_IFDIR => TarEntryType.Directory,
_ => throw new IOException(SR.Format(SR.TarUnsupportedFile, fullPath)),
};
diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs
index 29905beef85e0e..bd716678ef23f6 100644
--- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs
+++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs
@@ -3,8 +3,6 @@
using System.Diagnostics;
using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
namespace System.Formats.Tar
{
@@ -21,18 +19,33 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil
FileAttributes attributes = File.GetAttributes(fullPath);
+ bool isDirectory = (attributes & FileAttributes.Directory) != 0;
+
TarEntryType entryType;
if ((attributes & FileAttributes.ReparsePoint) != 0)
{
- entryType = TarEntryType.SymbolicLink;
+ Interop.Kernel32.WIN32_FIND_DATA data = default;
+ Interop.Kernel32.GetFindData(fullPath, isDirectory, ignoreAccessDenied: false, ref data);
+
+ if (data.dwReserved0 is Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK or
+ Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT)
+ {
+ entryType = TarEntryType.SymbolicLink;
+ }
+ else
+ {
+ // All other reparse points are not supported since they cannot be
+ // represented in a tar file using the existing entry types.
+ throw new IOException(SR.Format(SR.TarUnsupportedFile, fullPath));
+ }
}
- else if ((attributes & FileAttributes.Directory) != 0)
+ else if (isDirectory)
{
entryType = TarEntryType.Directory;
}
else if ((attributes & (FileAttributes.Normal | FileAttributes.Archive)) != 0)
{
- entryType = Format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+ entryType = TarHelpers.GetRegularFileEntryTypeForFormat(Format);
}
else
{
diff --git a/src/libraries/System.Formats.Tar/tests/Manual/ManualTests.cs b/src/libraries/System.Formats.Tar/tests/Manual/ManualTests.cs
index 1fa1c686e40e84..8926920d17aff3 100644
--- a/src/libraries/System.Formats.Tar/tests/Manual/ManualTests.cs
+++ b/src/libraries/System.Formats.Tar/tests/Manual/ManualTests.cs
@@ -38,7 +38,7 @@ public void WriteEntry_LongFileSize(TarEntryFormat entryFormat, long size, bool
using (TarWriter writer = new(s, leaveOpen: true))
{
- TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo");
+ TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, GetRegularFileEntryTypeForFormat(entryFormat), "foo");
writeEntry.DataStream = new SimulatedDataStream(size);
writer.WriteEntry(writeEntry);
}
diff --git a/src/libraries/System.Formats.Tar/tests/Manual/ManualTestsAsync.cs b/src/libraries/System.Formats.Tar/tests/Manual/ManualTestsAsync.cs
index 5262c715256179..168b7cb553feab 100644
--- a/src/libraries/System.Formats.Tar/tests/Manual/ManualTestsAsync.cs
+++ b/src/libraries/System.Formats.Tar/tests/Manual/ManualTestsAsync.cs
@@ -27,7 +27,7 @@ public async Task WriteEntry_LongFileSizeAsync(TarEntryFormat entryFormat, long
await using (TarWriter writer = new(s, leaveOpen: true))
{
- TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo");
+ TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, GetRegularFileEntryTypeForFormat(entryFormat), "foo");
writeEntry.DataStream = new SimulatedDataStream(size);
await writer.WriteEntryAsync(writeEntry);
}
diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj
index f3a341d4adc638..d99087b19d996c 100644
--- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj
+++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj
@@ -78,6 +78,8 @@
+
+
diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.Base.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.Base.cs
index 5aee27336d85d0..dcf33ab2f71add 100644
--- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.Base.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Async.Tests.Base.cs
@@ -224,7 +224,7 @@ protected async Task Read_Archive_Many_Small_Files_Async_Internal(TarEntryFormat
int directoriesCount = entries.Count(e => e.EntryType == TarEntryType.Directory);
Assert.Equal(10, directoriesCount);
- TarEntryType actualEntryType = format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+ TarEntryType actualEntryType = GetRegularFileEntryTypeForFormat(format);
for (int i = 0; i < 10; i++)
{
@@ -423,7 +423,7 @@ private void VerifyRegularFileEntry(TarEntry file, TarEntryFormat format, string
Assert.Equal(expectedContents, contents);
}
- TarEntryType expectedEntryType = format == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+ TarEntryType expectedEntryType = GetRegularFileEntryTypeForFormat(format);
Assert.Equal(expectedEntryType, file.EntryType);
Assert.Equal(AssetGid, file.Gid);
diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.Base.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.Base.cs
index f2a8c3e65ee61b..c275c38053b4ea 100644
--- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.Base.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.Base.cs
@@ -201,7 +201,7 @@ protected void Read_Archive_Many_Small_Files_Internal(TarEntryFormat format, Tes
int directoriesCount = entries.Count(e => e.EntryType == TarEntryType.Directory);
Assert.Equal(10, directoriesCount);
- TarEntryType actualEntryType = format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+ TarEntryType actualEntryType = GetRegularFileEntryTypeForFormat(format);
for (int i = 0; i < 10; i++)
{
@@ -382,7 +382,7 @@ private void VerifyRegularFileEntry(TarEntry file, TarEntryFormat format, string
Assert.Equal(expectedContents, contents);
}
- TarEntryType expectedEntryType = format == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+ TarEntryType expectedEntryType = GetRegularFileEntryTypeForFormat(format);
Assert.Equal(expectedEntryType, file.EntryType);
Assert.Equal(AssetGid, file.Gid);
diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs
index 3e14818f8eac4f..7ba8a48430d6cd 100644
--- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs
@@ -479,6 +479,8 @@ protected static TarEntryType GetTarEntryTypeForTarEntryFormat(TarEntryType entr
return entryType;
}
+ protected static TarEntryType GetRegularFileEntryTypeForFormat(TarEntryFormat format) => format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+
protected static TarEntry InvokeTarEntryCreationConstructor(TarEntryFormat targetFormat, TarEntryType entryType, string entryName)
=> targetFormat switch
{
diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Windows.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Windows.cs
new file mode 100644
index 00000000000000..89573ce79f252e
--- /dev/null
+++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Windows.cs
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.IO;
+using Microsoft.DotNet.XUnitExtensions;
+using Xunit;
+using Xunit.Sdk;
+
+namespace System.Formats.Tar.Tests;
+
+public partial class TarWriter_WriteEntry_File_Tests : TarWriter_File_Base
+{
+ [Theory]
+ [InlineData(TarEntryFormat.V7)]
+ [InlineData(TarEntryFormat.Ustar)]
+ [InlineData(TarEntryFormat.Pax)]
+ [InlineData(TarEntryFormat.Gnu)]
+ public void Add_Junction_As_SymbolicLink(TarEntryFormat format)
+ {
+ using TempDirectory root = new TempDirectory();
+ string targetName = "TargetDirectory";
+ string junctionName = "JunctionDirectory";
+ string targetPath = Path.Join(root.Path, targetName);
+ string junctionPath = Path.Join(root.Path, junctionName);
+
+ Directory.CreateDirectory(targetPath);
+
+ Assert.True(MountHelper.CreateJunction(junctionPath, targetPath));
+ DirectoryInfo junctionInfo = new(junctionPath);
+
+ using MemoryStream archive = new MemoryStream();
+ using (TarWriter writer = new TarWriter(archive, format, leaveOpen: true))
+ {
+ writer.WriteEntry(fileName: junctionPath, entryName: junctionPath);
+ }
+
+ archive.Position = 0;
+ using (TarReader reader = new TarReader(archive))
+ {
+ TarEntry entry = reader.GetNextEntry();
+ Assert.Equal(format, entry.Format);
+
+ Assert.NotNull(entry);
+ Assert.Equal(junctionPath, entry.Name);
+ Assert.Equal(targetPath, entry.LinkName);
+ Assert.Equal(TarEntryType.SymbolicLink, entry.EntryType);
+ Assert.Null(entry.DataStream);
+
+ VerifyPlatformSpecificMetadata(junctionPath, entry);
+
+ Assert.Null(reader.GetNextEntry());
+ }
+ }
+
+ [ConditionalTheory]
+ [InlineData(TarEntryFormat.V7)]
+ [InlineData(TarEntryFormat.Ustar)]
+ [InlineData(TarEntryFormat.Pax)]
+ [InlineData(TarEntryFormat.Gnu)]
+ public void Add_Unsupported_ReparsePoints_Throws(TarEntryFormat format)
+ {
+ string? appExecLinkPath = MountHelper.GetAppExecLinkPath();
+ if (appExecLinkPath == null)
+ {
+ throw new SkipTestException("Could not find an appexeclink in this machine.");
+ }
+
+ using MemoryStream archive = new MemoryStream();
+ using TarWriter writer = new TarWriter(archive, format);
+ Assert.Throws(() => writer.WriteEntry(fileName: appExecLinkPath, "UnsupportedAppExecLink"));
+ }
+}
diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs
index e1534a0d4960fc..04b1583838fc27 100644
--- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs
@@ -95,7 +95,7 @@ public void Add_File(TarEntryFormat format)
Assert.NotNull(entry);
Assert.Equal(format, entry.Format);
Assert.Equal(fileName, entry.Name);
- TarEntryType expectedEntryType = format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+ TarEntryType expectedEntryType = GetRegularFileEntryTypeForFormat(format);
Assert.Equal(expectedEntryType, entry.EntryType);
Assert.True(entry.Length > 0);
Assert.NotNull(entry.DataStream);
diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs
index 487c6ab04dbcd5..f81b0f48d8d5d7 100644
--- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs
@@ -337,7 +337,7 @@ private void WriteLongNameCore(TarEntryFormat format, string maxPathComponent)
MemoryStream ms = new();
using (TarWriter writer = new(ms, true))
{
- TarEntryType entryType = format == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;
+ TarEntryType entryType = GetRegularFileEntryTypeForFormat(format);
entry = InvokeTarEntryCreationConstructor(format, entryType, maxPathComponent);
writer.WriteEntry(entry);
@@ -469,7 +469,7 @@ public static IEnumerable