-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Implement Tar Global Extended Attributes API changes #70869
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
Changes from all commits
37cfb52
4e11d92
5279945
aa9c213
94315d3
5895408
df9e81d
273394d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Collections.ObjectModel; | ||
using System.Diagnostics; | ||
|
||
namespace System.Formats.Tar | ||
{ | ||
/// <summary> | ||
/// Represents a Global Extended Attributes TAR entry from an archive of the PAX format. | ||
/// </summary> | ||
public sealed class PaxGlobalExtendedAttributesTarEntry : PosixTarEntry | ||
{ | ||
private ReadOnlyDictionary<string, string>? _readOnlyGlobalExtendedAttributes; | ||
|
||
// Constructor used when reading an existing archive. | ||
internal PaxGlobalExtendedAttributesTarEntry(TarHeader header, TarReader readerOfOrigin) | ||
: base(header, readerOfOrigin, TarEntryFormat.Pax) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new <see cref="PaxGlobalExtendedAttributesTarEntry"/> instance with the specified Global Extended Attributes enumeration. | ||
/// </summary> | ||
/// <param name="globalExtendedAttributes">An enumeration of string key-value pairs that represents the metadata to include as Global Extended Attributes.</param> | ||
/// <exception cref="ArgumentNullException"><paramref name="globalExtendedAttributes"/> is <see langword="null"/>.</exception> | ||
public PaxGlobalExtendedAttributesTarEntry(IEnumerable<KeyValuePair<string, string>> globalExtendedAttributes) | ||
: base(TarEntryType.GlobalExtendedAttributes, TarHeader.GlobalHeadFormatPrefix, TarEntryFormat.Pax, isGea: true) | ||
{ | ||
ArgumentNullException.ThrowIfNull(globalExtendedAttributes); | ||
_header._extendedAttributes = new Dictionary<string, string>(globalExtendedAttributes); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the global extended attributes stored in this entry. | ||
/// </summary> | ||
public IReadOnlyDictionary<string, string> GlobalExtendedAttributes | ||
{ | ||
get | ||
{ | ||
_header._extendedAttributes ??= new Dictionary<string, string>(); | ||
return _readOnlyGlobalExtendedAttributes ??= _header._extendedAttributes.AsReadOnly(); | ||
} | ||
} | ||
|
||
// Determines if the current instance's entry type supports setting a data stream. | ||
internal override bool IsDataStreamSetterSupported() => false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,28 +22,9 @@ internal partial struct TarHeader | |
// "{dirName}/PaxHeaders.{processId}/{fileName}{trailingSeparator}" | ||
private const string PaxHeadersFormat = "{0}/PaxHeaders.{1}/{2}{3}"; | ||
|
||
// Global Extended Attribute entries have a special format in the Name field: | ||
// "{tmpFolder}/GlobalHead.{processId}.1" | ||
private const string GlobalHeadFormat = "{0}/GlobalHead.{1}.1"; | ||
|
||
// Predefined text for the Name field of a GNU long metadata entry. Applies for both LongPath ('L') and LongLink ('K'). | ||
private const string GnuLongMetadataName = "././@LongLink"; | ||
|
||
// Creates a PAX Global Extended Attributes header and writes it into the specified archive stream. | ||
internal static void WriteGlobalExtendedAttributesHeader(Stream archiveStream, Span<byte> buffer, IEnumerable<KeyValuePair<string, string>> globalExtendedAttributes) | ||
{ | ||
TarHeader geaHeader = default; | ||
geaHeader._name = GenerateGlobalExtendedAttributeName(); | ||
geaHeader._mode = (int)TarHelpers.DefaultMode; | ||
geaHeader._typeFlag = TarEntryType.GlobalExtendedAttributes; | ||
geaHeader._linkName = string.Empty; | ||
geaHeader._magic = string.Empty; | ||
geaHeader._version = string.Empty; | ||
geaHeader._gName = string.Empty; | ||
geaHeader._uName = string.Empty; | ||
geaHeader.WriteAsPaxExtendedAttributes(archiveStream, buffer, globalExtendedAttributes, isGea: true); | ||
} | ||
|
||
// Writes the current header as a V7 entry into the archive stream. | ||
internal void WriteAsV7(Stream archiveStream, Span<byte> buffer) | ||
{ | ||
|
@@ -82,14 +63,27 @@ internal void WriteAsUstar(Stream archiveStream, Span<byte> buffer) | |
} | ||
} | ||
|
||
// Writes the current header as a PAX Global Extended Attributes entry into the archive stream. | ||
internal void WriteAsPaxGlobalExtendedAttributes(Stream archiveStream, Span<byte> buffer, int globalExtendedAttributesEntryNumber) | ||
{ | ||
Debug.Assert(_typeFlag is TarEntryType.GlobalExtendedAttributes); | ||
|
||
_name = GenerateGlobalExtendedAttributeName(globalExtendedAttributesEntryNumber); | ||
_extendedAttributes ??= new Dictionary<string, string>(); | ||
WriteAsPaxExtendedAttributes(archiveStream, buffer, _extendedAttributes, isGea: true); | ||
} | ||
|
||
// Writes the current header as a PAX entry into the archive stream. | ||
// Makes sure to add the preceding exteded attributes entry before the actual entry. | ||
// Makes sure to add the preceding extended attributes entry before the actual entry. | ||
internal void WriteAsPax(Stream archiveStream, Span<byte> buffer) | ||
{ | ||
Debug.Assert(_typeFlag is not TarEntryType.GlobalExtendedAttributes); | ||
|
||
// First, we write the preceding extended attributes header | ||
TarHeader extendedAttributesHeader = default; | ||
// Fill the current header's dict | ||
CollectExtendedAttributesFromStandardFieldsIfNeeded(); | ||
// And pass the attributes to the preceding extended attributes header for writing | ||
Debug.Assert(_extendedAttributes != null); | ||
extendedAttributesHeader.WriteAsPaxExtendedAttributes(archiveStream, buffer, _extendedAttributes, isGea: false); | ||
|
||
|
@@ -611,30 +605,30 @@ private string GenerateExtendedAttributeName() | |
} | ||
|
||
// Gets the special name for the 'name' field in a global extended attribute entry. | ||
// Format: "%d/GlobalHead.%p/%f" | ||
// Format: "%d/GlobalHead.%p/%n" | ||
// - %d: The path of the $TMPDIR variable, if found. Otherwise, the value is '/tmp'. | ||
// - %p: The current process ID. | ||
// - %n: The sequence number of the global extended header record of the archive, starting at 1. In our case, since we only generate one, the value is always 1. | ||
// If the path of $TMPDIR makes the final string too long to fit in the 'name' field, | ||
// then the TMPDIR='/tmp' is used. | ||
private static string GenerateGlobalExtendedAttributeName() | ||
private static string GenerateGlobalExtendedAttributeName(int globalExtendedAttributesEntryNumber) | ||
{ | ||
string? tmpDir = Environment.GetEnvironmentVariable("TMPDIR"); | ||
if (string.IsNullOrWhiteSpace(tmpDir)) | ||
{ | ||
tmpDir = "/tmp"; | ||
} | ||
else if (Path.EndsInDirectorySeparator(tmpDir)) | ||
Debug.Assert(globalExtendedAttributesEntryNumber >= 1); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an aside - the code below reading TMPDIR then falling back to /tmp- why doesn't it just call Path.GetTempPath() that does the same thing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good suggestion. I'll use Here's why I wasn't using that: The GNU tar manual specifies how the global extended attribute header name field obtains its value:
This description looks very similar to the remarks section of Path.GetTempPath:
So a couple of things need to be considered if we are to use
|
||
string tmpDir = Path.GetTempPath(); | ||
if (Path.EndsInDirectorySeparator(tmpDir)) | ||
{ | ||
tmpDir = Path.TrimEndingDirectorySeparator(tmpDir); | ||
} | ||
int processId = Environment.ProcessId; | ||
|
||
string result = string.Format(GlobalHeadFormat, tmpDir, processId); | ||
if (result.Length >= FieldLengths.Name) | ||
string result = string.Format(GlobalHeadFormatPrefix, tmpDir, processId); | ||
string suffix = $".{globalExtendedAttributesEntryNumber}"; // GEA sequence number | ||
if (result.Length + suffix.Length >= FieldLengths.Name) | ||
{ | ||
result = string.Format(GlobalHeadFormat, "/tmp", processId); | ||
result = string.Format(GlobalHeadFormatPrefix, "/tmp", processId); | ||
} | ||
result += suffix; | ||
|
||
return result; | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.