Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
#if !NETFRAMEWORK
using System.IO.Enumeration;
#endif
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
Expand Down Expand Up @@ -184,6 +187,40 @@ public static bool CreateJunction(string junctionPath, string targetPath)
return RunProcess(CreateProcessStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath));
}

#if !NETFRAMEWORK
/// <summary>
/// Retrieves the first appexeclink in this machine, if any.
/// </summary>
/// <returns>A string that represents a path to an appexeclink, if found, or null if not.</returns>
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<string?>(
windowsAppsDir,
(ref FileSystemEntry entry) => entry.ToFullPath(),
opts)
{
ShouldIncludePredicate = (ref FileSystemEntry entry) =>
FileSystemName.MatchesWin32Expression("*.exe", entry.FileName) &&
(entry.Attributes & FileAttributes.ReparsePoint) != 0
}.FirstOrDefault();
}
#endif

public static void Mount(string volumeName, string mountPoint)
{
if (volumeName[volumeName.Length - 1] != Path.DirectorySeparatorChar)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ internal static bool TryGetStringAsBaseTenLong(IReadOnlyDictionary<string, strin
return false;
}

// 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;

// When writing an entry that came from an archive of a different format, if its entry type happens to
// be an incompatible regular file entry type, convert it to the compatible one.
// No change for all other entry types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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)),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace System.Formats.Tar
{
Expand All @@ -21,18 +19,26 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil

FileAttributes attributes = File.GetAttributes(fullPath);

bool isDirectory = (attributes & FileAttributes.Directory) != 0;

FileSystemInfo info = isDirectory ? new DirectoryInfo(fullPath) : new FileInfo(fullPath);

TarEntryType entryType;
if ((attributes & FileAttributes.ReparsePoint) != 0)
string? linkTarget = (attributes & FileAttributes.ReparsePoint) != 0 ? info.LinkTarget : null;
if (linkTarget is not null)
{
// Only treat reparse points that are symlinks or junctions as symbolic links.
// Other reparse points (e.g., deduplication, OneDrive) should be treated as
// regular files or directories.
entryType = TarEntryType.SymbolicLink;
}
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
{
Expand All @@ -48,8 +54,6 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil
_ => throw new InvalidDataException(SR.Format(SR.TarInvalidFormat, Format)),
};

FileSystemInfo info = (attributes & FileAttributes.Directory) != 0 ? new DirectoryInfo(fullPath) : new FileInfo(fullPath);

entry._header._mTime = info.LastWriteTimeUtc;
// We do not set atime and ctime by default because many external tools are unable to read GNU entries
// that have these fields set to non-zero values. This is because the GNU format writes atime and ctime in the same
Expand All @@ -61,7 +65,7 @@ private TarEntry ConstructEntryForWriting(string fullPath, string entryName, Fil

if (entry.EntryType == TarEntryType.SymbolicLink)
{
entry.LinkName = info.LinkTarget ?? string.Empty;
entry.LinkName = linkTarget ?? string.Empty;
}

if (entry.EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
<Compile Include="TarFile\TarFile.ExtractToDirectory.File.Tests.Windows.cs" />
<Compile Include="TarFile\TarFile.ExtractToDirectoryAsync.File.Tests.Windows.cs" />
<Compile Include="TarWriter\TarWriter.File.Base.Windows.cs" />
<Compile Include="TarWriter\TarWriter.WriteEntry.File.Tests.Windows.cs" />
<Compile Include="TarWriter\TarWriter.WriteEntryAsync.File.Tests.Windows.cs" />
<Compile Include="$(CommonPath)System\IO\PathInternal.Windows.cs" Link="Common\System\IO\PathInternal.Windows.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs" Link="Common\System\Text\ValueStringBuilder.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
{
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
{
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Formats.Tar/tests/TarTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,8 @@ protected static TarEntryType GetTarEntryTypeForTarEntryFormat(TarEntryType entr
return entryType;
}

protected static TarEntryType GetRegularFileEntryTypeForFormat(TarEntryFormat format) => format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile;

protected TarEntry InvokeTarEntryCreationConstructor(TarEntryFormat targetFormat, TarEntryType entryType, string entryName, bool setATimeCTime = false)
{
TarEntry entry = (targetFormat, setATimeCTime) switch
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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;

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));

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.NotNull(entry);
Assert.Equal(format, entry.Format);
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_Non_Symlink_ReparsePoint_Does_Not_Write_As_SymbolicLink(TarEntryFormat format)
{
string? appExecLinkPath = MountHelper.GetAppExecLinkPath();
if (appExecLinkPath is null)
{
throw new SkipTestException("Could not find an appexeclink in this machine.");
}

using MemoryStream archive = new MemoryStream();
using TarWriter writer = new TarWriter(archive, format);
// Non-symlink/junction reparse points should not be treated as SymbolicLink entries.
// They may throw when attempting to read file content, or succeed as regular files.
try
{
writer.WriteEntry(fileName: appExecLinkPath, "NonSymlinkReparsePoint");
}
catch (IOException)
{
// Expected: some reparse points cannot be opened as regular files.
return;
}

// If it didn't throw, verify it was not written as a symbolic link.
archive.Position = 0;
using TarReader reader = new TarReader(archive);
TarEntry entry = reader.GetNextEntry();
Assert.NotNull(entry);
Assert.NotEqual(TarEntryType.SymbolicLink, entry.EntryType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,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);

Expand Down Expand Up @@ -521,7 +521,7 @@ public static IEnumerable<object[]> 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 })
{
Expand Down
Loading
Loading