Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add TarEntry conversion constructors #70325

Merged
merged 47 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ac4d898
ref: Conversion constructors
carlossanlop Jun 1, 2022
9899220
src: Conversion constructors
carlossanlop Jun 7, 2022
476bec1
tests: Conversion constructors
carlossanlop Jun 7, 2022
4484202
src: Conversion constructor should call this instead of base
carlossanlop Jun 7, 2022
45d87a0
tests: Verify unseekable stream data can be read after conversion
carlossanlop Jun 7, 2022
adf074e
Adjust _readerOfOrigin nullability in constructors.
carlossanlop Jun 7, 2022
c9002d1
Move most constructor work to the base class.
carlossanlop Jun 7, 2022
42a000d
ref: Move TarFormat from TarReader to TarEntry
carlossanlop Jun 7, 2022
ac7ff62
ref: Move Format property from TarReader to TarEntry
carlossanlop Jun 7, 2022
a0a2d88
src: Move Format property from TarReader to TarEntry
carlossanlop Jun 7, 2022
0a54727
tests: Move Format property from TarReader to TarEntry
carlossanlop Jun 7, 2022
6de8f40
nit - Remove extra spacing in tests
carlossanlop Jun 7, 2022
b6f75cb
src: When converting, only transfer fields available in the new format.
carlossanlop Jun 9, 2022
2daa87e
tests: Verify only supported fields are transferred when converting.
carlossanlop Jun 9, 2022
247af82
Revert internal->private for magic and version
carlossanlop Jun 9, 2022
270403c
Add boolean arg to PaxTarEntry method that adds atime and ctime
carlossanlop Jun 9, 2022
d405d4e
Make sure back and forth tests create firstt timestamp with a slight …
carlossanlop Jun 9, 2022
7e79e57
Make _readerOfOrigin private again
carlossanlop Jun 9, 2022
ba4d394
src: Use UtcNow, not Now
carlossanlop Jun 9, 2022
e37c645
tests: Use UtcNow, not Now. Compare converted DateTimeOffset from dou…
carlossanlop Jun 9, 2022
f987f77
Address feedback for comments.
carlossanlop Jun 11, 2022
cd66da4
Add asserts that verify mtime has a non-default value when it is expe…
carlossanlop Jun 11, 2022
c61ce71
Adress feedback for setting field values in PosixTarEntry and TarEntr…
carlossanlop Jun 11, 2022
0a39b22
Remove code that reads atime and ctime from extended attributes and s…
carlossanlop Jun 11, 2022
948b3d7
Make sure that extended attributes always have an atime and a ctime w…
carlossanlop Jun 11, 2022
131608f
Address feedback around timestamps in tests.
carlossanlop Jun 11, 2022
1b82298
Add more tests for expected values in extended attributes after writi…
carlossanlop Jun 11, 2022
ed5ec9a
Reuse code for conversion tests.
carlossanlop Jun 13, 2022
c82f766
Change test comparisons of Assert.True with ">=" to use AssertExtensi…
carlossanlop Jun 13, 2022
98e6191
Reduce number of dictionary newing, only do it when the contents are …
carlossanlop Jun 13, 2022
b7c1c83
Avoid returning dateTimeOffset != default, instead return true on suc…
carlossanlop Jun 13, 2022
83888ba
Remove redundant assignment of format to header.
carlossanlop Jun 13, 2022
e958afd
Pass Dictionary<string,string> directly to make use of non-virtual Tr…
carlossanlop Jun 13, 2022
81376fe
Add tests that verify the default writer format is utilized when call…
carlossanlop Jun 13, 2022
b865bf4
Add overload that takes long for converting number of seconds to Date…
carlossanlop Jun 13, 2022
c37e9e7
Address latest suggestions by eerhardt
carlossanlop Jun 15, 2022
526b55a
Make sure dictionary is initialized once in PaxTarEntry constructor.
carlossanlop Jun 16, 2022
2b3dff3
Reuse code for single conversion construction tests and back-n-forth …
carlossanlop Jun 16, 2022
f10d0bf
Invert verification of default case and TarEntryFormat.Unknown in swi…
carlossanlop Jun 20, 2022
a363c3f
Change "if dictionary is not null" to "Debug.Assert "dictionary is no…
carlossanlop Jun 20, 2022
cedf1a2
Conversion tests should also check expected values of ATime and CTime…
carlossanlop Jun 20, 2022
30261f3
Chop long comment, remove another 'preferred default'.
carlossanlop Jun 20, 2022
f23d244
Make unseekable conversion tests theories to verify all writer formats.
carlossanlop Jun 20, 2022
4e3e4a3
Set _prefix to Empty if other is not ustar or pax
carlossanlop Jun 20, 2022
2d503fc
Rename TarWriter archiveFormat parameter to format
carlossanlop Jun 20, 2022
411aefa
Compare datetime offsets only up to seconds
carlossanlop Jun 20, 2022
767a5e8
Increase fixed point format specifier precision, use test comparison …
carlossanlop Jun 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ 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 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<System.Collections.Generic.KeyValuePair<string, string>> extendedAttributes) { }
public System.Collections.Generic.IReadOnlyDictionary<string, string> ExtendedAttributes { get { throw null; } }
Expand All @@ -32,6 +34,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 { } }
Expand Down Expand Up @@ -98,26 +101,28 @@ public enum TarFileMode
public sealed partial class TarReader : System.IDisposable
{
public TarReader(System.IO.Stream archiveStream, bool leaveOpen = false) { }
public System.Formats.Tar.TarEntryFormat Format { get { throw null; } }
public System.Collections.Generic.IReadOnlyDictionary<string, string>? 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) { }
public TarWriter(System.IO.Stream archiveStream, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string>>? globalExtendedAttributes = null, bool leaveOpen = false) { }
public TarWriter(System.IO.Stream archiveStream, System.Formats.Tar.TarEntryFormat archiveFormat, bool leaveOpen = false) { }
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) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ namespace System.Formats.Tar
/// <remarks>Even though the <see cref="TarEntryFormat.Gnu"/> 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: <c>devmajor</c>, <c>devminor</c>, <c>gname</c> and <c>uname</c>.</remarks>
public sealed class GnuTarEntry : PosixTarEntry
{
// Constructor used when reading an existing archive.
// Constructor called when reading a TarEntry from a TarReader.
internal GnuTarEntry(TarHeader header, TarReader readerOfOrigin)
: base(header, readerOfOrigin)
: base(header, readerOfOrigin, TarEntryFormat.Gnu)
{
}

Expand All @@ -31,6 +31,57 @@ internal GnuTarEntry(TarHeader header, TarReader readerOfOrigin)
public GnuTarEntry(TarEntryType entryType, string entryName)
: base(entryType, entryName, TarEntryFormat.Gnu)
{
_header._aTime = _header._mTime; // mtime was set in base constructor
_header._cTime = _header._mTime;
}

/// <summary>
/// Initializes a new <see cref="GnuTarEntry"/> instance by converting the specified <paramref name="other"/> entry into the GNU format.
/// </summary>
public GnuTarEntry(TarEntry other)
: base(other, TarEntryFormat.Gnu)
{
if (other is GnuTarEntry gnuOther)
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved
{
_header._aTime = gnuOther.AccessTime;
_header._cTime = gnuOther.ChangeTime;
_header._gnuUnusedBytes = other._header._gnuUnusedBytes;
}
else
{
bool changedATime = false;
bool changedCTime = false;

if (other is PaxTarEntry paxOther)
{
changedATime = TarHelpers.TryGetDateTimeOffsetFromTimestampString(paxOther._header._extendedAttributes, TarHeader.PaxEaATime, out DateTimeOffset aTime);
if (changedATime)
{
_header._aTime = aTime;
}

changedCTime = TarHelpers.TryGetDateTimeOffsetFromTimestampString(paxOther._header._extendedAttributes, TarHeader.PaxEaCTime, out DateTimeOffset cTime);
if (changedCTime)
{
_header._cTime = cTime;
}
}

// Either 'other' was V7 or Ustar (those formats do not have atime or ctime),
// or 'other' was PAX and at least one of the timestamps was not found in the extended attributes
if (!changedATime || !changedCTime)
{
DateTimeOffset now = DateTimeOffset.UtcNow;
if (!changedATime)
{
_header._aTime = now;
}
if (!changedCTime)
{
_header._cTime = now;
}
}
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ public sealed class PaxTarEntry : PosixTarEntry
{
private ReadOnlyDictionary<string, string>? _readOnlyExtendedAttributes;

// Constructor used when reading an existing archive.
// Constructor called when reading a TarEntry from a TarReader.
internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin)
: base(header, readerOfOrigin)
: base(header, readerOfOrigin, TarEntryFormat.Pax)
{
_header._extendedAttributes ??= new Dictionary<string, string>();
_readOnlyExtendedAttributes = null;
}

/// <summary>
Expand Down Expand Up @@ -52,6 +50,11 @@ internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin)
public PaxTarEntry(TarEntryType entryType, string entryName)
: base(entryType, entryName, TarEntryFormat.Pax)
{
_header._prefix = string.Empty;
_header._extendedAttributes = new Dictionary<string, string>();

Debug.Assert(_header._mTime != default);
AddNewAccessAndChangeTimestampsIfNotExist(useMTime: true);
}

/// <summary>
Expand Down Expand Up @@ -87,7 +90,40 @@ public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable<KeyValu
: base(entryType, entryName, TarEntryFormat.Pax)
{
ArgumentNullException.ThrowIfNull(extendedAttributes);
_header.ReplaceNormalAttributesWithExtended(extendedAttributes);

_header._prefix = string.Empty;
_header._extendedAttributes = new Dictionary<string, string>(extendedAttributes);

Debug.Assert(_header._mTime != default);
AddNewAccessAndChangeTimestampsIfNotExist(useMTime: true);
}

/// <summary>
/// Initializes a new <see cref="PaxTarEntry"/> instance by converting the specified <paramref name="other"/> entry into the PAX format.
/// </summary>
public PaxTarEntry(TarEntry other)
: base(other, TarEntryFormat.Pax)
{
if (other._header._format is TarEntryFormat.Ustar or TarEntryFormat.Pax)
{
_header._prefix = other._header._prefix;
}

if (other is PaxTarEntry paxOther)
{
_header._extendedAttributes = new Dictionary<string, string>(paxOther.ExtendedAttributes);
}
else
{
_header._extendedAttributes = new Dictionary<string, string>();
if (other is GnuTarEntry gnuOther)
{
_header._extendedAttributes[TarHeader.PaxEaATime] = TarHelpers.GetTimestampStringFromDateTimeOffset(gnuOther.AccessTime);
_header._extendedAttributes[TarHeader.PaxEaCTime] = TarHelpers.GetTimestampStringFromDateTimeOffset(gnuOther.ChangeTime);
}
}

AddNewAccessAndChangeTimestampsIfNotExist(useMTime: false);
}
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
Expand All @@ -112,12 +148,38 @@ public IReadOnlyDictionary<string, string> ExtendedAttributes
{
get
{
Debug.Assert(_header._extendedAttributes != null);
_header._extendedAttributes ??= new Dictionary<string, string>();
return _readOnlyExtendedAttributes ??= _header._extendedAttributes.AsReadOnly();
}
}

// Determines if the current instance's entry type supports setting a data stream.
internal override bool IsDataStreamSetterSupported() => EntryType == TarEntryType.RegularFile;

// Checks if the extended attributes dictionary contains 'atime' and 'ctime'.
// If any of them is not found, it is added with the value of either the current entry's 'mtime',
// or 'DateTimeOffset.UtcNow', depending on the value of 'useMTime'.
private void AddNewAccessAndChangeTimestampsIfNotExist(bool useMTime)
{
Debug.Assert(!useMTime || (useMTime && _header._mTime != default));
Debug.Assert(_header._extendedAttributes != null);
bool containsATime = _header._extendedAttributes.ContainsKey(TarHeader.PaxEaATime);
bool containsCTime = _header._extendedAttributes.ContainsKey(TarHeader.PaxEaCTime);

if (!containsATime || !containsCTime)
{
string secondsFromEpochString = TarHelpers.GetTimestampStringFromDateTimeOffset(useMTime ? _header._mTime : DateTimeOffset.UtcNow);
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved

if (!containsATime)
{
_header._extendedAttributes[TarHeader.PaxEaATime] = secondsFromEpochString;
}

if (!containsCTime)
{
_header._extendedAttributes[TarHeader.PaxEaCTime] = secondsFromEpochString;
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;

namespace System.Formats.Tar
{
/// <summary>
Expand All @@ -10,16 +12,37 @@ namespace System.Formats.Tar
/// Even though the <see cref="TarEntryFormat.Gnu"/> format is not POSIX compatible, it implements and supports the Unix-specific fields that were defined in that POSIX standard.</remarks>
public abstract partial class PosixTarEntry : TarEntry
{
// Constructor used when reading an existing archive.
internal PosixTarEntry(TarHeader header, TarReader readerOfOrigin)
: base(header, readerOfOrigin)
// Constructor called when reading a TarEntry from a TarReader.
internal PosixTarEntry(TarHeader header, TarReader readerOfOrigin, TarEntryFormat format)
: base(header, readerOfOrigin, format)
{
}

// Constructor called when creating a new 'TarEntry*' instance that can be passed to a TarWriter.
// Constructor called when the user creates a TarEntry instance from scratch.
internal PosixTarEntry(TarEntryType entryType, string entryName, TarEntryFormat format)
: base(entryType, entryName, format)
{
_header._uName = string.Empty;
_header._gName = string.Empty;
_header._devMajor = 0;
_header._devMinor = 0;
}

// Constructor called when converting an entry to the selected format.
internal PosixTarEntry(TarEntry other, TarEntryFormat format)
: base(other, format)
{
if (other is PosixTarEntry)
{
Debug.Assert(other._header._uName != null);
Debug.Assert(other._header._gName != null);
_header._uName = other._header._uName;
_header._gName = other._header._gName;
_header._devMajor = other._header._devMajor;
_header._devMinor = other._header._devMinor;
}
_header._uName ??= string.Empty;
_header._gName ??= string.Empty;
}

/// <summary>
Expand Down
72 changes: 53 additions & 19 deletions src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs
Original file line number Diff line number Diff line change
@@ -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.Diagnostics;
using System.IO;
using Microsoft.Win32.SafeHandles;
Expand All @@ -15,42 +14,72 @@ namespace System.Formats.Tar
public abstract partial class TarEntry
{
internal TarHeader _header;

// Used to access the data section of this entry in an unseekable file
private TarReader? _readerOfOrigin;

// Constructor used when reading an existing archive.
internal TarEntry(TarHeader header, TarReader readerOfOrigin)
// Constructor called when reading a TarEntry from a TarReader.
internal TarEntry(TarHeader header, TarReader readerOfOrigin, TarEntryFormat format)
{
// This constructor is called after reading a header from the archive,
// and we should've already detected the format of the header.
Debug.Assert(header._format == format);
_header = header;
_readerOfOrigin = readerOfOrigin;
}

// Constructor called when creating a new 'TarEntry*' instance that can be passed to a TarWriter.
// Constructor called when the user creates a TarEntry instance from scratch.
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;
TarHelpers.ThrowIfEntryTypeNotSupported(entryType, format);

_header = default;
_header._format = format;

_header._extendedAttributes = new Dictionary<string, string>();

// Default values for fields shared by all supported formats
_header._name = entryName;
_header._linkName = string.Empty;
_header._typeFlag = entryType;
_header._mode = (int)TarHelpers.DefaultMode;
_header._mTime = DateTimeOffset.UtcNow;
_header._typeFlag = entryType;
_header._linkName = string.Empty;
}

_header._gName = string.Empty;
_header._uName = string.Empty;
// Constructor called when converting an entry to the selected format.
internal TarEntry(TarEntry other, TarEntryFormat format)
{
TarEntryType compatibleEntryType;
if (other.Format is TarEntryFormat.V7 && other.EntryType is TarEntryType.V7RegularFile && format is TarEntryFormat.Ustar or TarEntryFormat.Pax or TarEntryFormat.Gnu)
{
compatibleEntryType = TarEntryType.RegularFile;
}
else if (other.Format is TarEntryFormat.Ustar or TarEntryFormat.Pax or TarEntryFormat.Gnu && other.EntryType is TarEntryType.RegularFile && format is TarEntryFormat.V7)
{
compatibleEntryType = TarEntryType.V7RegularFile;
}
else
{
compatibleEntryType = other.EntryType;
}

TarHelpers.ThrowIfEntryTypeNotSupported(compatibleEntryType, format);

DateTimeOffset now = DateTimeOffset.Now;
_header._mTime = now;
_header._aTime = now;
_header._cTime = now;
_readerOfOrigin = other._readerOfOrigin;

_header = default;
_header._format = format;

_header._name = other._header._name;
_header._mode = other._header._mode;
_header._uid = other._header._uid;
_header._gid = other._header._gid;
_header._size = other._header._size;
_header._mTime = other._header._mTime;
_header._checksum = 0;
_header._typeFlag = compatibleEntryType;
_header._linkName = other._header._linkName;

_header._dataStream = other._header._dataStream;
}

/// <summary>
Expand All @@ -63,6 +92,11 @@ internal TarEntry(TarEntryType entryType, string entryName, TarEntryFormat forma
/// </summary>
public TarEntryType EntryType => _header._typeFlag;

/// <summary>
/// The format of the entry.
/// </summary>
public TarEntryFormat Format => _header._format;

/// <summary>
/// The ID of the group that owns the file represented by this entry.
/// </summary>
Expand Down
Loading