Skip to content

Commit 943da70

Browse files
authored
Add TarEntry conversion constructors (#70325)
* ref: Conversion constructors * src: Conversion constructors * tests: Conversion constructors
1 parent 715c71a commit 943da70

35 files changed

+2038
-782
lines changed

src/libraries/System.Formats.Tar/ref/System.Formats.Tar.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ namespace System.Formats.Tar
88
{
99
public sealed partial class GnuTarEntry : System.Formats.Tar.PosixTarEntry
1010
{
11+
public GnuTarEntry(System.Formats.Tar.TarEntry other) { }
1112
public GnuTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { }
1213
public System.DateTimeOffset AccessTime { get { throw null; } set { } }
1314
public System.DateTimeOffset ChangeTime { get { throw null; } set { } }
1415
}
1516
public sealed partial class PaxTarEntry : System.Formats.Tar.PosixTarEntry
1617
{
18+
public PaxTarEntry(System.Formats.Tar.TarEntry other) { }
1719
public PaxTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { }
1820
public PaxTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string>> extendedAttributes) { }
1921
public System.Collections.Generic.IReadOnlyDictionary<string, string> ExtendedAttributes { get { throw null; } }
@@ -32,6 +34,7 @@ internal TarEntry() { }
3234
public int Checksum { get { throw null; } }
3335
public System.IO.Stream? DataStream { get { throw null; } set { } }
3436
public System.Formats.Tar.TarEntryType EntryType { get { throw null; } }
37+
public System.Formats.Tar.TarEntryFormat Format { get { throw null; } }
3538
public int Gid { get { throw null; } set { } }
3639
public long Length { get { throw null; } }
3740
public string LinkName { get { throw null; } set { } }
@@ -98,26 +101,28 @@ public enum TarFileMode
98101
public sealed partial class TarReader : System.IDisposable
99102
{
100103
public TarReader(System.IO.Stream archiveStream, bool leaveOpen = false) { }
101-
public System.Formats.Tar.TarEntryFormat Format { get { throw null; } }
102104
public System.Collections.Generic.IReadOnlyDictionary<string, string>? GlobalExtendedAttributes { get { throw null; } }
103105
public void Dispose() { }
104106
public System.Formats.Tar.TarEntry? GetNextEntry(bool copyData = false) { throw null; }
105107
}
106108
public sealed partial class TarWriter : System.IDisposable
107109
{
110+
public TarWriter(System.IO.Stream archiveStream) { }
108111
public TarWriter(System.IO.Stream archiveStream, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, string>>? globalExtendedAttributes = null, bool leaveOpen = false) { }
109-
public TarWriter(System.IO.Stream archiveStream, System.Formats.Tar.TarEntryFormat archiveFormat, bool leaveOpen = false) { }
112+
public TarWriter(System.IO.Stream archiveStream, System.Formats.Tar.TarEntryFormat format = System.Formats.Tar.TarEntryFormat.Pax, bool leaveOpen = false) { }
110113
public System.Formats.Tar.TarEntryFormat Format { get { throw null; } }
111114
public void Dispose() { }
112115
public void WriteEntry(System.Formats.Tar.TarEntry entry) { }
113116
public void WriteEntry(string fileName, string? entryName) { }
114117
}
115118
public sealed partial class UstarTarEntry : System.Formats.Tar.PosixTarEntry
116119
{
120+
public UstarTarEntry(System.Formats.Tar.TarEntry other) { }
117121
public UstarTarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { }
118122
}
119123
public sealed partial class V7TarEntry : System.Formats.Tar.TarEntry
120124
{
125+
public V7TarEntry(System.Formats.Tar.TarEntry other) { }
121126
public V7TarEntry(System.Formats.Tar.TarEntryType entryType, string entryName) { }
122127
}
123128
}

src/libraries/System.Formats.Tar/src/System/Formats/Tar/GnuTarEntry.cs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ namespace System.Formats.Tar
99
/// <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>
1010
public sealed class GnuTarEntry : PosixTarEntry
1111
{
12-
// Constructor used when reading an existing archive.
12+
// Constructor called when reading a TarEntry from a TarReader.
1313
internal GnuTarEntry(TarHeader header, TarReader readerOfOrigin)
14-
: base(header, readerOfOrigin)
14+
: base(header, readerOfOrigin, TarEntryFormat.Gnu)
1515
{
1616
}
1717

@@ -31,6 +31,57 @@ internal GnuTarEntry(TarHeader header, TarReader readerOfOrigin)
3131
public GnuTarEntry(TarEntryType entryType, string entryName)
3232
: base(entryType, entryName, TarEntryFormat.Gnu)
3333
{
34+
_header._aTime = _header._mTime; // mtime was set in base constructor
35+
_header._cTime = _header._mTime;
36+
}
37+
38+
/// <summary>
39+
/// Initializes a new <see cref="GnuTarEntry"/> instance by converting the specified <paramref name="other"/> entry into the GNU format.
40+
/// </summary>
41+
public GnuTarEntry(TarEntry other)
42+
: base(other, TarEntryFormat.Gnu)
43+
{
44+
if (other is GnuTarEntry gnuOther)
45+
{
46+
_header._aTime = gnuOther.AccessTime;
47+
_header._cTime = gnuOther.ChangeTime;
48+
_header._gnuUnusedBytes = other._header._gnuUnusedBytes;
49+
}
50+
else
51+
{
52+
bool changedATime = false;
53+
bool changedCTime = false;
54+
55+
if (other is PaxTarEntry paxOther)
56+
{
57+
changedATime = TarHelpers.TryGetDateTimeOffsetFromTimestampString(paxOther._header._extendedAttributes, TarHeader.PaxEaATime, out DateTimeOffset aTime);
58+
if (changedATime)
59+
{
60+
_header._aTime = aTime;
61+
}
62+
63+
changedCTime = TarHelpers.TryGetDateTimeOffsetFromTimestampString(paxOther._header._extendedAttributes, TarHeader.PaxEaCTime, out DateTimeOffset cTime);
64+
if (changedCTime)
65+
{
66+
_header._cTime = cTime;
67+
}
68+
}
69+
70+
// Either 'other' was V7 or Ustar (those formats do not have atime or ctime),
71+
// or 'other' was PAX and at least one of the timestamps was not found in the extended attributes
72+
if (!changedATime || !changedCTime)
73+
{
74+
DateTimeOffset now = DateTimeOffset.UtcNow;
75+
if (!changedATime)
76+
{
77+
_header._aTime = now;
78+
}
79+
if (!changedCTime)
80+
{
81+
_header._cTime = now;
82+
}
83+
}
84+
}
3485
}
3586

3687
/// <summary>

src/libraries/System.Formats.Tar/src/System/Formats/Tar/PaxTarEntry.cs

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ public sealed class PaxTarEntry : PosixTarEntry
1414
{
1515
private ReadOnlyDictionary<string, string>? _readOnlyExtendedAttributes;
1616

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

2523
/// <summary>
@@ -52,6 +50,11 @@ internal PaxTarEntry(TarHeader header, TarReader readerOfOrigin)
5250
public PaxTarEntry(TarEntryType entryType, string entryName)
5351
: base(entryType, entryName, TarEntryFormat.Pax)
5452
{
53+
_header._prefix = string.Empty;
54+
_header._extendedAttributes = new Dictionary<string, string>();
55+
56+
Debug.Assert(_header._mTime != default);
57+
AddNewAccessAndChangeTimestampsIfNotExist(useMTime: true);
5558
}
5659

5760
/// <summary>
@@ -87,7 +90,40 @@ public PaxTarEntry(TarEntryType entryType, string entryName, IEnumerable<KeyValu
8790
: base(entryType, entryName, TarEntryFormat.Pax)
8891
{
8992
ArgumentNullException.ThrowIfNull(extendedAttributes);
90-
_header.ReplaceNormalAttributesWithExtended(extendedAttributes);
93+
94+
_header._prefix = string.Empty;
95+
_header._extendedAttributes = new Dictionary<string, string>(extendedAttributes);
96+
97+
Debug.Assert(_header._mTime != default);
98+
AddNewAccessAndChangeTimestampsIfNotExist(useMTime: true);
99+
}
100+
101+
/// <summary>
102+
/// Initializes a new <see cref="PaxTarEntry"/> instance by converting the specified <paramref name="other"/> entry into the PAX format.
103+
/// </summary>
104+
public PaxTarEntry(TarEntry other)
105+
: base(other, TarEntryFormat.Pax)
106+
{
107+
if (other._header._format is TarEntryFormat.Ustar or TarEntryFormat.Pax)
108+
{
109+
_header._prefix = other._header._prefix;
110+
}
111+
112+
if (other is PaxTarEntry paxOther)
113+
{
114+
_header._extendedAttributes = new Dictionary<string, string>(paxOther.ExtendedAttributes);
115+
}
116+
else
117+
{
118+
_header._extendedAttributes = new Dictionary<string, string>();
119+
if (other is GnuTarEntry gnuOther)
120+
{
121+
_header._extendedAttributes[TarHeader.PaxEaATime] = TarHelpers.GetTimestampStringFromDateTimeOffset(gnuOther.AccessTime);
122+
_header._extendedAttributes[TarHeader.PaxEaCTime] = TarHelpers.GetTimestampStringFromDateTimeOffset(gnuOther.ChangeTime);
123+
}
124+
}
125+
126+
AddNewAccessAndChangeTimestampsIfNotExist(useMTime: false);
91127
}
92128

93129
/// <summary>
@@ -112,12 +148,38 @@ public IReadOnlyDictionary<string, string> ExtendedAttributes
112148
{
113149
get
114150
{
115-
Debug.Assert(_header._extendedAttributes != null);
151+
_header._extendedAttributes ??= new Dictionary<string, string>();
116152
return _readOnlyExtendedAttributes ??= _header._extendedAttributes.AsReadOnly();
117153
}
118154
}
119155

120156
// Determines if the current instance's entry type supports setting a data stream.
121157
internal override bool IsDataStreamSetterSupported() => EntryType == TarEntryType.RegularFile;
158+
159+
// Checks if the extended attributes dictionary contains 'atime' and 'ctime'.
160+
// If any of them is not found, it is added with the value of either the current entry's 'mtime',
161+
// or 'DateTimeOffset.UtcNow', depending on the value of 'useMTime'.
162+
private void AddNewAccessAndChangeTimestampsIfNotExist(bool useMTime)
163+
{
164+
Debug.Assert(!useMTime || (useMTime && _header._mTime != default));
165+
Debug.Assert(_header._extendedAttributes != null);
166+
bool containsATime = _header._extendedAttributes.ContainsKey(TarHeader.PaxEaATime);
167+
bool containsCTime = _header._extendedAttributes.ContainsKey(TarHeader.PaxEaCTime);
168+
169+
if (!containsATime || !containsCTime)
170+
{
171+
string secondsFromEpochString = TarHelpers.GetTimestampStringFromDateTimeOffset(useMTime ? _header._mTime : DateTimeOffset.UtcNow);
172+
173+
if (!containsATime)
174+
{
175+
_header._extendedAttributes[TarHeader.PaxEaATime] = secondsFromEpochString;
176+
}
177+
178+
if (!containsCTime)
179+
{
180+
_header._extendedAttributes[TarHeader.PaxEaCTime] = secondsFromEpochString;
181+
}
182+
}
183+
}
122184
}
123185
}

src/libraries/System.Formats.Tar/src/System/Formats/Tar/PosixTarEntry.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
46
namespace System.Formats.Tar
57
{
68
/// <summary>
@@ -10,16 +12,37 @@ namespace System.Formats.Tar
1012
/// 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>
1113
public abstract partial class PosixTarEntry : TarEntry
1214
{
13-
// Constructor used when reading an existing archive.
14-
internal PosixTarEntry(TarHeader header, TarReader readerOfOrigin)
15-
: base(header, readerOfOrigin)
15+
// Constructor called when reading a TarEntry from a TarReader.
16+
internal PosixTarEntry(TarHeader header, TarReader readerOfOrigin, TarEntryFormat format)
17+
: base(header, readerOfOrigin, format)
1618
{
1719
}
1820

19-
// Constructor called when creating a new 'TarEntry*' instance that can be passed to a TarWriter.
21+
// Constructor called when the user creates a TarEntry instance from scratch.
2022
internal PosixTarEntry(TarEntryType entryType, string entryName, TarEntryFormat format)
2123
: base(entryType, entryName, format)
2224
{
25+
_header._uName = string.Empty;
26+
_header._gName = string.Empty;
27+
_header._devMajor = 0;
28+
_header._devMinor = 0;
29+
}
30+
31+
// Constructor called when converting an entry to the selected format.
32+
internal PosixTarEntry(TarEntry other, TarEntryFormat format)
33+
: base(other, format)
34+
{
35+
if (other is PosixTarEntry)
36+
{
37+
Debug.Assert(other._header._uName != null);
38+
Debug.Assert(other._header._gName != null);
39+
_header._uName = other._header._uName;
40+
_header._gName = other._header._gName;
41+
_header._devMajor = other._header._devMajor;
42+
_header._devMinor = other._header._devMinor;
43+
}
44+
_header._uName ??= string.Empty;
45+
_header._gName ??= string.Empty;
2346
}
2447

2548
/// <summary>

src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Generic;
54
using System.Diagnostics;
65
using System.IO;
76
using Microsoft.Win32.SafeHandles;
@@ -15,42 +14,72 @@ namespace System.Formats.Tar
1514
public abstract partial class TarEntry
1615
{
1716
internal TarHeader _header;
17+
1818
// Used to access the data section of this entry in an unseekable file
1919
private TarReader? _readerOfOrigin;
2020

21-
// Constructor used when reading an existing archive.
22-
internal TarEntry(TarHeader header, TarReader readerOfOrigin)
21+
// Constructor called when reading a TarEntry from a TarReader.
22+
internal TarEntry(TarHeader header, TarReader readerOfOrigin, TarEntryFormat format)
2323
{
24+
// This constructor is called after reading a header from the archive,
25+
// and we should've already detected the format of the header.
26+
Debug.Assert(header._format == format);
2427
_header = header;
2528
_readerOfOrigin = readerOfOrigin;
2629
}
2730

28-
// Constructor called when creating a new 'TarEntry*' instance that can be passed to a TarWriter.
31+
// Constructor called when the user creates a TarEntry instance from scratch.
2932
internal TarEntry(TarEntryType entryType, string entryName, TarEntryFormat format)
3033
{
3134
ArgumentException.ThrowIfNullOrEmpty(entryName);
32-
33-
// Throws if format is unknown or out of range
34-
TarHelpers.VerifyEntryTypeIsSupported(entryType, format, forWriting: false);
35-
36-
_readerOfOrigin = null;
35+
TarHelpers.ThrowIfEntryTypeNotSupported(entryType, format);
3736

3837
_header = default;
38+
_header._format = format;
3939

40-
_header._extendedAttributes = new Dictionary<string, string>();
41-
40+
// Default values for fields shared by all supported formats
4241
_header._name = entryName;
43-
_header._linkName = string.Empty;
44-
_header._typeFlag = entryType;
4542
_header._mode = (int)TarHelpers.DefaultMode;
43+
_header._mTime = DateTimeOffset.UtcNow;
44+
_header._typeFlag = entryType;
45+
_header._linkName = string.Empty;
46+
}
4647

47-
_header._gName = string.Empty;
48-
_header._uName = string.Empty;
48+
// Constructor called when converting an entry to the selected format.
49+
internal TarEntry(TarEntry other, TarEntryFormat format)
50+
{
51+
TarEntryType compatibleEntryType;
52+
if (other.Format is TarEntryFormat.V7 && other.EntryType is TarEntryType.V7RegularFile && format is TarEntryFormat.Ustar or TarEntryFormat.Pax or TarEntryFormat.Gnu)
53+
{
54+
compatibleEntryType = TarEntryType.RegularFile;
55+
}
56+
else if (other.Format is TarEntryFormat.Ustar or TarEntryFormat.Pax or TarEntryFormat.Gnu && other.EntryType is TarEntryType.RegularFile && format is TarEntryFormat.V7)
57+
{
58+
compatibleEntryType = TarEntryType.V7RegularFile;
59+
}
60+
else
61+
{
62+
compatibleEntryType = other.EntryType;
63+
}
64+
65+
TarHelpers.ThrowIfEntryTypeNotSupported(compatibleEntryType, format);
4966

50-
DateTimeOffset now = DateTimeOffset.Now;
51-
_header._mTime = now;
52-
_header._aTime = now;
53-
_header._cTime = now;
67+
_readerOfOrigin = other._readerOfOrigin;
68+
69+
_header = default;
70+
_header._format = format;
71+
72+
_header._name = other._header._name;
73+
_header._mode = other._header._mode;
74+
_header._uid = other._header._uid;
75+
_header._gid = other._header._gid;
76+
_header._size = other._header._size;
77+
_header._mTime = other._header._mTime;
78+
_header._checksum = 0;
79+
_header._typeFlag = compatibleEntryType;
80+
_header._linkName = other._header._linkName;
81+
82+
_header._dataStream = other._header._dataStream;
5483
}
5584

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

95+
/// <summary>
96+
/// The format of the entry.
97+
/// </summary>
98+
public TarEntryFormat Format => _header._format;
99+
66100
/// <summary>
67101
/// The ID of the group that owns the file represented by this entry.
68102
/// </summary>

0 commit comments

Comments
 (0)