From 800cb17b2096ee0ba2eb1046b432af4a2ef016a7 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Fri, 7 Aug 2026 18:35:48 +0200 Subject: [PATCH] Improve PaxTarEntry construction performance Capture known PAX attributes while validating and inserting them to avoid repeated dictionary lookups during entry construction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 487aab98-d3a1-4f93-b2a0-97ee2950c4cf --- .../src/System/Formats/Tar/TarHeader.Read.cs | 118 ++++++++++++++---- .../src/System/Formats/Tar/TarHeader.cs | 25 ++-- .../src/System/Formats/Tar/TarHelpers.cs | 36 ++++-- 3 files changed, 139 insertions(+), 40 deletions(-) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs index 1a729094a68328..d591b1b9184bda 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs @@ -94,7 +94,65 @@ internal void ReplaceNormalAttributesWithExtended(IEnumerable> enumerator = extendedAttributes.GetEnumerator(); + while (enumerator.MoveNext()) + { + KeyValuePair extendedAttribute = enumerator.Current; + + ValidateExtendedAttribute(extendedAttribute); + _ea ??= new Dictionary(); + _ea.Add(extendedAttribute.Key, extendedAttribute.Value); + + switch (extendedAttribute.Key) + { + case PaxEaName: + values.Name = extendedAttribute.Value; + break; + case PaxEaLinkName: + values.LinkName = extendedAttribute.Value; + break; + case PaxEaMTime: + values.MTime = extendedAttribute.Value; + break; + case PaxEaMode: + values.Mode = extendedAttribute.Value; + break; + case PaxEaSize: + values.Size = extendedAttribute.Value; + break; + case PaxEaGnuSparseName: + values.GnuSparseName = extendedAttribute.Value; + break; + case PaxEaGnuSparseRealSize: + values.GnuSparseRealSize = extendedAttribute.Value; + break; + case PaxEaGnuSparseMajor: + values.GnuSparseMajor = extendedAttribute.Value; + break; + case PaxEaGnuSparseMinor: + values.GnuSparseMinor = extendedAttribute.Value; + break; + case PaxEaUid: + values.Uid = extendedAttribute.Value; + break; + case PaxEaGid: + values.Gid = extendedAttribute.Value; + break; + case PaxEaUName: + values.UName = extendedAttribute.Value; + break; + case PaxEaGName: + values.GName = extendedAttribute.Value; + break; + case PaxEaDevMajor: + values.DevMajor = extendedAttribute.Value; + break; + case PaxEaDevMinor: + values.DevMinor = extendedAttribute.Value; + break; + } + } if (_ea == null || _ea.Count == 0) { @@ -107,33 +165,33 @@ internal void ReplaceNormalAttributesWithExtended(IEnumerable> ex { KeyValuePair kvp = enumerator.Current; - int index = kvp.Key.AsSpan().IndexOfAny('=', '\n'); - if (index >= 0) - { - throw new ArgumentException(SR.Format(SR.TarExtAttrDisallowedKeyChar, kvp.Key, kvp.Key[index] == '\n' ? "\\n" : kvp.Key[index])); - } - if (kvp.Value.Contains('\n')) - { - throw new ArgumentException(SR.Format(SR.TarExtAttrDisallowedValueChar, kvp.Key, "\\n")); - } - + ValidateExtendedAttribute(kvp); _ea ??= new Dictionary(); - _ea.Add(kvp.Key, kvp.Value); } } + private static void ValidateExtendedAttribute(KeyValuePair extendedAttribute) + { + int index = extendedAttribute.Key.AsSpan().IndexOfAny('=', '\n'); + if (index >= 0) + { + throw new ArgumentException(SR.Format(SR.TarExtAttrDisallowedKeyChar, extendedAttribute.Key, extendedAttribute.Key[index] == '\n' ? "\\n" : extendedAttribute.Key[index])); + } + if (extendedAttribute.Value.Contains('\n')) + { + throw new ArgumentException(SR.Format(SR.TarExtAttrDisallowedValueChar, extendedAttribute.Key, "\\n")); + } + } + private static string GetMagicForFormat(TarEntryFormat format) => format switch { TarEntryFormat.Ustar or TarEntryFormat.Pax => UstarMagic, 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 89252d198d31dc..3d4f131ed74b9c 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 @@ -128,14 +128,20 @@ private static decimal GetSecondsSinceEpochFromDateTimeOffset(DateTimeOffset dat // If the specified fieldName is found in the provided dictionary and it is a valid decimal number, returns true and sets the value in 'dateTimeOffset'. internal static bool TryGetDateTimeOffsetFromTimestampString(Dictionary? dict, string fieldName, out DateTimeOffset dateTimeOffset) { - dateTimeOffset = default; - if (dict != null && - dict.TryGetValue(fieldName, out string? value) && - decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal secondsSinceEpoch)) + return TryGetDateTimeOffsetFromTimestampString( + dict is not null && dict.TryGetValue(fieldName, out string? value) ? value : null, + out dateTimeOffset); + } + + internal static bool TryGetDateTimeOffsetFromTimestampString(string? value, out DateTimeOffset dateTimeOffset) + { + if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal secondsSinceEpoch)) { dateTimeOffset = GetDateTimeOffsetFromSecondsSinceEpoch(secondsSinceEpoch); return true; } + + dateTimeOffset = default; return false; } @@ -151,9 +157,16 @@ internal static string GetTimestampStringFromDateTimeOffset(DateTimeOffset times // If the specified fieldName is found in the provided dictionary and is a valid string representation of a number, returns true and sets the value in 'baseTenInteger'. internal static bool TryGetStringAsBaseTenInteger(IReadOnlyDictionary dict, string fieldName, out int baseTenInteger) { - if (dict.TryGetValue(fieldName, out string? strNumber) && !string.IsNullOrEmpty(strNumber)) + return TryGetStringAsBaseTenInteger( + dict.TryGetValue(fieldName, out string? value) ? value : null, + out baseTenInteger); + } + + internal static bool TryGetStringAsBaseTenInteger(string? value, out int baseTenInteger) + { + if (!string.IsNullOrEmpty(value)) { - baseTenInteger = int.Parse(strNumber, CultureInfo.InvariantCulture); + baseTenInteger = int.Parse(value, CultureInfo.InvariantCulture); return true; } @@ -164,9 +177,16 @@ internal static bool TryGetStringAsBaseTenInteger(IReadOnlyDictionary dict, string fieldName, out long baseTenLong) { - if (dict.TryGetValue(fieldName, out string? strNumber) && !string.IsNullOrEmpty(strNumber)) + return TryGetStringAsBaseTenLong( + dict.TryGetValue(fieldName, out string? value) ? value : null, + out baseTenLong); + } + + internal static bool TryGetStringAsBaseTenLong(string? value, out long baseTenLong) + { + if (!string.IsNullOrEmpty(value)) { - baseTenLong = long.Parse(strNumber, CultureInfo.InvariantCulture); + baseTenLong = long.Parse(value, CultureInfo.InvariantCulture); return true; }