Skip to content

Fix a few Tar issues post perf improvements #74338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 22, 2022
Merged
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
20 changes: 20 additions & 0 deletions src/libraries/Common/src/System/IO/Archiving.Utils.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,25 @@ namespace System.IO
internal static partial class ArchivingUtils
{
internal static string SanitizeEntryFilePath(string entryPath) => entryPath.Replace('\0', '_');

public static unsafe string EntryFromPath(ReadOnlySpan<char> path, bool appendPathSeparator = false)
{
// Remove leading separators.
int nonSlash = path.IndexOfAnyExcept('/');
if (nonSlash == -1)
{
nonSlash = path.Length;
}
path = path.Slice(nonSlash);

// Append a separator if necessary.
return (path.IsEmpty, appendPathSeparator) switch
{
(false, false) => path.ToString(),
(false, true) => string.Concat(path, "/"),
(true, false) => string.Empty,
(true, true) => "/",
};
}
}
}
43 changes: 43 additions & 0 deletions src/libraries/Common/src/System/IO/Archiving.Utils.Windows.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using System.Text;

namespace System.IO
Expand Down Expand Up @@ -42,5 +43,47 @@ internal static string SanitizeEntryFilePath(string entryPath)
// There weren't any characters to sanitize. Just return the original string.
return entryPath;
}

