File: System\Formats\Tar\PaxTarEntry.cs
Web Access
Project: src\src\libraries\System.Formats.Tar\src\System.Formats.Tar.csproj (System.Formats.Tar)
// 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 tar entry from an archive of the PAX format.
    /// </summary>
    public sealed class PaxTarEntry : PosixTarEntry
    {
        private ReadOnlyDictionary<string, string>? _readOnlyExtendedAttributes;
 
        // Constructor called when reading a TarEntry from a TarReader.
        internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin)
            : base(header, readerOfOrigin, TarEntryFormat.Pax)
        {
        }
 
        /// <summary>
        /// Initializes a new <see cref="PaxTarEntry"/> instance with the specified entry type, entry name, and the default extended attributes.
        /// </summary>
        /// <param name="entryType">The type of the entry.</param>
        /// <param name="entryName">A string with the path and file name of this entry.</param>
        /// <remarks><para>When creating an instance using the <see cref="PaxTarEntry(TarEntryType, string)"/> constructor, only the following entry types are supported:</para>
        /// <list type="bullet">
        /// <item>In all platforms: <see cref="TarEntryType.Directory"/>, <see cref="TarEntryType.HardLink"/>, <see cref="TarEntryType.SymbolicLink"/>, <see cref="TarEntryType.RegularFile"/>.</item>
        /// <item>In Unix platforms only: <see cref="TarEntryType.BlockDevice"/>, <see cref="TarEntryType.CharacterDevice"/> and <see cref="TarEntryType.Fifo"/>.</item>
        /// </list>
        /// <para>Use the <see cref="PaxTarEntry(TarEntryType, string, IEnumerable{KeyValuePair{string, string}})"/> constructor to include additional extended attributes when creating the entry.</para>
        /// <para>The following entries are always found in the Extended Attributes dictionary of any PAX entry:</para>
        /// <list type="bullet">
        /// <item>Modification time, under the name <c>mtime</c>, as a <see cref="double"/> number.</item>
        /// <item>Access time, under the name <c>atime</c>, as a <see cref="double"/> number.</item>
        /// <item>Change time, under the name <c>ctime</c>, as a <see cref="double"/> number.</item>
        /// <item>Path, under the name <c>path</c>, as a string.</item>
        /// </list>
        /// <para>The following entries are only found in the Extended Attributes dictionary of a PAX entry if certain conditions are met:</para>
        /// <list type="bullet">
        /// <item>Group name, under the name <c>gname</c>, as a string, if it is larger than 32 bytes.</item>
        /// <item>User name, under the name <c>uname</c>, as a string, if it is larger than 32 bytes.</item>
        /// <item>File length, under the name <c>size</c>, as an <see cref="int"/>, if the string representation of the number is larger than 12 bytes.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="entryName"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException"><para><paramref name="entryName"/> is empty.</para>
        /// <para>-or-</para>
        /// <para><paramref name="entryType"/> is not supported in the specified format.</para></exception>
        public PaxTarEntry(TarEntryType entryType, string entryName)
            : base(entryType, entryName, TarEntryFormat.Pax, isGea: false)
        {
            _header._prefix = string.Empty;
 
            Debug.Assert(_header._mTime != default);
            AddNewAccessAndChangeTimestampsIfNotExist(useMTime: true);
        }
 
        /// <summary>
        /// Initializes a new <see cref="PaxTarEntry"/> instance with the specified entry type, entry name and Extended Attributes enumeration.
        /// </summary>
        /// <param name="entryType">The type of the entry.</param>
        /// <param name="entryName">A string with the path and file name of this entry.</param>
        /// <param name="extendedAttributes">An enumeration of string key-value pairs that represents the metadata to include in the Extended Attributes entry that precedes the current entry.</param>
        /// <remarks>When creating an instance using the <see cref="PaxTarEntry(TarEntryType, string)"/> constructor, only the following entry types are supported:
        /// <list type="bullet">
        /// <item>In all platforms: <see cref="TarEntryType.Directory"/>, <see cref="TarEntryType.HardLink"/>, <see cref="TarEntryType.SymbolicLink"/>, <see cref="TarEntryType.RegularFile"/>.</item>
        /// <item>In Unix platforms only: <see cref="TarEntryType.BlockDevice"/>, <see cref="TarEntryType.CharacterDevice"/> and <see cref="TarEntryType.Fifo"/>.</item>
        /// </list>
        /// The specified <paramref name="extendedAttributes"/> get appended to the default attributes, unless the specified enumeration overrides any of them.
        /// <para>The following entries are always found in the Extended Attributes dictionary of any PAX entry:</para>
        /// <list type="bullet">
        /// <item>Modification time, under the name <c>mtime</c>, as a <see cref="double"/> number.</item>
        /// <item>Access time, under the name <c>atime</c>, as a <see cref="double"/> number.</item>
        /// <item>Change time, under the name <c>ctime</c>, as a <see cref="double"/> number.</item>
        /// <item>Path, under the name <c>path</c>, as a string.</item>
        /// </list>
        /// <para>The following entries are only found in the Extended Attributes dictionary of a PAX entry if certain conditions are met:</para>
        /// <list type="bullet">
        /// <item>Group name, under the name <c>gname</c>, as a string, if it is larger than 32 bytes.</item>
        /// <item>User name, under the name <c>uname</c>, as a string, if it is larger than 32 bytes.</item>
        /// <item>File length, under the name <c>size</c>, as an <see cref="int"/>, if the string representation of the number is larger than 12 bytes.</item>
        /// </list>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="extendedAttributes"/> or <paramref name="entryName"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException"><para><paramref name="entryName"/> is empty.</para>
        /// <para>-or-</para>
        /// <para><paramref name="entryType"/> is not supported in the specified format.</para></exception>
        public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable<KeyValuePair<string, string>> extendedAttributes)
            : base(entryType, entryName, TarEntryFormat.Pax, isGea: false)
        {
            ArgumentNullException.ThrowIfNull(extendedAttributes);
 
            _header._prefix = string.Empty;
            _header.InitializeExtendedAttributesWithExisting(extendedAttributes);
 
            Debug.Assert(_header._mTime != default);
            AddNewAccessAndChangeTimestampsIfNotExist(useMTime: true);
        }
 
        /// <summary>
        /// Initializes a new <see cref="PaxTarEntry"/> instance by converting the specified <paramref name="other"/> entry into the PAX format.
        /// </summary>
        /// <exception cref="ArgumentException"><para><paramref name="other"/> is a <see cref="PaxGlobalExtendedAttributesTarEntry"/> and cannot be converted.</para>
        /// <para>-or-</para>
        /// <para>The entry type of <paramref name="other"/> is not supported for conversion to the PAX format.</para></exception>
        public PaxTarEntry(TarEntry other)
            : base(other, TarEntryFormat.Pax)
        {
            if (other._header._format is TarEntryFormat.Ustar or TarEntryFormat.Pax)
            {
                _header._prefix = other._header._prefix;
            }
 
            if (other is PaxTarEntry paxOther)
            {
                _header.InitializeExtendedAttributesWithExisting(paxOther.ExtendedAttributes);
            }
            else
            {
                if (other is GnuTarEntry gnuOther)
                {
                    _header.ExtendedAttributes[TarHeader.PaxEaATime] = TarHelpers.GetTimestampStringFromDateTimeOffset(gnuOther.AccessTime);
                    _header.ExtendedAttributes[TarHeader.PaxEaCTime] = TarHelpers.GetTimestampStringFromDateTimeOffset(gnuOther.ChangeTime);
                }
            }
 
            AddNewAccessAndChangeTimestampsIfNotExist(useMTime: false);
        }
 
        /// <summary>
        /// Returns the extended attributes for this entry.
        /// </summary>
        /// <remarks>The extended attributes are specified when constructing an entry. Use <see cref="PaxTarEntry(TarEntryType, string, IEnumerable{KeyValuePair{string, string}})"/> to append your own enumeration of extended attributes to the current entry on top of the default ones. Use <see cref="PaxTarEntry(TarEntryType, string)"/> to only use the default extended attributes.
        /// <para>The following entries are always found in the Extended Attributes dictionary of any PAX entry:</para>
        /// <list type="bullet">
        /// <item>Modification time, under the name <c>mtime</c>, as a <see cref="double"/> number.</item>
        /// <item>Access time, under the name <c>atime</c>, as a <see cref="double"/> number.</item>
        /// <item>Change time, under the name <c>ctime</c>, as a <see cref="double"/> number.</item>
        /// <item>Path, under the name <c>path</c>, as a string.</item>
        /// </list>
        /// <para>The following entries are only found in the Extended Attributes dictionary of a PAX entry if certain conditions are met:</para>
        /// <list type="bullet">
        /// <item>Group name, under the name <c>gname</c>, as a string, if it is larger than 32 bytes.</item>
        /// <item>User name, under the name <c>uname</c>, as a string, if it is larger than 32 bytes.</item>
        /// <item>File length, under the name <c>size</c>, as an <see cref="int"/>, if the string representation of the number is larger than 12 bytes.</item>
        /// </list>
        /// </remarks>
        public IReadOnlyDictionary<string, string> ExtendedAttributes => _readOnlyExtendedAttributes ??= _header.ExtendedAttributes.AsReadOnly();
 
        // Determines if the current instance's entry type supports setting a data stream.
        internal override bool IsDataStreamSetterSupported() => EntryType == TarEntryType.RegularFile;
 
        // Checks if the extended attributes dictionary contains 'atime' and 'ctime'.
        // If any of them is not found, it is added with the value of either the current entry's 'mtime',
        // or 'DateTimeOffset.UtcNow', depending on the value of 'useMTime'.
        private void AddNewAccessAndChangeTimestampsIfNotExist(bool useMTime)
        {
            Debug.Assert(!useMTime || (useMTime && _header._mTime != default));
            bool containsATime = _header.ExtendedAttributes.ContainsKey(TarHeader.PaxEaATime);
            bool containsCTime = _header.ExtendedAttributes.ContainsKey(TarHeader.PaxEaCTime);
 
            if (!containsATime || !containsCTime)
            {
                string secondsFromEpochString = TarHelpers.GetTimestampStringFromDateTimeOffset(useMTime ? _header._mTime : DateTimeOffset.UtcNow);
 
                if (!containsATime)
                {
                    _header.ExtendedAttributes[TarHeader.PaxEaATime] = secondsFromEpochString;
                }
 
                if (!containsCTime)
                {
                    _header.ExtendedAttributes[TarHeader.PaxEaCTime] = secondsFromEpochString;
                }
            }
        }
    }
}