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 WriteEntry_UsingTarEntry_FromTarReader_IntoT { foreach (var entryFormat in new[] { TarEntryFormat.V7, TarEntryFormat.Ustar, TarEntryFormat.Pax, TarEntryFormat.Gnu }) { - foreach (var entryType in new[] { entryFormat == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, TarEntryType.Directory, TarEntryType.SymbolicLink }) + foreach (var entryType in new[] { GetRegularFileEntryTypeForFormat(entryFormat), TarEntryType.Directory, TarEntryType.SymbolicLink }) { foreach (bool unseekableStream in new[] { false, true }) { @@ -520,7 +520,7 @@ public void WriteEntry_FileSizeOverLegacyLimit_Throws(TarEntryFormat entryFormat using Stream s = unseekableStream ? new WrappedStream(ms, ms.CanRead, ms.CanWrite, canSeek: false) : ms; using TarWriter writer = new(s); - TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo"); + TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, GetRegularFileEntryTypeForFormat(entryFormat), "foo"); writeEntry.DataStream = new SimulatedDataStream(FileSizeOverLimit); Assert.Equal(FileSizeOverLimit, writeEntry.Length); diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.Windows.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.Windows.cs new file mode 100644 index 00000000000000..a5159decd9c809 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.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 System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; +using Xunit; + +namespace System.Formats.Tar.Tests; + +public partial class TarWriter_WriteEntryAsync_File_Tests : TarWriter_File_Base +{ + [Theory] + [InlineData(TarEntryFormat.V7)] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public async Task Add_Junction_As_SymbolicLink_Async(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); + + await using MemoryStream archive = new MemoryStream(); + await using (TarWriter writer = new TarWriter(archive, format, leaveOpen: true)) + { + await writer.WriteEntryAsync(fileName: junctionPath, entryName: junctionPath); + } + + archive.Position = 0; + await using (TarReader reader = new TarReader(archive)) + { + TarEntry entry = await reader.GetNextEntryAsync(); + 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(await reader.GetNextEntryAsync()); + } + } + + [ConditionalTheory] + [InlineData(TarEntryFormat.V7)] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public async Task Add_Unsupported_ReparsePoints_Throws_Async(TarEntryFormat format) + { + string? appExecLinkPath = MountHelper.GetAppExecLinkPath(); + if (appExecLinkPath == null) + { + throw new SkipTestException("Could not find an appexeclink in this machine."); + } + + await using MemoryStream archive = new MemoryStream(); + await using TarWriter writer = new TarWriter(archive, format); + await Assert.ThrowsAsync(() => writer.WriteEntryAsync(fileName: appExecLinkPath, "UnsupportedAppExecLink")); + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.cs index b5bf3f59022bb7..cbcb9cb14ac544 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.File.Tests.cs @@ -102,7 +102,7 @@ public async Task Add_File_Async(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.WriteEntryAsync.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs index 45b26ad66e6005..23d234e15b4b7d 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntryAsync.Tests.cs @@ -439,7 +439,7 @@ public async Task WriteEntry_FileSizeOverLegacyLimit_Throws_Async(TarEntryFormat string tarFilePath = GetTestFilePath(); await using TarWriter writer = new(File.Create(tarFilePath)); - TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, entryFormat is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, "foo"); + TarEntry writeEntry = InvokeTarEntryCreationConstructor(entryFormat, GetRegularFileEntryTypeForFormat(entryFormat), "foo"); writeEntry.DataStream = new SimulatedDataStream(FileSizeOverLimit); Assert.Equal(FileSizeOverLimit, writeEntry.Length); diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs index c8e047cbd88b86..ad3b8cf2d63106 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs @@ -471,33 +471,5 @@ protected void CreateSymbolicLink_PathToTarget_RelativeToLinkPath_Internal(bool Directory.SetCurrentDirectory(Path.GetTempPath()); } - - protected 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(); - } } } diff --git a/src/libraries/System.IO.FileSystem/tests/File/SymbolicLinks.cs b/src/libraries/System.IO.FileSystem/tests/File/SymbolicLinks.cs index 56b79f940f57e3..e4c2cbc7f0d9e4 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/SymbolicLinks.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/SymbolicLinks.cs @@ -50,7 +50,7 @@ public void ResolveLinkTarget_Throws_NotExists() => [PlatformSpecific(TestPlatforms.Windows)] public void UnsupportedLink_ReturnsNull() { - string unsupportedLinkPath = GetAppExecLinkPath(); + string unsupportedLinkPath = MountHelper.GetAppExecLinkPath(); if (unsupportedLinkPath is null) { return; diff --git a/src/libraries/System.IO.FileSystem/tests/FileInfo/SymbolicLinks.cs b/src/libraries/System.IO.FileSystem/tests/FileInfo/SymbolicLinks.cs index 13c41150ffd5c6..af190eab7cae97 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileInfo/SymbolicLinks.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileInfo/SymbolicLinks.cs @@ -47,7 +47,7 @@ public void ResolveLinkTarget_Throws_NotExists() => [PlatformSpecific(TestPlatforms.Windows)] public void UnsupportedLink_ReturnsNull() { - string unsupportedLinkPath = GetAppExecLinkPath(); + string unsupportedLinkPath = MountHelper.GetAppExecLinkPath(); if (unsupportedLinkPath is null) { return; diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index fc063209e36fce..96b485a425ede8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -259,7 +259,7 @@ public static void RemoveDirectory(string fullPath, bool recursive) // FindFirstFile($path) (used by GetFindData) fails with ACCESS_DENIED when user has no ListDirectory rights // but FindFirstFile($path/*") (used by RemoveDirectoryRecursive) works fine in such scenario. // So we ignore it here and let RemoveDirectoryRecursive throw if FindFirstFile($path/*") fails with ACCESS_DENIED. - GetFindData(fullPath, isDirectory: true, ignoreAccessDenied: true, ref findData); + Interop.Kernel32.GetFindData(fullPath, isDirectory: true, ignoreAccessDenied: true, ref findData); if (IsNameSurrogateReparsePoint(ref findData)) { // Don't recurse @@ -273,21 +273,6 @@ public static void RemoveDirectory(string fullPath, bool recursive) RemoveDirectoryRecursive(fullPath, ref findData, topLevel: true); } - private static void GetFindData(string fullPath, bool isDirectory, bool ignoreAccessDenied, ref Interop.Kernel32.WIN32_FIND_DATA findData) - { - using SafeFindHandle handle = Interop.Kernel32.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 == Interop.Errors.ERROR_FILE_NOT_FOUND) - errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND; - if (ignoreAccessDenied && errorCode == Interop.Errors.ERROR_ACCESS_DENIED) - return; - throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); - } - } - private static bool IsNameSurrogateReparsePoint(ref Interop.Kernel32.WIN32_FIND_DATA data) { // Name surrogates are reparse points that point to other named entities local to the file system. @@ -668,7 +653,7 @@ static string GetTargetPathWithoutNTPrefix(ReadOnlySpan targetPath) private static unsafe string? GetFinalLinkTarget(string linkPath, bool isDirectory) { Interop.Kernel32.WIN32_FIND_DATA data = default; - GetFindData(linkPath, isDirectory, ignoreAccessDenied: false, ref data); + Interop.Kernel32.GetFindData(linkPath, isDirectory, ignoreAccessDenied: false, ref data); // The file or directory is not a reparse point. if ((data.dwFileAttributes & (uint)FileAttributes.ReparsePoint) == 0 ||