public static unsafe string EntryFromPath(ReadOnlySpan<char> path, bool appendPathSeparator = false)
{
// Remove leading separators.
int nonSlash = path.IndexOfAnyExcept('/', '\\');
if (nonSlash == -1)
{
nonSlash = path.Length;
}
path = path.Slice(nonSlash);

// Replace \ with /, and append a separator if necessary.

if (path.IsEmpty)
{
return appendPathSeparator ?
"/" :
string.Empty;
}

fixed (char* pathPtr = &MemoryMarshal.GetReference(path))
{
return string.Create(appendPathSeparator ? path.Length + 1 : path.Length, (appendPathSeparator, (IntPtr)pathPtr, path.Length), static (dest, state) =>
{
ReadOnlySpan<char> path = new ReadOnlySpan<char>((char*)state.Item2, state.Length);
path.CopyTo(dest);
if (state.appendPathSeparator)
{
dest[^1] = '/';
}

// To ensure tar files remain compatible with Unix, and per the ZIP File Format Specification 4.4.17.1,
// all slashes should be forward slashes.
int pos;
while ((pos = dest.IndexOf('\\')) >= 0)
{
dest[pos] = '/';
dest = dest.Slice(pos + 1);
}
});
}
}
}
}
54 changes: 0 additions & 54 deletions src/libraries/Common/src/System/IO/Archiving.Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,60 +9,6 @@ namespace System.IO
{
internal static partial class ArchivingUtils
{
// To ensure tar files remain compatible with Unix,
// and per the ZIP File Format Specification 4.4.17.1,
// all slashes should be forward slashes.
private const char PathSeparatorChar = '/';
private const string PathSeparatorString = "/";

public static string EntryFromPath(string entry, int offset, int length, bool appendPathSeparator = false)
{
Debug.Assert(length <= entry.Length - offset);

// Remove any leading slashes from the entry name:
while (length > 0)
{
if (entry[offset] != Path.DirectorySeparatorChar &&
entry[offset] != Path.AltDirectorySeparatorChar)
break;

offset++;
length--;
}

if (length == 0)
{
return appendPathSeparator ? PathSeparatorString : string.Empty;
}

if (appendPathSeparator)
{
length++;
}

return string.Create(length, (appendPathSeparator, offset, entry), static (dest, state) =>
{
state.entry.AsSpan(state.offset).CopyTo(dest);

// '/' is a more broadly recognized directory separator on all platforms (eg: mac, linux)
// We don't use Path.DirectorySeparatorChar or AltDirectorySeparatorChar because this is
// explicitly trying to standardize to '/'
for (int i = 0; i < dest.Length; i++)
{
char ch = dest[i];
if (ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar)
{
dest[i] = PathSeparatorChar;
}
}

if (state.appendPathSeparator)
{
dest[^1] = PathSeparatorChar;
}
});
}

public static void EnsureCapacity(ref char[] buffer, int min)
{
Debug.Assert(buffer != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,16 +344,13 @@ private static string GetBasePathForCreateFromDirectory(DirectoryInfo di, bool i
// Constructs the entry name used for a filesystem entry when creating an archive.
private static string GetEntryNameForFileSystemInfo(FileSystemInfo file, int basePathLength)
{
int entryNameLength = file.FullName.Length - basePathLength;
Debug.Assert(entryNameLength > 0);

bool isDirectory = (file.Attributes & FileAttributes.Directory) != 0;
return ArchivingUtils.EntryFromPath(file.FullName, basePathLength, entryNameLength, appendPathSeparator: isDirectory);
return ArchivingUtils.EntryFromPath(file.FullName.AsSpan(basePathLength), appendPathSeparator: isDirectory);
}

private static string GetEntryNameForBaseDirectory(string name)
{
return ArchivingUtils.EntryFromPath(name, 0, name.Length, appendPathSeparator: true);
return ArchivingUtils.EntryFromPath(name, appendPathSeparator: true);
}

// Extracts an archive into the specified directory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,16 @@ private static async Task WriteDataAsync(Stream archiveStream, Stream dataStream
// The format is:
// "XX attribute=value\n"
// where "XX" is the number of characters in the entry, including those required for the count itself.
// If prepending the length digits increases the number of digits, we need to expand.
int length = 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value);
length += CountDigits(length);
int originalDigitCount = CountDigits(length), newDigitCount;
length += originalDigitCount;
while ((newDigitCount = CountDigits(length)) != originalDigitCount)
{
length += newDigitCount - originalDigitCount;
originalDigitCount = newDigitCount;
}
Debug.Assert(length == CountDigits(length) + 3 + Encoding.UTF8.GetByteCount(attribute) + Encoding.UTF8.GetByteCount(value));

// Get a large enough buffer if we don't already have one.
if (span.Length < length)
Expand All @@ -569,8 +577,7 @@ private static async Task WriteDataAsync(Stream archiveStream, Stream dataStream
{
ArrayPool<byte>.Shared.Return(buffer);
}
buffer = ArrayPool<byte>.Shared.Rent(length);
span = buffer;
span = buffer = ArrayPool<byte>.Shared.Rent(length);
}

// Format the contents.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,10 @@ private static void DoCreateFromDirectory(string sourceDirectoryName, string des
{
directoryIsEmpty = false;

int entryNameLength = file.FullName.Length - basePath.Length;
Debug.Assert(entryNameLength > 0);

if (file is FileInfo)
{
// Create entry for file:
string entryName = ArchivingUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength);
string entryName = ArchivingUtils.EntryFromPath(file.FullName.AsSpan(basePath.Length));
ZipFileExtensions.DoCreateEntryFromFile(archive, file.FullName, entryName, compressionLevel);
}
else
Expand All @@ -395,15 +392,15 @@ private static void DoCreateFromDirectory(string sourceDirectoryName, string des
{
// FullName never returns a directory separator character on the end,
// but Zip archives require it to specify an explicit directory:
string entryName = ArchivingUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, appendPathSeparator: true);
string entryName = ArchivingUtils.EntryFromPath(file.FullName.AsSpan(basePath.Length), appendPathSeparator: true);
archive.CreateEntry(entryName);
}
}
}

// If no entries create an empty root directory entry:
if (includeBaseDirectory && directoryIsEmpty)
archive.CreateEntry(ArchivingUtils.EntryFromPath(di.Name, 0, di.Name.Length, appendPathSeparator: true));
archive.CreateEntry(ArchivingUtils.EntryFromPath(di.Name, appendPathSeparator: true));
}
}
}
Expand Down