diff --git a/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs b/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs index 642433227b1cd2..c0a2b1295e0dd0 100644 --- a/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs +++ b/src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs @@ -8,12 +8,19 @@ namespace System.Formats.Tar { public sealed partial class GnuTarEntry : System.Formats.Tar.PosixTarEntry { + public GnuTarEntry(System.Formats.Tar.TarEntry other) { } public GnuTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { } public System.DateTimeOffset AccessTime { get { throw null; } set { } } public System.DateTimeOffset ChangeTime { get { throw null; } set { } } } + public sealed partial class PaxGlobalExtendedAttributesTarEntry : System.Formats.Tar.PosixTarEntry + { + public PaxGlobalExtendedAttributesTarEntry(System.Collections.Generic.IEnumerable> globalExtendedAttributes) { } + public System.Collections.Generic.IReadOnlyDictionary GlobalExtendedAttributes { get { throw null; } } + } public sealed partial class PaxTarEntry : System.Formats.Tar.PosixTarEntry { + public PaxTarEntry(System.Formats.Tar.TarEntry other) { } public PaxTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { } public PaxTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName, System.Collections.Generic.IEnumerable> extendedAttributes) { } public System.Collections.Generic.IReadOnlyDictionary ExtendedAttributes { get { throw null; } } @@ -32,6 +39,7 @@ internal TarEntry() { } public int Checksum { get { throw null; } } public System.IO.Stream? DataStream { get { throw null; } set { } } public System.Formats.Tar.TarEntryType EntryType { get { throw null; } } + public System.Formats.Tar.TarEntryFormat Format { get { throw null; } } public int Gid { get { throw null; } set { } } public long Length { get { throw null; } } public string LinkName { get { throw null; } set { } } @@ -42,6 +50,14 @@ internal TarEntry() { } public void ExtractToFile(string destinationFileName, bool overwrite) { } public override string ToString() { throw null; } } + public enum TarEntryFormat + { + Unknown = 0, + V7 = 1, + Ustar = 2, + Pax = 3, + Gnu = 4, + } public enum TarEntryType : byte { V7RegularFile = (byte)0, @@ -87,37 +103,29 @@ public enum TarFileMode GroupSpecial = 1024, UserSpecial = 2048, } - public enum TarFormat - { - Unknown = 0, - V7 = 1, - Ustar = 2, - Pax = 3, - Gnu = 4, - } public sealed partial class TarReader : System.IDisposable { public TarReader(System.IO.Stream archiveStream, bool leaveOpen = false) { } - public System.Formats.Tar.TarFormat Format { get { throw null; } } - public System.Collections.Generic.IReadOnlyDictionary? GlobalExtendedAttributes { get { throw null; } } public void Dispose() { } public System.Formats.Tar.TarEntry? GetNextEntry(bool copyData = false) { throw null; } } public sealed partial class TarWriter : System.IDisposable { - public TarWriter(System.IO.Stream archiveStream, System.Collections.Generic.IEnumerable>? globalExtendedAttributes = null, bool leaveOpen = false) { } - public TarWriter(System.IO.Stream archiveStream, System.Formats.Tar.TarFormat archiveFormat, bool leaveOpen = false) { } - public System.Formats.Tar.TarFormat Format { get { throw null; } } + public TarWriter(System.IO.Stream archiveStream) { } + public TarWriter(System.IO.Stream archiveStream, System.Formats.Tar.TarEntryFormat archiveFormat = System.Formats.Tar.TarEntryFormat.Pax, bool leaveOpen = false) { } + public System.Formats.Tar.TarEntryFormat Format { get { throw null; } } public void Dispose() { } public void WriteEntry(System.Formats.Tar.TarEntry entry) { } public void WriteEntry(string fileName, string? entryName) { } } public sealed partial class UstarTarEntry : System.Formats.Tar.PosixTarEntry { + public UstarTarEntry(System.Formats.Tar.TarEntry other) { } public UstarTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { } } public sealed partial class V7TarEntry : System.Formats.Tar.TarEntry { + public V7TarEntry(System.Formats.Tar.TarEntry other) { } public V7TarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { } } } diff --git a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx index 3140ec6831a833..c9c28ff81bdfa6 100644 --- a/src/libraries/System.Formats.Tar/src/Resources/Strings.resx +++ b/src/libraries/System.Formats.Tar/src/Resources/Strings.resx @@ -189,9 +189,6 @@ The entry '{0}' has a duplicate extended attribute. - - An entry in '{0}' format was found in an archive where other entries of format '{1}' have been found. - Cannot set the 'DeviceMajor' or 'DeviceMinor' fields on an entry that does not represent a block or character device. @@ -240,9 +237,6 @@ Cannot create the symbolic link '{0}' because the specified target '{1}' does not exist. - - The archive has more than one global extended attributes entry. - A metadata entry of type '{0}' was unexpectedly found after a metadata entry of type '{1}'. diff --git a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj index d866dc3e3f96ca..52a0a88512d309 100644 --- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj +++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj @@ -23,10 +23,11 @@ + - + diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs index 252a95fe378fe9..3e497bdf58d746 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs @@ -6,7 +6,7 @@ namespace System.Formats.Tar /// /// Represents a tar entry from an archive of the GNU format. /// - /// Even though the format is not POSIX compatible, it implements and supports the Unix-specific fields that were defined in the POSIX IEEE P1003.1 standard from 1988: devmajor, devminor, gname and uname. + /// Even though the format is not POSIX compatible, it implements and supports the Unix-specific fields that were defined in the POSIX IEEE P1003.1 standard from 1988: devmajor, devminor, gname and uname. public sealed class GnuTarEntry : PosixTarEntry { // Constructor used when reading an existing archive. @@ -29,10 +29,25 @@ internal GnuTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// public GnuTarEntry(TarEntryType entryType, string entryName) - : base(entryType, entryName, TarFormat.Gnu) + : base(entryType, entryName, TarEntryFormat.Gnu) { } + /// + /// Initializes a new instance by converting the specified entry into the GNU format. + /// + public GnuTarEntry(TarEntry other) + : base(other._header, other._readerOfOrigin!) + { + if (_header._typeFlag == TarEntryType.V7RegularFile) + { + _header._typeFlag = TarEntryType.RegularFile; + } + TarHelpers.VerifyEntryTypeIsSupported(_header._typeFlag, TarEntryFormat.Gnu, forWriting: false); + + _header._format = TarEntryFormat.Gnu; + } + /// /// A timestamp that represents the last time the file represented by this entry was accessed. /// diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxGlobalExtendedAttributesTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxGlobalExtendedAttributesTarEntry.cs new file mode 100644 index 00000000000000..946cceef02c6d7 --- /dev/null +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxGlobalExtendedAttributesTarEntry.cs @@ -0,0 +1,62 @@ +// 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 +{ + /// + /// Represents a Global Extended Attributes tar entry from an archive of the PAX format. + /// + public sealed class PaxGlobalExtendedAttributesTarEntry : PosixTarEntry + { + private ReadOnlyDictionary? _readOnlyGlobalExtendedAttributes; + + // Constructor used when reading an existing archive. + internal PaxGlobalExtendedAttributesTarEntry(TarHeader header, TarReader readerOfOrigin) + : base(header, readerOfOrigin) + { + _readOnlyGlobalExtendedAttributes = null; + } + + /// + /// Initializes a new instance with the specified Global Extended Attributes enumeration. + /// + /// An enumeration of string key-value pairs that represents the metadata to include as Global Extended Attributes. + /// is . + public PaxGlobalExtendedAttributesTarEntry(IEnumerable> globalExtendedAttributes) + : this(header: default, readerOfOrigin: null!) + { + ArgumentNullException.ThrowIfNull(globalExtendedAttributes); + + _header._format = TarEntryFormat.Pax; + _header._extendedAttributes = new Dictionary(globalExtendedAttributes); + + _header._name = TarHeader.GlobalHeadFormatPrefix; // Does not contain the sequence number, since that depends on the archive to write + _header._mode = (int)TarHelpers.DefaultMode; + _header._typeFlag = TarEntryType.GlobalExtendedAttributes; + _header._linkName = string.Empty; + _header._magic = string.Empty; + _header._version = string.Empty; + _header._gName = string.Empty; + _header._uName = string.Empty; + } + + /// + /// Returns the global extended attributes stored in this entry. + /// + public IReadOnlyDictionary GlobalExtendedAttributes + { + get + { + _header._extendedAttributes ??= new Dictionary(); + return _readOnlyGlobalExtendedAttributes ??= _header._extendedAttributes.AsReadOnly(); + } + } + + // Determines if the current instance's entry type supports setting a data stream. + internal override bool IsDataStreamSetterSupported() => false; + } +} diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs index 54b44f71a55b23..df1dddc52ec610 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs @@ -18,7 +18,6 @@ public sealed class PaxTarEntry : PosixTarEntry internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin) : base(header, readerOfOrigin) { - _header._extendedAttributes ??= new Dictionary(); _readOnlyExtendedAttributes = null; } @@ -50,8 +49,9 @@ internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// public PaxTarEntry(TarEntryType entryType, string entryName) - : base(entryType, entryName, TarFormat.Pax) + : base(entryType, entryName, TarEntryFormat.Pax) { + _readOnlyExtendedAttributes = null; } /// @@ -84,10 +84,25 @@ public PaxTarEntry(TarEntryType entryType, string entryName) /// /// public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable> extendedAttributes) - : base(entryType, entryName, TarFormat.Pax) + : base(entryType, entryName, TarEntryFormat.Pax) { ArgumentNullException.ThrowIfNull(extendedAttributes); - _header.ReplaceNormalAttributesWithExtended(extendedAttributes); + _header._extendedAttributes = new Dictionary(extendedAttributes); + } + + /// + /// Initializes a new instance by converting the specified entry into the PAX format. + /// + public PaxTarEntry(TarEntry other) + : base(other._header, other._readerOfOrigin!) + { + if (_header._typeFlag == TarEntryType.V7RegularFile) + { + _header._typeFlag = TarEntryType.RegularFile; + } + TarHelpers.VerifyEntryTypeIsSupported(_header._typeFlag, TarEntryFormat.Pax, forWriting: false); + + _header._format = TarEntryFormat.Pax; } /// @@ -112,7 +127,7 @@ public IReadOnlyDictionary ExtendedAttributes { get { - Debug.Assert(_header._extendedAttributes != null); + _header._extendedAttributes ??= new Dictionary(); return _readOnlyExtendedAttributes ??= _header._extendedAttributes.AsReadOnly(); } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PosixTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PosixTarEntry.cs index 0b682454f38706..6f9ea7581e0431 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PosixTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/PosixTarEntry.cs @@ -4,10 +4,10 @@ namespace System.Formats.Tar { /// - /// Abstract class that represents a tar entry from an archive of a format that is based on the POSIX IEEE P1003.1 standard from 1988. This includes the formats (represented by the class), (represented by the class) and (represented by the class). + /// Abstract class that represents a tar entry from an archive of a format that is based on the POSIX IEEE P1003.1 standard from 1988. This includes the formats (represented by the class), (represented by the and classes) and (represented by the class). /// /// Formats that implement the POSIX IEEE P1003.1 standard from 1988, support the following header fields: devmajor, devminor, gname and uname. - /// Even though the format is not POSIX compatible, it implements and supports the Unix-specific fields that were defined in that POSIX standard. + /// Even though the format is not POSIX compatible, it implements and supports the Unix-specific fields that were defined in that POSIX standard. public abstract partial class PosixTarEntry : TarEntry { // Constructor used when reading an existing archive. @@ -17,7 +17,7 @@ internal PosixTarEntry(TarHeader header, TarReader readerOfOrigin) } // Constructor called when creating a new 'TarEntry*' instance that can be passed to a TarWriter. - internal PosixTarEntry(TarEntryType entryType, string entryName, TarFormat format) + internal PosixTarEntry(TarEntryType entryType, string entryName, TarEntryFormat format) : base(entryType, entryName, format) { } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs index ce5e076cfc38ae..1d22d036ea0c27 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs @@ -11,12 +11,12 @@ namespace System.Formats.Tar /// /// Abstract class that represents a tar entry from an archive. /// - /// All the properties exposed by this class are supported by the , , and formats. + /// All the properties exposed by this class are supported by the , , and formats. public abstract partial class TarEntry { internal TarHeader _header; // Used to access the data section of this entry in an unseekable file - private TarReader? _readerOfOrigin; + internal TarReader? _readerOfOrigin; // Constructor used when reading an existing archive. internal TarEntry(TarHeader header, TarReader readerOfOrigin) @@ -26,18 +26,16 @@ internal TarEntry(TarHeader header, TarReader readerOfOrigin) } // Constructor called when creating a new 'TarEntry*' instance that can be passed to a TarWriter. - internal TarEntry(TarEntryType entryType, string entryName, TarFormat format) + internal TarEntry(TarEntryType entryType, string entryName, TarEntryFormat format) { ArgumentException.ThrowIfNullOrEmpty(entryName); - // Throws if format is unknown or out of range TarHelpers.VerifyEntryTypeIsSupported(entryType, format, forWriting: false); _readerOfOrigin = null; _header = default; - - _header._extendedAttributes = new Dictionary(); + _header._format = format; _header._name = entryName; _header._linkName = string.Empty; @@ -63,6 +61,11 @@ internal TarEntry(TarEntryType entryType, string entryName, TarFormat format) /// public TarEntryType EntryType => _header._typeFlag; + /// + /// The format of the entry. + /// + public TarEntryFormat Format => _header._format; + /// /// The ID of the group that owns the file represented by this entry. /// @@ -93,7 +96,7 @@ public DateTimeOffset ModificationTime /// /// When the indicates an entry that can contain data, this property returns the length in bytes of such data. /// - /// The entry type that commonly contains data is (or in the format). Other uncommon entry types that can also contain data are: , , and . + /// The entry type that commonly contains data is (or in the format). Other uncommon entry types that can also contain data are: , , and . public long Length => _header._dataStream != null ? _header._dataStream.Length : _header._size; /// @@ -174,11 +177,11 @@ public int Uid /// Operation not permitted due to insufficient permissions. public void ExtractToFile(string destinationFileName, bool overwrite) { - if (EntryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) + if (EntryType is TarEntryType.SymbolicLink or TarEntryType.HardLink or TarEntryType.GlobalExtendedAttributes) { throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupportedForExtracting, EntryType)); } - ExtractToFileInternal(destinationFileName, linkTargetPath: null, overwrite); + ExtractToFileInternal(destinationFileName, linkTargetPath: null, overwrite, currentGlobalExtendedAttributes: null); } // /// @@ -211,7 +214,7 @@ public void ExtractToFile(string destinationFileName, bool overwrite) /// Gets a stream that represents the data section of this entry. /// Sets a new stream that represents the data section, if it makes sense for the to contain data; if a stream already existed, the old stream gets disposed before substituting it with the new stream. Setting a stream is allowed. /// If you write data to this data stream, make sure to rewind it to the desired start position before writing this entry into an archive using . - /// Setting a data section is not supported because the is not (or for an archive of format). + /// Setting a data section is not supported because the is not (or for an archive of format). /// Cannot set an unreadable stream. /// -or- /// An I/O problem occurred. @@ -256,7 +259,7 @@ public Stream? DataStream internal abstract bool IsDataStreamSetterSupported(); // Extracts the current entry to a location relative to the specified directory. - internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool overwrite) + internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool overwrite, IReadOnlyDictionary? currentGlobalExtendedAttributes) { Debug.Assert(!string.IsNullOrEmpty(destinationDirectoryPath)); Debug.Assert(Path.IsPathFullyQualified(destinationDirectoryPath)); @@ -276,7 +279,7 @@ internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool o linkTargetPath = GetSanitizedFullPath(destinationDirectoryFullPath, LinkName, SR.TarExtractingResultsLinkOutside); } - if (EntryType == TarEntryType.Directory) + if (EntryType is TarEntryType.Directory) { Directory.CreateDirectory(fileDestinationPath); } @@ -284,7 +287,7 @@ internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool o { // If it is a file, create containing directory. Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!); - ExtractToFileInternal(fileDestinationPath, linkTargetPath, overwrite); + ExtractToFileInternal(fileDestinationPath, linkTargetPath, overwrite, currentGlobalExtendedAttributes); } // If the path can be extracted in the specified destination directory, returns the full path with sanitized file name. Otherwise, throws. @@ -309,7 +312,7 @@ static string GetSanitizedFullPath(string destinationDirectoryFullPath, string p } // Extracts the current entry into the filesystem, regardless of the entry type. - private void ExtractToFileInternal(string filePath, string? linkTargetPath, bool overwrite) + private void ExtractToFileInternal(string filePath, string? linkTargetPath, bool overwrite, IReadOnlyDictionary? currentGlobalExtendedAttributes) { ArgumentException.ThrowIfNullOrEmpty(filePath); @@ -365,6 +368,12 @@ private void ExtractToFileInternal(string filePath, string? linkTargetPath, bool default: throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupportedForExtracting, EntryType)); } + + if (currentGlobalExtendedAttributes != null) + { + // TODO: pass it to the ExtractAs* methods or find any filesystem-related entries in the dictionary that should be applied on the extracted file if the extended attributes were not set + } + } // Verifies if the specified paths make sense for the current type of entry. diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFormat.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntryFormat.cs similarity index 89% rename from src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFormat.cs rename to src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntryFormat.cs index 1f4bd40327ff84..7c0b6ce911ce97 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFormat.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntryFormat.cs @@ -4,9 +4,9 @@ namespace System.Formats.Tar { /// - /// Specifies the supported Tar formats. + /// Specifies the supported Tar formats that entries can use. /// - public enum TarFormat + public enum TarEntryFormat { /// /// Tar format undetermined. diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntryType.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntryType.cs index 3f3e61556eb666..9c0fc6370097f0 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntryType.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntryType.cs @@ -11,7 +11,7 @@ public enum TarEntryType : byte { /// /// Regular file. - /// This entry type is specific to the , and formats. + /// This entry type is specific to the , and formats. /// RegularFile = (byte)'0', /// @@ -43,7 +43,7 @@ public enum TarEntryType : byte Fifo = (byte)'6', /// /// GNU contiguous file - /// This entry type is specific to the format, and is treated as a entry type. + /// This entry type is specific to the format, and is treated as a entry type. /// // According to the GNU spec, it's extremely rare to encounter a contiguous entry. ContiguousFile = (byte)'7', @@ -54,12 +54,12 @@ public enum TarEntryType : byte ExtendedAttributes = (byte)'x', /// /// PAX Global Extended Attributes entry. - /// Metadata entry type. + /// This entry type is specific to the format, represents metadata that affects all subsequent entries, and can be used via the class. /// GlobalExtendedAttributes = (byte)'g', /// /// GNU directory with a list of entries. - /// This entry type is specific to the format, and is treated as a entry type that contains a data section. + /// This entry type is specific to the format, and is treated as a entry type that contains a data section. /// DirectoryList = (byte)'D', /// @@ -74,27 +74,27 @@ public enum TarEntryType : byte LongPath = (byte)'L', /// /// GNU multi-volume file. - /// This entry type is specific to the format and is not supported for writing. + /// This entry type is specific to the format and is not supported for writing. /// MultiVolume = (byte)'M', /// /// V7 Regular file. - /// This entry type is specific to the format. + /// This entry type is specific to the format. /// V7RegularFile = (byte)'\0', /// /// GNU file to be renamed/symlinked. - /// This entry type is specific to the format. It is considered unsafe and is ignored by other tools. + /// This entry type is specific to the format. It is considered unsafe and is ignored by other tools. /// RenamedOrSymlinked = (byte)'N', /// /// GNU sparse file. - /// This entry type is specific to the format and is not supported for writing. + /// This entry type is specific to the format and is not supported for writing. /// SparseFile = (byte)'S', /// /// GNU tape volume. - /// This entry type is specific to the format and is not supported for writing. + /// This entry type is specific to the format and is not supported for writing. /// TapeVolume = (byte)'V', } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs index ba56ac87441614..1a034fbf562a2e 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -196,7 +197,7 @@ private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stre Debug.Assert(Path.IsPathFullyQualified(sourceDirectoryName)); Debug.Assert(destination.CanWrite); - using (TarWriter writer = new TarWriter(destination, TarFormat.Pax, leaveOpen)) + using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Pax, leaveOpen)) { bool baseDirectoryIsEmpty = true; DirectoryInfo di = new(sourceDirectoryName); @@ -253,9 +254,17 @@ private static void ExtractToDirectoryInternal(Stream source, string destination using TarReader reader = new TarReader(source, leaveOpen); TarEntry? entry; + IReadOnlyDictionary? currentGlobalExtendedAttributes = null; while ((entry = reader.GetNextEntry()) != null) { - entry.ExtractRelativeToDirectory(destinationDirectoryPath, overwriteFiles); + if (entry.EntryType is TarEntryType.GlobalExtendedAttributes) + { + currentGlobalExtendedAttributes = entry._header._extendedAttributes ?? new Dictionary(); + } + else + { + entry.ExtractRelativeToDirectory(destinationDirectoryPath, overwriteFiles, currentGlobalExtendedAttributes); + } } } } 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 1e46719c839e93..32ff1a71918924 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 @@ -39,7 +39,7 @@ internal bool TryGetNextHeader(Stream archiveStream, bool copyData) // Confirms if gnu, or tentatively selects ustar ReadMagicAttribute(buffer); - if (_format != TarFormat.V7) + if (_format != TarEntryFormat.V7) { // Confirms if gnu ReadVersionAttribute(buffer); @@ -47,12 +47,12 @@ internal bool TryGetNextHeader(Stream archiveStream, bool copyData) // Fields that ustar, pax and gnu share identically ReadPosixAndGnuSharedAttributes(buffer); - Debug.Assert(_format is TarFormat.Ustar or TarFormat.Pax or TarFormat.Gnu); - if (_format == TarFormat.Ustar) + Debug.Assert(_format is TarEntryFormat.Ustar or TarEntryFormat.Pax or TarEntryFormat.Gnu); + if (_format == TarEntryFormat.Ustar) { ReadUstarAttributes(buffer); } - else if (_format == TarFormat.Gnu) + else if (_format == TarEntryFormat.Gnu) { ReadGnuAttributes(buffer); } @@ -69,143 +69,77 @@ internal bool TryGetNextHeader(Stream archiveStream, bool copyData) } } - // Reads the elements from the passed dictionary, which comes from the first global extended attributes entry, - // and inserts or replaces those elements into the current header's dictionary. - // If any of the dictionary entries use the name of a standard attribute (not all of them), that attribute's value gets replaced with the one from the dictionary. + // If any of the exstended attributes entries use the name of a standard field, that field's value gets replaced with the one from the dictionary. + // Values with reserved field names are stored in the extended attributes dictionary when they don't fit in the standard header field. // Unlike the historic header, numeric values in extended attributes are stored using decimal, not octal. // Throws if any conversion from string to the expected data type fails. - internal void ReplaceNormalAttributesWithGlobalExtended(IReadOnlyDictionary gea) + internal void ReplaceNormalAttributesWithExtended() { - // First step: Insert or replace all the elements in the passed dictionary into the current header's dictionary. - foreach ((string key, string value) in gea) - { - _extendedAttributes ??= new Dictionary(); - _extendedAttributes[key] = value; - } - - // Second, find only the attributes that make sense to substitute, and replace them. - if (gea.TryGetValue(PaxEaATime, out string? paxEaATime)) - { - if (TarHelpers.TryConvertToDateTimeOffset(paxEaATime, out DateTimeOffset aTime)) - { - _aTime = aTime; - } - } - if (gea.TryGetValue(PaxEaCTime, out string? paxEaCTime)) - { - if (TarHelpers.TryConvertToDateTimeOffset(paxEaCTime, out DateTimeOffset cTime)) - { - _cTime = cTime; - } - } - if (gea.TryGetValue(PaxEaMTime, out string? paxEaMTime)) - { - if (TarHelpers.TryConvertToDateTimeOffset(paxEaMTime, out DateTimeOffset mTime)) - { - _mTime = mTime; - } - } - if (gea.TryGetValue(PaxEaMode, out string? paxEaMode)) - { - _mode = Convert.ToInt32(paxEaMode); - } - if (gea.TryGetValue(PaxEaUid, out string? paxEaUid)) - { - _uid = Convert.ToInt32(paxEaUid); - } - if (gea.TryGetValue(PaxEaGid, out string? paxEaGid)) - { - _gid = Convert.ToInt32(paxEaGid); - } - if (gea.TryGetValue(PaxEaUName, out string? paxEaUName)) - { - _uName = paxEaUName; - } - if (gea.TryGetValue(PaxEaGName, out string? paxEaGName)) - { - _gName = paxEaGName; - } - } - - // Reads the elements from the passed dictionary, which comes from the previous extended attributes entry, - // and inserts or replaces those elements into the current header's dictionary. - // If any of the dictionary entries use the name of a standard attribute, that attribute's value gets replaced with the one from the dictionary. - // Unlike the historic header, numeric values in extended attributes are stored using decimal, not octal. - // Throws if any conversion from string to the expected data type fails. - internal void ReplaceNormalAttributesWithExtended(IEnumerable> extendedAttributesEnumerable) - { - Dictionary ea = new Dictionary(extendedAttributesEnumerable); - if (ea.Count == 0) + Debug.Assert(_extendedAttributes != null); + if (_extendedAttributes.Count == 0) { return; } - _extendedAttributes ??= new Dictionary(); - - // First step: Insert or replace all the elements in the passed dictionary into the current header's dictionary. - foreach ((string key, string value) in ea) - { - _extendedAttributes[key] = value; - } // Second, find all the extended attributes with known names and save them in the expected standard attribute. - if (ea.TryGetValue(PaxEaName, out string? paxEaName)) + if (_extendedAttributes.TryGetValue(PaxEaName, out string? paxEaName)) { _name = paxEaName; } - if (ea.TryGetValue(PaxEaLinkName, out string? paxEaLinkName)) + if (_extendedAttributes.TryGetValue(PaxEaLinkName, out string? paxEaLinkName)) { _linkName = paxEaLinkName; } - if (ea.TryGetValue(PaxEaATime, out string? paxEaATime)) + if (_extendedAttributes.TryGetValue(PaxEaATime, out string? paxEaATime)) { if (TarHelpers.TryConvertToDateTimeOffset(paxEaATime, out DateTimeOffset aTime)) { _aTime = aTime; } } - if (ea.TryGetValue(PaxEaCTime, out string? paxEaCTime)) + if (_extendedAttributes.TryGetValue(PaxEaCTime, out string? paxEaCTime)) { if (TarHelpers.TryConvertToDateTimeOffset(paxEaCTime, out DateTimeOffset cTime)) { _cTime = cTime; } } - if (ea.TryGetValue(PaxEaMTime, out string? paxEaMTime)) + if (_extendedAttributes.TryGetValue(PaxEaMTime, out string? paxEaMTime)) { if (TarHelpers.TryConvertToDateTimeOffset(paxEaMTime, out DateTimeOffset mTime)) { _mTime = mTime; } } - if (ea.TryGetValue(PaxEaMode, out string? paxEaMode)) + if (_extendedAttributes.TryGetValue(PaxEaMode, out string? paxEaMode)) { _mode = Convert.ToInt32(paxEaMode); } - if (ea.TryGetValue(PaxEaSize, out string? paxEaSize)) + if (_extendedAttributes.TryGetValue(PaxEaSize, out string? paxEaSize)) { _size = Convert.ToInt32(paxEaSize); } - if (ea.TryGetValue(PaxEaUid, out string? paxEaUid)) + if (_extendedAttributes.TryGetValue(PaxEaUid, out string? paxEaUid)) { _uid = Convert.ToInt32(paxEaUid); } - if (ea.TryGetValue(PaxEaGid, out string? paxEaGid)) + if (_extendedAttributes.TryGetValue(PaxEaGid, out string? paxEaGid)) { _gid = Convert.ToInt32(paxEaGid); } - if (ea.TryGetValue(PaxEaUName, out string? paxEaUName)) + if (_extendedAttributes.TryGetValue(PaxEaUName, out string? paxEaUName)) { _uName = paxEaUName; } - if (ea.TryGetValue(PaxEaGName, out string? paxEaGName)) + if (_extendedAttributes.TryGetValue(PaxEaGName, out string? paxEaGName)) { _gName = paxEaGName; } - if (ea.TryGetValue(PaxEaDevMajor, out string? paxEaDevMajor)) + if (_extendedAttributes.TryGetValue(PaxEaDevMajor, out string? paxEaDevMajor)) { _devMajor = int.Parse(paxEaDevMajor); } - if (ea.TryGetValue(PaxEaDevMinor, out string? paxEaDevMinor)) + if (_extendedAttributes.TryGetValue(PaxEaDevMinor, out string? paxEaDevMinor)) { _devMinor = int.Parse(paxEaDevMinor); } @@ -334,12 +268,12 @@ private bool TryReadCommonAttributes(Span buffer) _typeFlag = (TarEntryType)buffer[FieldLocations.TypeFlag]; _linkName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)); - if (_format == TarFormat.Unknown) + if (_format == TarEntryFormat.Unknown) { _format = _typeFlag switch { TarEntryType.ExtendedAttributes or - TarEntryType.GlobalExtendedAttributes => TarFormat.Pax, + TarEntryType.GlobalExtendedAttributes => TarEntryFormat.Pax, TarEntryType.DirectoryList or TarEntryType.LongLink or @@ -347,14 +281,14 @@ TarEntryType.LongPath or TarEntryType.MultiVolume or TarEntryType.RenamedOrSymlinked or TarEntryType.SparseFile or - TarEntryType.TapeVolume => TarFormat.Gnu, + TarEntryType.TapeVolume => TarEntryFormat.Gnu, // V7 is the only one that uses 'V7RegularFile'. - TarEntryType.V7RegularFile => TarFormat.V7, + TarEntryType.V7RegularFile => TarEntryFormat.V7, // We can quickly determine the *minimum* possible format if the entry type // is the POSIX 'RegularFile', although later we could upgrade it to PAX or GNU - _ => (_typeFlag == TarEntryType.RegularFile) ? TarFormat.Ustar : TarFormat.V7 + _ => (_typeFlag == TarEntryType.RegularFile) ? TarEntryFormat.Ustar : TarEntryFormat.V7 }; } @@ -370,7 +304,7 @@ private void ReadMagicAttribute(Span buffer) // If at this point the magic value is all nulls, we definitely have a V7 if (TarHelpers.IsAllNullBytes(magic)) { - _format = TarFormat.V7; + _format = TarEntryFormat.V7; return; } @@ -379,12 +313,12 @@ private void ReadMagicAttribute(Span buffer) if (_magic == GnuMagic) { - _format = TarFormat.Gnu; + _format = TarEntryFormat.Gnu; } - else if (_format == TarFormat.V7 && _magic == UstarMagic) + else if (_format == TarEntryFormat.V7 && _magic == UstarMagic) { // Important: Only change to ustar if we had not changed the format to pax already - _format = TarFormat.Ustar; + _format = TarEntryFormat.Ustar; } } @@ -392,7 +326,7 @@ private void ReadMagicAttribute(Span buffer) // Throws if converting the bytes to string fails or if an unexpected version string is found. private void ReadVersionAttribute(Span buffer) { - if (_format == TarFormat.V7) + if (_format == TarEntryFormat.V7) { return; } @@ -402,13 +336,13 @@ private void ReadVersionAttribute(Span buffer) _version = Encoding.ASCII.GetString(version); // The POSIX formats have a 6 byte Magic "ustar\0", followed by a 2 byte Version "00" - if ((_format is TarFormat.Ustar or TarFormat.Pax) && _version != UstarVersion) + if ((_format is TarEntryFormat.Ustar or TarEntryFormat.Pax) && _version != UstarVersion) { throw new FormatException(string.Format(SR.TarPosixFormatExpected, _name)); } // The GNU format has a Magic+Version 8 byte string "ustar \0" - if (_format == TarFormat.Gnu && _version != GnuVersion) + if (_format == TarEntryFormat.Gnu && _version != GnuVersion) { throw new FormatException(string.Format(SR.TarGnuFormatExpected, _name)); } @@ -470,11 +404,9 @@ private void ReadExtendedAttributesBlock(Stream archiveStream) { Debug.Assert(_typeFlag is TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes); - // Regardless of the size, this entry should always have a valid dictionary object - _extendedAttributes ??= new Dictionary(); - if (_size == 0) { + // The data section has no contents (no extended attributes) return; } @@ -485,6 +417,8 @@ private void ReadExtendedAttributesBlock(Stream archiveStream) throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForExtendedAttribute, _typeFlag.ToString())); } + _extendedAttributes ??= new Dictionary(); + byte[] buffer = new byte[(int)_size]; archiveStream.ReadExactly(buffer); diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs index 0c2cf88c9c7e87..c00bbf94a45af5 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Write.cs @@ -22,33 +22,14 @@ 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 buffer, IEnumerable> 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 buffer) { long actualLength = GetTotalDataBytesToWrite(); - TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarFormat.V7); + TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarEntryFormat.V7); int checksum = WriteName(buffer, out _); checksum += WriteCommonFields(buffer, actualLength, actualEntryType); @@ -66,7 +47,7 @@ internal void WriteAsV7(Stream archiveStream, Span buffer) internal void WriteAsUstar(Stream archiveStream, Span buffer) { long actualLength = GetTotalDataBytesToWrite(); - TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarFormat.Ustar); + TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarEntryFormat.Ustar); int checksum = WritePosixName(buffer); checksum += WriteCommonFields(buffer, actualLength, actualEntryType); @@ -82,15 +63,26 @@ internal void WriteAsUstar(Stream archiveStream, Span buffer) } } + internal void WriteAsPaxGlobalExtendedAttributes(Stream archiveStream, Span buffer, int globalExtendedAttributesEntryNumber) + { + Debug.Assert(_typeFlag is TarEntryType.GlobalExtendedAttributes); + + _name = GenerateGlobalExtendedAttributeName(globalExtendedAttributesEntryNumber); + 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. internal void WriteAsPax(Stream archiveStream, Span buffer) { + Debug.Assert(_typeFlag is not TarEntryType.GlobalExtendedAttributes); + // First, we write the preceding extended attributes header TarHeader extendedAttributesHeader = default; + extendedAttributesHeader._format = TarEntryFormat.Pax; // Fill the current header's dict CollectExtendedAttributesFromStandardFieldsIfNeeded(); - // And pass them to the extended attributes header for writing + // And pass the attributes to the preceding extended attributes header for writing extendedAttributesHeader.WriteAsPaxExtendedAttributes(archiveStream, buffer, _extendedAttributes, isGea: false); buffer.Clear(); // Reset it to reuse it @@ -129,6 +121,7 @@ private static TarHeader GetGnuLongMetadataHeader(TarEntryType entryType, string (entryType is TarEntryType.LongLink && longText.Length > FieldLengths.LinkName)); TarHeader longMetadataHeader = default; + longMetadataHeader._format = TarEntryFormat.Gnu; longMetadataHeader._name = GnuLongMetadataName; // Same name for both longpath or longlink longMetadataHeader._mode = (int)TarHelpers.DefaultMode; @@ -153,7 +146,7 @@ internal void WriteAsGnuInternal(Stream archiveStream, Span buffer) _gnuUnusedBytes ??= new byte[FieldLengths.AllGnuUnused]; long actualLength = GetTotalDataBytesToWrite(); - TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarFormat.Gnu); + TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarEntryFormat.Gnu); int checksum = WriteName(buffer, out _); checksum += WriteCommonFields(buffer, actualLength, actualEntryType); @@ -194,7 +187,7 @@ private void WriteAsPaxExtendedAttributes(Stream archiveStream, Span buffe private void WriteAsPaxInternal(Stream archiveStream, Span buffer) { long actualLength = GetTotalDataBytesToWrite(); - TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarFormat.Pax); + TarEntryType actualEntryType = GetCorrectTypeFlagForFormat(TarEntryFormat.Pax); int checksum = WritePosixName(buffer); checksum += WriteCommonFields(buffer, actualLength, actualEntryType); @@ -275,9 +268,9 @@ private int WriteCommonFields(Span buffer, long actualLength, TarEntryType // 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. - private TarEntryType GetCorrectTypeFlagForFormat(TarFormat format) + private TarEntryType GetCorrectTypeFlagForFormat(TarEntryFormat format) { - if (format is TarFormat.V7) + if (format is TarEntryFormat.V7) { if (_typeFlag is TarEntryType.RegularFile) { @@ -393,6 +386,8 @@ private static void WriteData(Stream archiveStream, Stream dataStream, long actu // extended attributes. They get collected and saved in that dictionary, with no restrictions. private void CollectExtendedAttributesFromStandardFieldsIfNeeded() { + _extendedAttributes ??= new Dictionary(); + _extendedAttributes.Add(PaxEaName, _name); AddTimestampAsUnixSeconds(_extendedAttributes, PaxEaATime, _aTime); @@ -600,14 +595,16 @@ 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. + // - %n: The sequence number of the global extended header record of the archive, starting at 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) { + Debug.Assert(globalExtendedAttributesEntryNumber >= 1); + string? tmpDir = Environment.GetEnvironmentVariable("TMPDIR"); if (string.IsNullOrWhiteSpace(tmpDir)) { @@ -619,12 +616,14 @@ private static string GenerateGlobalExtendedAttributeName() } int processId = Environment.ProcessId; - string result = string.Format(GlobalHeadFormat, tmpDir, processId); + string result = string.Format(GlobalHeadFormatPrefix, tmpDir, processId); if (result.Length >= FieldLengths.Name) { - result = string.Format(GlobalHeadFormat, "/tmp", processId); + result = string.Format(GlobalHeadFormatPrefix, "/tmp", processId); } + result += $".{globalExtendedAttributesEntryNumber}"; // Suffix is ".{sequenceNumber}" + return result; } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.cs index aa0bab5aae02c9..e54c98ff6b94ec 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.cs @@ -38,12 +38,16 @@ internal partial struct TarHeader private const string PaxEaDevMajor = "devmajor"; private const string PaxEaDevMinor = "devminor"; + // Global Extended Attribute entries have a special format in the Name field: + // "{tmpFolder}/GlobalHead.{processId}.{GEAEntryNumber}" + internal const string GlobalHeadFormatPrefix = "{0}/GlobalHead.{1}"; // Excludes ".{GEAEntryNumber}" because the number gets added on write + internal Stream? _dataStream; // Position in the stream where the data ends in this header. internal long _endOfHeaderAndDataAndBlockAlignment; - internal TarFormat _format; + internal TarEntryFormat _format; // Common attributes 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 db80de8ef0257e..f52f5ca755fc38 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 @@ -179,11 +179,11 @@ internal static int SkipBlockAlignmentPadding(Stream archiveStream, long size) // Throws if the specified entry type is not supported for the specified format. // If 'forWriting' is true, an incompatible 'Regular File' entry type is allowed. It will be converted to the compatible version before writing. - internal static void VerifyEntryTypeIsSupported(TarEntryType entryType, TarFormat archiveFormat, bool forWriting) + internal static void VerifyEntryTypeIsSupported(TarEntryType entryType, TarEntryFormat archiveFormat, bool forWriting) { switch (archiveFormat) { - case TarFormat.V7: + case TarEntryFormat.V7: if (entryType is TarEntryType.Directory or TarEntryType.HardLink or @@ -198,7 +198,7 @@ TarEntryType.V7RegularFile or } break; - case TarFormat.Ustar: + case TarEntryFormat.Ustar: if (entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice or @@ -216,7 +216,7 @@ TarEntryType.RegularFile or } break; - case TarFormat.Pax: + case TarEntryFormat.Pax: if (entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice or @@ -228,16 +228,16 @@ TarEntryType.RegularFile or { // Not supported for writing - internally autogenerated: // - ExtendedAttributes - // - GlobalExtendedAttributes return; } - if (forWriting && entryType is TarEntryType.V7RegularFile) + if (forWriting && + entryType is TarEntryType.V7RegularFile or TarEntryType.GlobalExtendedAttributes) { return; } break; - case TarFormat.Gnu: + case TarEntryFormat.Gnu: if (entryType is TarEntryType.BlockDevice or TarEntryType.CharacterDevice or @@ -266,7 +266,7 @@ TarEntryType.RegularFile or } break; - case TarFormat.Unknown: + case TarEntryFormat.Unknown: default: throw new FormatException(string.Format(SR.TarInvalidFormat, archiveFormat)); } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs index 2aa5b3875d848e..b5ab14b7e7935b 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarReader.cs @@ -40,25 +40,11 @@ public TarReader(Stream archiveStream, bool leaveOpen = false) _leaveOpen = leaveOpen; _previouslyReadEntry = null; - GlobalExtendedAttributes = null; - Format = TarFormat.Unknown; _isDisposed = false; _readFirstEntry = false; _reachedEndMarkers = false; } - /// - /// The format of the archive. It is initially . The archive format is detected after the first call to . - /// - public TarFormat Format { get; private set; } - - /// - /// If the archive format is , returns a read-only dictionary containing the string key-value pairs of the Global Extended Attributes in the first entry of the archive. - /// If there is no Global Extended Attributes entry at the beginning of the archive, this returns an empty read-only dictionary. - /// If the first entry has not been read by calling , this returns . - /// - public IReadOnlyDictionary? GlobalExtendedAttributes { get; private set; } - /// /// Disposes the current instance, and disposes the streams of all the entries that were read from the archive. /// @@ -89,9 +75,9 @@ public void Dispose() /// -or- /// The archive contains entries in different formats. /// -or- - /// More than one Global Extended Attributes Entry was found in the current archive. + /// More than one Global Extended Attributes Entry was found in the current archive. /// -or- - /// Two or more Extended Attributes entries were found consecutively in the current archive. + /// Two or more Extended Attributes entries were found consecutively in the current archive. /// An I/O problem occurred. public TarEntry? GetNextEntry(bool copyData = false) { @@ -115,21 +101,16 @@ public void Dispose() { if (!_readFirstEntry) { - Debug.Assert(Format == TarFormat.Unknown); - Format = header._format; _readFirstEntry = true; } - else if (header._format != Format) - { - throw new FormatException(string.Format(SR.TarEntriesInDifferentFormats, header._format, Format)); - } - TarEntry entry = Format switch + TarEntry entry = header._format switch { - TarFormat.Pax => new PaxTarEntry(header, this), - TarFormat.Gnu => new GnuTarEntry(header, this), - TarFormat.Ustar => new UstarTarEntry(header, this), - TarFormat.V7 or TarFormat.Unknown or _ => new V7TarEntry(header, this), + TarEntryFormat.Pax => header._typeFlag is TarEntryType.GlobalExtendedAttributes ? + new PaxGlobalExtendedAttributesTarEntry(header, this) : new PaxTarEntry(header, this), + TarEntryFormat.Gnu => new GnuTarEntry(header, this), + TarEntryFormat.Ustar => new UstarTarEntry(header, this), + TarEntryFormat.V7 or TarEntryFormat.Unknown or _ => new V7TarEntry(header, this), }; _previouslyReadEntry = entry; @@ -227,49 +208,13 @@ private bool TryGetNextEntryHeader(out TarHeader header, bool copyData) Debug.Assert(!_reachedEndMarkers); header = default; - - // Set the initial format that is expected to be retrieved when calling TarHeader.TryReadAttributes. - // If the archive format is set to unknown here, it means this is the first entry we read and the value will be changed as fields get discovered. - // If the archive format is initially detected as pax, then any subsequent entries detected as ustar will be assumed to be pax. - header._format = Format; + header._format = TarEntryFormat.Unknown; if (!header.TryGetNextHeader(_archiveStream, copyData)) { return false; } - // Special case: First header. Collect GEA from data section, then get next entry. - if (header._typeFlag is TarEntryType.GlobalExtendedAttributes) - { - if (GlobalExtendedAttributes != null) - { - // We can only have one extended attributes entry. - throw new FormatException(SR.TarTooManyGlobalExtendedAttributesEntries); - } - - GlobalExtendedAttributes = header._extendedAttributes?.AsReadOnly(); - - header = default; - header._format = TarFormat.Pax; - try - { - if (!header.TryGetNextHeader(_archiveStream, copyData)) - { - return false; - } - } - catch (EndOfStreamException) - { - // Edge case: The only entry in the archive was a Global Extended Attributes entry - Format = TarFormat.Pax; - return false; - } - if (header._typeFlag == TarEntryType.GlobalExtendedAttributes) - { - throw new FormatException(SR.TarTooManyGlobalExtendedAttributesEntries); - } - } - // If a metadata typeflag entry is retrieved, handle it here, then read the next entry // PAX metadata @@ -305,10 +250,13 @@ private bool TryGetNextEntryHeader(out TarHeader header, bool copyData) return true; } + // When an extended attributes entry is retrieved, we need to collect the key-value pairs from the data section in this first header, + // then retrieve the next header, and save the collected kvps in that second header's extended attributes dictionary. + // Finally, we return the second header, which is what we will give to the user as an entry. private bool TryProcessExtendedAttributesHeader(TarHeader firstHeader, bool copyData, out TarHeader secondHeader) { secondHeader = default; - secondHeader._format = TarFormat.Pax; + secondHeader._format = TarEntryFormat.Pax; // Now get the actual entry if (!secondHeader.TryGetNextHeader(_archiveStream, copyData)) @@ -316,37 +264,30 @@ private bool TryProcessExtendedAttributesHeader(TarHeader firstHeader, bool copy return false; } - // Should never read a GEA entry at this point - if (secondHeader._typeFlag == TarEntryType.GlobalExtendedAttributes) + // We're currently processing an extended attributes header, so we + // can never have two extended entries in a row + if (secondHeader._typeFlag is TarEntryType.GlobalExtendedAttributes or TarEntryType.ExtendedAttributes or TarEntryType.LongLink or TarEntryType.LongPath) { - throw new FormatException(SR.TarTooManyGlobalExtendedAttributesEntries); + throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, secondHeader._typeFlag, TarEntryType.ExtendedAttributes)); } - // Can't have two metadata entries in a row, no matter the archive format - if (secondHeader._typeFlag is TarEntryType.ExtendedAttributes) - { - throw new FormatException(string.Format(SR.TarUnexpectedMetadataEntry, TarEntryType.ExtendedAttributes, TarEntryType.ExtendedAttributes)); - } - - Debug.Assert(firstHeader._extendedAttributes != null); - if (GlobalExtendedAttributes != null) - { - // First, replace some of the entry's standard attributes with the global ones - secondHeader.ReplaceNormalAttributesWithGlobalExtended(GlobalExtendedAttributes); - } - // Then replace all the standard attributes with the extended attributes ones, - // overwriting the previous global replacements if needed - secondHeader.ReplaceNormalAttributesWithExtended(firstHeader._extendedAttributes); + secondHeader._extendedAttributes = firstHeader._extendedAttributes ?? new Dictionary(); + // Replace all the attributes representing standard fields with the extended ones, if any + secondHeader.ReplaceNormalAttributesWithExtended(); return true; } + // When a GNU metadata entry is retrieved, we need to read the long link or long path from the data section, + // then collect the next header, replace the long link or long path on that second header. + // Finally, we return the second header, which is what we will give to the user as an entry. private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out TarHeader finalHeader) { finalHeader = default; + finalHeader._format = TarEntryFormat.Gnu; TarHeader secondHeader = default; - secondHeader._format = TarFormat.Gnu; + secondHeader._format = TarEntryFormat.Gnu; // Get the second entry, which is the actual entry if (!secondHeader.TryGetNextHeader(_archiveStream, copyData)) @@ -365,7 +306,7 @@ private bool TryProcessGnuMetadataHeader(TarHeader header, bool copyData, out Ta (header._typeFlag is TarEntryType.LongPath && secondHeader._typeFlag is TarEntryType.LongLink)) { TarHeader thirdHeader = default; - thirdHeader._format = TarFormat.Gnu; + thirdHeader._format = TarEntryFormat.Gnu; // Get the third entry, which is the actual entry if (!thirdHeader.TryGetNextHeader(_archiveStream, copyData)) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs index 7fa73a3cf13cb5..8296910d386043 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Unix.cs @@ -29,7 +29,7 @@ partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, str 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 TarFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, + Interop.Sys.FileTypes.S_IFREG => Format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile, Interop.Sys.FileTypes.S_IFDIR => TarEntryType.Directory, _ => throw new IOException(string.Format(SR.TarUnsupportedFile, fullPath)), }; @@ -38,10 +38,10 @@ partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, str TarEntry entry = Format switch { - TarFormat.V7 => new V7TarEntry(entryType, entryName), - TarFormat.Ustar => new UstarTarEntry(entryType, entryName), - TarFormat.Pax => new PaxTarEntry(entryType, entryName), - TarFormat.Gnu => new GnuTarEntry(entryType, entryName), + TarEntryFormat.V7 => new V7TarEntry(entryType, entryName), + TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), + TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), + TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), }; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs index 5fdd3b97d85c84..bee4ac9b7c319f 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs @@ -28,7 +28,7 @@ partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, str } else if (attributes.HasFlag(FileAttributes.Normal) || attributes.HasFlag(FileAttributes.Archive)) { - entryType = Format is TarFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; + entryType = Format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; } else { @@ -37,10 +37,10 @@ partial void ReadFileFromDiskAndWriteToArchiveStreamAsEntry(string fullPath, str TarEntry entry = Format switch { - TarFormat.V7 => new V7TarEntry(entryType, entryName), - TarFormat.Ustar => new UstarTarEntry(entryType, entryName), - TarFormat.Pax => new PaxTarEntry(entryType, entryName), - TarFormat.Gnu => new GnuTarEntry(entryType, entryName), + TarEntryFormat.V7 => new V7TarEntry(entryType, entryName), + TarEntryFormat.Ustar => new UstarTarEntry(entryType, entryName), + TarEntryFormat.Pax => new PaxTarEntry(entryType, entryName), + TarEntryFormat.Gnu => new GnuTarEntry(entryType, entryName), _ => throw new FormatException(string.Format(SR.TarInvalidFormat, Format)), }; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs index a9efcf84b5240a..bfad88463499b5 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.cs @@ -13,37 +13,32 @@ namespace System.Formats.Tar /// public sealed partial class TarWriter : IDisposable { - private bool _wroteGEA; private bool _wroteEntries; private bool _isDisposed; private readonly bool _leaveOpen; private readonly Stream _archiveStream; - private readonly IEnumerable>? _globalExtendedAttributes; + private int _globalExtendedAttributesEntryNumber; /// - /// Initializes a instance that can write tar entries to the specified stream, optionally leave the stream open upon disposal of this instance, and can optionally add a Global Extended Attributes entry at the beginning of the archive. When using this constructor, the format of the resulting archive is . + /// Initializes a instance that can write tar entries to the specified stream and closes the upon disposal of this instance. When using this constructor, the format of the resulting archive is . /// /// The stream to write to. - /// An optional enumeration of string key-value pairs that represent Global Extended Attributes metadata that should apply to all subsquent entries. If , then no Global Extended Attributes entry is written. If an empty instance is passed, a Global Extended Attributes entry is written with default values. - /// to dispose the when this instance is disposed; to leave the stream open. - public TarWriter(Stream archiveStream, IEnumerable>? globalExtendedAttributes = null, bool leaveOpen = false) - : this(archiveStream, TarFormat.Pax, leaveOpen) + public TarWriter(Stream archiveStream) + : this(archiveStream, TarEntryFormat.Pax, leaveOpen: false) { - _globalExtendedAttributes = globalExtendedAttributes; } /// - /// Initializes a instance that can write tar entries to the specified stream, optionally leave the stream open upon disposal of this instance, and can specify the format of the underlying archive. + /// Initializes a instance that can write tar entries to the specified stream, optionally leaves the stream open upon disposal of this instance, and can optionally specify the preferred default format when writing entries using the method. /// /// The stream to write to. - /// The format of the archive. - /// to dispose the when this instance is disposed; to leave the stream open. - /// If the selected is , no Global Extended Attributes entry is written. To write a PAX archive with a Global Extended Attributes entry inserted at the beginning of the archive, use the constructor instead. - /// The recommended format is for its flexibility. + /// The preferred default format when writing entries to the archive using the method. The default format is . + /// to dispose the when this instance is disposed; to leave the stream open. The default is . + /// The recommended format is for its flexibility. /// is . /// is unwritable. - /// is either , or not one of the other enum values. - public TarWriter(Stream archiveStream, TarFormat archiveFormat, bool leaveOpen = false) + /// is either , or not one of the other enum values. + public TarWriter(Stream archiveStream, TarEntryFormat archiveFormat = TarEntryFormat.Pax, bool leaveOpen = false) { ArgumentNullException.ThrowIfNull(archiveStream); @@ -52,7 +47,7 @@ public TarWriter(Stream archiveStream, TarFormat archiveFormat, bool leaveOpen = throw new IOException(SR.IO_NotSupported_UnwritableStream); } - if (archiveFormat is not TarFormat.V7 and not TarFormat.Ustar and not TarFormat.Pax and not TarFormat.Gnu) + if (archiveFormat is not TarEntryFormat.V7 and not TarEntryFormat.Ustar and not TarEntryFormat.Pax and not TarEntryFormat.Gnu) { throw new ArgumentOutOfRangeException(nameof(archiveFormat)); } @@ -62,14 +57,14 @@ public TarWriter(Stream archiveStream, TarFormat archiveFormat, bool leaveOpen = _leaveOpen = leaveOpen; _isDisposed = false; _wroteEntries = false; - _wroteGEA = false; - _globalExtendedAttributes = null; + _globalExtendedAttributesEntryNumber = 1; } /// - /// The format of the archive. + /// The preferred default format of the entries when writing entries to the archive using the method. /// - public TarFormat Format { get; private set; } + /// This format is used when writing entries into the archive using the method, but entries of any can be written to the archive when using the method . + public TarEntryFormat Format { get; private set; } /// /// Disposes the current instance, and closes the archive stream if the leaveOpen argument was set to in the constructor. @@ -89,7 +84,7 @@ public void Dispose() // } /// - /// Writes the specified file into the archive stream as a tar entry. + /// Writes the specified file into the archive stream as a tar entry, using the format specified by . /// /// The path to the file to write to the archive. /// The name of the file as it should be represented in the archive. It should include the optional relative path and the filename. @@ -109,11 +104,6 @@ public void WriteEntry(string fileName, string? entryName) entryName = Path.GetFileName(fileName); } - if (Format is TarFormat.Pax) - { - WriteGlobalExtendedAttributesEntryIfNeeded(); - } - ReadFileFromDiskAndWriteToArchiveStreamAsEntry(fullPath, entryName); } @@ -129,14 +119,14 @@ public void WriteEntry(string fileName, string? entryName) // } /// - /// Writes the specified entry into the archive stream. + /// Writes the specified entry into the archive stream, in the specific of the entry. /// /// The tar entry to write. /// Before writing an entry to the archive, if you wrote data into the entry's , make sure to rewind it to the desired start position. /// These are the entry types supported for writing on each format: /// /// - /// + /// /// /// /// @@ -145,7 +135,7 @@ public void WriteEntry(string fileName, string? entryName) /// /// /// - /// , and + /// , and /// /// /// @@ -165,30 +155,36 @@ public void WriteEntry(TarEntry entry) { ThrowIfDisposed(); - TarHelpers.VerifyEntryTypeIsSupported(entry.EntryType, Format, forWriting: true); - - WriteGlobalExtendedAttributesEntryIfNeeded(); + TarHelpers.VerifyEntryTypeIsSupported(entry.EntryType, entry.Format, forWriting: true); byte[] rented = ArrayPool.Shared.Rent(minimumLength: TarHelpers.RecordSize); Span buffer = rented.AsSpan(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger buffer.Clear(); // Rented arrays aren't clean try { - switch (Format) + switch (entry.Format) // Respect the underlying format of the passed entry { - case TarFormat.V7: + case TarEntryFormat.V7: entry._header.WriteAsV7(_archiveStream, buffer); break; - case TarFormat.Ustar: + case TarEntryFormat.Ustar: entry._header.WriteAsUstar(_archiveStream, buffer); break; - case TarFormat.Pax: - entry._header.WriteAsPax(_archiveStream, buffer); + case TarEntryFormat.Pax: + if (entry._header._typeFlag is TarEntryType.GlobalExtendedAttributes) + { + entry._header.WriteAsPaxGlobalExtendedAttributes(_archiveStream, buffer, _globalExtendedAttributesEntryNumber); + _globalExtendedAttributesEntryNumber++; + } + else + { + entry._header.WriteAsPax(_archiveStream, buffer); + } break; - case TarFormat.Gnu: + case TarEntryFormat.Gnu: entry._header.WriteAsGnu(_archiveStream, buffer); break; - case TarFormat.Unknown: + case TarEntryFormat.Unknown: default: throw new FormatException(string.Format(SR.TarInvalidFormat, Format)); } @@ -202,7 +198,7 @@ public void WriteEntry(TarEntry entry) } // /// - // /// Asynchronously writes the specified entry into the archive stream. + // /// Asynchronously writes the specified entry into the archive stream, in the specific of the entry. // /// // /// The tar entry to write. // /// The token to monitor for cancellation requests. The default value is . @@ -210,7 +206,7 @@ public void WriteEntry(TarEntry entry) // /// These are the entry types supported for writing on each format: // /// // /// - // /// + // /// // /// // /// // /// @@ -219,7 +215,7 @@ public void WriteEntry(TarEntry entry) // /// // /// // /// - // /// , and + // /// , and // /// // /// // /// @@ -245,8 +241,6 @@ private void Dispose(bool disposing) { try { - WriteGlobalExtendedAttributesEntryIfNeeded(); - if (_wroteEntries) { WriteFinalRecords(); @@ -274,36 +268,6 @@ private void ThrowIfDisposed() } } - // Writes a Global Extended Attributes entry at the beginning of the archive. - private void WriteGlobalExtendedAttributesEntryIfNeeded() - { - Debug.Assert(!_isDisposed); - - if (_wroteGEA || Format != TarFormat.Pax) - { - return; - } - - Debug.Assert(!_wroteEntries); // The GEA entry can only be the first entry - - if (_globalExtendedAttributes != null) - { - byte[] rented = ArrayPool.Shared.Rent(minimumLength: TarHelpers.RecordSize); - try - { - Span buffer = rented.AsSpan(0, TarHelpers.RecordSize); - buffer.Clear(); // Rented arrays aren't clean - // Write the GEA entry regardless if it has values or not - TarHeader.WriteGlobalExtendedAttributesHeader(_archiveStream, buffer, _globalExtendedAttributes); - } - finally - { - ArrayPool.Shared.Return(rented); - } - } - _wroteGEA = true; - } - // The spec indicates that the end of the archive is indicated // by two records consisting entirely of zero bytes. private void WriteFinalRecords() diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs index 13e1d9c358a69b..519fa3aad56e16 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/UstarTarEntry.cs @@ -28,10 +28,25 @@ internal UstarTarEntry(TarHeader header, TarReader readerOfOrigin) /// /// public UstarTarEntry(TarEntryType entryType, string entryName) - : base(entryType, entryName, TarFormat.Ustar) + : base(entryType, entryName, TarEntryFormat.Ustar) { } + /// + /// Initializes a new instance by converting the specified entry into the Ustar format. + /// + public UstarTarEntry(TarEntry other) + : base(other._header, other._readerOfOrigin!) + { + if (_header._typeFlag == TarEntryType.V7RegularFile) + { + _header._typeFlag = TarEntryType.RegularFile; + } + TarHelpers.VerifyEntryTypeIsSupported(_header._typeFlag, TarEntryFormat.Ustar, forWriting: false); + + _header._format = TarEntryFormat.Ustar; + } + // Determines if the current instance's entry type supports setting a data stream. internal override bool IsDataStreamSetterSupported() => EntryType == TarEntryType.RegularFile; } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs index 84389365c73725..3dfcbc5c48bfec 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/V7TarEntry.cs @@ -23,10 +23,25 @@ internal V7TarEntry(TarHeader header, TarReader readerOfOrigin) /// The entry type is not supported for creating an entry. /// When creating an instance using the constructor, only the following entry types are supported: , , and . public V7TarEntry(TarEntryType entryType, string entryName) - : base(entryType, entryName, TarFormat.V7) + : base(entryType, entryName, TarEntryFormat.V7) { } + /// + /// Initializes a new instance by converting the specified entry into the V7 format. + /// + public V7TarEntry(TarEntry other) + : base(other._header, other._readerOfOrigin!) + { + if (_header._typeFlag == TarEntryType.RegularFile) + { + _header._typeFlag = TarEntryType.V7RegularFile; + } + TarHelpers.VerifyEntryTypeIsSupported(_header._typeFlag, TarEntryFormat.V7, forWriting: false); + + _header._format = TarEntryFormat.V7; + } + // Determines if the current instance's entry type supports setting a data stream. internal override bool IsDataStreamSetterSupported() => EntryType == TarEntryType.V7RegularFile; } diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj index d41bc4a1f1d118..c5f1aefdaa92d9 100644 --- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj +++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj @@ -16,6 +16,8 @@ + + diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryGnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryGnu.Tests.cs index 5ff711b5ee6ad0..b88986ae02c4b3 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryGnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryGnu.Tests.cs @@ -38,6 +38,41 @@ public void Constructor_UnsupportedEntryTypes() Assert.Throws(() => new GnuTarEntry(TarEntryType.LongPath, InitialEntryName)); } + [Fact] + public void Constructor_ConversionFromV7() + { + V7TarEntry v7 = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); + GnuTarEntry convertedV7 = new GnuTarEntry(v7); + + Assert.Equal(TarEntryFormat.Gnu, convertedV7.Format); + Assert.Equal(TarEntryType.RegularFile, convertedV7.EntryType); + Assert.Equal(InitialEntryName, convertedV7.Name); + } + + + [Fact] + public void Constructor_ConversionFromUstar() + { + UstarTarEntry ustar = new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName); + GnuTarEntry convertedUstar = new GnuTarEntry(ustar); + + Assert.Equal(TarEntryFormat.Gnu, convertedUstar.Format); + Assert.Equal(TarEntryType.RegularFile, convertedUstar.EntryType); + Assert.Equal(InitialEntryName, convertedUstar.Name); + } + + + [Fact] + public void Constructor_ConversionFromPax() + { + PaxTarEntry pax = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); + GnuTarEntry convertedPax = new GnuTarEntry(pax); + + Assert.Equal(TarEntryFormat.Gnu, convertedPax.Format); + Assert.Equal(TarEntryType.RegularFile, convertedPax.EntryType); + Assert.Equal(InitialEntryName, convertedPax.Name); + } + [Fact] public void SupportedEntryType_RegularFile() { diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryPax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryPax.Tests.cs index 9729f05789f886..76b40d65d6b325 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryPax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryPax.Tests.cs @@ -33,7 +33,41 @@ public void Constructor_UnsupportedEntryTypes() // The user should not be creating these entries manually in pax Assert.Throws(() => new PaxTarEntry(TarEntryType.ExtendedAttributes, InitialEntryName)); - Assert.Throws(() => new PaxTarEntry(TarEntryType.GlobalExtendedAttributes, InitialEntryName)); + } + + [Fact] + public void Constructor_ConversionFromV7() + { + V7TarEntry v7 = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); + PaxTarEntry convertedV7 = new PaxTarEntry(v7); + + Assert.Equal(TarEntryFormat.Pax, convertedV7.Format); + Assert.Equal(TarEntryType.RegularFile, convertedV7.EntryType); + Assert.Equal(InitialEntryName, convertedV7.Name); + } + + + [Fact] + public void Constructor_ConversionFromUstar() + { + UstarTarEntry ustar = new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName); + PaxTarEntry convertedUstar = new PaxTarEntry(ustar); + + Assert.Equal(TarEntryFormat.Pax, convertedUstar.Format); + Assert.Equal(TarEntryType.RegularFile, convertedUstar.EntryType); + Assert.Equal(InitialEntryName, convertedUstar.Name); + } + + + [Fact] + public void Constructor_ConversionFromGnu() + { + GnuTarEntry gnu = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + PaxTarEntry convertedGnu = new PaxTarEntry(gnu); + + Assert.Equal(TarEntryFormat.Pax, convertedGnu.Format); + Assert.Equal(TarEntryType.RegularFile, convertedGnu.EntryType); + Assert.Equal(InitialEntryName, convertedGnu.Name); } [Fact] diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryUstar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryUstar.Tests.cs index 461c278b7f67e7..6514223be46645 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryUstar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryUstar.Tests.cs @@ -34,6 +34,41 @@ public void Constructor_UnsupportedEntryTypes() Assert.Throws(() => new UstarTarEntry(TarEntryType.TapeVolume, InitialEntryName)); } + [Fact] + public void Constructor_ConversionFromV7() + { + V7TarEntry v7 = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); + UstarTarEntry convertedV7 = new UstarTarEntry(v7); + + Assert.Equal(TarEntryFormat.Ustar, convertedV7.Format); + Assert.Equal(TarEntryType.RegularFile, convertedV7.EntryType); + Assert.Equal(InitialEntryName, convertedV7.Name); + } + + + [Fact] + public void Constructor_ConversionFromPax() + { + PaxTarEntry pax = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); + UstarTarEntry convertedPax = new UstarTarEntry(pax); + + Assert.Equal(TarEntryFormat.Ustar, convertedPax.Format); + Assert.Equal(TarEntryType.RegularFile, convertedPax.EntryType); + Assert.Equal(InitialEntryName, convertedPax.Name); + } + + + [Fact] + public void Constructor_ConversionFromGnu() + { + GnuTarEntry gnu = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + UstarTarEntry convertedGnu = new UstarTarEntry(gnu); + + Assert.Equal(TarEntryFormat.Ustar, convertedGnu.Format); + Assert.Equal(TarEntryType.RegularFile, convertedGnu.EntryType); + Assert.Equal(InitialEntryName, convertedGnu.Name); + } + [Fact] public void SupportedEntryType_RegularFile() { diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryV7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryV7.Tests.cs index ff1e6bb8c14ce7..6b4af1a86fd0d2 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryV7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntryV7.Tests.cs @@ -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.Collections.Generic; using System.IO; using System.Linq; using Xunit; @@ -37,6 +38,68 @@ public void Constructor_UnsupportedEntryTypes() Assert.Throws(() => new V7TarEntry(TarEntryType.TapeVolume, InitialEntryName)); } + [Fact] + public void Constructor_ConversionFromUstar() + { + UstarTarEntry ustar = new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName); + V7TarEntry convertedUstar = new V7TarEntry(ustar); + + Assert.Equal(TarEntryFormat.V7, convertedUstar.Format); + Assert.Equal(TarEntryType.V7RegularFile, convertedUstar.EntryType); + Assert.Equal(InitialEntryName, convertedUstar.Name); + } + + + [Fact] + public void Constructor_ConversionFromPax() + { + PaxTarEntry pax = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); + V7TarEntry convertedPax = new V7TarEntry(pax); + + Assert.Equal(TarEntryFormat.V7, convertedPax.Format); + Assert.Equal(TarEntryType.V7RegularFile, convertedPax.EntryType); + Assert.Equal(InitialEntryName, convertedPax.Name); + } + + + [Fact] + public void Constructor_ConversionFromGnu() + { + GnuTarEntry gnu = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); + V7TarEntry convertedGnu = new V7TarEntry(gnu); + + Assert.Equal(TarEntryFormat.V7, convertedGnu.Format); + Assert.Equal(TarEntryType.V7RegularFile, convertedGnu.EntryType); + Assert.Equal(InitialEntryName, convertedGnu.Name); + } + + [Fact] + public void Constructor_Conversion_UnsupportedEntryTypes_Ustar() + { + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new UstarTarEntry(TarEntryType.Fifo, InitialEntryName))); + } + + [Fact] + public void Constructor_Conversion_UnsupportedEntryTypes_Pax() + { + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new PaxTarEntry(TarEntryType.Fifo, InitialEntryName))); + + // PaxGlobalExtendedAttributesTarEntry is a TarEntry but cannot be converted + Assert.Throws(() => new V7TarEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary()))); + } + + [Fact] + public void Constructor_Conversion_UnsupportedEntryTypes_Gnu() + { + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); + Assert.Throws(() => new V7TarEntry(new GnuTarEntry(TarEntryType.Fifo, InitialEntryName))); + } + [Fact] public void SupportedEntryType_V7RegularFile() { diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs index 5bb0d9b2c3eb49..8b9af8b3bc3e26 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs @@ -52,7 +52,7 @@ public void ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries() string fileWithTwoSegments = Path.Join(secondSegment, "c.txt"); using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { // No preceding directory entries for the segments UstarTarEntry entry = new UstarTarEntry(TarEntryType.RegularFile, fileWithTwoSegments); @@ -78,7 +78,7 @@ public void ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries() public void Extract_LinkEntry_TargetOutsideDirectory(TarEntryType entryType) { using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry = new UstarTarEntry(entryType, "link"); entry.LinkName = PlatformDetection.IsWindows ? @"C:\Windows\System32\notepad.exe" : "/usr/bin/nano"; @@ -112,7 +112,7 @@ private void Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType entry File.Create(targetPath).Dispose(); using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry = new UstarTarEntry(entryType, linkName); entry.LinkName = targetPath; diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.GlobalExtendedAttributes.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.GlobalExtendedAttributes.Tests.cs new file mode 100644 index 00000000000000..7d6552d2dac6cd --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.GlobalExtendedAttributes.Tests.cs @@ -0,0 +1,328 @@ +// 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.IO; +using System.Linq; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarReader_File_GlobalExtendedAttributes_Tests : TarReader_File_Tests_Base + { + [Fact] + public void Read_Archive_File() + { + string testCaseName = "file"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + PaxTarEntry file = reader.GetNextEntry() as PaxTarEntry; + + VerifyRegularFileEntry(file, TarEntryFormat.Pax, "file.txt", $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_File_HardLink() + { + string testCaseName = "file_hardlink"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry file = reader.GetNextEntry(); + + VerifyRegularFileEntry(file, TarEntryFormat.Pax, "file.txt", $"Hello {testCaseName}"); + + TarEntry hardLink = reader.GetNextEntry(); + // The 'tar' tool detects hardlinks as regular files and saves them as such in the archives, for all formats + VerifyRegularFileEntry(hardLink, TarEntryFormat.Pax, "hardlink.txt", $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_File_SymbolicLink() + { + string testCaseName = "file_symlink"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry file = reader.GetNextEntry(); + + VerifyRegularFileEntry(file, TarEntryFormat.Pax, "file.txt", $"Hello {testCaseName}"); + + TarEntry symbolicLink = reader.GetNextEntry(); + VerifySymbolicLinkEntry(symbolicLink, TarEntryFormat.Pax, "link.txt", "file.txt"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_Folder_File() + { + string testCaseName = "folder_file"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry directory = reader.GetNextEntry(); + + VerifyDirectoryEntry(directory, TarEntryFormat.Pax, "folder/"); + + TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, TarEntryFormat.Pax, "folder/file.txt", $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_Folder_File_Utf8() + { + string testCaseName = "folder_file_utf8"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry directory = reader.GetNextEntry(); + + VerifyDirectoryEntry(directory, TarEntryFormat.Pax, "földër/"); + + TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, TarEntryFormat.Pax, "földër/áöñ.txt", $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_Folder_Subfolder_File() + { + string testCaseName = "folder_subfolder_file"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry parent = reader.GetNextEntry(); + + VerifyDirectoryEntry(parent, TarEntryFormat.Pax, "parent/"); + + TarEntry child = reader.GetNextEntry(); + VerifyDirectoryEntry(child, TarEntryFormat.Pax, "parent/child/"); + + TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, TarEntryFormat.Pax, "parent/child/file.txt", $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_FolderSymbolicLink_Folder_Subfolder_File() + { + string testCaseName = "foldersymlink_folder_subfolder_file"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry childlink = reader.GetNextEntry(); + + VerifySymbolicLinkEntry(childlink, TarEntryFormat.Pax, "childlink", "parent/child"); + + TarEntry parent = reader.GetNextEntry(); + VerifyDirectoryEntry(parent, TarEntryFormat.Pax, "parent/"); + + TarEntry child = reader.GetNextEntry(); + VerifyDirectoryEntry(child, TarEntryFormat.Pax, "parent/child/"); + + TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, TarEntryFormat.Pax, "parent/child/file.txt", $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_Many_Small_Files() + { + string testCaseName = "many_small_files"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + List entries = new List(); + TarEntry entry; + bool isFirstEntry = true; + while ((entry = reader.GetNextEntry()) != null) + { + if (isFirstEntry) + { + isFirstEntry = false; + } + entries.Add(entry); + } + + int directoriesCount = entries.Count(e => e.EntryType == TarEntryType.Directory); + Assert.Equal(10, directoriesCount); + + for (int i = 0; i < 10; i++) + { + int filesCount = entries.Count(e => e.EntryType == TarEntryType.RegularFile && e.Name.StartsWith($"{i}/")); + Assert.Equal(10, filesCount); + } + } + + [Fact] + public void Read_Archive_LongPath_Splitable_Under255() + { + string testCaseName = "longpath_splitable_under255"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry directory = reader.GetNextEntry(); + + VerifyDirectoryEntry(directory, TarEntryFormat.Pax, + "00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999/"); + + TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, TarEntryFormat.Pax, + $"00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999.txt", + $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_SpecialFiles() + { + string testCaseName = "specialfiles"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + PosixTarEntry blockDevice = reader.GetNextEntry() as PosixTarEntry; + + VerifyBlockDeviceEntry(blockDevice, TarEntryFormat.Pax, AssetBlockDeviceFileName); + + PosixTarEntry characterDevice = reader.GetNextEntry() as PosixTarEntry; + VerifyCharacterDeviceEntry(characterDevice, TarEntryFormat.Pax, AssetCharacterDeviceFileName); + + PosixTarEntry fifo = reader.GetNextEntry() as PosixTarEntry; + VerifyFifoEntry(fifo, TarEntryFormat.Pax, "fifofile"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_File_LongSymbolicLink() + { + string testCaseName = "file_longsymlink"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry directory = reader.GetNextEntry(); + + VerifyDirectoryEntry(directory, TarEntryFormat.Pax, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/"); + + TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, TarEntryFormat.Pax, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt", + $"Hello {testCaseName}"); + + TarEntry symbolicLink = reader.GetNextEntry(); + VerifySymbolicLinkEntry(symbolicLink, TarEntryFormat.Pax, + "link.txt", + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_LongFileName_Over100_Under255() + { + string testCaseName = "longfilename_over100_under255"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry file = reader.GetNextEntry(); + + VerifyRegularFileEntry(file, TarEntryFormat.Pax, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444.txt", + $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + [Fact] + public void Read_Archive_LongPath_Over255() + { + string testCaseName = "longpath_over255"; + using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.pax_gea, testCaseName); + + using TarReader reader = new TarReader(ms); + + IReadOnlyDictionary gea = VerifyGlobalExtendedAttributes(reader); + + TarEntry directory = reader.GetNextEntry(); + + VerifyDirectoryEntry(directory, TarEntryFormat.Pax, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/"); + + TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, TarEntryFormat.Pax, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt", + $"Hello {testCaseName}"); + + Assert.Null(reader.GetNextEntry()); + } + + private IReadOnlyDictionary VerifyGlobalExtendedAttributes(TarReader reader) + { + PaxGlobalExtendedAttributesTarEntry gea = reader.GetNextEntry() as PaxGlobalExtendedAttributesTarEntry; + Assert.NotNull(gea); + Assert.Equal(TarEntryType.GlobalExtendedAttributes, gea.EntryType); + Assert.Equal(TarEntryFormat.Pax, gea.Format); + + // Format: %d/GlobalHead.%p.%n, where: + // - %d is the tmp path (platform dependent, and if too long, gets truncated to just '/tmp') + // - %p is current process ID + // - %n is the sequence number, which is always 1 for the first entry of the asset archive files + Assert.Matches(@".+\/GlobalHead\.\d+\.1", gea.Name); + + Assert.True(gea.GlobalExtendedAttributes.Any()); + Assert.Contains(AssetPaxGeaKey, gea.GlobalExtendedAttributes); + Assert.Equal(AssetPaxGeaValue, gea.GlobalExtendedAttributes[AssetPaxGeaKey]); + + return gea.GlobalExtendedAttributes; + } + } +} diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.Base.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.Base.cs new file mode 100644 index 00000000000000..29df7d7178f394 --- /dev/null +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.Base.cs @@ -0,0 +1,263 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using System.IO; +using System.Linq; +using Xunit; + +namespace System.Formats.Tar.Tests +{ + public class TarReader_File_Tests_Base : TarTestsBase + { + protected void VerifyRegularFileEntry(TarEntry file, TarEntryFormat format, string expectedFileName, string expectedContents) + { + Assert.NotNull(file); + Assert.Equal(format, file.Format); + + Assert.True(file.Checksum > 0); + Assert.NotNull(file.DataStream); + Assert.True(file.DataStream.Length > 0); + Assert.True(file.DataStream.CanRead); + Assert.True(file.DataStream.CanSeek); + file.DataStream.Seek(0, SeekOrigin.Begin); + using (StreamReader reader = new StreamReader(file.DataStream, leaveOpen: true)) + { + string contents = reader.ReadLine(); + Assert.Equal(expectedContents, contents); + } + + TarEntryType expectedEntryType = format == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; + Assert.Equal(expectedEntryType, file.EntryType); + + Assert.Equal(AssetGid, file.Gid); + Assert.Equal(file.Length, file.DataStream.Length); + Assert.Equal(DefaultLinkName, file.LinkName); + Assert.Equal(AssetMode, file.Mode); + Assert.True(file.ModificationTime > DateTimeOffset.UnixEpoch); + Assert.Equal(expectedFileName, file.Name); + Assert.Equal(AssetUid, file.Uid); + + if (file is PosixTarEntry posix) + { + Assert.Equal(DefaultDeviceMajor, posix.DeviceMajor); + Assert.Equal(DefaultDeviceMinor, posix.DeviceMinor); + Assert.Equal(AssetGName, posix.GroupName); + Assert.Equal(AssetUName, posix.UserName); + + if (posix is PaxTarEntry pax) + { + VerifyExtendedAttributes(pax); + } + else if (posix is GnuTarEntry gnu) + { + VerifyGnuFields(gnu); + } + } + } + + protected void VerifySymbolicLinkEntry(TarEntry symbolicLink, TarEntryFormat format, string expectedFileName, string expectedTargetName) + { + Assert.NotNull(symbolicLink); + Assert.Equal(format, symbolicLink.Format); + + Assert.True(symbolicLink.Checksum > 0); + Assert.Null(symbolicLink.DataStream); + + Assert.Equal(TarEntryType.SymbolicLink, symbolicLink.EntryType); + + Assert.Equal(AssetGid, symbolicLink.Gid); + Assert.Equal(0, symbolicLink.Length); + Assert.Equal(expectedTargetName, symbolicLink.LinkName); + Assert.Equal(AssetSymbolicLinkMode, symbolicLink.Mode); + Assert.True(symbolicLink.ModificationTime > DateTimeOffset.UnixEpoch); + Assert.Equal(expectedFileName, symbolicLink.Name); + Assert.Equal(AssetUid, symbolicLink.Uid); + + if (symbolicLink is PosixTarEntry posix) + { + Assert.Equal(DefaultDeviceMajor, posix.DeviceMajor); + Assert.Equal(DefaultDeviceMinor, posix.DeviceMinor); + Assert.Equal(AssetGName, posix.GroupName); + Assert.Equal(AssetUName, posix.UserName); + } + + if (symbolicLink is PaxTarEntry pax) + { + VerifyExtendedAttributes(pax); + } + else if (symbolicLink is GnuTarEntry gnu) + { + VerifyGnuFields(gnu); + } + } + + protected void VerifyDirectoryEntry(TarEntry directory, TarEntryFormat format, string expectedFileName) + { + Assert.NotNull(directory); + Assert.Equal(format, directory.Format); + + Assert.True(directory.Checksum > 0); + Assert.Null(directory.DataStream); + + Assert.Equal(TarEntryType.Directory, directory.EntryType); + + Assert.Equal(AssetGid, directory.Gid); + Assert.Equal(0, directory.Length); + Assert.Equal(DefaultLinkName, directory.LinkName); + Assert.Equal(AssetMode, directory.Mode); + Assert.True(directory.ModificationTime > DateTimeOffset.UnixEpoch); + Assert.Equal(expectedFileName, directory.Name); + Assert.Equal(AssetUid, directory.Uid); + + if (directory is PosixTarEntry posix) + { + Assert.Equal(DefaultDeviceMajor, posix.DeviceMajor); + Assert.Equal(DefaultDeviceMinor, posix.DeviceMinor); + Assert.Equal(AssetGName, posix.GroupName); + Assert.Equal(AssetUName, posix.UserName); + } + + if (directory is PaxTarEntry pax) + { + VerifyExtendedAttributes(pax); + } + else if (directory is GnuTarEntry gnu) + { + VerifyGnuFields(gnu); + } + } + + protected void VerifyBlockDeviceEntry(PosixTarEntry blockDevice, TarEntryFormat format, string expectedFileName) + { + Assert.NotNull(blockDevice); + Assert.Equal(TarEntryType.BlockDevice, blockDevice.EntryType); + Assert.Equal(format, blockDevice.Format); + + Assert.True(blockDevice.Checksum > 0); + Assert.Null(blockDevice.DataStream); + + Assert.Equal(AssetGid, blockDevice.Gid); + Assert.Equal(0, blockDevice.Length); + Assert.Equal(DefaultLinkName, blockDevice.LinkName); + Assert.Equal(AssetSpecialFileMode, blockDevice.Mode); + Assert.True(blockDevice.ModificationTime > DateTimeOffset.UnixEpoch); + Assert.Equal(expectedFileName, blockDevice.Name); + Assert.Equal(AssetUid, blockDevice.Uid); + + // TODO: Figure out why the numbers don't match https://github.com/dotnet/runtime/issues/68230 + // Assert.Equal(AssetBlockDeviceMajor, blockDevice.DeviceMajor); + // Assert.Equal(AssetBlockDeviceMinor, blockDevice.DeviceMinor); + // Remove these two temporary checks when the above is fixed + Assert.True(blockDevice.DeviceMajor > 0); + Assert.True(blockDevice.DeviceMinor > 0); + Assert.Equal(AssetGName, blockDevice.GroupName); + Assert.Equal(AssetUName, blockDevice.UserName); + + if (blockDevice is PaxTarEntry pax) + { + VerifyExtendedAttributes(pax); + } + else if (blockDevice is GnuTarEntry gnu) + { + VerifyGnuFields(gnu); + } + } + + protected void VerifyCharacterDeviceEntry(PosixTarEntry characterDevice, TarEntryFormat format, string expectedFileName) + { + Assert.NotNull(characterDevice); + Assert.Equal(TarEntryType.CharacterDevice, characterDevice.EntryType); + Assert.Equal(format, characterDevice.Format); + + Assert.True(characterDevice.Checksum > 0); + Assert.Null(characterDevice.DataStream); + + Assert.Equal(AssetGid, characterDevice.Gid); + Assert.Equal(0, characterDevice.Length); + Assert.Equal(DefaultLinkName, characterDevice.LinkName); + Assert.Equal(AssetSpecialFileMode, characterDevice.Mode); + Assert.True(characterDevice.ModificationTime > DateTimeOffset.UnixEpoch); + Assert.Equal(expectedFileName, characterDevice.Name); + Assert.Equal(AssetUid, characterDevice.Uid); + + // TODO: Figure out why the numbers don't match https://github.com/dotnet/runtime/issues/68230 + //Assert.Equal(AssetBlockDeviceMajor, characterDevice.DeviceMajor); + //Assert.Equal(AssetBlockDeviceMinor, characterDevice.DeviceMinor); + // Remove these two temporary checks when the above is fixed + Assert.True(characterDevice.DeviceMajor > 0); + Assert.True(characterDevice.DeviceMinor > 0); + Assert.Equal(AssetGName, characterDevice.GroupName); + Assert.Equal(AssetUName, characterDevice.UserName); + + if (characterDevice is PaxTarEntry pax) + { + VerifyExtendedAttributes(pax); + } + else if (characterDevice is GnuTarEntry gnu) + { + VerifyGnuFields(gnu); + } + } + + protected void VerifyFifoEntry(PosixTarEntry fifo, TarEntryFormat format, string expectedFileName) + { + Assert.NotNull(fifo); + Assert.Equal(format, fifo.Format); + + Assert.True(fifo.Checksum > 0); + Assert.Null(fifo.DataStream); + + Assert.Equal(TarEntryType.Fifo, fifo.EntryType); + + Assert.Equal(AssetGid, fifo.Gid); + Assert.Equal(0, fifo.Length); + Assert.Equal(DefaultLinkName, fifo.LinkName); + Assert.Equal(AssetSpecialFileMode, fifo.Mode); + Assert.True(fifo.ModificationTime > DateTimeOffset.UnixEpoch); + Assert.Equal(expectedFileName, fifo.Name); + Assert.Equal(AssetUid, fifo.Uid); + + Assert.Equal(DefaultDeviceMajor, fifo.DeviceMajor); + Assert.Equal(DefaultDeviceMinor, fifo.DeviceMinor); + Assert.Equal(AssetGName, fifo.GroupName); + Assert.Equal(AssetUName, fifo.UserName); + + if (fifo is PaxTarEntry pax) + { + VerifyExtendedAttributes(pax); + } + else if (fifo is GnuTarEntry gnu) + { + VerifyGnuFields(gnu); + } + } + + private void VerifyExtendedAttributes(PaxTarEntry pax) + { + Assert.NotNull(pax.ExtendedAttributes); + Assert.Equal(TarEntryFormat.Pax, pax.Format); + Assert.True(pax.ExtendedAttributes.Count() >= 3); // Expect to at least collect mtime, ctime and atime + + Assert.Contains("mtime", pax.ExtendedAttributes); + Assert.Contains("atime", pax.ExtendedAttributes); + Assert.Contains("ctime", pax.ExtendedAttributes); + + Assert.True(double.TryParse(pax.ExtendedAttributes["mtime"], NumberStyles.Any, CultureInfo.InvariantCulture, out double mtimeSecondsSinceEpoch)); + Assert.True(mtimeSecondsSinceEpoch > 0); + + Assert.True(double.TryParse(pax.ExtendedAttributes["atime"], NumberStyles.Any, CultureInfo.InvariantCulture, out double atimeSecondsSinceEpoch)); + Assert.True(atimeSecondsSinceEpoch > 0); + + Assert.True(double.TryParse(pax.ExtendedAttributes["ctime"], NumberStyles.Any, CultureInfo.InvariantCulture, out double ctimeSecondsSinceEpoch)); + Assert.True(ctimeSecondsSinceEpoch > 0); + } + + private void VerifyGnuFields(GnuTarEntry gnu) + { + Assert.Equal(TarEntryFormat.Gnu, gnu.Format); + Assert.True(gnu.AccessTime >= DateTimeOffset.UnixEpoch); + Assert.True(gnu.ChangeTime >= DateTimeOffset.UnixEpoch); + } + } +} \ No newline at end of file diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs index ef619edb472f96..c4f2bce2622b2f 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.File.Tests.cs @@ -2,353 +2,200 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using Xunit; namespace System.Formats.Tar.Tests { - public class TarReader_File_Tests : TarTestsBase + public class TarReader_File_Tests : TarReader_File_Tests_Base { [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_File(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_File(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "file"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry file = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "file.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, "file.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_File_HardLink(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_File_HardLink(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "file_hardlink"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry file = reader.GetNextEntry(); + VerifyRegularFileEntry(file, format, "file.txt", $"Hello {testCaseName}"); - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "file.txt", $"Hello {testCaseName}"); - + // The 'tar' unix tool detects hardlinks as regular files and saves them as such in the archives, for all formats TarEntry hardLink = reader.GetNextEntry(); - // The 'tar' tool detects hardlinks as regular files and saves them as such in the archives, for all formats - Verify_Archive_RegularFile(hardLink, format, reader.GlobalExtendedAttributes, "hardlink.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(hardLink, format, "hardlink.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_File_SymbolicLink(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_File_SymbolicLink(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "file_symlink"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry file = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "file.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, "file.txt", $"Hello {testCaseName}"); TarEntry symbolicLink = reader.GetNextEntry(); - Verify_Archive_SymbolicLink(symbolicLink, reader.GlobalExtendedAttributes, "link.txt", "file.txt"); + VerifySymbolicLinkEntry(symbolicLink, format, "link.txt", "file.txt"); Assert.Null(reader.GetNextEntry()); } [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_Folder_File(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_Folder_File(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "folder_file"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry directory = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_Directory(directory, reader.GlobalExtendedAttributes, "folder/"); + VerifyDirectoryEntry(directory, format, "folder/"); TarEntry file = reader.GetNextEntry(); - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "folder/file.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, "folder/file.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_Folder_File_Utf8(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_Folder_File_Utf8(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "folder_file_utf8"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry directory = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_Directory(directory, reader.GlobalExtendedAttributes, "földër/"); + VerifyDirectoryEntry(directory, format, "földër/"); TarEntry file = reader.GetNextEntry(); - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "földër/áöñ.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, "földër/áöñ.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_Folder_Subfolder_File(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_Folder_Subfolder_File(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "folder_subfolder_file"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry parent = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_Directory(parent, reader.GlobalExtendedAttributes, "parent/"); + VerifyDirectoryEntry(parent, format, "parent/"); TarEntry child = reader.GetNextEntry(); - Verify_Archive_Directory(child, reader.GlobalExtendedAttributes, "parent/child/"); + VerifyDirectoryEntry(child, format, "parent/child/"); TarEntry file = reader.GetNextEntry(); - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "parent/child/file.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, "parent/child/file.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_FolderSymbolicLink_Folder_Subfolder_File(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_FolderSymbolicLink_Folder_Subfolder_File(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "foldersymlink_folder_subfolder_file"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry childlink = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_SymbolicLink(childlink, reader.GlobalExtendedAttributes, "childlink", "parent/child"); + VerifySymbolicLinkEntry(childlink, format, "childlink", "parent/child"); TarEntry parent = reader.GetNextEntry(); - Verify_Archive_Directory(parent, reader.GlobalExtendedAttributes, "parent/"); + VerifyDirectoryEntry(parent, format, "parent/"); TarEntry child = reader.GetNextEntry(); - Verify_Archive_Directory(child, reader.GlobalExtendedAttributes, "parent/child/"); + VerifyDirectoryEntry(child, format, "parent/child/"); TarEntry file = reader.GetNextEntry(); - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "parent/child/file.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, "parent/child/file.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] - [InlineData(TarFormat.V7, TestTarFormat.v7)] - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_Many_Small_Files(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.V7, TestTarFormat.v7)] + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_Many_Small_Files(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "many_small_files"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); List entries = new List(); TarEntry entry; - bool isFirstEntry = true; while ((entry = reader.GetNextEntry()) != null) { - if (isFirstEntry) - { - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - isFirstEntry = false; - } + Assert.Equal(format, entry.Format); entries.Add(entry); } int directoriesCount = entries.Count(e => e.EntryType == TarEntryType.Directory); Assert.Equal(10, directoriesCount); - TarEntryType regularFileEntryType = format == TarFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; + TarEntryType regularFileEntryType = format == TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; for (int i = 0; i < 10; i++) { int filesCount = entries.Count(e => e.EntryType == regularFileEntryType && e.Name.StartsWith($"{i}/")); @@ -358,439 +205,120 @@ public void Read_Archive_Many_Small_Files(TarFormat format, TestTarFormat testFo [Theory] // V7 does not support longer filenames - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_LongPath_Splitable_Under255(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_LongPath_Splitable_Under255(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "longpath_splitable_under255"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry directory = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_Directory(directory, reader.GlobalExtendedAttributes, "00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999/"); + VerifyDirectoryEntry(directory, format, "00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999/"); TarEntry file = reader.GetNextEntry(); - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, $"00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, $"00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] // V7 does not support block devices, character devices or fifos - [InlineData(TarFormat.Ustar, TestTarFormat.ustar)] - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_SpecialFiles(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.Ustar, TestTarFormat.ustar)] + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_SpecialFiles(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "specialfiles"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); PosixTarEntry blockDevice = reader.GetNextEntry() as PosixTarEntry; - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_BlockDevice(blockDevice, reader.GlobalExtendedAttributes, AssetBlockDeviceFileName); + VerifyBlockDeviceEntry(blockDevice, format, AssetBlockDeviceFileName); PosixTarEntry characterDevice = reader.GetNextEntry() as PosixTarEntry; - Verify_Archive_CharacterDevice(characterDevice, reader.GlobalExtendedAttributes, AssetCharacterDeviceFileName); + VerifyCharacterDeviceEntry(characterDevice, format, AssetCharacterDeviceFileName); PosixTarEntry fifo = reader.GetNextEntry() as PosixTarEntry; - Verify_Archive_Fifo(fifo, reader.GlobalExtendedAttributes, "fifofile"); + VerifyFifoEntry(fifo, format, "fifofile"); Assert.Null(reader.GetNextEntry()); } [Theory] // Neither V7 not Ustar can handle links with long target filenames - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_File_LongSymbolicLink(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_File_LongSymbolicLink(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "file_longsymlink"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry directory = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_Directory(directory, reader.GlobalExtendedAttributes, "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/"); + VerifyDirectoryEntry(directory, format, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/"); TarEntry file = reader.GetNextEntry(); - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt", + $"Hello {testCaseName}"); TarEntry symbolicLink = reader.GetNextEntry(); - Verify_Archive_SymbolicLink(symbolicLink, reader.GlobalExtendedAttributes, "link.txt", "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt"); + VerifySymbolicLinkEntry(symbolicLink, format, + "link.txt", + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt"); Assert.Null(reader.GetNextEntry()); } [Theory] // Neither V7 not Ustar can handle a path that does not have separators that can be split under 100 bytes - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_LongFileName_Over100_Under255(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_LongFileName_Over100_Under255(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "longfilename_over100_under255"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry file = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444.txt", $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } [Theory] // Neither V7 not Ustar can handle path lenghts waaaay beyond name+prefix length - [InlineData(TarFormat.Pax, TestTarFormat.pax)] - [InlineData(TarFormat.Pax, TestTarFormat.pax_gea)] - [InlineData(TarFormat.Gnu, TestTarFormat.gnu)] - [InlineData(TarFormat.Gnu, TestTarFormat.oldgnu)] - public void Read_Archive_LongPath_Over255(TarFormat format, TestTarFormat testFormat) + [InlineData(TarEntryFormat.Pax, TestTarFormat.pax)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.gnu)] + [InlineData(TarEntryFormat.Gnu, TestTarFormat.oldgnu)] + public void Read_Archive_LongPath_Over255(TarEntryFormat format, TestTarFormat testFormat) { string testCaseName = "longpath_over255"; using MemoryStream ms = GetTarMemoryStream(CompressionMethod.Uncompressed, testFormat, testCaseName); using TarReader reader = new TarReader(ms); - if (testFormat == TestTarFormat.pax_gea) - { - // The GEA are collected after reading the first entry, not on the constructor - Assert.Null(reader.GlobalExtendedAttributes); - } - // Format is determined after reading the first entry, not on the constructor - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry directory = reader.GetNextEntry(); - - Assert.Equal(format, reader.Format); - if (testFormat == TestTarFormat.pax_gea) - { - Assert.NotNull(reader.GlobalExtendedAttributes); - Assert.True(reader.GlobalExtendedAttributes.Any()); - Assert.Contains(AssetPaxGeaKey, reader.GlobalExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, reader.GlobalExtendedAttributes[AssetPaxGeaKey]); - } - - Verify_Archive_Directory(directory, reader.GlobalExtendedAttributes, "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/"); + VerifyDirectoryEntry(directory, format, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/"); TarEntry file = reader.GetNextEntry(); - Verify_Archive_RegularFile(file, format, reader.GlobalExtendedAttributes, "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt", $"Hello {testCaseName}"); + VerifyRegularFileEntry(file, format, + "000000000011111111112222222222333333333344444444445555555555666666666677777777778888888888999999999900000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555/00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999000000000011111111112222222222333333333344444444445.txt", + $"Hello {testCaseName}"); Assert.Null(reader.GetNextEntry()); } - - private void Verify_Archive_RegularFile(TarEntry file, TarFormat format, IReadOnlyDictionary gea, string expectedFileName, string expectedContents) - { - Assert.NotNull(file); - - Assert.True(file.Checksum > 0); - Assert.NotNull(file.DataStream); - Assert.True(file.DataStream.Length > 0); - Assert.True(file.DataStream.CanRead); - Assert.True(file.DataStream.CanSeek); - file.DataStream.Seek(0, SeekOrigin.Begin); - using (StreamReader reader = new StreamReader(file.DataStream, leaveOpen: true)) - { - string contents = reader.ReadLine(); - Assert.Equal(expectedContents, contents); - } - - TarEntryType expectedEntryType = format == TarFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; - Assert.Equal(expectedEntryType, file.EntryType); - - Assert.Equal(AssetGid, file.Gid); - Assert.Equal(file.Length, file.DataStream.Length); - Assert.Equal(DefaultLinkName, file.LinkName); - Assert.Equal(AssetMode, file.Mode); - Assert.True(file.ModificationTime > DateTimeOffset.UnixEpoch); - Assert.Equal(expectedFileName, file.Name); - Assert.Equal(AssetUid, file.Uid); - - if (file is PosixTarEntry posix) - { - Assert.Equal(DefaultDeviceMajor, posix.DeviceMajor); - Assert.Equal(DefaultDeviceMinor, posix.DeviceMinor); - Assert.Equal(AssetGName, posix.GroupName); - Assert.Equal(AssetUName, posix.UserName); - - if (posix is PaxTarEntry pax) - { - VerifyAssetExtendedAttributes(pax, gea); - } - else if (posix is GnuTarEntry gnu) - { - Assert.True(gnu.AccessTime >= DateTimeOffset.UnixEpoch); - Assert.True(gnu.ChangeTime >= DateTimeOffset.UnixEpoch); - } - } - } - - private void VerifyAssetExtendedAttributes(PaxTarEntry pax, IReadOnlyDictionary gea) - { - Assert.NotNull(pax.ExtendedAttributes); - Assert.True(pax.ExtendedAttributes.Count() >= 3); // Expect to at least collect mtime, ctime and atime - if (gea != null && gea.Any()) - { - Assert.Contains(AssetPaxGeaKey, pax.ExtendedAttributes); - Assert.Equal(AssetPaxGeaValue, pax.ExtendedAttributes[AssetPaxGeaKey]); - } - - Assert.Contains("mtime", pax.ExtendedAttributes); - Assert.Contains("atime", pax.ExtendedAttributes); - Assert.Contains("ctime", pax.ExtendedAttributes); - - Assert.True(double.TryParse(pax.ExtendedAttributes["mtime"], NumberStyles.Any, CultureInfo.InvariantCulture, out double mtimeSecondsSinceEpoch)); - Assert.True(mtimeSecondsSinceEpoch > 0); - - Assert.True(double.TryParse(pax.ExtendedAttributes["atime"], NumberStyles.Any, CultureInfo.InvariantCulture, out double atimeSecondsSinceEpoch)); - Assert.True(atimeSecondsSinceEpoch > 0); - - Assert.True(double.TryParse(pax.ExtendedAttributes["ctime"], NumberStyles.Any, CultureInfo.InvariantCulture, out double ctimeSecondsSinceEpoch)); - Assert.True(ctimeSecondsSinceEpoch > 0); - } - - private void Verify_Archive_SymbolicLink(TarEntry symbolicLink, IReadOnlyDictionary gea, string expectedFileName, string expectedTargetName) - { - Assert.NotNull(symbolicLink); - - Assert.True(symbolicLink.Checksum > 0); - Assert.Null(symbolicLink.DataStream); - - Assert.Equal(TarEntryType.SymbolicLink, symbolicLink.EntryType); - - Assert.Equal(AssetGid, symbolicLink.Gid); - Assert.Equal(0, symbolicLink.Length); - Assert.Equal(expectedTargetName, symbolicLink.LinkName); - Assert.Equal(AssetSymbolicLinkMode, symbolicLink.Mode); - Assert.True(symbolicLink.ModificationTime > DateTimeOffset.UnixEpoch); - Assert.Equal(expectedFileName, symbolicLink.Name); - Assert.Equal(AssetUid, symbolicLink.Uid); - - if (symbolicLink is PosixTarEntry posix) - { - Assert.Equal(DefaultDeviceMajor, posix.DeviceMajor); - Assert.Equal(DefaultDeviceMinor, posix.DeviceMinor); - Assert.Equal(AssetGName, posix.GroupName); - Assert.Equal(AssetUName, posix.UserName); - } - - if (symbolicLink is PaxTarEntry pax) - { - // TODO: Check ext attrs https://github.com/dotnet/runtime/issues/68230 - } - else if (symbolicLink is GnuTarEntry gnu) - { - Assert.True(gnu.AccessTime >= DateTimeOffset.UnixEpoch); - Assert.True(gnu.ChangeTime >= DateTimeOffset.UnixEpoch); - } - } - - private void Verify_Archive_Directory(TarEntry directory, IReadOnlyDictionary gea, string expectedFileName) - { - Assert.NotNull(directory); - - Assert.True(directory.Checksum > 0); - Assert.Null(directory.DataStream); - - Assert.Equal(TarEntryType.Directory, directory.EntryType); - - Assert.Equal(AssetGid, directory.Gid); - Assert.Equal(0, directory.Length); - Assert.Equal(DefaultLinkName, directory.LinkName); - Assert.Equal(AssetMode, directory.Mode); - Assert.True(directory.ModificationTime > DateTimeOffset.UnixEpoch); - Assert.Equal(expectedFileName, directory.Name); - Assert.Equal(AssetUid, directory.Uid); - - if (directory is PosixTarEntry posix) - { - Assert.Equal(DefaultDeviceMajor, posix.DeviceMajor); - Assert.Equal(DefaultDeviceMinor, posix.DeviceMinor); - Assert.Equal(AssetGName, posix.GroupName); - Assert.Equal(AssetUName, posix.UserName); - } - - if (directory is PaxTarEntry pax) - { - // TODO: Check ext attrs https://github.com/dotnet/runtime/issues/68230 - } - else if (directory is GnuTarEntry gnu) - { - Assert.True(gnu.AccessTime >= DateTimeOffset.UnixEpoch); - Assert.True(gnu.ChangeTime >= DateTimeOffset.UnixEpoch); - } - } - - private void Verify_Archive_BlockDevice(PosixTarEntry blockDevice, IReadOnlyDictionary gea, string expectedFileName) - { - Assert.NotNull(blockDevice); - Assert.Equal(TarEntryType.BlockDevice, blockDevice.EntryType); - - Assert.True(blockDevice.Checksum > 0); - Assert.Null(blockDevice.DataStream); - - Assert.Equal(AssetGid, blockDevice.Gid); - Assert.Equal(0, blockDevice.Length); - Assert.Equal(DefaultLinkName, blockDevice.LinkName); - Assert.Equal(AssetSpecialFileMode, blockDevice.Mode); - Assert.True(blockDevice.ModificationTime > DateTimeOffset.UnixEpoch); - Assert.Equal(expectedFileName, blockDevice.Name); - Assert.Equal(AssetUid, blockDevice.Uid); - Assert.Equal(AssetBlockDeviceMajor, blockDevice.DeviceMajor); - Assert.Equal(AssetBlockDeviceMinor, blockDevice.DeviceMinor); - Assert.Equal(AssetGName, blockDevice.GroupName); - Assert.Equal(AssetUName, blockDevice.UserName); - - if (blockDevice is PaxTarEntry pax) - { - // TODO: Check ext attrs https://github.com/dotnet/runtime/issues/68230 - } - else if (blockDevice is GnuTarEntry gnu) - { - Assert.True(gnu.AccessTime >= DateTimeOffset.UnixEpoch); - Assert.True(gnu.ChangeTime >= DateTimeOffset.UnixEpoch); - } - } - - private void Verify_Archive_CharacterDevice(PosixTarEntry characterDevice, IReadOnlyDictionary gea, string expectedFileName) - { - Assert.NotNull(characterDevice); - Assert.Equal(TarEntryType.CharacterDevice, characterDevice.EntryType); - - Assert.True(characterDevice.Checksum > 0); - Assert.Null(characterDevice.DataStream); - - Assert.Equal(AssetGid, characterDevice.Gid); - Assert.Equal(0, characterDevice.Length); - Assert.Equal(DefaultLinkName, characterDevice.LinkName); - Assert.Equal(AssetSpecialFileMode, characterDevice.Mode); - Assert.True(characterDevice.ModificationTime > DateTimeOffset.UnixEpoch); - Assert.Equal(expectedFileName, characterDevice.Name); - Assert.Equal(AssetUid, characterDevice.Uid); - Assert.Equal(AssetCharacterDeviceMajor, characterDevice.DeviceMajor); - Assert.Equal(AssetCharacterDeviceMinor, characterDevice.DeviceMinor); - Assert.Equal(AssetGName, characterDevice.GroupName); - Assert.Equal(AssetUName, characterDevice.UserName); - - if (characterDevice is PaxTarEntry pax) - { - // TODO: Check ext attrs https://github.com/dotnet/runtime/issues/68230 - } - else if (characterDevice is GnuTarEntry gnu) - { - Assert.True(gnu.AccessTime >= DateTimeOffset.UnixEpoch); - Assert.True(gnu.ChangeTime >= DateTimeOffset.UnixEpoch); - } - } - - private void Verify_Archive_Fifo(PosixTarEntry fifo, IReadOnlyDictionary gea, string expectedFileName) - { - Assert.NotNull(fifo); - - Assert.True(fifo.Checksum > 0); - Assert.Null(fifo.DataStream); - - Assert.Equal(TarEntryType.Fifo, fifo.EntryType); - - Assert.Equal(AssetGid, fifo.Gid); - Assert.Equal(0, fifo.Length); - Assert.Equal(DefaultLinkName, fifo.LinkName); - Assert.Equal(AssetSpecialFileMode, fifo.Mode); - Assert.True(fifo.ModificationTime > DateTimeOffset.UnixEpoch); - Assert.Equal(expectedFileName, fifo.Name); - Assert.Equal(AssetUid, fifo.Uid); - - Assert.Equal(DefaultDeviceMajor, fifo.DeviceMajor); - Assert.Equal(DefaultDeviceMinor, fifo.DeviceMinor); - Assert.Equal(AssetGName, fifo.GroupName); - Assert.Equal(AssetUName, fifo.UserName); - - if (fifo is PaxTarEntry pax) - { - // TODO: Check ext attrs https://github.com/dotnet/runtime/issues/68230 - } - else if (fifo is GnuTarEntry gnu) - { - Assert.True(gnu.AccessTime >= DateTimeOffset.UnixEpoch); - Assert.True(gnu.ChangeTime >= DateTimeOffset.UnixEpoch); - } - } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs index a9eccc625c0388..bad9bf8fa179bf 100644 --- a/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarReader/TarReader.GetNextEntry.Tests.cs @@ -48,7 +48,7 @@ public void LongEndMarkers_DoNotAdvanceStream() { using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry = new UstarTarEntry(TarEntryType.Directory, "dir"); writer.WriteEntry(entry); @@ -72,7 +72,7 @@ public void GetNextEntry_CopyDataTrue_SeekableArchive() { string expectedText = "Hello world!"; MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt"); entry1.DataStream = new MemoryStream(); @@ -118,7 +118,7 @@ public void GetNextEntry_CopyDataTrue_UnseekableArchive() { string expectedText = "Hello world!"; MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt"); entry1.DataStream = new MemoryStream(); @@ -165,7 +165,7 @@ public void GetNextEntry_CopyDataTrue_UnseekableArchive() public void GetNextEntry_CopyDataFalse_UnseekableArchive_Exceptions() { MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt"); entry1.DataStream = new MemoryStream(); @@ -208,7 +208,7 @@ public void GetNextEntry_CopyDataFalse_UnseekableArchive_Exceptions() public void GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposing(bool copyData) { MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt"); entry1.DataStream = new MemoryStream(); diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs index 6b75ac5655d2d6..34f5de2ba07453 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.Tests.cs @@ -1,7 +1,6 @@ // 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.IO; using Xunit; @@ -13,7 +12,7 @@ public class TarWriter_Tests : TarTestsBase public void Constructors_NullStream() { Assert.Throws(() => new TarWriter(archiveStream: null)); - Assert.Throws(() => new TarWriter(archiveStream: null, TarFormat.V7)); + Assert.Throws(() => new TarWriter(archiveStream: null, TarEntryFormat.V7)); } [Fact] @@ -36,29 +35,26 @@ public void Constructor_Format() using MemoryStream archiveStream = new MemoryStream(); using TarWriter writerDefault = new TarWriter(archiveStream, leaveOpen: true); - Assert.Equal(TarFormat.Pax, writerDefault.Format); + Assert.Equal(TarEntryFormat.Pax, writerDefault.Format); - using TarWriter writerV7 = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true); - Assert.Equal(TarFormat.V7, writerV7.Format); + using TarWriter writerV7 = new TarWriter(archiveStream, TarEntryFormat.V7, leaveOpen: true); + Assert.Equal(TarEntryFormat.V7, writerV7.Format); - using TarWriter writerUstar = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true); - Assert.Equal(TarFormat.Ustar, writerUstar.Format); + using TarWriter writerUstar = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true); + Assert.Equal(TarEntryFormat.Ustar, writerUstar.Format); - using TarWriter writerPax = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true); - Assert.Equal(TarFormat.Pax, writerPax.Format); + using TarWriter writerPax = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true); + Assert.Equal(TarEntryFormat.Pax, writerPax.Format); - using TarWriter writerGnu = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true); - Assert.Equal(TarFormat.Gnu, writerGnu.Format); + using TarWriter writerGnu = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true); + Assert.Equal(TarEntryFormat.Gnu, writerGnu.Format); - using TarWriter writerNullGeaDefaultPax = new TarWriter(archiveStream, leaveOpen: true, globalExtendedAttributes: null); - Assert.Equal(TarFormat.Pax, writerNullGeaDefaultPax.Format); + using TarWriter writerNullGeaDefaultPax = new TarWriter(archiveStream, leaveOpen: true); + Assert.Equal(TarEntryFormat.Pax, writerNullGeaDefaultPax.Format); - using TarWriter writerValidGeaDefaultPax = new TarWriter(archiveStream, leaveOpen: true, globalExtendedAttributes: new Dictionary()); - Assert.Equal(TarFormat.Pax, writerValidGeaDefaultPax.Format); - - Assert.Throws(() => new TarWriter(archiveStream, TarFormat.Unknown)); - Assert.Throws(() => new TarWriter(archiveStream, (TarFormat)int.MinValue)); - Assert.Throws(() => new TarWriter(archiveStream, (TarFormat)int.MaxValue)); + Assert.Throws(() => new TarWriter(archiveStream, TarEntryFormat.Unknown)); + Assert.Throws(() => new TarWriter(archiveStream, (TarEntryFormat)int.MinValue)); + Assert.Throws(() => new TarWriter(archiveStream, (TarEntryFormat)int.MaxValue)); } [Fact] @@ -67,7 +63,7 @@ public void Constructors_UnwritableStream_Throws() using MemoryStream archiveStream = new MemoryStream(); using WrappedStream wrappedStream = new WrappedStream(archiveStream, canRead: true, canWrite: false, canSeek: false); Assert.Throws(() => new TarWriter(wrappedStream)); - Assert.Throws(() => new TarWriter(wrappedStream, TarFormat.V7)); + Assert.Throws(() => new TarWriter(wrappedStream, TarEntryFormat.V7)); } [Fact] @@ -83,7 +79,7 @@ public void Constructor_NoEntryInsertion_WritesNothing() public void VerifyChecksumV7() { using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, TarFormat.V7, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, TarEntryFormat.V7, leaveOpen: true)) { V7TarEntry entry = new V7TarEntry( // '\0' = 0 diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs index da5cc1117f1240..003904f3bb5051 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Gnu.Tests.cs @@ -13,20 +13,21 @@ public class TarWriter_WriteEntry_Gnu_Tests : TarTestsBase public void Write_V7RegularFileEntry_As_RegularFileEntry() { using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, archiveFormat: TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, archiveFormat: TarEntryFormat.Gnu, leaveOpen: true)) { V7TarEntry entry = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); - // Should be written as RegularFile + // Should be written in the format of the entry writer.WriteEntry(entry); } archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - GnuTarEntry entry = reader.GetNextEntry() as GnuTarEntry; + TarEntry entry = reader.GetNextEntry(); Assert.NotNull(entry); - Assert.Equal(TarEntryType.RegularFile, entry.EntryType); + Assert.Equal(TarEntryFormat.V7, entry.Format); + Assert.True(entry is V7TarEntry); Assert.Null(reader.GetNextEntry()); } @@ -36,7 +37,7 @@ public void Write_V7RegularFileEntry_As_RegularFileEntry() public void WriteRegularFile() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry regularFile = new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName); SetRegularFile(regularFile); @@ -56,7 +57,7 @@ public void WriteRegularFile() public void WriteHardLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry hardLink = new GnuTarEntry(TarEntryType.HardLink, InitialEntryName); SetHardLink(hardLink); @@ -76,7 +77,7 @@ public void WriteHardLink() public void WriteSymbolicLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry symbolicLink = new GnuTarEntry(TarEntryType.SymbolicLink, InitialEntryName); SetSymbolicLink(symbolicLink); @@ -96,7 +97,7 @@ public void WriteSymbolicLink() public void WriteDirectory() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry directory = new GnuTarEntry(TarEntryType.Directory, InitialEntryName); SetDirectory(directory); @@ -116,7 +117,7 @@ public void WriteDirectory() public void WriteCharacterDevice() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry charDevice = new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName); SetCharacterDevice(charDevice); @@ -136,7 +137,7 @@ public void WriteCharacterDevice() public void WriteBlockDevice() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry blockDevice = new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName); SetBlockDevice(blockDevice); @@ -156,7 +157,7 @@ public void WriteBlockDevice() public void WriteFifo() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry fifo = new GnuTarEntry(TarEntryType.Fifo, InitialEntryName); SetFifo(fifo); @@ -183,7 +184,7 @@ public void Write_Long_Name(TarEntryType entryType) string longName = new string('a', 101); using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); writer.WriteEntry(entry); @@ -207,7 +208,7 @@ public void Write_LongLinKName(TarEntryType entryType) string longLinkName = new string('a', 101); using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, "file.txt"); entry.LinkName = longLinkName; @@ -234,7 +235,7 @@ public void Write_LongName_And_LongLinKName(TarEntryType entryType) string longLinkName = new string('a', 101); using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Gnu, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true)) { GnuTarEntry entry = new GnuTarEntry(entryType, longName); entry.LinkName = longLinkName; diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs index a746c609d23db2..d9d4e261695371 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Pax.Tests.cs @@ -15,20 +15,21 @@ public class TarWriter_WriteEntry_Pax_Tests : TarTestsBase public void Write_V7RegularFileEntry_As_RegularFileEntry() { using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, archiveFormat: TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, archiveFormat: TarEntryFormat.Pax, leaveOpen: true)) { V7TarEntry entry = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); - // Should be written as RegularFile + // Should be written in the format of the entry writer.WriteEntry(entry); } archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - PaxTarEntry entry = reader.GetNextEntry() as PaxTarEntry; + TarEntry entry = reader.GetNextEntry(); Assert.NotNull(entry); - Assert.Equal(TarEntryType.RegularFile, entry.EntryType); + Assert.Equal(TarEntryFormat.V7, entry.Format); + Assert.True(entry is V7TarEntry); Assert.Null(reader.GetNextEntry()); } @@ -38,7 +39,7 @@ public void Write_V7RegularFileEntry_As_RegularFileEntry() public void WriteRegularFile() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); SetRegularFile(regularFile); @@ -58,7 +59,7 @@ public void WriteRegularFile() public void WriteHardLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry hardLink = new PaxTarEntry(TarEntryType.HardLink, InitialEntryName); SetHardLink(hardLink); @@ -78,7 +79,7 @@ public void WriteHardLink() public void WriteSymbolicLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry symbolicLink = new PaxTarEntry(TarEntryType.SymbolicLink, InitialEntryName); SetSymbolicLink(symbolicLink); @@ -98,7 +99,7 @@ public void WriteSymbolicLink() public void WriteDirectory() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry directory = new PaxTarEntry(TarEntryType.Directory, InitialEntryName); SetDirectory(directory); @@ -118,7 +119,7 @@ public void WriteDirectory() public void WriteCharacterDevice() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry charDevice = new PaxTarEntry(TarEntryType.CharacterDevice, InitialEntryName); SetCharacterDevice(charDevice); @@ -138,7 +139,7 @@ public void WriteCharacterDevice() public void WriteBlockDevice() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry blockDevice = new PaxTarEntry(TarEntryType.BlockDevice, InitialEntryName); SetBlockDevice(blockDevice); @@ -158,7 +159,7 @@ public void WriteBlockDevice() public void WriteFifo() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry fifo = new PaxTarEntry(TarEntryType.Fifo, InitialEntryName); SetFifo(fifo); @@ -184,7 +185,7 @@ public void WritePaxAttributes_CustomAttribute() extendedAttributes.Add(expectedKey, expectedValue); using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName, extendedAttributes); SetRegularFile(regularFile); @@ -221,7 +222,7 @@ public void WritePaxAttributes_Timestamps() extendedAttributes.Add("ctime", ConvertDateTimeOffsetToDouble(TestChangeTime).ToString("F6", CultureInfo.InvariantCulture)); using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName, extendedAttributes); SetRegularFile(regularFile); @@ -252,7 +253,7 @@ public void WritePaxAttributes_LongGroupName_LongUserName() string groupName = "IAmAGroupNameWhoseLengthIsWayBeyondTheThirtyTwoByteLimit"; using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Pax, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true)) { PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName); SetRegularFile(regularFile); diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs index a130ae87edc3cd..3820db28f04cbc 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.Ustar.Tests.cs @@ -13,20 +13,21 @@ public class TarWriter_WriteEntry_Ustar_Tests : TarTestsBase public void Write_V7RegularFileEntry_As_RegularFileEntry() { using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, archiveFormat: TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, archiveFormat: TarEntryFormat.Ustar, leaveOpen: true)) { V7TarEntry entry = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); - // Should be written as RegularFile + // Should be written in the format of the entry writer.WriteEntry(entry); } archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - UstarTarEntry entry = reader.GetNextEntry() as UstarTarEntry; + TarEntry entry = reader.GetNextEntry(); Assert.NotNull(entry); - Assert.Equal(TarEntryType.RegularFile, entry.EntryType); + Assert.Equal(TarEntryFormat.V7, entry.Format); + Assert.True(entry is V7TarEntry); Assert.Null(reader.GetNextEntry()); } @@ -36,7 +37,7 @@ public void Write_V7RegularFileEntry_As_RegularFileEntry() public void WriteRegularFile() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry regularFile = new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName); SetRegularFile(regularFile); @@ -56,7 +57,7 @@ public void WriteRegularFile() public void WriteHardLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry hardLink = new UstarTarEntry(TarEntryType.HardLink, InitialEntryName); SetHardLink(hardLink); @@ -76,7 +77,7 @@ public void WriteHardLink() public void WriteSymbolicLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry symbolicLink = new UstarTarEntry(TarEntryType.SymbolicLink, InitialEntryName); SetSymbolicLink(symbolicLink); @@ -96,7 +97,7 @@ public void WriteSymbolicLink() public void WriteDirectory() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry directory = new UstarTarEntry(TarEntryType.Directory, InitialEntryName); SetDirectory(directory); @@ -116,7 +117,7 @@ public void WriteDirectory() public void WriteCharacterDevice() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry charDevice = new UstarTarEntry(TarEntryType.CharacterDevice, InitialEntryName); SetCharacterDevice(charDevice); @@ -136,7 +137,7 @@ public void WriteCharacterDevice() public void WriteBlockDevice() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry blockDevice = new UstarTarEntry(TarEntryType.BlockDevice, InitialEntryName); SetBlockDevice(blockDevice); @@ -156,7 +157,7 @@ public void WriteBlockDevice() public void WriteFifo() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Ustar, leaveOpen: true)) { UstarTarEntry fifo = new UstarTarEntry(TarEntryType.Fifo, InitialEntryName); SetFifo(fifo); diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs index 7b74d769fa13ed..f4b9d062c175ed 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Entry.V7.Tests.cs @@ -9,50 +9,24 @@ namespace System.Formats.Tar.Tests // Tests specific to V7 format. public class TarWriter_WriteEntry_V7_Tests : TarTestsBase { - [Fact] - public void ThrowIf_WriteEntry_UnsupportedFile() - { - // Verify that entry types that can be manually constructed in other types, cannot be inserted in a v7 writer - using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, archiveFormat: TarFormat.V7, leaveOpen: true)) - { - // Entry types supported in ustar but not in v7 - Assert.Throws(() => writer.WriteEntry(new UstarTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => writer.WriteEntry(new UstarTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => writer.WriteEntry(new UstarTarEntry(TarEntryType.Fifo, InitialEntryName))); - - // Entry types supported in pax but not in v7 - Assert.Throws(() => writer.WriteEntry(new PaxTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => writer.WriteEntry(new PaxTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => writer.WriteEntry(new PaxTarEntry(TarEntryType.Fifo, InitialEntryName))); - - // Entry types supported in gnu but not in v7 - Assert.Throws(() => writer.WriteEntry(new GnuTarEntry(TarEntryType.BlockDevice, InitialEntryName))); - Assert.Throws(() => writer.WriteEntry(new GnuTarEntry(TarEntryType.CharacterDevice, InitialEntryName))); - Assert.Throws(() => writer.WriteEntry(new GnuTarEntry(TarEntryType.Fifo, InitialEntryName))); - } - // Verify nothing was written, not even the empty records - Assert.Equal(0, archiveStream.Length); - } - [Theory] - [InlineData(TarFormat.Ustar)] - [InlineData(TarFormat.Pax)] - [InlineData(TarFormat.Gnu)] - public void Write_RegularFileEntry_As_V7RegularFileEntry(TarFormat entryFormat) + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void Write_RegularFileEntry_As_V7RegularFileEntry(TarEntryFormat entryFormat) { using MemoryStream archive = new MemoryStream(); - using (TarWriter writer = new TarWriter(archive, archiveFormat: TarFormat.V7, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, archiveFormat: TarEntryFormat.V7, leaveOpen: true)) { TarEntry entry = entryFormat switch { - TarFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), - TarFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), - TarFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), - _ => throw new FormatException() + TarEntryFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName), + TarEntryFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName), + TarEntryFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName), + _ => throw new FormatException($"Unexpected format: {entryFormat}") }; - // Should be written as V7RegularFile + // Should be written in the format of the entry writer.WriteEntry(entry); } @@ -60,8 +34,21 @@ public void Write_RegularFileEntry_As_V7RegularFileEntry(TarFormat entryFormat) using (TarReader reader = new TarReader(archive)) { TarEntry entry = reader.GetNextEntry(); - Assert.True(entry is V7TarEntry); - Assert.Equal(TarEntryType.V7RegularFile, entry.EntryType); + Assert.NotNull(entry); + Assert.Equal(entryFormat, entry.Format); + + switch (entryFormat) + { + case TarEntryFormat.Ustar: + Assert.True(entry is UstarTarEntry); + break; + case TarEntryFormat.Pax: + Assert.True(entry is PaxTarEntry); + break; + case TarEntryFormat.Gnu: + Assert.True(entry is GnuTarEntry); + break; + } Assert.Null(reader.GetNextEntry()); } @@ -72,7 +59,7 @@ public void Write_RegularFileEntry_As_V7RegularFileEntry(TarFormat entryFormat) public void WriteRegularFile() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.V7, leaveOpen: true)) { V7TarEntry oldRegularFile = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName); SetRegularFile(oldRegularFile); @@ -92,7 +79,7 @@ public void WriteRegularFile() public void WriteHardLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.V7, leaveOpen: true)) { V7TarEntry hardLink = new V7TarEntry(TarEntryType.HardLink, InitialEntryName); SetHardLink(hardLink); @@ -112,7 +99,7 @@ public void WriteHardLink() public void WriteSymbolicLink() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.V7, leaveOpen: true)) { V7TarEntry symbolicLink = new V7TarEntry(TarEntryType.SymbolicLink, InitialEntryName); SetSymbolicLink(symbolicLink); @@ -132,7 +119,7 @@ public void WriteSymbolicLink() public void WriteDirectory() { using MemoryStream archiveStream = new MemoryStream(); - using (TarWriter writer = new TarWriter(archiveStream, TarFormat.V7, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.V7, leaveOpen: true)) { V7TarEntry directory = new V7TarEntry(TarEntryType.Directory, InitialEntryName); SetDirectory(directory); diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs index 95239178941661..bf8a11bb8184ba 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.Unix.cs @@ -12,14 +12,14 @@ public partial class TarWriter_WriteEntry_File_Tests : TarTestsBase private static bool IsRemoteExecutorSupportedAndOnUnixAndSuperUser => RemoteExecutor.IsSupported && PlatformDetection.IsUnixAndSuperUser; [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] - [InlineData(TarFormat.Ustar)] - [InlineData(TarFormat.Pax)] - [InlineData(TarFormat.Gnu)] - public void Add_Fifo(TarFormat format) + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void Add_Fifo(TarEntryFormat format) { RemoteExecutor.Invoke((string strFormat) => { - TarFormat expectedFormat = Enum.Parse(strFormat); + TarEntryFormat expectedFormat = Enum.Parse(strFormat); using TempDirectory root = new TempDirectory(); string fifoName = "fifofile"; @@ -36,9 +36,8 @@ public void Add_Fifo(TarFormat format) archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - Assert.Equal(TarFormat.Unknown, reader.Format); PosixTarEntry entry = reader.GetNextEntry() as PosixTarEntry; - Assert.Equal(expectedFormat, reader.Format); + Assert.Equal(expectedFormat, entry.Format); Assert.NotNull(entry); Assert.Equal(fifoName, entry.Name); @@ -55,14 +54,14 @@ public void Add_Fifo(TarFormat format) } [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] - [InlineData(TarFormat.Ustar)] - [InlineData(TarFormat.Pax)] - [InlineData(TarFormat.Gnu)] - public void Add_BlockDevice(TarFormat format) + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void Add_BlockDevice(TarEntryFormat format) { RemoteExecutor.Invoke((string strFormat) => { - TarFormat expectedFormat = Enum.Parse(strFormat); + TarEntryFormat expectedFormat = Enum.Parse(strFormat); using TempDirectory root = new TempDirectory(); string blockDevicePath = Path.Join(root.Path, AssetBlockDeviceFileName); @@ -79,9 +78,8 @@ public void Add_BlockDevice(TarFormat format) archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - Assert.Equal(TarFormat.Unknown, reader.Format); PosixTarEntry entry = reader.GetNextEntry() as PosixTarEntry; - Assert.Equal(expectedFormat, reader.Format); + Assert.Equal(expectedFormat, entry.Format); Assert.NotNull(entry); Assert.Equal(AssetBlockDeviceFileName, entry.Name); @@ -101,14 +99,14 @@ public void Add_BlockDevice(TarFormat format) } [ConditionalTheory(nameof(IsRemoteExecutorSupportedAndOnUnixAndSuperUser))] - [InlineData(TarFormat.Ustar)] - [InlineData(TarFormat.Pax)] - [InlineData(TarFormat.Gnu)] - public void Add_CharacterDevice(TarFormat format) + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void Add_CharacterDevice(TarEntryFormat format) { RemoteExecutor.Invoke((string strFormat) => { - TarFormat expectedFormat = Enum.Parse(strFormat); + TarEntryFormat expectedFormat = Enum.Parse(strFormat); using TempDirectory root = new TempDirectory(); string characterDevicePath = Path.Join(root.Path, AssetCharacterDeviceFileName); @@ -124,9 +122,8 @@ public void Add_CharacterDevice(TarFormat format) archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - Assert.Equal(TarFormat.Unknown, reader.Format); PosixTarEntry entry = reader.GetNextEntry() as PosixTarEntry; - Assert.Equal(expectedFormat, reader.Format); + Assert.Equal(expectedFormat, entry.Format); Assert.NotNull(entry); Assert.Equal(AssetCharacterDeviceFileName, entry.Name); diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs index 52d34232c4da7e..837f1aa8249a07 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.File.Tests.cs @@ -67,11 +67,11 @@ public void EntryName_NullOrEmpty() [ActiveIssue("https://github.com/dotnet/runtime/issues/69474", TestPlatforms.Linux)] [Theory] - [InlineData(TarFormat.V7)] - [InlineData(TarFormat.Ustar)] - [InlineData(TarFormat.Pax)] - [InlineData(TarFormat.Gnu)] - public void Add_File(TarFormat format) + [InlineData(TarEntryFormat.V7)] + [InlineData(TarEntryFormat.Ustar)] + [InlineData(TarEntryFormat.Pax)] + [InlineData(TarEntryFormat.Gnu)] + public void Add_File(TarEntryFormat format) { using TempDirectory root = new TempDirectory(); string fileName = "file.txt"; @@ -92,12 +92,11 @@ public void Add_File(TarFormat format) archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry entry = reader.GetNextEntry(); Assert.NotNull(entry); - Assert.Equal(format, reader.Format); + Assert.Equal(format, entry.Format); Assert.Equal(fileName, entry.Name); - TarEntryType expectedEntryType = format is TarFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; + TarEntryType expectedEntryType = format is TarEntryFormat.V7 ? TarEntryType.V7RegularFile : TarEntryType.RegularFile; Assert.Equal(expectedEntryType, entry.EntryType); Assert.True(entry.Length > 0); Assert.NotNull(entry.DataStream); @@ -115,15 +114,15 @@ public void Add_File(TarFormat format) } [Theory] - [InlineData(TarFormat.V7, false)] - [InlineData(TarFormat.V7, true)] - [InlineData(TarFormat.Ustar, false)] - [InlineData(TarFormat.Ustar, true)] - [InlineData(TarFormat.Pax, false)] - [InlineData(TarFormat.Pax, true)] - [InlineData(TarFormat.Gnu, false)] - [InlineData(TarFormat.Gnu, true)] - public void Add_Directory(TarFormat format, bool withContents) + [InlineData(TarEntryFormat.V7, false)] + [InlineData(TarEntryFormat.V7, true)] + [InlineData(TarEntryFormat.Ustar, false)] + [InlineData(TarEntryFormat.Ustar, true)] + [InlineData(TarEntryFormat.Pax, false)] + [InlineData(TarEntryFormat.Pax, true)] + [InlineData(TarEntryFormat.Gnu, false)] + [InlineData(TarEntryFormat.Gnu, true)] + public void Add_Directory(TarEntryFormat format, bool withContents) { using TempDirectory root = new TempDirectory(); string dirName = "dir"; @@ -146,9 +145,8 @@ public void Add_Directory(TarFormat format, bool withContents) archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry entry = reader.GetNextEntry(); - Assert.Equal(format, reader.Format); + Assert.Equal(format, entry.Format); Assert.NotNull(entry); Assert.Equal(dirName, entry.Name); @@ -162,15 +160,15 @@ public void Add_Directory(TarFormat format, bool withContents) } [ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] - [InlineData(TarFormat.V7, false)] - [InlineData(TarFormat.V7, true)] - [InlineData(TarFormat.Ustar, false)] - [InlineData(TarFormat.Ustar, true)] - [InlineData(TarFormat.Pax, false)] - [InlineData(TarFormat.Pax, true)] - [InlineData(TarFormat.Gnu, false)] - [InlineData(TarFormat.Gnu, true)] - public void Add_SymbolicLink(TarFormat format, bool createTarget) + [InlineData(TarEntryFormat.V7, false)] + [InlineData(TarEntryFormat.V7, true)] + [InlineData(TarEntryFormat.Ustar, false)] + [InlineData(TarEntryFormat.Ustar, true)] + [InlineData(TarEntryFormat.Pax, false)] + [InlineData(TarEntryFormat.Pax, true)] + [InlineData(TarEntryFormat.Gnu, false)] + [InlineData(TarEntryFormat.Gnu, true)] + public void Add_SymbolicLink(TarEntryFormat format, bool createTarget) { using TempDirectory root = new TempDirectory(); string targetName = "file.txt"; @@ -195,9 +193,8 @@ public void Add_SymbolicLink(TarFormat format, bool createTarget) archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - Assert.Equal(TarFormat.Unknown, reader.Format); TarEntry entry = reader.GetNextEntry(); - Assert.Equal(format, reader.Format); + Assert.Equal(format, entry.Format); Assert.NotNull(entry); Assert.Equal(linkName, entry.Name); @@ -218,35 +215,35 @@ public void Add_PaxGlobalExtendedAttributes_NoEntries(bool withAttributes) { using MemoryStream archive = new MemoryStream(); - Dictionary globalExtendedAttributes = new Dictionary(); + Dictionary attrs = new Dictionary(); if (withAttributes) { - globalExtendedAttributes.Add("hello", "world"); + attrs.Add("hello", "world"); } - using (TarWriter writer = new TarWriter(archive, globalExtendedAttributes, leaveOpen: true)) + using (TarWriter writer = new TarWriter(archive, leaveOpen: true)) { - } // Dispose with no entries + PaxGlobalExtendedAttributesTarEntry gea = new PaxGlobalExtendedAttributesTarEntry(attrs); + writer.WriteEntry(gea); + } archive.Seek(0, SeekOrigin.Begin); using (TarReader reader = new TarReader(archive)) { - // Unknown until reading first entry - Assert.Equal(TarFormat.Unknown, reader.Format); - Assert.Null(reader.GlobalExtendedAttributes); - - Assert.Null(reader.GetNextEntry()); - - Assert.Equal(TarFormat.Pax, reader.Format); - Assert.NotNull(reader.GlobalExtendedAttributes); + TarEntry entry = reader.GetNextEntry(); + Assert.Equal(TarEntryFormat.Pax, entry.Format); + Assert.Equal(TarEntryType.GlobalExtendedAttributes, entry.EntryType); + PaxGlobalExtendedAttributesTarEntry gea = entry as PaxGlobalExtendedAttributesTarEntry; + Assert.NotNull(gea); + Assert.Equal(TarEntryFormat.Pax, gea.Format); int expectedCount = withAttributes ? 1 : 0; - Assert.Equal(expectedCount, reader.GlobalExtendedAttributes.Count); + Assert.Equal(expectedCount, gea.GlobalExtendedAttributes.Count); if (expectedCount > 0) { - Assert.Equal("world", reader.GlobalExtendedAttributes["hello"]); + Assert.Equal("world", gea.GlobalExtendedAttributes["hello"]); } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs index c68690c0a7fa4e..2722095996aa4c 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.WriteEntry.Tests.cs @@ -35,7 +35,7 @@ public void WriteEntry_FromUnseekableStream_AdvanceDataStream_WriteFromThatPosit Assert.NotNull(entry.DataStream); entry.DataStream.ReadByte(); // Advance one byte, now the expected string would be "ello file" - using (TarWriter writer = new TarWriter(destination, TarFormat.Ustar, leaveOpen: true)) + using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Ustar, leaveOpen: true)) { writer.WriteEntry(entry); }